Merge m-i to m-c, a=merge
authorPhil Ringnalda <philringnalda@gmail.com>
Tue, 29 Nov 2016 19:28:12 -0800
changeset 324737 a69583d2dbc6fdc18f63761a89cf539c356668be
parent 324690 5d49c9792a3417455bc5683ee2264b3a36d68682 (current diff)
parent 324736 a544ec8cb49864b7c8ff2921e47c9b64160ac1e7 (diff)
child 324738 13736e2db6eb94b02dd28cc88f2943b8109aa374
child 324767 cceff8069b0b623ee59e63f77fbe74ef4f97aa79
child 324806 e6f4b042c540714cabae05788b348c74cd36956d
push id24
push usermaklebus@msu.edu
push dateTue, 20 Dec 2016 03:11:33 +0000
reviewersmerge
milestone53.0a1
Merge m-i to m-c, a=merge MozReview-Commit-ID: DAm5uRF7n91
layout/style/nsCSSPropList.h
layout/style/nsRuleNode.cpp
--- a/browser/components/search/test/browser_amazon.js
+++ b/browser/components/search/test/browser_amazon.js
@@ -8,33 +8,33 @@
 "use strict";
 
 const BROWSER_SEARCH_PREF = "browser.search.";
 
 function test() {
   let engine = Services.search.getEngineByName("Amazon.com");
   ok(engine, "Amazon.com");
 
-  let base = "https://www.amazon.com/exec/obidos/external-search/?field-keywords=foo&mode=blended&tag=mozilla-20&sourceid=Mozilla-search";
+  let base = "https://www.amazon.com/exec/obidos/external-search/?field-keywords=foo&ie=UTF-8&mode=blended&tag=mozilla-20&sourceid=Mozilla-search";
   let url;
 
   // Test search URLs (including purposes).
   url = engine.getSubmission("foo").uri.spec;
   is(url, base, "Check search URL for 'foo'");
 
   // Check search suggestion URL.
   url = engine.getSubmission("foo", "application/x-suggestions+json").uri.spec;
   is(url, "https://completion.amazon.com/search/complete?q=foo&search-alias=aps&mkt=1", "Check search suggestion URL for 'foo'");
 
   // Check all other engine properties.
   const EXPECTED_ENGINE = {
     name: "Amazon.com",
     alias: null,
     description: "Amazon.com Search",
-    searchForm: "https://www.amazon.com/exec/obidos/external-search/?field-keywords=&mode=blended&tag=mozilla-20&sourceid=Mozilla-search",
+    searchForm: "https://www.amazon.com/exec/obidos/external-search/?field-keywords=&ie=UTF-8&mode=blended&tag=mozilla-20&sourceid=Mozilla-search",
     hidden: false,
     wrappedJSObject: {
       queryCharset: "UTF-8",
       "_iconURL": "data:image/x-icon;base64,AAABAAIAEBAAAAAAAAC0AQAAJgAAACAgAAAAAAAA6QIAANoBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAF7SURBVDjLlZPLasJAFIaFRF+iVV+h6hO0GF+gVB9AaHwDt64qCG03tQgtdCFIuyhUelmGli66MXThSt24kNiFBUlAYi6ezjnNxSuawB/ITP7v/HNmJgQAEaZzpgHs/gwcTyTEXuXl2U6nA8ViEbK5HKler28CVRAwnB9ptVrAh8MrQuCaZ4iA8fzIqSgCxwzpTIaSuN/RWGwdYLwCUBQFZFkGSZLgqdmEE7YEN8VOAKyaSKUW4nNBAFmnYiKZpDRX1WqwBBzP089n5f/NEQsFL4WqqtsBWJlzDAJr5PwSMM1awEzzdxIbGI3Hvc6jCZeVFgRQRwpY7Qcw3ktgfpR8wLRxCPaot/X4GS95MppfF6DX9n2A3f+kAZycaT8bAZjU6r6B/duD6d3BYg9wQq/tkYzHY1blEiz5lmQyGc95mrO6r2CxgpjCBXgNsJVviolpXJiraeOIjJRE10juUa4sR8V+mO17VvmGqtuOcdNlwut8zTQJcJ0njifyB2bgTdKh6w4BAAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACsElEQVRYw71XQWsTURBe2LQgeNKLB+tVemt6txcteNSD/QGC6VEIGDx5s+eKPQqFgJhLNdFLBWMP7cU0oSAWjB70koC9WHbVQ5SO8+XtS14mr7svyaYDH9m87Jv55puZt1nPi4yIzjMeMj7T9OwjI88455nGC1cZX+nsDESumJmPFDwIAqrX6z00Gg1qt9vjkJgFgUeuO16Vy3RjeZkyMzM9+MY1fsM9I9h9zyV7ZAznZrA4FAoFVwJ1z+WuOysrg1lnMolkHJX4k0igzI5sARYWF7vEZEk0rvO6iyUSuJfLJUqM7zYSqRDIra4OOUZPmNZsNrsl8UVTpkJAjh1GzmaSpJ8mAWmYeZB5urHRhW5SNOfUCCDo47W1bvPZsp2qAhipy3Nz1kaLG8dUCEBqM5AvpgElqFar01NgIZsdco7Zb7VasU2YigIYL5tjqCL7Q5YkFQXKlcqQ7DbHthIALk/IWAKor82xPIhshxWABCYioDMz51sexcVi0XoG4DPLIyvJjkTArK3scDQnRvO0MdTrUHGiKZCP4tNgO6BAEI08EQH9Z2Qow0hyPypJGIa9p6JWKCn4SA8jSKmJIDgyRvPJkcRxjfUwNGr/i8+Mo32iHzWiThBD4NM60bet9P77/ubA728RlTjMiwiH6zEEfvIrwdZFtQmMJ7W/ofIDBZD5m3mVZGwJcOP2kmILIlCkE45HoPWurwCSg0+UQRD4ZyXxId+T7gQb9+4q9sioY5ltrOG3L5vqXiiJffDx/aUi83ZJ7jr2ohcEu8Hh6/m+I7OWGiVxbWKHsz+O3vSOakqFQdsFgQeJUiKD7Wv9YKXBgCeSUC3v2kM5EJhlHDh3NcgcPlG1BXZu98sDmTuBa4fsMnz9fniJUaGzs+eMC540XuR0aDO2L8Y3qPyMcdOM+R/8XcqRA3qp9gAAAABJRU5ErkJggg==",
       _urls : [
         {
           type: "application/x-suggestions+json",
           method: "GET",
@@ -47,16 +47,21 @@ function test() {
           template: "https://www.amazon.com/exec/obidos/external-search/",
           params: [
             {
               name: "field-keywords",
               value: "{searchTerms}",
               purpose: undefined,
             },
             {
+              name: "ie",
+              value: "{inputEncoding}",
+              purpose: undefined,
+            },
+            {
               name: "mode",
               value: "blended",
               purpose: undefined,
             },
             {
               name: "tag",
               value: "mozilla-20",
               purpose: undefined,
--- a/browser/components/search/test/browser_amazon_behavior.js
+++ b/browser/components/search/test/browser_amazon_behavior.js
@@ -13,17 +13,17 @@ const BROWSER_SEARCH_PREF = "browser.sea
 function test() {
   let engine = Services.search.getEngineByName("Amazon.com");
   ok(engine, "Amazon is installed");
 
   let previouslySelectedEngine = Services.search.currentEngine;
   Services.search.currentEngine = engine;
   engine.alias = "a";
 
-  let base = "https://www.amazon.com/exec/obidos/external-search/?field-keywords=foo&mode=blended&tag=mozilla-20&sourceid=Mozilla-search";
+  let base = "https://www.amazon.com/exec/obidos/external-search/?field-keywords=foo&ie=UTF-8&mode=blended&tag=mozilla-20&sourceid=Mozilla-search";
   let url;
 
   // Test search URLs (including purposes).
   url = engine.getSubmission("foo").uri.spec;
   is(url, base, "Check search URL for 'foo'");
 
   waitForExplicitFinish();
 
--- a/browser/locales/searchplugins/amazon-en-GB.xml
+++ b/browser/locales/searchplugins/amazon-en-GB.xml
@@ -1,17 +1,18 @@
 <!-- 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/. -->
 
 <SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
 <ShortName>Amazon.co.uk</ShortName>
 <Description>Amazon.co.uk Search</Description>
-<InputEncoding>ISO-8859-1</InputEncoding>
+<InputEncoding>UTF-8</InputEncoding>
 <Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAC0AQAAJgAAACAgAAAAAAAA6QIAANoBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAF7SURBVDjLlZPLasJAFIaFRF+iVV+h6hO0GF+gVB9AaHwDt64qCG03tQgtdCFIuyhUelmGli66MXThSt24kNiFBUlAYi6ezjnNxSuawB/ITP7v/HNmJgQAEaZzpgHs/gwcTyTEXuXl2U6nA8ViEbK5HKler28CVRAwnB9ptVrAh8MrQuCaZ4iA8fzIqSgCxwzpTIaSuN/RWGwdYLwCUBQFZFkGSZLgqdmEE7YEN8VOAKyaSKUW4nNBAFmnYiKZpDRX1WqwBBzP089n5f/NEQsFL4WqqtsBWJlzDAJr5PwSMM1awEzzdxIbGI3Hvc6jCZeVFgRQRwpY7Qcw3ktgfpR8wLRxCPaot/X4GS95MppfF6DX9n2A3f+kAZycaT8bAZjU6r6B/duD6d3BYg9wQq/tkYzHY1blEiz5lmQyGc95mrO6r2CxgpjCBXgNsJVviolpXJiraeOIjJRE10juUa4sR8V+mO17VvmGqtuOcdNlwut8zTQJcJ0njifyB2bgTdKh6w4BAAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACsElEQVRYw71XQWsTURBe2LQgeNKLB+tVemt6txcteNSD/QGC6VEIGDx5s+eKPQqFgJhLNdFLBWMP7cU0oSAWjB70koC9WHbVQ5SO8+XtS14mr7svyaYDH9m87Jv55puZt1nPi4yIzjMeMj7T9OwjI88455nGC1cZX+nsDESumJmPFDwIAqrX6z00Gg1qt9vjkJgFgUeuO16Vy3RjeZkyMzM9+MY1fsM9I9h9zyV7ZAznZrA4FAoFVwJ1z+WuOysrg1lnMolkHJX4k0igzI5sARYWF7vEZEk0rvO6iyUSuJfLJUqM7zYSqRDIra4OOUZPmNZsNrsl8UVTpkJAjh1GzmaSpJ8mAWmYeZB5urHRhW5SNOfUCCDo47W1bvPZsp2qAhipy3Nz1kaLG8dUCEBqM5AvpgElqFar01NgIZsdco7Zb7VasU2YigIYL5tjqCL7Q5YkFQXKlcqQ7DbHthIALk/IWAKor82xPIhshxWABCYioDMz51sexcVi0XoG4DPLIyvJjkTArK3scDQnRvO0MdTrUHGiKZCP4tNgO6BAEI08EQH9Z2Qow0hyPypJGIa9p6JWKCn4SA8jSKmJIDgyRvPJkcRxjfUwNGr/i8+Mo32iHzWiThBD4NM60bet9P77/ubA728RlTjMiwiH6zEEfvIrwdZFtQmMJ7W/ofIDBZD5m3mVZGwJcOP2kmILIlCkE45HoPWurwCSg0+UQRD4ZyXxId+T7gQb9+4q9sioY5ltrOG3L5vqXiiJffDx/aUi83ZJ7jr2ohcEu8Hh6/m+I7OWGiVxbWKHsz+O3vSOakqFQdsFgQeJUiKD7Wv9YKXBgCeSUC3v2kM5EJhlHDh3NcgcPlG1BXZu98sDmTuBa4fsMnz9fniJUaGzs+eMC540XuR0aDO2L8Y3qPyMcdOM+R/8XcqRA3qp9gAAAABJRU5ErkJggg==</Image>
 <Url type="text/html" method="GET" template="https://www.amazon.co.uk/exec/obidos/external-search/" resultdomain="amazon.co.uk">
   <Param name="field-keywords" value="{searchTerms}"/>
+  <Param name="ie" value="{inputEncoding}"/>
   <Param name="mode" value="blended"/>
   <Param name="tag" value="firefox-uk-21"/>
   <Param name="sourceid" value="Mozilla-search"/>
 </Url>
 <SearchForm>https://www.amazon.co.uk/</SearchForm>
 </SearchPlugin>
--- a/browser/locales/searchplugins/amazon-france.xml
+++ b/browser/locales/searchplugins/amazon-france.xml
@@ -1,17 +1,18 @@
 <!-- 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/. -->
 
 <SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
 <ShortName>Amazon.fr</ShortName>
 <Description>Recherche Amazon.fr</Description>
-<InputEncoding>ISO-8859-1</InputEncoding>
+<InputEncoding>UTF-8</InputEncoding>
 <Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAC0AQAAJgAAACAgAAAAAAAA6QIAANoBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAF7SURBVDjLlZPLasJAFIaFRF+iVV+h6hO0GF+gVB9AaHwDt64qCG03tQgtdCFIuyhUelmGli66MXThSt24kNiFBUlAYi6ezjnNxSuawB/ITP7v/HNmJgQAEaZzpgHs/gwcTyTEXuXl2U6nA8ViEbK5HKler28CVRAwnB9ptVrAh8MrQuCaZ4iA8fzIqSgCxwzpTIaSuN/RWGwdYLwCUBQFZFkGSZLgqdmEE7YEN8VOAKyaSKUW4nNBAFmnYiKZpDRX1WqwBBzP089n5f/NEQsFL4WqqtsBWJlzDAJr5PwSMM1awEzzdxIbGI3Hvc6jCZeVFgRQRwpY7Qcw3ktgfpR8wLRxCPaot/X4GS95MppfF6DX9n2A3f+kAZycaT8bAZjU6r6B/duD6d3BYg9wQq/tkYzHY1blEiz5lmQyGc95mrO6r2CxgpjCBXgNsJVviolpXJiraeOIjJRE10juUa4sR8V+mO17VvmGqtuOcdNlwut8zTQJcJ0njifyB2bgTdKh6w4BAAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACsElEQVRYw71XQWsTURBe2LQgeNKLB+tVemt6txcteNSD/QGC6VEIGDx5s+eKPQqFgJhLNdFLBWMP7cU0oSAWjB70koC9WHbVQ5SO8+XtS14mr7svyaYDH9m87Jv55puZt1nPi4yIzjMeMj7T9OwjI88455nGC1cZX+nsDESumJmPFDwIAqrX6z00Gg1qt9vjkJgFgUeuO16Vy3RjeZkyMzM9+MY1fsM9I9h9zyV7ZAznZrA4FAoFVwJ1z+WuOysrg1lnMolkHJX4k0igzI5sARYWF7vEZEk0rvO6iyUSuJfLJUqM7zYSqRDIra4OOUZPmNZsNrsl8UVTpkJAjh1GzmaSpJ8mAWmYeZB5urHRhW5SNOfUCCDo47W1bvPZsp2qAhipy3Nz1kaLG8dUCEBqM5AvpgElqFar01NgIZsdco7Zb7VasU2YigIYL5tjqCL7Q5YkFQXKlcqQ7DbHthIALk/IWAKor82xPIhshxWABCYioDMz51sexcVi0XoG4DPLIyvJjkTArK3scDQnRvO0MdTrUHGiKZCP4tNgO6BAEI08EQH9Z2Qow0hyPypJGIa9p6JWKCn4SA8jSKmJIDgyRvPJkcRxjfUwNGr/i8+Mo32iHzWiThBD4NM60bet9P77/ubA728RlTjMiwiH6zEEfvIrwdZFtQmMJ7W/ofIDBZD5m3mVZGwJcOP2kmILIlCkE45HoPWurwCSg0+UQRD4ZyXxId+T7gQb9+4q9sioY5ltrOG3L5vqXiiJffDx/aUi83ZJ7jr2ohcEu8Hh6/m+I7OWGiVxbWKHsz+O3vSOakqFQdsFgQeJUiKD7Wv9YKXBgCeSUC3v2kM5EJhlHDh3NcgcPlG1BXZu98sDmTuBa4fsMnz9fniJUaGzs+eMC540XuR0aDO2L8Y3qPyMcdOM+R/8XcqRA3qp9gAAAABJRU5ErkJggg==</Image>
 <Url type="text/html" method="GET" template="https://www.amazon.fr/exec/obidos/external-search/" resultdomain="amazon.fr">
   <Param name="field-keywords" value="{searchTerms}"/>
+  <Param name="ie" value="{inputEncoding}"/>
   <Param name="mode" value="blended"/>
   <Param name="tag" value="firefox-fr-21"/>
   <Param name="sourceid" value="Mozilla-search"/>
 </Url>
 <SearchForm>https://www.amazon.fr/</SearchForm>
 </SearchPlugin>
--- a/browser/locales/searchplugins/amazon-it.xml
+++ b/browser/locales/searchplugins/amazon-it.xml
@@ -1,17 +1,18 @@
 <!-- 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/. -->
 
 <SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
 <ShortName>Amazon.it</ShortName>
 <Description>Ricerca Amazon.it</Description>
-<InputEncoding>ISO-8859-1</InputEncoding>
+<InputEncoding>UTF-8</InputEncoding>
 <Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAC0AQAAJgAAACAgAAAAAAAA6QIAANoBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAF7SURBVDjLlZPLasJAFIaFRF+iVV+h6hO0GF+gVB9AaHwDt64qCG03tQgtdCFIuyhUelmGli66MXThSt24kNiFBUlAYi6ezjnNxSuawB/ITP7v/HNmJgQAEaZzpgHs/gwcTyTEXuXl2U6nA8ViEbK5HKler28CVRAwnB9ptVrAh8MrQuCaZ4iA8fzIqSgCxwzpTIaSuN/RWGwdYLwCUBQFZFkGSZLgqdmEE7YEN8VOAKyaSKUW4nNBAFmnYiKZpDRX1WqwBBzP089n5f/NEQsFL4WqqtsBWJlzDAJr5PwSMM1awEzzdxIbGI3Hvc6jCZeVFgRQRwpY7Qcw3ktgfpR8wLRxCPaot/X4GS95MppfF6DX9n2A3f+kAZycaT8bAZjU6r6B/duD6d3BYg9wQq/tkYzHY1blEiz5lmQyGc95mrO6r2CxgpjCBXgNsJVviolpXJiraeOIjJRE10juUa4sR8V+mO17VvmGqtuOcdNlwut8zTQJcJ0njifyB2bgTdKh6w4BAAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACsElEQVRYw71XQWsTURBe2LQgeNKLB+tVemt6txcteNSD/QGC6VEIGDx5s+eKPQqFgJhLNdFLBWMP7cU0oSAWjB70koC9WHbVQ5SO8+XtS14mr7svyaYDH9m87Jv55puZt1nPi4yIzjMeMj7T9OwjI88455nGC1cZX+nsDESumJmPFDwIAqrX6z00Gg1qt9vjkJgFgUeuO16Vy3RjeZkyMzM9+MY1fsM9I9h9zyV7ZAznZrA4FAoFVwJ1z+WuOysrg1lnMolkHJX4k0igzI5sARYWF7vEZEk0rvO6iyUSuJfLJUqM7zYSqRDIra4OOUZPmNZsNrsl8UVTpkJAjh1GzmaSpJ8mAWmYeZB5urHRhW5SNOfUCCDo47W1bvPZsp2qAhipy3Nz1kaLG8dUCEBqM5AvpgElqFar01NgIZsdco7Zb7VasU2YigIYL5tjqCL7Q5YkFQXKlcqQ7DbHthIALk/IWAKor82xPIhshxWABCYioDMz51sexcVi0XoG4DPLIyvJjkTArK3scDQnRvO0MdTrUHGiKZCP4tNgO6BAEI08EQH9Z2Qow0hyPypJGIa9p6JWKCn4SA8jSKmJIDgyRvPJkcRxjfUwNGr/i8+Mo32iHzWiThBD4NM60bet9P77/ubA728RlTjMiwiH6zEEfvIrwdZFtQmMJ7W/ofIDBZD5m3mVZGwJcOP2kmILIlCkE45HoPWurwCSg0+UQRD4ZyXxId+T7gQb9+4q9sioY5ltrOG3L5vqXiiJffDx/aUi83ZJ7jr2ohcEu8Hh6/m+I7OWGiVxbWKHsz+O3vSOakqFQdsFgQeJUiKD7Wv9YKXBgCeSUC3v2kM5EJhlHDh3NcgcPlG1BXZu98sDmTuBa4fsMnz9fniJUaGzs+eMC540XuR0aDO2L8Y3qPyMcdOM+R/8XcqRA3qp9gAAAABJRU5ErkJggg==</Image>
 <Url type="text/html" method="GET" template="https://www.amazon.it/exec/obidos/external-search/" resultdomain="amazon.it">
   <Param name="field-keywords" value="{searchTerms}"/>
+  <Param name="ie" value="{inputEncoding}"/>
   <Param name="mode" value="blended"/>
   <Param name="tag" value="firefoxit-21"/>
   <Param name="sourceid" value="Mozilla-search"/>
 </Url>
 <SearchForm>https://www.amazon.it/</SearchForm>
 </SearchPlugin>
--- a/browser/locales/searchplugins/amazondotcom-de.xml
+++ b/browser/locales/searchplugins/amazondotcom-de.xml
@@ -1,17 +1,18 @@
 <!-- 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/. -->
 
 <SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
 <ShortName>Amazon.de</ShortName>
 <Description>Amazon.de Suche</Description>
-<InputEncoding>ISO-8859-1</InputEncoding>
+<InputEncoding>UTF-8</InputEncoding>
 <Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAC0AQAAJgAAACAgAAAAAAAA6QIAANoBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAF7SURBVDjLlZPLasJAFIaFRF+iVV+h6hO0GF+gVB9AaHwDt64qCG03tQgtdCFIuyhUelmGli66MXThSt24kNiFBUlAYi6ezjnNxSuawB/ITP7v/HNmJgQAEaZzpgHs/gwcTyTEXuXl2U6nA8ViEbK5HKler28CVRAwnB9ptVrAh8MrQuCaZ4iA8fzIqSgCxwzpTIaSuN/RWGwdYLwCUBQFZFkGSZLgqdmEE7YEN8VOAKyaSKUW4nNBAFmnYiKZpDRX1WqwBBzP089n5f/NEQsFL4WqqtsBWJlzDAJr5PwSMM1awEzzdxIbGI3Hvc6jCZeVFgRQRwpY7Qcw3ktgfpR8wLRxCPaot/X4GS95MppfF6DX9n2A3f+kAZycaT8bAZjU6r6B/duD6d3BYg9wQq/tkYzHY1blEiz5lmQyGc95mrO6r2CxgpjCBXgNsJVviolpXJiraeOIjJRE10juUa4sR8V+mO17VvmGqtuOcdNlwut8zTQJcJ0njifyB2bgTdKh6w4BAAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACsElEQVRYw71XQWsTURBe2LQgeNKLB+tVemt6txcteNSD/QGC6VEIGDx5s+eKPQqFgJhLNdFLBWMP7cU0oSAWjB70koC9WHbVQ5SO8+XtS14mr7svyaYDH9m87Jv55puZt1nPi4yIzjMeMj7T9OwjI88455nGC1cZX+nsDESumJmPFDwIAqrX6z00Gg1qt9vjkJgFgUeuO16Vy3RjeZkyMzM9+MY1fsM9I9h9zyV7ZAznZrA4FAoFVwJ1z+WuOysrg1lnMolkHJX4k0igzI5sARYWF7vEZEk0rvO6iyUSuJfLJUqM7zYSqRDIra4OOUZPmNZsNrsl8UVTpkJAjh1GzmaSpJ8mAWmYeZB5urHRhW5SNOfUCCDo47W1bvPZsp2qAhipy3Nz1kaLG8dUCEBqM5AvpgElqFar01NgIZsdco7Zb7VasU2YigIYL5tjqCL7Q5YkFQXKlcqQ7DbHthIALk/IWAKor82xPIhshxWABCYioDMz51sexcVi0XoG4DPLIyvJjkTArK3scDQnRvO0MdTrUHGiKZCP4tNgO6BAEI08EQH9Z2Qow0hyPypJGIa9p6JWKCn4SA8jSKmJIDgyRvPJkcRxjfUwNGr/i8+Mo32iHzWiThBD4NM60bet9P77/ubA728RlTjMiwiH6zEEfvIrwdZFtQmMJ7W/ofIDBZD5m3mVZGwJcOP2kmILIlCkE45HoPWurwCSg0+UQRD4ZyXxId+T7gQb9+4q9sioY5ltrOG3L5vqXiiJffDx/aUi83ZJ7jr2ohcEu8Hh6/m+I7OWGiVxbWKHsz+O3vSOakqFQdsFgQeJUiKD7Wv9YKXBgCeSUC3v2kM5EJhlHDh3NcgcPlG1BXZu98sDmTuBa4fsMnz9fniJUaGzs+eMC540XuR0aDO2L8Y3qPyMcdOM+R/8XcqRA3qp9gAAAABJRU5ErkJggg==</Image>
 <Url type="text/html" method="GET" template="https://www.amazon.de/exec/obidos/external-search/" resultdomain="amazon.de">
   <Param name="field-keywords" value="{searchTerms}"/>
+  <Param name="ie" value="{inputEncoding}"/>
   <Param name="mode" value="blended"/>
   <Param name="tag" value="firefox-de-21"/>
   <Param name="sourceid" value="Mozilla-search"/>
 </Url>
 <SearchForm>https://www.amazon.de/</SearchForm>
 </SearchPlugin>
--- a/browser/locales/searchplugins/amazondotcom.xml
+++ b/browser/locales/searchplugins/amazondotcom.xml
@@ -5,13 +5,14 @@
 <SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
 <ShortName>Amazon.com</ShortName>
 <Description>Amazon.com Search</Description>
 <InputEncoding>UTF-8</InputEncoding>
 <Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAC0AQAAJgAAACAgAAAAAAAA6QIAANoBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAF7SURBVDjLlZPLasJAFIaFRF+iVV+h6hO0GF+gVB9AaHwDt64qCG03tQgtdCFIuyhUelmGli66MXThSt24kNiFBUlAYi6ezjnNxSuawB/ITP7v/HNmJgQAEaZzpgHs/gwcTyTEXuXl2U6nA8ViEbK5HKler28CVRAwnB9ptVrAh8MrQuCaZ4iA8fzIqSgCxwzpTIaSuN/RWGwdYLwCUBQFZFkGSZLgqdmEE7YEN8VOAKyaSKUW4nNBAFmnYiKZpDRX1WqwBBzP089n5f/NEQsFL4WqqtsBWJlzDAJr5PwSMM1awEzzdxIbGI3Hvc6jCZeVFgRQRwpY7Qcw3ktgfpR8wLRxCPaot/X4GS95MppfF6DX9n2A3f+kAZycaT8bAZjU6r6B/duD6d3BYg9wQq/tkYzHY1blEiz5lmQyGc95mrO6r2CxgpjCBXgNsJVviolpXJiraeOIjJRE10juUa4sR8V+mO17VvmGqtuOcdNlwut8zTQJcJ0njifyB2bgTdKh6w4BAAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACsElEQVRYw71XQWsTURBe2LQgeNKLB+tVemt6txcteNSD/QGC6VEIGDx5s+eKPQqFgJhLNdFLBWMP7cU0oSAWjB70koC9WHbVQ5SO8+XtS14mr7svyaYDH9m87Jv55puZt1nPi4yIzjMeMj7T9OwjI88455nGC1cZX+nsDESumJmPFDwIAqrX6z00Gg1qt9vjkJgFgUeuO16Vy3RjeZkyMzM9+MY1fsM9I9h9zyV7ZAznZrA4FAoFVwJ1z+WuOysrg1lnMolkHJX4k0igzI5sARYWF7vEZEk0rvO6iyUSuJfLJUqM7zYSqRDIra4OOUZPmNZsNrsl8UVTpkJAjh1GzmaSpJ8mAWmYeZB5urHRhW5SNOfUCCDo47W1bvPZsp2qAhipy3Nz1kaLG8dUCEBqM5AvpgElqFar01NgIZsdco7Zb7VasU2YigIYL5tjqCL7Q5YkFQXKlcqQ7DbHthIALk/IWAKor82xPIhshxWABCYioDMz51sexcVi0XoG4DPLIyvJjkTArK3scDQnRvO0MdTrUHGiKZCP4tNgO6BAEI08EQH9Z2Qow0hyPypJGIa9p6JWKCn4SA8jSKmJIDgyRvPJkcRxjfUwNGr/i8+Mo32iHzWiThBD4NM60bet9P77/ubA728RlTjMiwiH6zEEfvIrwdZFtQmMJ7W/ofIDBZD5m3mVZGwJcOP2kmILIlCkE45HoPWurwCSg0+UQRD4ZyXxId+T7gQb9+4q9sioY5ltrOG3L5vqXiiJffDx/aUi83ZJ7jr2ohcEu8Hh6/m+I7OWGiVxbWKHsz+O3vSOakqFQdsFgQeJUiKD7Wv9YKXBgCeSUC3v2kM5EJhlHDh3NcgcPlG1BXZu98sDmTuBa4fsMnz9fniJUaGzs+eMC540XuR0aDO2L8Y3qPyMcdOM+R/8XcqRA3qp9gAAAABJRU5ErkJggg==</Image>
 <Url type="application/x-suggestions+json" method="GET" template="https://completion.amazon.com/search/complete?q={searchTerms}&amp;search-alias=aps&amp;mkt=1"/>
 <Url type="text/html" method="GET" template="https://www.amazon.com/exec/obidos/external-search/" rel="searchform">
   <Param name="field-keywords" value="{searchTerms}"/>
+  <Param name="ie" value="{inputEncoding}"/>
   <Param name="mode" value="blended"/>
   <Param name="tag" value="mozilla-20"/>
   <Param name="sourceid" value="Mozilla-search"/>
 </Url>
 </SearchPlugin>
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -609,17 +609,35 @@ Inspector.prototype = {
    * @param {string} title tab title
    * @param {React.Component} panel component. See `InspectorPanelTab` as an example.
    * @param {boolean} selected true if the panel should be selected
    */
   addSidebarTab: function (id, title, panel, selected) {
     this.sidebar.addTab(id, title, panel, selected);
   },
 
-  setupToolbar: function () {
+  /**
+   * Method to check whether the document is a HTML document and
+   * pickColorFromPage method is available or not.
+   * Returns a boolean value
+   */
+  supportsEyeDropper: Task.async(function* () {
+    let isInHTMLDocument = this.selection.nodeFront &&
+                            this.selection.nodeFront.isInHTMLDocument;
+    let pickColorAvailable = false;
+    try {
+      pickColorAvailable = yield this.target
+                                      .actorHasMethod("inspector", "pickColorFromPage");
+    } catch (e) {
+      console.error(e);
+    }
+    return isInHTMLDocument && pickColorAvailable;
+  }),
+
+  setupToolbar: Task.async(function* () {
     this.teardownToolbar();
 
     // Setup the sidebar toggle button.
     let SidebarToggle = this.React.createFactory(this.browserRequire(
       "devtools/client/shared/components/sidebar-toggle"));
 
     let sidebarToggle = SidebarToggle({
       onClick: this.onPaneToggleButtonClicked,
@@ -632,36 +650,31 @@ Inspector.prototype = {
     this._sidebarToggle = this.ReactDOM.render(sidebarToggle, parentBox);
 
     // Setup the add-node button.
     this.addNode = this.addNode.bind(this);
     this.addNodeButton = this.panelDoc.getElementById("inspector-element-add-button");
     this.addNodeButton.addEventListener("click", this.addNode);
 
     // Setup the eye-dropper icon if we're in an HTML document and we have actor support.
-    if (this.selection.nodeFront && this.selection.nodeFront.isInHTMLDocument) {
-      this.target.actorHasMethod("inspector", "pickColorFromPage").then(value => {
-        if (!value) {
-          return;
-        }
-
-        this.onEyeDropperDone = this.onEyeDropperDone.bind(this);
-        this.onEyeDropperButtonClicked = this.onEyeDropperButtonClicked.bind(this);
-        this.eyeDropperButton = this.panelDoc
+    let canShowEyeDropper = yield this.supportsEyeDropper();
+    if (canShowEyeDropper) {
+      this.onEyeDropperDone = this.onEyeDropperDone.bind(this);
+      this.onEyeDropperButtonClicked = this.onEyeDropperButtonClicked.bind(this);
+      this.eyeDropperButton = this.panelDoc
                                     .getElementById("inspector-eyedropper-toggle");
-        this.eyeDropperButton.disabled = false;
-        this.eyeDropperButton.title = INSPECTOR_L10N.getStr("inspector.eyedropper.label");
-        this.eyeDropperButton.addEventListener("click", this.onEyeDropperButtonClicked);
-      }, e => console.error(e));
+      this.eyeDropperButton.disabled = false;
+      this.eyeDropperButton.title = INSPECTOR_L10N.getStr("inspector.eyedropper.label");
+      this.eyeDropperButton.addEventListener("click", this.onEyeDropperButtonClicked);
     } else {
       let eyeDropperButton = this.panelDoc.getElementById("inspector-eyedropper-toggle");
       eyeDropperButton.disabled = true;
       eyeDropperButton.title = INSPECTOR_L10N.getStr("eyedropper.disabled.title");
     }
-  },
+  }),
 
   teardownToolbar: function () {
     this._sidebarToggle = null;
 
     if (this.addNodeButton) {
       this.addNodeButton.removeEventListener("click", this.addNode);
       this.addNodeButton = null;
     }
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/layout/actions/highlighter-settings.js
@@ -0,0 +1,40 @@
+/* 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 {
+  UPDATE_SHOW_GRID_LINE_NUMBERS,
+  UPDATE_SHOW_INFINITE_LINES,
+} = require("./index");
+
+module.exports = {
+
+  /**
+   * Update the grid highlighter's show grid line numbers preference.
+   *
+   * @param  {Boolean} enabled
+   *         Whether or not the grid highlighter should show the grid line numbers.
+   */
+  updateShowGridLineNumbers(enabled) {
+    return {
+      type: UPDATE_SHOW_GRID_LINE_NUMBERS,
+      enabled,
+    };
+  },
+
+  /**
+   * Update the grid highlighter's show infinite lines preference.
+   *
+   * @param  {Boolean} enabled
+   *         Whether or not the grid highlighter should extend grid lines infinitely.
+   */
+  updateShowInfiniteLines(enabled) {
+    return {
+      type: UPDATE_SHOW_INFINITE_LINES,
+      enabled,
+    };
+  },
+
+};
--- a/devtools/client/inspector/layout/actions/index.js
+++ b/devtools/client/inspector/layout/actions/index.js
@@ -9,9 +9,15 @@ const { createEnum } = require("devtools
 createEnum([
 
   // Update the grid highlighted state.
   "UPDATE_GRID_HIGHLIGHTED",
 
   // Update the entire grids state with the new list of grids.
   "UPDATE_GRIDS",
 
+  // Update the grid highlighter's show grid line numbers state.
+  "UPDATE_SHOW_GRID_LINE_NUMBERS",
+
+  // Update the grid highlighter's show infinite lines state.
+  "UPDATE_SHOW_INFINITE_LINES",
+
 ], module.exports);
--- a/devtools/client/inspector/layout/actions/moz.build
+++ b/devtools/client/inspector/layout/actions/moz.build
@@ -1,10 +1,11 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
     'grids.js',
+    'highlighter-settings.js',
     'index.js',
 )
--- a/devtools/client/inspector/layout/components/App.js
+++ b/devtools/client/inspector/layout/components/App.js
@@ -15,16 +15,20 @@ const Types = require("../types");
 const { getStr } = require("../utils/l10n");
 
 const App = createClass({
 
   displayName: "App",
 
   propTypes: {
     grids: PropTypes.arrayOf(PropTypes.shape(Types.grid)).isRequired,
+    highlighterSettings: PropTypes.shape(Types.highlighterSettings).isRequired,
+    onToggleGridHighlighter: PropTypes.func.isRequired,
+    onToggleShowGridLineNumbers: PropTypes.func.isRequired,
+    onToggleShowInfiniteLines: PropTypes.func.isRequired,
   },
 
   mixins: [ addons.PureRenderMixin ],
 
   render() {
     return dom.div(
       {
         id: "layout-container",
--- a/devtools/client/inspector/layout/components/Grid.js
+++ b/devtools/client/inspector/layout/components/Grid.js
@@ -1,42 +1,63 @@
 /* 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 { addons, createClass, DOM: dom, PropTypes } =
+const { addons, createClass, createFactory, DOM: dom, PropTypes } =
   require("devtools/client/shared/vendor/react");
 
+const GridDisplaySettings = createFactory(require("./GridDisplaySettings"));
+const GridList = createFactory(require("./GridList"));
+
 const Types = require("../types");
 const { getStr } = require("../utils/l10n");
 
 module.exports = createClass({
 
   displayName: "Grid",
 
   propTypes: {
     grids: PropTypes.arrayOf(PropTypes.shape(Types.grid)).isRequired,
+    highlighterSettings: PropTypes.shape(Types.highlighterSettings).isRequired,
+    onToggleGridHighlighter: PropTypes.func.isRequired,
+    onToggleShowGridLineNumbers: PropTypes.func.isRequired,
+    onToggleShowInfiniteLines: PropTypes.func.isRequired,
   },
 
   mixins: [ addons.PureRenderMixin ],
 
   render() {
     let {
       grids,
+      highlighterSettings,
+      onToggleGridHighlighter,
+      onToggleShowGridLineNumbers,
+      onToggleShowInfiniteLines,
     } = this.props;
 
-    return dom.div(
-      {
-        id: "layout-grid-container",
-      },
-      !grids.length ?
-        dom.div(
-          {
-            className: "layout-no-grids"
-          },
-          getStr("layout.noGrids")
-        ) : null
-    );
+    return grids.length ?
+      dom.div(
+        {
+          id: "layout-grid-container",
+        },
+        GridList({
+          grids,
+          onToggleGridHighlighter,
+        }),
+        GridDisplaySettings({
+          highlighterSettings,
+          onToggleShowGridLineNumbers,
+          onToggleShowInfiniteLines,
+        })
+      )
+      :
+      dom.div(
+        {
+          className: "layout-no-grids",
+        },
+        getStr("layout.noGrids")
+      );
   },
 
 });
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/layout/components/GridDisplaySettings.js
@@ -0,0 +1,90 @@
+/* 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 { addons, createClass, DOM: dom, PropTypes } =
+  require("devtools/client/shared/vendor/react");
+
+const Types = require("../types");
+const { getStr } = require("../utils/l10n");
+
+module.exports = createClass({
+
+  displayName: "GridDisplaySettings",
+
+  propTypes: {
+    highlighterSettings: PropTypes.shape(Types.highlighterSettings).isRequired,
+    onToggleShowGridLineNumbers: PropTypes.func.isRequired,
+    onToggleShowInfiniteLines: PropTypes.func.isRequired,
+  },
+
+  mixins: [ addons.PureRenderMixin ],
+
+  onShowGridLineNumbersCheckboxClick() {
+    let {
+      highlighterSettings,
+      onToggleShowGridLineNumbers,
+    } = this.props;
+
+    onToggleShowGridLineNumbers(!highlighterSettings.showGridLineNumbers);
+  },
+
+  onShowInfiniteLinesCheckboxClick() {
+    let {
+      highlighterSettings,
+      onToggleShowInfiniteLines,
+    } = this.props;
+
+    onToggleShowInfiniteLines(!highlighterSettings.showInfiniteLines);
+  },
+
+  render() {
+    let {
+      highlighterSettings,
+    } = this.props;
+
+    return dom.div(
+      {
+        className: "grid-container",
+      },
+      dom.span(
+        {},
+        getStr("layout.gridDisplaySettings")
+      ),
+      dom.ul(
+        {},
+        dom.li(
+          {},
+          dom.label(
+            {},
+            dom.input(
+              {
+                type: "checkbox",
+                checked: highlighterSettings.showInfiniteLines,
+                onChange: this.onShowInfiniteLinesCheckboxClick,
+              }
+            ),
+            getStr("layout.extendGridLinesInfinitely")
+          )
+        ),
+        dom.li(
+          {},
+          dom.label(
+            {},
+            dom.input(
+              {
+                type: "checkbox",
+                checked: highlighterSettings.showGridLineNumbers,
+                onChange: this.onShowGridLineNumbersCheckboxClick,
+              }
+            ),
+            getStr("layout.displayNumbersOnLines")
+          )
+        )
+      )
+    );
+  },
+
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/layout/components/GridList.js
@@ -0,0 +1,87 @@
+/* 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 { addons, createClass, DOM: dom, PropTypes } =
+  require("devtools/client/shared/vendor/react");
+
+const Types = require("../types");
+const { getStr } = require("../utils/l10n");
+
+module.exports = createClass({
+
+  displayName: "GridList",
+
+  propTypes: {
+    grids: PropTypes.arrayOf(PropTypes.shape(Types.grid)).isRequired,
+    onToggleGridHighlighter: PropTypes.func.isRequired,
+  },
+
+  mixins: [ addons.PureRenderMixin ],
+
+  onGridCheckboxClick({ target }) {
+    let {
+      grids,
+      onToggleGridHighlighter,
+    } = this.props;
+
+    onToggleGridHighlighter(grids[target.value].nodeFront);
+  },
+
+  render() {
+    let {
+      grids,
+    } = this.props;
+
+    return dom.div(
+      {
+        className: "grid-container",
+      },
+      dom.span(
+        {},
+        getStr("layout.overlayMultipleGrids")
+      ),
+      dom.ul(
+        {},
+        grids.map(grid => {
+          let { nodeFront } = grid;
+          let { displayName, attributes } = nodeFront;
+
+          let gridName = displayName;
+
+          let idIndex = attributes.findIndex(({ name }) => name === "id");
+          if (idIndex > -1 && attributes[idIndex].value) {
+            gridName += "#" + attributes[idIndex].value;
+          }
+
+          let classIndex = attributes.findIndex(({name}) => name === "class");
+          if (classIndex > -1 && attributes[classIndex].value) {
+            gridName += "." + attributes[classIndex].value.split(" ").join(".");
+          }
+
+          return dom.li(
+            {
+              key: grid.id,
+            },
+            dom.label(
+              {},
+              dom.input(
+                {
+                  type: "checkbox",
+                  value: grid.id,
+                  checked: grid.highlighted,
+                  onChange: this.onGridCheckboxClick,
+                }
+              ),
+              gridName
+            )
+          );
+        })
+      )
+    );
+  },
+
+});
+
--- a/devtools/client/inspector/layout/components/moz.build
+++ b/devtools/client/inspector/layout/components/moz.build
@@ -4,9 +4,11 @@
 # 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/.
 
 DevToolsModules(
     'Accordion.css',
     'Accordion.js',
     'App.js',
     'Grid.js',
+    'GridDisplaySettings.js',
+    'GridList.js',
 )
--- a/devtools/client/inspector/layout/layout.js
+++ b/devtools/client/inspector/layout/layout.js
@@ -4,33 +4,48 @@
 
 "use strict";
 
 const Services = require("Services");
 const { Task } = require("devtools/shared/task");
 const { createFactory, createElement } = require("devtools/client/shared/vendor/react");
 const { Provider } = require("devtools/client/shared/vendor/react-redux");
 
-const { updateGrids } = require("./actions/grids");
+const {
+  updateGridHighlighted,
+  updateGrids,
+} = require("./actions/grids");
+const {
+  updateShowGridLineNumbers,
+  updateShowInfiniteLines,
+} = require("./actions/highlighter-settings");
+
 const App = createFactory(require("./components/App"));
 const Store = require("./store");
 
 const { LocalizationHelper } = require("devtools/shared/l10n");
 const INSPECTOR_L10N =
   new LocalizationHelper("devtools/client/locales/inspector.properties");
 
+const SHOW_GRID_LINE_NUMBERS = "devtools.gridinspector.showGridLineNumbers";
+const SHOW_INFINITE_LINES_PREF = "devtools.gridinspector.showInfiniteLines";
+
 function LayoutView(inspector, window) {
   this.document = window.document;
+  this.highlighters = inspector.highlighters;
   this.inspector = inspector;
   this.store = null;
   this.walker = this.inspector.walker;
 
   this.onGridLayoutChange = this.onGridLayoutChange.bind(this);
+  this.onHighlighterChange = this.onHighlighterChange.bind(this);
   this.onSidebarSelect = this.onSidebarSelect.bind(this);
 
+  this.highlighters.on("grid-highlighter-hidden", this.onHighlighterChange);
+  this.highlighters.on("grid-highlighter-shown", this.onHighlighterChange);
   this.inspector.sidebar.on("select", this.onSidebarSelect);
 
   this.init();
 }
 
 LayoutView.prototype = {
 
   /**
@@ -38,40 +53,105 @@ LayoutView.prototype = {
    * the redux store and adding the view into the inspector sidebar.
    */
   init: Task.async(function* () {
     if (!this.inspector) {
       return;
     }
 
     this.layoutInspector = yield this.inspector.walker.getLayoutInspector();
+    let store = this.store = Store();
 
-    let store = this.store = Store();
+    this.loadHighlighterSettings();
+
+    let app = App({
+
+      /**
+       * Handler for a change in the input checkboxes in the GridList component.
+       * Toggles on/off the grid highlighter for the provided grid container element.
+       *
+       * @param  {NodeFront} node
+       *         The NodeFront of the grid container element for which the grid
+       *         highlighter is toggled on/off for.
+       */
+      onToggleGridHighlighter: node => {
+        let { highlighterSettings } = this.store.getState();
+        this.highlighters.toggleGridHighlighter(node, highlighterSettings);
+      },
+
+      /**
+       * Handler for a change in the show grid line numbers checkbox in the
+       * GridDisplaySettings component. TOggles on/off the option to show the grid line
+       * numbers in the grid highlighter. Refreshes the shown grid highlighter for the
+       * grids currently highlighted.
+       *
+       * @param  {Boolean} enabled
+       *         Whether or not the grid highlighter should show the grid line numbers.
+       */
+      onToggleShowGridLineNumbers: enabled => {
+        this.store.dispatch(updateShowGridLineNumbers(enabled));
+        Services.prefs.setBoolPref(SHOW_GRID_LINE_NUMBERS, enabled);
+
+        let { grids, highlighterSettings } = this.store.getState();
+
+        for (let grid of grids) {
+          if (grid.highlighted) {
+            this.highlighters.showGridHighlighter(grid.nodeFront, highlighterSettings);
+          }
+        }
+      },
+
+      /**
+       * Handler for a change in the extend grid lines infinitely checkbox in the
+       * GridDisplaySettings component. Toggles on/off the option to extend the grid
+       * lines infinitely in the grid highlighter. Refreshes the shown grid highlighter
+       * for grids currently highlighted.
+       *
+       * @param  {Boolean} enabled
+       *         Whether or not the grid highlighter should extend grid lines infinitely.
+       */
+      onToggleShowInfiniteLines: enabled => {
+        this.store.dispatch(updateShowInfiniteLines(enabled));
+        Services.prefs.setBoolPref(SHOW_INFINITE_LINES_PREF, enabled);
+
+        let { grids, highlighterSettings } = this.store.getState();
+
+        for (let grid of grids) {
+          if (grid.highlighted) {
+            this.highlighters.showGridHighlighter(grid.nodeFront, highlighterSettings);
+          }
+        }
+      },
+
+    });
+
     let provider = createElement(Provider, {
       store,
       id: "layoutview",
       title: INSPECTOR_L10N.getStr("inspector.sidebar.layoutViewTitle"),
       key: "layoutview",
-    }, App());
+    }, app);
 
     let defaultTab = Services.prefs.getCharPref("devtools.inspector.activeSidebar");
 
     this.inspector.addSidebarTab(
       "layoutview",
       INSPECTOR_L10N.getStr("inspector.sidebar.layoutViewTitle"),
       provider,
       defaultTab == "layoutview"
     );
   }),
 
   /**
    * Destruction function called when the inspector is destroyed. Removes event listeners
    * and cleans up references.
    */
   destroy() {
+    this.highlighters.off("grid-highlighter-hidden", this.onHighlighterChange);
+    this.highlighters.off("grid-highlighter-shown", this.onHighlighterChange);
     this.inspector.sidebar.off("select", this.onSidebarSelect);
     this.layoutInspector.off("grid-layout-changed", this.onGridLayoutChange);
 
     this.document = null;
     this.inspector = null;
     this.layoutInspector = null;
     this.store = null;
     this.walker = null;
@@ -82,16 +162,29 @@ LayoutView.prototype = {
    */
   isPanelVisible() {
     return this.inspector.toolbox.currentToolId === "inspector" &&
            this.inspector.sidebar &&
            this.inspector.sidebar.getCurrentTabID() === "layoutview";
   },
 
   /**
+   * Load the grid highligher display settings into the store from the stored preferences.
+   */
+  loadHighlighterSettings() {
+    let { dispatch } = this.store;
+
+    let showGridLineNumbers = Services.prefs.getBoolPref(SHOW_GRID_LINE_NUMBERS);
+    let showInfinteLines = Services.prefs.getBoolPref(SHOW_INFINITE_LINES_PREF);
+
+    dispatch(updateShowGridLineNumbers(showGridLineNumbers));
+    dispatch(updateShowInfiniteLines(showInfinteLines));
+  },
+
+  /**
    * Refreshes the layout view by dispatching the new grid data. This is called when the
    * layout view becomes visible or the view needs to be updated with new grid data.
    *
    * @param {Array|null} gridFronts
    *        Optional array of all GridFront in the current page.
    */
   refresh: Task.async(function* (gridFronts) {
     // Stop refreshing if the inspector or store is already destroyed.
@@ -106,37 +199,53 @@ LayoutView.prototype = {
 
     let grids = [];
     for (let i = 0; i < gridFronts.length; i++) {
       let grid = gridFronts[i];
       let nodeFront = yield this.walker.getNodeFromActor(grid.actorID, ["containerEl"]);
 
       grids.push({
         id: i,
+        gridFragments: grid.gridFragments,
+        highlighted: nodeFront == this.highlighters.gridHighlighterShown,
         nodeFront,
-        gridFragments: grid.gridFragments
       });
     }
 
     this.store.dispatch(updateGrids(grids));
   }),
 
   /**
-   * Handler for 'grid-layout-changed' events emitted from the LayoutActor.
+   * Handler for "grid-layout-changed" events emitted from the LayoutActor.
    *
    * @param  {Array} grids
    *         Array of all GridFront in the current page.
    */
   onGridLayoutChange(grids) {
     if (this.isPanelVisible()) {
       this.refresh(grids);
     }
   },
 
   /**
+   * Handler for "grid-highlighter-shown" and "grid-highlighter-hidden" events emitted
+   * from the HighlightersOverlay. Updates the NodeFront's grid highlighted state.
+   *
+   * @param  {Event} event
+   *         Event that was triggered.
+   * @param  {NodeFront} nodeFront
+   *         The NodeFront of the grid container element for which the grid highlighter
+   *         is shown for.
+   */
+  onHighlighterChange(event, nodeFront) {
+    let highlighted = event === "grid-highlighter-shown";
+    this.store.dispatch(updateGridHighlighted(nodeFront, highlighted));
+  },
+
+  /**
    * Handler for the inspector sidebar select event. Starts listening for
    * "grid-layout-changed" if the layout panel is visible. Otherwise, stop
    * listening for grid layout changes. Finally, refresh the layout view if
    * it is visible.
    */
   onSidebarSelect() {
     if (!this.isPanelVisible()) {
       this.layoutInspector.off("grid-layout-changed", this.onGridLayoutChange);
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/layout/reducers/highlighter-settings.js
@@ -0,0 +1,39 @@
+/* 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 {
+  UPDATE_SHOW_GRID_LINE_NUMBERS,
+  UPDATE_SHOW_INFINITE_LINES
+} = require("../actions/index");
+
+const INITIAL_HIGHLIGHTER_SETTINGS = {
+  showGridLineNumbers: false,
+  showInfiniteLines: false,
+};
+
+let reducers = {
+
+  [UPDATE_SHOW_GRID_LINE_NUMBERS](highlighterSettings, { enabled }) {
+    return Object.assign({}, highlighterSettings, {
+      showGridLineNumbers: enabled,
+    });
+  },
+
+  [UPDATE_SHOW_INFINITE_LINES](highlighterSettings, { enabled }) {
+    return Object.assign({}, highlighterSettings, {
+      showInfiniteLines: enabled,
+    });
+  },
+
+};
+
+module.exports = function (highlighterSettings = INITIAL_HIGHLIGHTER_SETTINGS, action) {
+  let reducer = reducers[action.type];
+  if (!reducer) {
+    return highlighterSettings;
+  }
+  return reducer(highlighterSettings, action);
+};
--- a/devtools/client/inspector/layout/reducers/index.js
+++ b/devtools/client/inspector/layout/reducers/index.js
@@ -1,7 +1,8 @@
 /* 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";
 
 exports.grids = require("./grids");
+exports.highlighterSettings = require("./highlighter-settings");
--- a/devtools/client/inspector/layout/reducers/moz.build
+++ b/devtools/client/inspector/layout/reducers/moz.build
@@ -1,10 +1,11 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
     'grids.js',
+    'highlighter-settings.js',
     'index.js',
 )
--- a/devtools/client/inspector/layout/types.js
+++ b/devtools/client/inspector/layout/types.js
@@ -10,17 +10,30 @@ const { PropTypes } = require("devtools/
  * A single grid container in the document.
  */
 exports.grid = {
 
   // The id of the grid
   id: PropTypes.number,
 
   // The grid fragment object of the grid container
-  gridFragments: PropTypes.object,
+  gridFragments: PropTypes.array,
 
   // Whether or not the grid highlighter is highlighting the grid
   highlighted: PropTypes.bool,
 
   // The node front of the grid container
   nodeFront: PropTypes.object,
 
 };
+
+/**
+ * The grid highlighter settings on what to display in its grid overlay in the document.
+ */
+exports.highlighterSettings = {
+
+  // Whether or not the grid highlighter should show the grid line numbers
+  showGridLineNumbers: PropTypes.bool,
+
+  // Whether or not the grid highlighter extends the grid lines infinitely
+  showInfiniteLines: PropTypes.bool,
+
+};
--- a/devtools/client/inspector/rules/test/browser_rules_edit-display-grid-property.js
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-display-grid-property.js
@@ -24,23 +24,23 @@ add_task(function* () {
   let {inspector, view} = yield openRuleView();
   let highlighters = view.highlighters;
 
   yield selectNode("#grid", inspector);
   let container = getRuleViewProperty(view, "#grid", "display").valueSpan;
   let gridToggle = container.querySelector(".ruleview-grid");
 
   info("Toggling ON the CSS grid highlighter from the rule-view.");
-  let onHighlighterShown = highlighters.once("highlighter-shown");
+  let onHighlighterShown = highlighters.once("grid-highlighter-shown");
   gridToggle.click();
   yield onHighlighterShown;
 
   info("Edit the 'grid' property value to 'block'.");
   let editor = yield focusEditableField(view, container);
-  let onHighlighterHidden = highlighters.once("highlighter-hidden");
+  let onHighlighterHidden = highlighters.once("grid-highlighter-hidden");
   let onDone = view.once("ruleview-changed");
   editor.input.value = "block;";
   EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
   yield onHighlighterHidden;
   yield onDone;
 
   info("Check the grid highlighter and grid toggle button are hidden.");
   gridToggle = container.querySelector(".ruleview-grid");
--- a/devtools/client/inspector/rules/test/browser_rules_grid-highlighter-on-navigate.js
+++ b/devtools/client/inspector/rules/test/browser_rules_grid-highlighter-on-navigate.js
@@ -25,17 +25,17 @@ add_task(function* () {
   let {inspector, view} = yield openRuleView();
   let highlighters = view.highlighters;
 
   yield selectNode("#grid", inspector);
   let container = getRuleViewProperty(view, "#grid", "display").valueSpan;
   let gridToggle = container.querySelector(".ruleview-grid");
 
   info("Toggling ON the CSS grid highlighter from the rule-view.");
-  let onHighlighterShown = highlighters.once("highlighter-shown");
+  let onHighlighterShown = highlighters.once("grid-highlighter-shown");
   gridToggle.click();
   yield onHighlighterShown;
 
   ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown.");
 
   yield navigateTo(inspector, TEST_URI_2);
   ok(!highlighters.gridHighlighterShown, "CSS grid highlighter is hidden.");
 });
--- a/devtools/client/inspector/rules/test/browser_rules_grid-toggle_01.js
+++ b/devtools/client/inspector/rules/test/browser_rules_grid-toggle_01.js
@@ -34,30 +34,30 @@ add_task(function* () {
   ok(gridToggle, "Grid highlighter toggle is visible.");
   ok(!gridToggle.classList.contains("active"),
     "Grid highlighter toggle button is not active.");
   ok(!highlighters.highlighters[HIGHLIGHTER_TYPE],
     "No CSS grid highlighter exists in the rule-view.");
   ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
 
   info("Toggling ON the CSS grid highlighter from the rule-view.");
-  let onHighlighterShown = highlighters.once("highlighter-shown");
+  let onHighlighterShown = highlighters.once("grid-highlighter-shown");
   gridToggle.click();
   yield onHighlighterShown;
 
   info("Checking the CSS grid highlighter is created and toggle button is active in " +
     "the rule-view.");
   ok(gridToggle.classList.contains("active"),
     "Grid highlighter toggle is active.");
   ok(highlighters.highlighters[HIGHLIGHTER_TYPE],
     "CSS grid highlighter created in the rule-view.");
   ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown.");
 
   info("Toggling OFF the CSS grid highlighter from the rule-view.");
-  let onHighlighterHidden = highlighters.once("highlighter-hidden");
+  let onHighlighterHidden = highlighters.once("grid-highlighter-hidden");
   gridToggle.click();
   yield onHighlighterHidden;
 
   info("Checking the CSS grid highlighter is not shown and toggle button is not active " +
     "in the rule-view.");
   ok(!gridToggle.classList.contains("active"),
     "Grid highlighter toggle button is not active.");
   ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
--- a/devtools/client/inspector/rules/test/browser_rules_grid-toggle_02.js
+++ b/devtools/client/inspector/rules/test/browser_rules_grid-toggle_02.js
@@ -40,32 +40,32 @@ add_task(function* () {
   ok(!gridToggle.classList.contains("active") &&
     !overriddenGridToggle.classList.contains("active"),
     "Grid highlighter toggle buttons are not active.");
   ok(!highlighters.highlighters[HIGHLIGHTER_TYPE],
     "No CSS grid highlighter exists in the rule-view.");
   ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
 
   info("Toggling ON the CSS grid highlighter from the overridden rule in the rule-view.");
-  let onHighlighterShown = highlighters.once("highlighter-shown");
+  let onHighlighterShown = highlighters.once("grid-highlighter-shown");
   overriddenGridToggle.click();
   yield onHighlighterShown;
 
   info("Checking the CSS grid highlighter is created and toggle buttons are active in " +
     "the rule-view.");
   ok(gridToggle.classList.contains("active") &&
     overriddenGridToggle.classList.contains("active"),
     "Grid highlighter toggle is active.");
   ok(highlighters.highlighters[HIGHLIGHTER_TYPE],
     "CSS grid highlighter created in the rule-view.");
   ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown.");
 
   info("Toggling off the CSS grid highlighter from the normal grid declaration in the " +
     "rule-view.");
-  let onHighlighterHidden = highlighters.once("highlighter-hidden");
+  let onHighlighterHidden = highlighters.once("grid-highlighter-hidden");
   gridToggle.click();
   yield onHighlighterHidden;
 
   info("Checking the CSS grid highlighter is not shown and toggle buttons are not " +
     "active in the rule-view.");
   ok(!gridToggle.classList.contains("active") &&
     !overriddenGridToggle.classList.contains("active"),
     "Grid highlighter toggle buttons are not active.");
--- a/devtools/client/inspector/rules/test/browser_rules_grid-toggle_03.js
+++ b/devtools/client/inspector/rules/test/browser_rules_grid-toggle_03.js
@@ -40,17 +40,17 @@ add_task(function* () {
   ok(!gridToggle.classList.contains("active"),
     "Grid highlighter toggle button is not active.");
   ok(!highlighters.highlighters[HIGHLIGHTER_TYPE],
     "No CSS grid highlighter exists in the rule-view.");
   ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
 
   info("Toggling ON the CSS grid highlighter for the first grid container from the " +
     "rule-view.");
-  let onHighlighterShown = highlighters.once("highlighter-shown");
+  let onHighlighterShown = highlighters.once("grid-highlighter-shown");
   gridToggle.click();
   yield onHighlighterShown;
 
   info("Checking the CSS grid highlighter is created and toggle button is active in " +
     "the rule-view.");
   ok(gridToggle.classList.contains("active"),
     "Grid highlighter toggle is active.");
   ok(highlighters.highlighters[HIGHLIGHTER_TYPE],
@@ -67,17 +67,17 @@ add_task(function* () {
     "rule-view.");
   ok(gridToggle, "Grid highlighter toggle is visible.");
   ok(!gridToggle.classList.contains("active"),
     "Grid highlighter toggle button is not active.");
   ok(highlighters.gridHighlighterShown, "CSS grid highlighter is still shown.");
 
   info("Toggling ON the CSS grid highlighter for the second grid container from the " +
     "rule-view.");
-  onHighlighterShown = highlighters.once("highlighter-shown");
+  onHighlighterShown = highlighters.once("grid-highlighter-shown");
   gridToggle.click();
   yield onHighlighterShown;
 
   info("Checking the CSS grid highlighter is created for the second grid container and " +
     "toggle button is active in the rule-view.");
   ok(gridToggle.classList.contains("active"),
     "Grid highlighter toggle is active.");
   ok(highlighters.gridHighlighterShown != firstGridHighterShown,
--- a/devtools/client/inspector/rules/views/text-property-editor.js
+++ b/devtools/client/inspector/rules/views/text-property-editor.js
@@ -731,17 +731,17 @@ TextPropertyEditor.prototype = {
     if (value.trim() && isValueUnchanged) {
       this.ruleEditor.rule.previewPropertyValue(this.prop, val.value,
                                                 val.priority);
       this.rule.setPropertyEnabled(this.prop, this.prop.enabled);
       return;
     }
 
     if (this.isDisplayGrid()) {
-      this.ruleView.highlighters._hideGridHighlighter();
+      this.ruleView.highlighters.hideGridHighlighter();
     }
 
     // First, set this property value (common case, only modified a property)
     this.prop.setValue(val.value, val.priority);
 
     if (!this.prop.enabled) {
       this.prop.setEnabled(true);
     }
--- a/devtools/client/inspector/shared/highlighters-overlay.js
+++ b/devtools/client/inspector/shared/highlighters-overlay.js
@@ -1,22 +1,18 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-/**
- * The highlighter overlays are in-content highlighters that appear when hovering over
- * property values.
- */
-
 const promise = require("promise");
+const {Task} = require("devtools/shared/task");
 const EventEmitter = require("devtools/shared/event-emitter");
 const { VIEW_NODE_VALUE_TYPE } = require("devtools/client/inspector/shared/node-types");
 
 /**
  * Highlighters overlay is a singleton managing all highlighters in the Inspector.
  *
  * @param  {Inspector} inspector
  *         Inspector toolbox panel.
@@ -31,20 +27,20 @@ function HighlightersOverlay(inspector) 
 
   // NodeFront of the grid container that is highlighted.
   this.gridHighlighterShown = null;
   // Name of the highlighter shown on mouse hover.
   this.hoveredHighlighterShown = null;
   // Name of the selector highlighter shown.
   this.selectorHighlighterShown = null;
 
-  this._onClick = this._onClick.bind(this);
-  this._onMouseMove = this._onMouseMove.bind(this);
-  this._onMouseOut = this._onMouseOut.bind(this);
-  this._onWillNavigate = this._onWillNavigate.bind(this);
+  this.onClick = this.onClick.bind(this);
+  this.onMouseMove = this.onMouseMove.bind(this);
+  this.onMouseOut = this.onMouseOut.bind(this);
+  this.onWillNavigate = this.onWillNavigate.bind(this);
 
   EventEmitter.decorate(this);
 }
 
 HighlightersOverlay.prototype = {
   get isRuleView() {
     return this.inspector.sidebar.getCurrentTabID() == "ruleview";
   },
@@ -58,81 +54,223 @@ HighlightersOverlay.prototype = {
    *
    */
   addToView: function (view) {
     if (!this.supportsHighlighters) {
       return;
     }
 
     let el = view.element;
-    el.addEventListener("click", this._onClick, true);
-    el.addEventListener("mousemove", this._onMouseMove, false);
-    el.addEventListener("mouseout", this._onMouseOut, false);
-    el.ownerDocument.defaultView.addEventListener("mouseout", this._onMouseOut, false);
+    el.addEventListener("click", this.onClick, true);
+    el.addEventListener("mousemove", this.onMouseMove, false);
+    el.addEventListener("mouseout", this.onMouseOut, false);
+    el.ownerDocument.defaultView.addEventListener("mouseout", this.onMouseOut, false);
 
-    this.inspector.target.on("will-navigate", this._onWillNavigate);
+    this.inspector.target.on("will-navigate", this.onWillNavigate);
   },
 
   /**
    * Remove the overlay from the given view. This will stop tracking mouse movement and
    * showing highlighters.
    *
    * @param  {CssRuleView|CssComputedView|LayoutView} view
    *         Either the rule-view or computed-view panel to remove the highlighters
    *         overlay.
    */
   removeFromView: function (view) {
     if (!this.supportsHighlighters) {
       return;
     }
 
     let el = view.element;
-    el.removeEventListener("click", this._onClick, true);
-    el.removeEventListener("mousemove", this._onMouseMove, false);
-    el.removeEventListener("mouseout", this._onMouseOut, false);
+    el.removeEventListener("click", this.onClick, true);
+    el.removeEventListener("mousemove", this.onMouseMove, false);
+    el.removeEventListener("mouseout", this.onMouseOut, false);
 
-    this.inspector.target.off("will-navigate", this._onWillNavigate);
+    this.inspector.target.off("will-navigate", this.onWillNavigate);
   },
 
-  _onClick: function (event) {
+  /**
+   * Toggle the grid highlighter for the given grid container element.
+   *
+   * @param  {NodeFront} node
+   *         The NodeFront of the grid container element to highlight.
+   * @param  {Object} options
+   *         Object used for passing options to the grid highlighter.
+   */
+  toggleGridHighlighter: Task.async(function* (node, options = {}) {
+    if (node == this.gridHighlighterShown) {
+      yield this.hideGridHighlighter(node);
+      return;
+    }
+
+    yield this.showGridHighlighter(node, options);
+  }),
+
+  /**
+   * Show the grid highlighter for the given grid container element.
+   *
+   * @param  {NodeFront} node
+   *         The NodeFront of the grid container element to highlight.
+   * @param  {Object} options
+   *         Object used for passing options to the grid highlighter.
+   */
+  showGridHighlighter: Task.async(function* (node, options) {
+    let highlighter = yield this._getHighlighter("CssGridHighlighter");
+    if (!highlighter) {
+      return;
+    }
+
+    let isShown = yield highlighter.show(node, options);
+    if (!isShown) {
+      return;
+    }
+
+    this._toggleRuleViewGridIcon(node, true);
+
+    // Emit the NodeFront of the grid container element that the grid highlighter was
+    // shown for.
+    this.emit("grid-highlighter-shown", node);
+    this.gridHighlighterShown = node;
+  }),
+
+  /**
+   * Hide the grid highlighter for the given grid container element.
+   *
+   * @param  {NodeFront} node
+   *         The NodeFront of the grid container element to unhighlight.
+   */
+  hideGridHighlighter: Task.async(function* (node) {
+    if (!this.gridHighlighterShown || !this.highlighters.CssGridHighlighter) {
+      return;
+    }
+
+    this._toggleRuleViewGridIcon(node, false);
+
+    yield this.highlighters.CssGridHighlighter.hide();
+
+    // Emit the NodeFront of the grid container element that the grid highlighter was
+    // hidden for.
+    this.emit("grid-highlighter-hidden", this.gridHighlighterShown);
+    this.gridHighlighterShown = null;
+  }),
+
+  /**
+   * Get a highlighter front given a type. It will only be initialized once.
+   *
+   * @param  {String} type
+   *         The highlighter type. One of this.highlighters.
+   * @return {Promise} that resolves to the highlighter
+   */
+  _getHighlighter: function (type) {
+    let utils = this.highlighterUtils;
+
+    if (this.highlighters[type]) {
+      return promise.resolve(this.highlighters[type]);
+    }
+
+    return utils.getHighlighterByType(type).then(highlighter => {
+      this.highlighters[type] = highlighter;
+      return highlighter;
+    });
+  },
+
+  /**
+   * Toggle all the grid icons in the rule view if the current inspector selection is the
+   * highlighted node.
+   *
+   * @param  {NodeFront} node
+   *         The NodeFront of the grid container element to highlight.
+   * @param  {Boolean} active
+   *         Whether or not the grid icon should be active.
+   */
+  _toggleRuleViewGridIcon: function (node, active) {
+    if (this.inspector.selection.nodeFront != node) {
+      return;
+    }
+
+    let ruleViewEl = this.inspector.ruleview.view.element;
+
+    for (let gridIcon of ruleViewEl.querySelectorAll(".ruleview-grid")) {
+      gridIcon.classList.toggle("active", active);
+    }
+  },
+
+  /**
+   * Hide the currently shown hovered highlighter.
+   */
+  _hideHoveredHighlighter: function () {
+    if (!this.hoveredHighlighterShown ||
+        !this.highlighters[this.hoveredHighlighterShown]) {
+      return;
+    }
+
+    // For some reason, the call to highlighter.hide doesn't always return a
+    // promise. This causes some tests to fail when trying to install a
+    // rejection handler on the result of the call. To avoid this, check
+    // whether the result is truthy before installing the handler.
+    let onHidden = this.highlighters[this.hoveredHighlighterShown].hide();
+    if (onHidden) {
+      onHidden.then(null, e => console.error(e));
+    }
+
+    this.hoveredHighlighterShown = null;
+    this.emit("highlighter-hidden");
+  },
+
+  /**
+   * Is the current hovered node a css transform property value in the
+   * computed-view.
+   *
+   * @param  {Object} nodeInfo
+   * @return {Boolean}
+   */
+  _isComputedViewTransform: function (nodeInfo) {
+    let isTransform = nodeInfo.type === VIEW_NODE_VALUE_TYPE &&
+                      nodeInfo.value.property === "transform";
+    return !this.isRuleView && isTransform;
+  },
+
+  /**
+   * Is the current clicked node a grid display property value in the
+   * rule-view.
+   *
+   * @param  {DOMNode} node
+   * @return {Boolean}
+   */
+  _isRuleViewDisplayGrid: function (node) {
+    return this.isRuleView && node.classList.contains("ruleview-grid");
+  },
+
+  /**
+   * Is the current hovered node a css transform property value in the rule-view.
+   *
+   * @param  {Object} nodeInfo
+   * @return {Boolean}
+   */
+  _isRuleViewTransform: function (nodeInfo) {
+    let isTransform = nodeInfo.type === VIEW_NODE_VALUE_TYPE &&
+                      nodeInfo.value.property === "transform";
+    let isEnabled = nodeInfo.value.enabled &&
+                    !nodeInfo.value.overridden &&
+                    !nodeInfo.value.pseudoElement;
+    return this.isRuleView && isTransform && isEnabled;
+  },
+
+  onClick: function (event) {
     // Bail out if the target is not a grid property value.
     if (!this._isRuleViewDisplayGrid(event.target)) {
       return;
     }
 
     event.stopPropagation();
-
-    this._getHighlighter("CssGridHighlighter").then(highlighter => {
-      let node = this.inspector.selection.nodeFront;
-
-      // Toggle off the grid highlighter if the grid highlighter toggle is clicked
-      // for the current highlighted grid.
-      if (node === this.gridHighlighterShown) {
-        return highlighter.hide();
-      }
-
-      return highlighter.show(node);
-    }).then(isGridShown => {
-      // Toggle all the grid icons in the current rule view.
-      let ruleViewEl = this.inspector.ruleview.view.element;
-      for (let gridIcon of ruleViewEl.querySelectorAll(".ruleview-grid")) {
-        gridIcon.classList.toggle("active", isGridShown);
-      }
-
-      if (isGridShown) {
-        this.gridHighlighterShown = this.inspector.selection.nodeFront;
-        this.emit("highlighter-shown");
-      } else {
-        this.gridHighlighterShown = null;
-        this.emit("highlighter-hidden");
-      }
-    }).catch(e => console.error(e));
+    this.toggleGridHighlighter(this.inspector.selection.nodeFront);
   },
 
-  _onMouseMove: function (event) {
+  onMouseMove: function (event) {
     // Bail out if the target is the same as for the last mousemove.
     if (event.target === this._lastHovered) {
       return;
     }
 
     // Only one highlighter can be displayed at a time, hide the currently shown.
     this._hideHoveredHighlighter();
 
@@ -160,147 +298,51 @@ HighlightersOverlay.prototype = {
           .then(shown => {
             if (shown) {
               this.emit("highlighter-shown");
             }
           });
     }
   },
 
-  _onMouseOut: function (event) {
+  onMouseOut: function (event) {
     // Only hide the highlighter if the mouse leaves the currently hovered node.
     if (!this._lastHovered ||
         (event && this._lastHovered.contains(event.relatedTarget))) {
       return;
     }
 
     // Otherwise, hide the highlighter.
     this._lastHovered = null;
     this._hideHoveredHighlighter();
   },
 
   /**
    * Clear saved highlighter shown properties on will-navigate.
    */
-  _onWillNavigate: function () {
+  onWillNavigate: function () {
     this.gridHighlighterShown = null;
     this.hoveredHighlighterShown = null;
     this.selectorHighlighterShown = null;
   },
 
   /**
-   * Is the current hovered node a css transform property value in the rule-view.
-   *
-   * @param  {Object} nodeInfo
-   * @return {Boolean}
-   */
-  _isRuleViewTransform: function (nodeInfo) {
-    let isTransform = nodeInfo.type === VIEW_NODE_VALUE_TYPE &&
-                      nodeInfo.value.property === "transform";
-    let isEnabled = nodeInfo.value.enabled &&
-                    !nodeInfo.value.overridden &&
-                    !nodeInfo.value.pseudoElement;
-    return this.isRuleView && isTransform && isEnabled;
-  },
-
-  /**
-   * Is the current hovered node a css transform property value in the
-   * computed-view.
-   *
-   * @param  {Object} nodeInfo
-   * @return {Boolean}
-   */
-  _isComputedViewTransform: function (nodeInfo) {
-    let isTransform = nodeInfo.type === VIEW_NODE_VALUE_TYPE &&
-                      nodeInfo.value.property === "transform";
-    return !this.isRuleView && isTransform;
-  },
-
-  /**
-   * Is the current clicked node a grid display property value in the
-   * rule-view.
-   *
-   * @param  {DOMNode} node
-   * @return {Boolean}
-   */
-  _isRuleViewDisplayGrid: function (node) {
-    return this.isRuleView && node.classList.contains("ruleview-grid");
-  },
-
-  /**
-   * Hide the currently shown grid highlighter.
-   */
-  _hideGridHighlighter: function () {
-    if (!this.gridHighlighterShown || !this.highlighters.CssGridHighlighter) {
-      return;
-    }
-
-    let onHidden = this.highlighters.CssGridHighlighter.hide();
-    if (onHidden) {
-      onHidden.then(null, e => console.error(e));
-    }
-
-    this.gridHighlighterShown = null;
-    this.emit("highlighter-hidden");
-  },
-
-  /**
-   * Hide the currently shown hovered highlighter.
-   */
-  _hideHoveredHighlighter: function () {
-    if (!this.hoveredHighlighterShown ||
-        !this.highlighters[this.hoveredHighlighterShown]) {
-      return;
-    }
-
-    // For some reason, the call to highlighter.hide doesn't always return a
-    // promise. This causes some tests to fail when trying to install a
-    // rejection handler on the result of the call. To avoid this, check
-    // whether the result is truthy before installing the handler.
-    let onHidden = this.highlighters[this.hoveredHighlighterShown].hide();
-    if (onHidden) {
-      onHidden.then(null, e => console.error(e));
-    }
-
-    this.hoveredHighlighterShown = null;
-    this.emit("highlighter-hidden");
-  },
-
-  /**
-   * Get a highlighter front given a type. It will only be initialized once.
-   *
-   * @param  {String} type
-   *         The highlighter type. One of this.highlighters.
-   * @return {Promise} that resolves to the highlighter
-   */
-  _getHighlighter: function (type) {
-    let utils = this.highlighterUtils;
-
-    if (this.highlighters[type]) {
-      return promise.resolve(this.highlighters[type]);
-    }
-
-    return utils.getHighlighterByType(type).then(highlighter => {
-      this.highlighters[type] = highlighter;
-      return highlighter;
-    });
-  },
-
-  /**
    * Destroy this overlay instance, removing it from the view and destroying
    * all initialized highlighters.
    */
   destroy: function () {
     for (let type in this.highlighters) {
       if (this.highlighters[type]) {
         this.highlighters[type].finalize();
         this.highlighters[type] = null;
       }
     }
 
+    this._lastHovered = null;
+
     this.inspector = null;
     this.highlighters = null;
     this.highlighterUtils = null;
     this.supportsHighlighters = null;
     this.gridHighlighterShown = null;
     this.hoveredHighlighterShown = null;
     this.selectorHighlighterShown = null;
   }
--- a/devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-02.js
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-02.js
@@ -23,40 +23,40 @@ add_task(function* () {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {inspector, view} = yield openRuleView();
   let hs = view.highlighters;
 
   ok(!hs.highlighters[TYPE], "No highlighter exists in the rule-view (1)");
 
   info("Faking a mousemove on a non-transform property");
   let {valueSpan} = getRuleViewProperty(view, "body", "color");
-  hs._onMouseMove({target: valueSpan});
+  hs.onMouseMove({target: valueSpan});
   ok(!hs.highlighters[TYPE], "No highlighter exists in the rule-view (2)");
 
   info("Faking a mousemove on a transform property");
   ({valueSpan} = getRuleViewProperty(view, "body", "transform"));
   let onHighlighterShown = hs.once("highlighter-shown");
-  hs._onMouseMove({target: valueSpan});
+  hs.onMouseMove({target: valueSpan});
   yield onHighlighterShown;
 
   let onComputedViewReady = inspector.once("computed-view-refreshed");
   let cView = selectComputedView(inspector);
   yield onComputedViewReady;
   hs = cView.highlighters;
 
   info("Remove the created transform highlighter");
   hs.highlighters[TYPE].finalize();
   hs.highlighters[TYPE] = null;
 
   info("Faking a mousemove on a non-transform property");
   ({valueSpan} = getComputedViewProperty(cView, "color"));
-  hs._onMouseMove({target: valueSpan});
+  hs.onMouseMove({target: valueSpan});
   ok(!hs.highlighters[TYPE], "No highlighter exists in the computed-view (3)");
 
   info("Faking a mousemove on a transform property");
   ({valueSpan} = getComputedViewProperty(cView, "transform"));
   onHighlighterShown = hs.once("highlighter-shown");
-  hs._onMouseMove({target: valueSpan});
+  hs.onMouseMove({target: valueSpan});
   yield onHighlighterShown;
 
   ok(hs.highlighters[TYPE],
     "The highlighter has been created in the computed-view");
 });
--- a/devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-03.js
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-03.js
@@ -51,53 +51,53 @@ add_task(function* () {
   // Inject the mock highlighter in the rule-view
   let hs = view.highlighters;
   hs.highlighters[TYPE] = HighlighterFront;
 
   let {valueSpan} = getRuleViewProperty(view, "body", "transform");
 
   info("Checking that the HighlighterFront's show/hide methods are called");
   let onHighlighterShown = hs.once("highlighter-shown");
-  hs._onMouseMove({target: valueSpan});
+  hs.onMouseMove({target: valueSpan});
   yield onHighlighterShown;
   ok(HighlighterFront.isShown, "The highlighter is shown");
   let onHighlighterHidden = hs.once("highlighter-hidden");
-  hs._onMouseOut();
+  hs.onMouseOut();
   yield onHighlighterHidden;
   ok(!HighlighterFront.isShown, "The highlighter is hidden");
 
   info("Checking that hovering several times over the same property doesn't" +
     " show the highlighter several times");
   let nb = HighlighterFront.nbOfTimesShown;
   onHighlighterShown = hs.once("highlighter-shown");
-  hs._onMouseMove({target: valueSpan});
+  hs.onMouseMove({target: valueSpan});
   yield onHighlighterShown;
   is(HighlighterFront.nbOfTimesShown, nb + 1, "The highlighter was shown once");
-  hs._onMouseMove({target: valueSpan});
-  hs._onMouseMove({target: valueSpan});
+  hs.onMouseMove({target: valueSpan});
+  hs.onMouseMove({target: valueSpan});
   is(HighlighterFront.nbOfTimesShown, nb + 1,
     "The highlighter was shown once, after several mousemove");
 
   info("Checking that the right NodeFront reference is passed");
   yield selectNode("html", inspector);
   ({valueSpan} = getRuleViewProperty(view, "html", "transform"));
   onHighlighterShown = hs.once("highlighter-shown");
-  hs._onMouseMove({target: valueSpan});
+  hs.onMouseMove({target: valueSpan});
   yield onHighlighterShown;
   is(HighlighterFront.nodeFront.tagName, "HTML",
     "The right NodeFront is passed to the highlighter (1)");
 
   yield selectNode("body", inspector);
   ({valueSpan} = getRuleViewProperty(view, "body", "transform"));
   onHighlighterShown = hs.once("highlighter-shown");
-  hs._onMouseMove({target: valueSpan});
+  hs.onMouseMove({target: valueSpan});
   yield onHighlighterShown;
   is(HighlighterFront.nodeFront.tagName, "BODY",
     "The right NodeFront is passed to the highlighter (2)");
 
   info("Checking that the highlighter gets hidden when hovering a " +
     "non-transform property");
   ({valueSpan} = getRuleViewProperty(view, "body", "color"));
   onHighlighterHidden = hs.once("highlighter-hidden");
-  hs._onMouseMove({target: valueSpan});
+  hs.onMouseMove({target: valueSpan});
   yield onHighlighterHidden;
   ok(!HighlighterFront.isShown, "The highlighter is hidden");
 });
--- a/devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-04.js
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-04.js
@@ -31,30 +31,30 @@ add_task(function* () {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {inspector, view} = yield openRuleView();
   yield selectNode(".test", inspector);
 
   let hs = view.highlighters;
 
   info("Faking a mousemove on the overriden property");
   let {valueSpan} = getRuleViewProperty(view, "div", "transform");
-  hs._onMouseMove({target: valueSpan});
+  hs.onMouseMove({target: valueSpan});
   ok(!hs.highlighters[TYPE],
     "No highlighter was created for the overriden property");
 
   info("Disabling the applied property");
   let classRuleEditor = getRuleViewRuleEditor(view, 1);
   let propEditor = classRuleEditor.rule.textProps[0].editor;
   propEditor.enable.click();
   yield classRuleEditor.rule._applyingModifications;
 
   info("Faking a mousemove on the disabled property");
   ({valueSpan} = getRuleViewProperty(view, ".test", "transform"));
-  hs._onMouseMove({target: valueSpan});
+  hs.onMouseMove({target: valueSpan});
   ok(!hs.highlighters[TYPE],
     "No highlighter was created for the disabled property");
 
   info("Faking a mousemove on the now unoverriden property");
   ({valueSpan} = getRuleViewProperty(view, "div", "transform"));
   let onHighlighterShown = hs.once("highlighter-shown");
-  hs._onMouseMove({target: valueSpan});
+  hs.onMouseMove({target: valueSpan});
   yield onHighlighterShown;
 });
--- a/devtools/client/locales/en-US/layout.properties
+++ b/devtools/client/locales/en-US/layout.properties
@@ -2,14 +2,30 @@
 # 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/.
 
 # LOCALIZATION NOTE This file contains the Layout Inspector strings.
 # The Layout Inspector is a panel accessible in the Inspector sidebar.
 # The Layout Inspector may need to be enabled in about:config by setting
 # devtools.layoutview.enabled to true.
 
+# LOCALIZATION NOTE (layout.displayNumbersOnLines): Label of the display numbers on lines
+# setting option in the CSS Grid pane.
+layout.displayNumbersOnLines=Display numbers on lines
+
+# LOCALIZATION NOTE (layout.extendGridLinesInfinitely): Label of the extend grid lines
+# infinitely setting option in the CSS Grid pane.
+layout.extendGridLinesInfinitely=Extend grid lines infinitely
+
 # LOCALIZATION NOTE (layout.header): The accordion header for the CSS Grid pane.
 layout.header=Grid
 
+# LOCALIZATION NOTE (layout.gridDisplaySettings): The header for the grid display
+# settings container in the CSS Grid pane.
+layout.gridDisplaySettings=Grid Display Settings
+
 # LOCALIZATION NOTE (layout.noGrids): In the case where there are no CSS grid
 # containers to display.
 layout.noGrids=No grids
+
+# LOCALIZATION NOTE (layout.overlayMultipleGrids): The header for the list of grid
+# container elements that can be highlighted in the CSS Grid pane.
+layout.overlayMultipleGrids=Overlay Multiple Grids
--- a/devtools/client/preferences/devtools.js
+++ b/devtools/client/preferences/devtools.js
@@ -66,16 +66,17 @@ pref("devtools.inspector.mdnDocsTooltip.
 
 // Enable the Font Inspector
 pref("devtools.fontinspector.enabled", true);
 
 // Enable the Layout View
 pref("devtools.layoutview.enabled", false);
 
 // Grid highlighter preferences
+pref("devtools.gridinspector.showGridLineNumbers", false);
 pref("devtools.gridinspector.showInfiniteLines", false);
 
 // By how many times eyedropper will magnify pixels
 pref("devtools.eyedropper.zoom", 6);
 
 // Enable to collapse attributes that are too long.
 pref("devtools.markup.collapseAttributes", true);
 
--- a/devtools/client/shared/widgets/tooltip/SwatchColorPickerTooltip.js
+++ b/devtools/client/shared/widgets/tooltip/SwatchColorPickerTooltip.js
@@ -90,30 +90,28 @@ SwatchColorPickerTooltip.prototype = Her
       this._originalColor = this.currentSwatchColor.textContent;
       let color = this.activeSwatch.style.backgroundColor;
       this.spectrum.off("changed", this._onSpectrumColorChange);
       this.spectrum.rgb = this._colorToRgba(color);
       this.spectrum.on("changed", this._onSpectrumColorChange);
       this.spectrum.updateUI();
     }
 
-    let {target} = this.inspector;
-    target.actorHasMethod("inspector", "pickColorFromPage").then(value => {
-      let tooltipDoc = this.tooltip.doc;
-      let eyeButton = tooltipDoc.querySelector("#eyedropper-button");
-      if (value && this.inspector.selection.nodeFront.isInHTMLDocument) {
-        eyeButton.disabled = false;
-        eyeButton.removeAttribute("title");
-        eyeButton.addEventListener("click", this._openEyeDropper);
-      } else {
-        eyeButton.disabled = true;
-        eyeButton.title = L10N.getStr("eyedropper.disabled.title");
-      }
-      this.emit("ready");
-    }, e => console.error(e));
+    let tooltipDoc = this.tooltip.doc;
+    let eyeButton = tooltipDoc.querySelector("#eyedropper-button");
+    let canShowEyeDropper = yield this.inspector.supportsEyeDropper();
+    if (canShowEyeDropper) {
+      eyeButton.disabled = false;
+      eyeButton.removeAttribute("title");
+      eyeButton.addEventListener("click", this._openEyeDropper);
+    } else {
+      eyeButton.disabled = true;
+      eyeButton.title = L10N.getStr("eyedropper.disabled.title");
+    }
+    this.emit("ready");
   }),
 
   _onSpectrumColorChange: function (event, rgba, cssColor) {
     this._selectColor(cssColor);
   },
 
   _selectColor: function (color) {
     if (this.activeSwatch) {
--- a/devtools/client/themes/layout.css
+++ b/devtools/client/themes/layout.css
@@ -1,14 +1,54 @@
 /* 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/. */
 
 #layout-container {
   height: 100%;
   width: 100%;
+  overflow: auto;
 }
 
+/**
+ * Common styles for shared components
+ */
+
+.grid-container {
+  display: flex;
+  flex-direction: column;
+  flex: 1;
+  align-items: center;
+}
+
+.grid-container > span {
+  font-weight: bold;
+  margin-bottom: 3px;
+}
+
+.grid-container > ul {
+  list-style: none;
+  margin: 0;
+  padding: 0;
+}
+
+.grid-container li {
+  padding: 4px 0;
+}
+
+/**
+ * Grid Container
+ */
+
+#layout-grid-container {
+  display: flex;
+  margin: 5px;
+}
+
+/**
+ * Container when no grids are present
+ */
+
 .layout-no-grids {
   font-style: italic;
   text-align: center;
   padding: 0.5em;
 }
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -8912,16 +8912,18 @@ nsDocShell::RestoreFromHistory()
   nsViewManager* newVM = shell ? shell->GetViewManager() : nullptr;
   nsView* newRootView = newVM ? newVM->GetRootView() : nullptr;
 
   // Insert the new root view at the correct location in the view tree.
   if (container) {
     nsSubDocumentFrame* subDocFrame =
       do_QueryFrame(container->GetPrimaryFrame());
     rootViewParent = subDocFrame ? subDocFrame->EnsureInnerView() : nullptr;
+  } else {
+    rootViewParent = nullptr;
   }
   if (sibling &&
       sibling->GetShell() &&
       sibling->GetShell()->GetViewManager()) {
     rootViewSibling = sibling->GetShell()->GetViewManager()->GetRootView();
   } else {
     rootViewSibling = nullptr;
   }
--- a/dom/base/TabGroup.cpp
+++ b/dom/base/TabGroup.cpp
@@ -141,17 +141,18 @@ TabGroup::FindItemWithName(const nsAStri
 }
 
 nsTArray<nsPIDOMWindowOuter*>
 TabGroup::GetTopLevelWindows()
 {
   nsTArray<nsPIDOMWindowOuter*> array;
 
   for (nsPIDOMWindowOuter* outerWindow : mWindows) {
-    if (!outerWindow->GetScriptableParentOrNull()) {
+    if (outerWindow->GetDocShell() &&
+        !outerWindow->GetScriptableParentOrNull()) {
       array.AppendElement(outerWindow);
     }
   }
 
   return array;
 }
 
 ThrottledEventQueue*
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -48,16 +48,21 @@
 # descriptor to use when generating that interface's binding.
 
 DOMInterfaces = {
 
 'AbstractWorker': {
     'concrete': False
 },
 
+'AddonManagerPermissions': {
+    'wrapperCache': False,
+    'concrete': False
+},
+
 'AnimationEffectReadOnly': {
     'concrete': False
 },
 
 'AnimationTimeline': {
     'concrete': False
 },
 
--- a/dom/media/webrtc/MediaEngineWebRTC.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTC.cpp
@@ -86,16 +86,17 @@ void AudioInputCubeb::UpdateDeviceList()
       auto j = mDeviceNames->IndexOf(devices->device[i]->device_id);
       if (j != nsTArray<nsCString>::NoIndex) {
         // match! update the mapping
         (*mDeviceIndexes)[j] = i;
       } else {
         // new device, add to the array
         mDeviceIndexes->AppendElement(i);
         mDeviceNames->AppendElement(devices->device[i]->device_id);
+        j = mDeviceIndexes->Length()-1;
       }
       if (devices->device[i]->preferred & CUBEB_DEVICE_PREF_VOICE) {
         // There can be only one... we hope
         NS_ASSERTION(mDefaultDevice == -1, "multiple default cubeb input devices!");
         mDefaultDevice = j;
       }
     }
   }
--- a/dom/webidl/AddonManager.webidl
+++ b/dom/webidl/AddonManager.webidl
@@ -78,8 +78,14 @@ interface AddonManager : EventTarget {
   Promise<AddonInstall> createInstall(optional addonInstallOptions options);
 
   /* Hooks for managing event listeners */
   [ChromeOnly]
   void eventListenerWasAdded(DOMString type);
   [ChromeOnly]
   void eventListenerWasRemoved(DOMString type);
 };
+
+[ChromeOnly,Exposed=System,HeaderFile="mozilla/AddonManagerWebAPI.h"]
+interface AddonManagerPermissions {
+  static boolean isHostPermitted(DOMString host);
+};
+
--- a/gfx/2d/ScaledFontDWrite.cpp
+++ b/gfx/2d/ScaledFontDWrite.cpp
@@ -139,17 +139,17 @@ SkTypeface*
 ScaledFontDWrite::GetSkTypeface()
 {
   if (!mTypeface) {
     IDWriteFactory *factory = DrawTargetD2D1::GetDWriteFactory();
     if (!factory) {
       return nullptr;
     }
 
-    mTypeface = SkCreateTypefaceFromDWriteFont(factory, mFontFace, mStyle);
+    mTypeface = SkCreateTypefaceFromDWriteFont(factory, mFontFace, mStyle, mForceGDIMode);
   }
   return mTypeface;
 }
 #endif
 
 void
 ScaledFontDWrite::CopyGlyphsToBuilder(const GlyphBuffer &aBuffer, PathBuilder *aBuilder, const Matrix *aTransformHint)
 {
--- a/gfx/skia/skia/include/ports/SkTypeface_win.h
+++ b/gfx/skia/skia/include/ports/SkTypeface_win.h
@@ -47,17 +47,18 @@ struct IDWriteFontFallback;
 
 /**
  *  Like the other Typeface create methods, this returns a new reference to the
  *  corresponding typeface for the specified dwrite font. The caller is responsible
  *  for calling unref() when it is finished.
  */
 SK_API SkTypeface* SkCreateTypefaceFromDWriteFont(IDWriteFactory* aFactory,
                                                   IDWriteFontFace* aFontFace,
-                                                  SkFontStyle aStyle);
+                                                  SkFontStyle aStyle,
+                                                  bool aForceGDI);
 
 SK_API SkFontMgr* SkFontMgr_New_GDI();
 SK_API SkFontMgr* SkFontMgr_New_DirectWrite(IDWriteFactory* factory = NULL,
                                             IDWriteFontCollection* collection = NULL);
 SK_API SkFontMgr* SkFontMgr_New_DirectWrite(IDWriteFactory* factory,
                                             IDWriteFontCollection* collection,
                                             IDWriteFontFallback* fallback);
 
--- a/gfx/skia/skia/src/effects/gradients/Sk4fGradientBase.cpp
+++ b/gfx/skia/skia/src/effects/gradients/Sk4fGradientBase.cpp
@@ -122,17 +122,24 @@ private:
 SkGradientShaderBase::GradientShaderBase4fContext::
 Interval::Interval(const Sk4f& c0, SkScalar p0,
                    const Sk4f& c1, SkScalar p1)
     : fP0(p0)
     , fP1(p1)
     , fZeroRamp((c0 == c1).allTrue()) {
 
     SkASSERT(p0 != p1);
-    const Sk4f dc = (c1 - c0) / (p1 - p0);
+    // Either p0 or p1 can be (-)inf for synthetic clamp edge intervals.
+    SkASSERT(SkScalarIsFinite(p0) || SkScalarIsFinite(p1));
+
+    const auto dp = p1 - p0;
+
+    // Clamp edge intervals are always zero-ramp.
+    SkASSERT(SkScalarIsFinite(dp) || fZeroRamp);
+    const Sk4f dc = SkScalarIsFinite(dp) ? (c1 - c0) / dp : 0;
 
     c0.store(&fC0.fVec);
     dc.store(&fDc.fVec);
 }
 
 SkGradientShaderBase::
 GradientShaderBase4fContext::GradientShaderBase4fContext(const SkGradientShaderBase& shader,
                                                          const ContextRec& rec)
@@ -218,17 +225,17 @@ GradientShaderBase4fContext::buildInterv
     const int last_index = shader.fColorCount - 1 - first_index;
     const SkScalar first_pos = reverse ? SK_Scalar1 : 0;
     const SkScalar last_pos = SK_Scalar1 - first_pos;
 
     if (shader.fTileMode == SkShader::kClamp_TileMode) {
         // synthetic edge interval: -/+inf .. P0
         const Sk4f clamp_color = pack_color(shader.fOrigColors[first_index],
                                             fColorsArePremul, componentScale);
-        const SkScalar clamp_pos = reverse ? SK_ScalarMax : SK_ScalarMin;
+        const SkScalar clamp_pos = reverse ? SK_ScalarInfinity : SK_ScalarNegativeInfinity;
         fIntervals.emplace_back(clamp_color, clamp_pos,
                                 clamp_color, first_pos);
     } else if (shader.fTileMode == SkShader::kMirror_TileMode && reverse) {
         // synthetic mirror intervals injected before main intervals: (2 .. 1]
         addMirrorIntervals(shader, componentScale, false);
     }
 
     const IntervalIterator iter(shader.fOrigColors,
@@ -243,17 +250,17 @@ GradientShaderBase4fContext::buildInterv
                                 pack_color(c1, fColorsArePremul, componentScale),
                                 p1);
     });
 
     if (shader.fTileMode == SkShader::kClamp_TileMode) {
         // synthetic edge interval: Pn .. +/-inf
         const Sk4f clamp_color = pack_color(shader.fOrigColors[last_index],
                                             fColorsArePremul, componentScale);
-        const SkScalar clamp_pos = reverse ? SK_ScalarMin : SK_ScalarMax;
+        const SkScalar clamp_pos = reverse ? SK_ScalarNegativeInfinity : SK_ScalarInfinity;
         fIntervals.emplace_back(clamp_color, last_pos,
                                 clamp_color, clamp_pos);
     } else if (shader.fTileMode == SkShader::kMirror_TileMode && !reverse) {
         // synthetic mirror intervals injected after main intervals: [1 .. 2)
         addMirrorIntervals(shader, componentScale, true);
     }
 }
 
--- a/gfx/skia/skia/src/effects/gradients/Sk4fLinearGradient.cpp
+++ b/gfx/skia/skia/src/effects/gradients/Sk4fLinearGradient.cpp
@@ -317,21 +317,26 @@ public:
         SkASSERT(advX < fAdvX);
 
         fCc = fCc + fDcDx * Sk4f(advX);
         fAdvX -= advX;
     }
 
 private:
     void compute_interval_props(SkScalar t) {
-        const Sk4f dC = DstTraits<dstType>::load(fInterval->fDc);
+        fZeroRamp     = fIsVertical || fInterval->isZeroRamp();
         fCc           = DstTraits<dstType>::load(fInterval->fC0);
-        fCc           = fCc + dC * Sk4f(t);
-        fDcDx         = dC * fDx;
-        fZeroRamp     = fIsVertical || fInterval->isZeroRamp();
+
+        if (fInterval->isZeroRamp()) {
+            fDcDx = 0;
+        } else {
+            const Sk4f dC = DstTraits<dstType>::load(fInterval->fDc);
+            fCc           = fCc + dC * Sk4f(t);
+            fDcDx         = dC * fDx;
+        }
     }
 
     const Interval* next_interval(const Interval* i) const {
         SkASSERT(i >= fFirstInterval);
         SkASSERT(i <= fLastInterval);
         i++;
 
         if (tileMode == kClamp_TileMode) {
--- a/gfx/skia/skia/src/ports/SkFontHost_win.cpp
+++ b/gfx/skia/skia/src/ports/SkFontHost_win.cpp
@@ -330,19 +330,20 @@ SkTypeface* SkCreateTypefaceFromLOGFONT(
 }
 
 /***
  * This guy is public.
  */
 
 SkTypeface* SkCreateTypefaceFromDWriteFont(IDWriteFactory* aFactory,
                                            IDWriteFontFace* aFontFace,
-                                           SkFontStyle aStyle)
+                                           SkFontStyle aStyle,
+                                           bool aForceGDI)
 {
-  return DWriteFontTypeface::Create(aFactory, aFontFace, aStyle);
+  return DWriteFontTypeface::Create(aFactory, aFontFace, aStyle, aForceGDI);
 }
 
 /**
  *  The created SkTypeface takes ownership of fontMemResource.
  */
 SkTypeface* SkCreateFontMemResourceTypefaceFromLOGFONT(const LOGFONT& origLF, HANDLE fontMemResource) {
     LOGFONT lf = origLF;
     make_canonical(&lf);
--- a/gfx/skia/skia/src/ports/SkScalerContext_win_dw.cpp
+++ b/gfx/skia/skia/src/ports/SkScalerContext_win_dw.cpp
@@ -283,17 +283,17 @@ SkScalerContext_DW::SkScalerContext_DW(D
         fTextSizeRender = gdiTextSize;
         fRenderingMode = DWRITE_RENDERING_MODE_ALIASED;
         fTextureType = DWRITE_TEXTURE_ALIASED_1x1;
         fTextSizeMeasure = gdiTextSize;
         fMeasuringMode = DWRITE_MEASURING_MODE_GDI_CLASSIC;
 
     // If we can use a bitmap, use gdi classic rendering and measurement.
     // This will not always provide a bitmap, but matches expected behavior.
-    } else if (treatLikeBitmap && axisAlignedBitmap) {
+    } else if ((treatLikeBitmap && axisAlignedBitmap) || typeface->ForceGDI()) {
         fTextSizeRender = gdiTextSize;
         fRenderingMode = DWRITE_RENDERING_MODE_CLEARTYPE_GDI_CLASSIC;
         fTextureType = DWRITE_TEXTURE_CLEARTYPE_3x1;
         fTextSizeMeasure = gdiTextSize;
         fMeasuringMode = DWRITE_MEASURING_MODE_GDI_CLASSIC;
 
     // If rotated but the horizontal text could have used a bitmap,
     // render high quality rotated glyphs but measure using bitmap metrics.
--- a/gfx/skia/skia/src/ports/SkTypeface_win_dw.h
+++ b/gfx/skia/skia/src/ports/SkTypeface_win_dw.h
@@ -48,16 +48,17 @@ private:
                        IDWriteFontCollectionLoader* fontCollectionLoader = nullptr)
         : SkTypeface(style, false)
         , fFactory(SkRefComPtr(factory))
         , fDWriteFontCollectionLoader(SkSafeRefComPtr(fontCollectionLoader))
         , fDWriteFontFileLoader(SkSafeRefComPtr(fontFileLoader))
         , fDWriteFontFamily(SkSafeRefComPtr(fontFamily))
         , fDWriteFont(SkSafeRefComPtr(font))
         , fDWriteFontFace(SkRefComPtr(fontFace))
+        , fForceGDI(false)
     {
 #if SK_HAS_DWRITE_1_H
         if (!SUCCEEDED(fDWriteFontFace->QueryInterface(&fDWriteFontFace1))) {
             // IUnknown::QueryInterface states that if it fails, punk will be set to nullptr.
             // http://blogs.msdn.com/b/oldnewthing/archive/2004/03/26/96777.aspx
             SkASSERT_RELEASE(nullptr == fDWriteFontFace1.get());
         }
 #endif
@@ -71,32 +72,38 @@ public:
     SkTScopedComPtr<IDWriteFont> fDWriteFont;
     SkTScopedComPtr<IDWriteFontFace> fDWriteFontFace;
 #if SK_HAS_DWRITE_1_H
     SkTScopedComPtr<IDWriteFontFace1> fDWriteFontFace1;
 #endif
 
     static DWriteFontTypeface* Create(IDWriteFactory* factory,
                                       IDWriteFontFace* fontFace,
-                                      SkFontStyle aStyle) {
-        return new DWriteFontTypeface(aStyle, factory, fontFace,
-                                      nullptr, nullptr,
-                                      nullptr, nullptr);
+                                      SkFontStyle aStyle,
+                                      bool aForceGDI) {
+        DWriteFontTypeface* typeface =
+                new DWriteFontTypeface(aStyle, factory, fontFace,
+                                       nullptr, nullptr,
+                                       nullptr, nullptr);
+        typeface->fForceGDI = aForceGDI;
+        return typeface;
     }
 
     static DWriteFontTypeface* Create(IDWriteFactory* factory,
                                       IDWriteFontFace* fontFace,
                                       IDWriteFont* font,
                                       IDWriteFontFamily* fontFamily,
                                       IDWriteFontFileLoader* fontFileLoader = nullptr,
                                       IDWriteFontCollectionLoader* fontCollectionLoader = nullptr) {
         return new DWriteFontTypeface(get_style(font), factory, fontFace, font, fontFamily,
                                       fontFileLoader, fontCollectionLoader);
     }
 
+    bool ForceGDI() { return fForceGDI; }
+
 protected:
     void weak_dispose() const override {
         if (fDWriteFontCollectionLoader.get()) {
             HRV(fFactory->UnregisterFontCollectionLoader(fDWriteFontCollectionLoader.get()));
         }
         if (fDWriteFontFileLoader.get()) {
             HRV(fFactory->UnregisterFontFileLoader(fDWriteFontFileLoader.get()));
         }
@@ -119,11 +126,12 @@ protected:
     void onGetFamilyName(SkString* familyName) const override;
     SkTypeface::LocalizedStrings* onCreateFamilyNameIterator() const override;
     int onGetTableTags(SkFontTableTag tags[]) const override;
     virtual size_t onGetTableData(SkFontTableTag, size_t offset,
                                   size_t length, void* data) const override;
 
 private:
     typedef SkTypeface INHERITED;
+    bool fForceGDI;
 };
 
 #endif
--- a/image/ImageURL.h
+++ b/image/ImageURL.h
@@ -6,16 +6,17 @@
 #ifndef mozilla_image_ImageURL_h
 #define mozilla_image_ImageURL_h
 
 #include "nsIURI.h"
 #include "MainThreadUtils.h"
 #include "nsNetUtil.h"
 #include "mozilla/HashFunctions.h"
 #include "nsHashKeys.h"
+#include "nsProxyRelease.h"
 
 namespace mozilla {
 namespace image {
 
 class ImageCacheKey;
 
 /** ImageURL
  *
@@ -28,16 +29,17 @@ class ImageCacheKey;
  * By not implementing nsIURI, external code cannot unintentionally be given an
  * nsIURI pointer with this limited class behind it; instead, conversion to a
  * fully implemented nsIURI is required (e.g. through NS_NewURI).
  */
 class ImageURL
 {
 public:
   explicit ImageURL(nsIURI* aURI, nsresult& aRv)
+    : mURI(new nsMainThreadPtrHolder<nsIURI>(aURI))
   {
     MOZ_ASSERT(NS_IsMainThread(), "Cannot use nsIURI off main thread!");
 
     aRv = aURI->GetSpec(mSpec);
     NS_ENSURE_SUCCESS_VOID(aRv);
 
     aRv = aURI->GetScheme(mScheme);
     NS_ENSURE_SUCCESS_VOID(aRv);
@@ -92,20 +94,17 @@ public:
   nsresult GetRef(nsACString& result)
   {
     result = mRef;
     return NS_OK;
   }
 
   already_AddRefed<nsIURI> ToIURI()
   {
-    MOZ_ASSERT(NS_IsMainThread(),
-               "Convert to nsIURI on main thread only; it is not threadsafe.");
-    nsCOMPtr<nsIURI> newURI;
-    NS_NewURI(getter_AddRefs(newURI), mSpec);
+    nsCOMPtr<nsIURI> newURI = mURI.get();
     return newURI.forget();
   }
 
   bool operator==(const ImageURL& aOther) const
   {
     // Note that we don't need to consider mScheme and mRef, because they're
     // already represented in mSpec.
     return mSpec == aOther.mSpec;
@@ -128,16 +127,18 @@ private:
       // requires us to create different Image objects even if the source data is
       // the same.
       return HashGeneric(*aBlobSerial, HashString(mRef));
     }
     // For non-blob URIs, we hash the URI spec.
     return HashString(mSpec);
   }
 
+  nsMainThreadPtrHandle<nsIURI> mURI;
+
   // Since this is a basic storage class, no duplication of spec parsing is
   // included in the functionality. Instead, the class depends upon the
   // parsing implementation in the nsIURI class used in object construction.
   // This means each field is stored separately, but since only a few are
   // required, this small memory tradeoff for threadsafe usage should be ok.
   nsAutoCString mSpec;
   nsAutoCString mScheme;
   nsAutoCString mRef;
--- a/js/public/Utility.h
+++ b/js/public/Utility.h
@@ -55,17 +55,17 @@ namespace oom {
  * To make testing OOM in certain helper threads more effective,
  * allow restricting the OOM testing to a certain helper thread
  * type. This allows us to fail e.g. in off-thread script parsing
  * without causing an OOM in the main thread first.
  */
 enum ThreadType {
     THREAD_TYPE_NONE = 0,       // 0
     THREAD_TYPE_MAIN,           // 1
-    THREAD_TYPE_ASMJS,          // 2
+    THREAD_TYPE_WASM,           // 2
     THREAD_TYPE_ION,            // 3
     THREAD_TYPE_PARSE,          // 4
     THREAD_TYPE_COMPRESS,       // 5
     THREAD_TYPE_GCHELPER,       // 6
     THREAD_TYPE_GCPARALLEL,     // 7
     THREAD_TYPE_PROMISE_TASK,   // 8
     THREAD_TYPE_MAX             // Used to check shell function arguments
 };
--- a/js/src/jit/MacroAssembler.cpp
+++ b/js/src/jit/MacroAssembler.cpp
@@ -1,16 +1,18 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "jit/MacroAssembler-inl.h"
 
+#include "mozilla/CheckedInt.h"
+
 #include "jsfriendapi.h"
 #include "jsprf.h"
 
 #include "builtin/TypedObject.h"
 #include "gc/GCTrace.h"
 #include "jit/AtomicOp.h"
 #include "jit/Bailouts.h"
 #include "jit/BaselineFrame.h"
@@ -29,16 +31,18 @@
 #include "vm/Interpreter-inl.h"
 
 using namespace js;
 using namespace js::jit;
 
 using JS::GenericNaN;
 using JS::ToInt32;
 
+using mozilla::CheckedUint32;
+
 template <typename Source> void
 MacroAssembler::guardTypeSet(const Source& address, const TypeSet* types, BarrierKind kind,
                              Register scratch, Label* miss)
 {
     MOZ_ASSERT(kind == BarrierKind::TypeTagOnly || kind == BarrierKind::TypeSet);
     MOZ_ASSERT(!types->unknown());
 
     Label matched;
@@ -1055,16 +1059,19 @@ AllocateObjectBufferWithInit(JSContext* 
             return; \
         break;
 JS_FOR_EACH_TYPED_ARRAY(CREATE_TYPED_ARRAY)
 #undef CREATE_TYPED_ARRAY
       default:
         MOZ_CRASH("Unsupported TypedArray type");
     }
 
+    if (!(CheckedUint32(nbytes) + sizeof(Value)).isValid())
+        return;
+
     nbytes = JS_ROUNDUP(nbytes, sizeof(Value));
     Nursery& nursery = cx->runtime()->gc.nursery;
     void* buf = nursery.allocateBuffer(obj, nbytes);
     if (buf) {
         obj->initPrivate(buf);
         memset(buf, 0, nbytes);
     }
 }
--- a/js/src/jit/x64/MacroAssembler-x64.cpp
+++ b/js/src/jit/x64/MacroAssembler-x64.cpp
@@ -419,17 +419,17 @@ MacroAssemblerX64::asMasm() const
 
 void
 MacroAssembler::subFromStackPtr(Imm32 imm32)
 {
     if (imm32.value) {
         // On windows, we cannot skip very far down the stack without touching the
         // memory pages in-between.  This is a corner-case code for situations where the
         // Ion frame data for a piece of code is very large.  To handle this special case,
-        // for frames over 1k in size we allocate memory on the stack incrementally, touching
+        // for frames over 4k in size we allocate memory on the stack incrementally, touching
         // it as we go.
         //
         // When the amount is quite large, which it can be, we emit an actual loop, in order
         // to keep the function prologue compact.  Compactness is a requirement for eg
         // Wasm's CodeRange data structure, which can encode only 8-bit offsets.
         uint32_t amountLeft = imm32.value;
         uint32_t fullPages = amountLeft / 4096;
         if (fullPages <= 8) {
--- a/js/src/jit/x86/MacroAssembler-x86.cpp
+++ b/js/src/jit/x86/MacroAssembler-x86.cpp
@@ -326,17 +326,17 @@ MacroAssemblerX86::asMasm() const
 
 void
 MacroAssembler::subFromStackPtr(Imm32 imm32)
 {
     if (imm32.value) {
         // On windows, we cannot skip very far down the stack without touching the
         // memory pages in-between.  This is a corner-case code for situations where the
         // Ion frame data for a piece of code is very large.  To handle this special case,
-        // for frames over 1k in size we allocate memory on the stack incrementally, touching
+        // for frames over 4k in size we allocate memory on the stack incrementally, touching
         // it as we go.
         //
         // When the amount is quite large, which it can be, we emit an actual loop, in order
         // to keep the function prologue compact.  Compactness is a requirement for eg
         // Wasm's CodeRange data structure, which can encode only 8-bit offsets.
         uint32_t amountLeft = imm32.value;
         uint32_t fullPages = amountLeft / 4096;
         if (fullPages <= 8) {
--- a/js/src/proxy/Wrapper.cpp
+++ b/js/src/proxy/Wrapper.cpp
@@ -324,17 +324,20 @@ Wrapper::wrapperHandler(JSObject* wrappe
     MOZ_ASSERT(wrapper->is<WrapperObject>());
     return static_cast<const Wrapper*>(wrapper->as<ProxyObject>().handler());
 }
 
 JSObject*
 Wrapper::wrappedObject(JSObject* wrapper)
 {
     MOZ_ASSERT(wrapper->is<WrapperObject>());
-    return wrapper->as<ProxyObject>().target();
+    JSObject* target = wrapper->as<ProxyObject>().target();
+    if (target)
+        JS::ExposeObjectToActiveJS(target);
+    return target;
 }
 
 JS_FRIEND_API(JSObject*)
 js::UncheckedUnwrap(JSObject* wrapped, bool stopAtWindowProxy, unsigned* flagsp)
 {
     unsigned flags = 0;
     while (true) {
         if (!wrapped->is<WrapperObject>() ||
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -15,17 +15,16 @@
 #include "builtin/Promise.h"
 #include "frontend/BytecodeCompiler.h"
 #include "gc/GCInternals.h"
 #include "jit/IonBuilder.h"
 #include "vm/Debugger.h"
 #include "vm/SharedImmutableStringsCache.h"
 #include "vm/Time.h"
 #include "vm/TraceLogging.h"
-#include "wasm/WasmIonCompile.h"
 
 #include "jscntxtinlines.h"
 #include "jscompartmentinlines.h"
 #include "jsobjinlines.h"
 #include "jsscriptinlines.h"
 
 using namespace js;
 
@@ -79,17 +78,17 @@ js::SetFakeCPUCount(size_t count)
     // This must be called before the threads have been initialized.
     MOZ_ASSERT(!HelperThreadState().threads);
 
     HelperThreadState().cpuCount = count;
     HelperThreadState().threadCount = ThreadCountForCPUCount(count);
 }
 
 bool
-js::StartOffThreadWasmCompile(wasm::IonCompileTask* task)
+js::StartOffThreadWasmCompile(wasm::CompileTask* task)
 {
     AutoLockHelperThreadState lock;
 
     // Don't append this task if another failed.
     if (HelperThreadState().wasmFailed(lock))
         return false;
 
     if (!HelperThreadState().wasmWorklist(lock).append(task))
@@ -862,17 +861,17 @@ size_t
 GlobalHelperThreadState::maxUnpausedIonCompilationThreads() const
 {
     return 1;
 }
 
 size_t
 GlobalHelperThreadState::maxWasmCompilationThreads() const
 {
-    if (IsHelperThreadSimulatingOOM(js::oom::THREAD_TYPE_ASMJS))
+    if (IsHelperThreadSimulatingOOM(js::oom::THREAD_TYPE_WASM))
         return 1;
     if (cpuCount < 2)
         return 2;
     return cpuCount;
 }
 
 size_t
 GlobalHelperThreadState::maxParseThreads() const
@@ -915,17 +914,17 @@ bool
 GlobalHelperThreadState::canStartWasmCompile(const AutoLockHelperThreadState& lock)
 {
     // Don't execute an wasm job if an earlier one failed.
     if (wasmWorklist(lock).empty() || numWasmFailedJobs)
         return false;
 
     // Honor the maximum allowed threads to compile wasm jobs at once,
     // to avoid oversaturating the machine.
-    if (!checkTaskThreadLimit<wasm::IonCompileTask*>(maxWasmCompilationThreads()))
+    if (!checkTaskThreadLimit<wasm::CompileTask*>(maxWasmCompilationThreads()))
         return false;
 
     return true;
 }
 
 bool
 GlobalHelperThreadState::canStartPromiseTask(const AutoLockHelperThreadState& lock)
 {
@@ -1414,17 +1413,17 @@ void
 HelperThread::handleWasmWorkload(AutoLockHelperThreadState& locked)
 {
     MOZ_ASSERT(HelperThreadState().canStartWasmCompile(locked));
     MOZ_ASSERT(idle());
 
     currentTask.emplace(HelperThreadState().wasmWorklist(locked).popCopy());
     bool success = false;
 
-    wasm::IonCompileTask* task = wasmTask();
+    wasm::CompileTask* task = wasmTask();
     {
         AutoUnlockHelperThreadState unlock(locked);
         success = wasm::CompileFunction(task);
     }
 
     // On success, try to move work to the finished list.
     if (success)
         success = HelperThreadState().wasmFinishedList(locked).append(task);
@@ -1867,17 +1866,17 @@ HelperThread::threadLoop()
             }
             HelperThreadState().wait(lock, GlobalHelperThreadState::PRODUCER);
         }
 
         if (ionCompile) {
             js::oom::SetThreadType(js::oom::THREAD_TYPE_ION);
             handleIonWorkload(lock);
         } else if (HelperThreadState().canStartWasmCompile(lock)) {
-            js::oom::SetThreadType(js::oom::THREAD_TYPE_ASMJS);
+            js::oom::SetThreadType(js::oom::THREAD_TYPE_WASM);
             handleWasmWorkload(lock);
         } else if (HelperThreadState().canStartPromiseTask(lock)) {
             js::oom::SetThreadType(js::oom::THREAD_TYPE_PROMISE_TASK);
             handlePromiseTaskWorkload(lock);
         } else if (HelperThreadState().canStartParseTask(lock)) {
             js::oom::SetThreadType(js::oom::THREAD_TYPE_PARSE);
             handleParseWorkload(lock, stackLimit);
         } else if (HelperThreadState().canStartCompressionTask(lock)) {
--- a/js/src/vm/HelperThreads.h
+++ b/js/src/vm/HelperThreads.h
@@ -8,16 +8,17 @@
  * Definitions for managing off-main-thread work using a process wide list
  * of worklist items and pool of threads. Worklist items are engine internal,
  * and are distinct from e.g. web workers.
  */
 
 #ifndef vm_HelperThreads_h
 #define vm_HelperThreads_h
 
+#include "mozilla/Attributes.h"
 #include "mozilla/GuardObjects.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Variant.h"
 
 #include "jscntxt.h"
 
 #include "frontend/TokenStream.h"
@@ -37,18 +38,18 @@ class PromiseTask;
 struct HelperThread;
 struct ParseTask;
 namespace jit {
   class IonBuilder;
 } // namespace jit
 namespace wasm {
   class FuncIR;
   class FunctionCompileResults;
-  class IonCompileTask;
-  typedef Vector<IonCompileTask*, 0, SystemAllocPolicy> IonCompileTaskPtrVector;
+  class CompileTask;
+  typedef Vector<CompileTask*, 0, SystemAllocPolicy> CompileTaskPtrVector;
 } // namespace wasm
 
 enum class ParseTaskKind
 {
     Script,
     Module
 };
 
@@ -79,17 +80,17 @@ class GlobalHelperThreadState
 
   private:
     // The lists below are all protected by |lock|.
 
     // Ion compilation worklist and finished jobs.
     IonBuilderVector ionWorklist_, ionFinishedList_;
 
     // wasm worklist and finished jobs.
-    wasm::IonCompileTaskPtrVector wasmWorklist_, wasmFinishedList_;
+    wasm::CompileTaskPtrVector wasmWorklist_, wasmFinishedList_;
 
   public:
     // For now, only allow a single parallel wasm compilation to happen at a
     // time. This avoids race conditions on wasmWorklist/wasmFinishedList/etc.
     mozilla::Atomic<bool> wasmCompilationInProgress;
 
   private:
     // Async tasks that, upon completion, are dispatched back to the JSContext's
@@ -158,20 +159,20 @@ class GlobalHelperThreadState
 
     IonBuilderVector& ionWorklist(const AutoLockHelperThreadState&) {
         return ionWorklist_;
     }
     IonBuilderVector& ionFinishedList(const AutoLockHelperThreadState&) {
         return ionFinishedList_;
     }
 
-    wasm::IonCompileTaskPtrVector& wasmWorklist(const AutoLockHelperThreadState&) {
+    wasm::CompileTaskPtrVector& wasmWorklist(const AutoLockHelperThreadState&) {
         return wasmWorklist_;
     }
-    wasm::IonCompileTaskPtrVector& wasmFinishedList(const AutoLockHelperThreadState&) {
+    wasm::CompileTaskPtrVector& wasmFinishedList(const AutoLockHelperThreadState&) {
         return wasmFinishedList_;
     }
 
     PromiseTaskVector& promiseTasks(const AutoLockHelperThreadState&) {
         return promiseTasks_;
     }
 
     ParseTaskVector& parseWorklist(const AutoLockHelperThreadState&) {
@@ -303,35 +304,35 @@ struct HelperThread
      * Indicate to a thread that it should pause execution. This is only
      * written with the helper thread state lock held, but may be read from
      * without the lock held.
      */
     mozilla::Atomic<bool, mozilla::Relaxed> pause;
 
     /* The current task being executed by this thread, if any. */
     mozilla::Maybe<mozilla::Variant<jit::IonBuilder*,
-                                    wasm::IonCompileTask*,
+                                    wasm::CompileTask*,
                                     PromiseTask*,
                                     ParseTask*,
                                     SourceCompressionTask*,
                                     GCHelperState*,
                                     GCParallelTask*>> currentTask;
 
     bool idle() const {
         return currentTask.isNothing();
     }
 
     /* Any builder currently being compiled by Ion on this thread. */
     jit::IonBuilder* ionBuilder() {
         return maybeCurrentTaskAs<jit::IonBuilder*>();
     }
 
     /* Any wasm data currently being optimized on this thread. */
-    wasm::IonCompileTask* wasmTask() {
-        return maybeCurrentTaskAs<wasm::IonCompileTask*>();
+    wasm::CompileTask* wasmTask() {
+        return maybeCurrentTaskAs<wasm::CompileTask*>();
     }
 
     /* Any source being parsed/emitted on this thread. */
     ParseTask* parseTask() {
         return maybeCurrentTaskAs<ParseTask*>();
     }
 
     /* Any source being compressed on this thread. */
@@ -390,19 +391,27 @@ EnsureHelperThreadsInitialized();
 // --thread-count=N option.
 void
 SetFakeCPUCount(size_t count);
 
 // Pause the current thread until it's pause flag is unset.
 void
 PauseCurrentHelperThread();
 
-/* Perform MIR optimization and LIR generation on a single function. */
+// Enqueues a wasm compilation task.
 bool
-StartOffThreadWasmCompile(wasm::IonCompileTask* task);
+StartOffThreadWasmCompile(wasm::CompileTask* task);
+
+namespace wasm {
+
+// Performs MIR optimization and LIR generation on one or several functions.
+MOZ_MUST_USE bool
+CompileFunction(CompileTask* task);
+
+}
 
 /*
  * If helper threads are available, start executing the given PromiseTask on a
  * helper thread, finishing back on the originating JSContext's owner thread. If
  * no helper threads are available, the PromiseTask is synchronously executed
  * and finished.
  */
 bool
--- a/js/src/vm/ObjectGroup.cpp
+++ b/js/src/vm/ObjectGroup.cpp
@@ -551,48 +551,42 @@ ObjectGroup::defaultNewGroup(ExclusiveCo
     if (!group)
         return nullptr;
 
     if (!table->add(p, ObjectGroupCompartment::NewEntry(group, associated))) {
         ReportOutOfMemory(cx);
         return nullptr;
     }
 
-    if (proto.isObject()) {
-        RootedObject obj(cx, proto.toObject());
-
-        if (associated) {
-            if (associated->is<JSFunction>()) {
-                if (!TypeNewScript::make(cx->asJSContext(), group, &associated->as<JSFunction>()))
-                    return nullptr;
-            } else {
-                group->setTypeDescr(&associated->as<TypeDescr>());
-            }
+    if (associated) {
+        if (associated->is<JSFunction>()) {
+            if (!TypeNewScript::make(cx->asJSContext(), group, &associated->as<JSFunction>()))
+                return nullptr;
+        } else {
+            group->setTypeDescr(&associated->as<TypeDescr>());
         }
+    }
 
-        /*
-         * Some builtin objects have slotful native properties baked in at
-         * creation via the Shape::{insert,get}initialShape mechanism. Since
-         * these properties are never explicitly defined on new objects, update
-         * the type information for them here.
-         */
+    /*
+     * Some builtin objects have slotful native properties baked in at
+     * creation via the Shape::{insert,get}initialShape mechanism. Since
+     * these properties are never explicitly defined on new objects, update
+     * the type information for them here.
+     */
 
-        const JSAtomState& names = cx->names();
+    const JSAtomState& names = cx->names();
 
-        if (StandardProtoKeyOrNull(obj) == JSProto_RegExp)
-            AddTypePropertyId(cx, group, nullptr, NameToId(names.lastIndex), TypeSet::Int32Type());
-
-        if (obj->is<StringObject>())
-            AddTypePropertyId(cx, group, nullptr, NameToId(names.length), TypeSet::Int32Type());
-
-        if (IsErrorProtoKey(StandardProtoKeyOrNull(obj))) {
-            AddTypePropertyId(cx, group, nullptr, NameToId(names.fileName), TypeSet::StringType());
-            AddTypePropertyId(cx, group, nullptr, NameToId(names.lineNumber), TypeSet::Int32Type());
-            AddTypePropertyId(cx, group, nullptr, NameToId(names.columnNumber), TypeSet::Int32Type());
-        }
+    if (clasp == &RegExpObject::class_) {
+        AddTypePropertyId(cx, group, nullptr, NameToId(names.lastIndex), TypeSet::Int32Type());
+    } else if (clasp == &StringObject::class_) {
+        AddTypePropertyId(cx, group, nullptr, NameToId(names.length), TypeSet::Int32Type());
+    } else if (ErrorObject::isErrorClass(clasp)) {
+        AddTypePropertyId(cx, group, nullptr, NameToId(names.fileName), TypeSet::StringType());
+        AddTypePropertyId(cx, group, nullptr, NameToId(names.lineNumber), TypeSet::Int32Type());
+        AddTypePropertyId(cx, group, nullptr, NameToId(names.columnNumber), TypeSet::Int32Type());
     }
 
     return group;
 }
 
 /* static */ ObjectGroup*
 ObjectGroup::lazySingletonGroup(ExclusiveContext* cx, const Class* clasp, TaggedProto proto)
 {
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -7597,19 +7597,19 @@ js::wasm::BaselineCanCompile(const Funct
 
     return true;
 #else
     return false;
 #endif
 }
 
 bool
-js::wasm::BaselineCompileFunction(IonCompileTask* task)
-{
-    MOZ_ASSERT(task->mode() == IonCompileTask::CompileMode::Baseline);
+js::wasm::BaselineCompileFunction(CompileTask* task)
+{
+    MOZ_ASSERT(task->mode() == CompileTask::CompileMode::Baseline);
 
     const FuncBytes& func = task->func();
     FuncCompileResults& results = task->results();
 
     Decoder d(func.bytes());
 
     // Build the local types vector.
 
--- a/js/src/wasm/WasmBaselineCompile.h
+++ b/js/src/wasm/WasmBaselineCompile.h
@@ -14,35 +14,34 @@
  * 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.
  */
 
 #ifndef asmjs_wasm_baseline_compile_h
 #define asmjs_wasm_baseline_compile_h
 
-#include "wasm/WasmIonCompile.h"
-
 namespace js {
 namespace wasm {
 
 class FunctionGenerator;
+class CompileTask;
 
 // Return true if BaselineCompileFunction can generate code for the
 // function held in the FunctionGenerator.  If false is returned a
 // different compilation strategy must be chosen.
 //
 // This allows the baseline compiler to have different capabilities on
 // different platforms and defer to the full Ion compiler if
 // capabilities are missing.  The FunctionGenerator and other data
 // structures contain information about the capabilities that are
 // required to compile the function.
 bool
 BaselineCanCompile(const FunctionGenerator* fg);
 
 // Generate adequate code quickly.
 bool
-BaselineCompileFunction(IonCompileTask* task);
+BaselineCompileFunction(CompileTask* task);
 
 } // namespace wasm
 } // namespace js
 
 #endif // asmjs_wasm_baseline_compile_h
--- a/js/src/wasm/WasmCompile.cpp
+++ b/js/src/wasm/WasmCompile.cpp
@@ -67,31 +67,31 @@ DecodeCodeSection(Decoder& d, ModuleGene
     if (!mg.startFuncDefs())
         return false;
 
     uint32_t sectionStart, sectionSize;
     if (!d.startSection(SectionId::Code, &sectionStart, &sectionSize, "code"))
         return false;
 
     if (sectionStart == Decoder::NotStarted) {
-        if (mg.numFuncDefs() != 0)
+        if (mg.env().numFuncDefs() != 0)
             return d.fail("expected function bodies");
 
         return mg.finishFuncDefs();
     }
 
     uint32_t numFuncDefs;
     if (!d.readVarU32(&numFuncDefs))
         return d.fail("expected function body count");
 
-    if (numFuncDefs != mg.numFuncDefs())
+    if (numFuncDefs != mg.env().numFuncDefs())
         return d.fail("function body count does not match function signature count");
 
     for (uint32_t funcDefIndex = 0; funcDefIndex < numFuncDefs; funcDefIndex++) {
-        if (!DecodeFunctionBody(d, mg, mg.numFuncImports() + funcDefIndex))
+        if (!DecodeFunctionBody(d, mg, mg.env().numFuncImports() + funcDefIndex))
             return false;
     }
 
     if (!d.finishSection(sectionStart, sectionSize, "code"))
         return false;
 
     return mg.finishFuncDefs();
 }
--- a/js/src/wasm/WasmGenerator.cpp
+++ b/js/src/wasm/WasmGenerator.cpp
@@ -65,22 +65,22 @@ ModuleGenerator::ModuleGenerator()
 
 ModuleGenerator::~ModuleGenerator()
 {
     if (parallel_) {
         // Wait for any outstanding jobs to fail or complete.
         if (outstanding_) {
             AutoLockHelperThreadState lock;
             while (true) {
-                IonCompileTaskPtrVector& worklist = HelperThreadState().wasmWorklist(lock);
+                CompileTaskPtrVector& worklist = HelperThreadState().wasmWorklist(lock);
                 MOZ_ASSERT(outstanding_ >= worklist.length());
                 outstanding_ -= worklist.length();
                 worklist.clear();
 
-                IonCompileTaskPtrVector& finished = HelperThreadState().wasmFinishedList(lock);
+                CompileTaskPtrVector& finished = HelperThreadState().wasmFinishedList(lock);
                 MOZ_ASSERT(outstanding_ >= finished.length());
                 outstanding_ -= finished.length();
                 finished.clear();
 
                 uint32_t numFailed = HelperThreadState().harvestFailedWasmJobs(lock);
                 MOZ_ASSERT(outstanding_ >= numFailed);
                 outstanding_ -= numFailed;
 
@@ -219,17 +219,17 @@ ModuleGenerator::init(UniqueModuleEnviro
     return true;
 }
 
 bool
 ModuleGenerator::finishOutstandingTask()
 {
     MOZ_ASSERT(parallel_);
 
-    IonCompileTask* task = nullptr;
+    CompileTask* task = nullptr;
     {
         AutoLockHelperThreadState lock;
         while (true) {
             MOZ_ASSERT(outstanding_ > 0);
 
             if (HelperThreadState().wasmFailed(lock))
                 return false;
 
@@ -380,17 +380,17 @@ ModuleGenerator::patchFarJumps(const Tra
 
     for (const TrapFarJump& farJump : masm_.trapFarJumps())
         masm_.patchFarJump(farJump.jump, trapExits[farJump.trap].begin);
 
     return true;
 }
 
 bool
-ModuleGenerator::finishTask(IonCompileTask* task)
+ModuleGenerator::finishTask(CompileTask* task)
 {
     const FuncBytes& func = task->func();
     FuncCompileResults& results = task->results();
 
     masm_.haltingAlign(CodeAlignment);
 
     // Before merging in the new function's code, if calls in a prior function
     // body might go out of range, insert far jumps to extend the range.
@@ -414,18 +414,20 @@ ModuleGenerator::finishTask(IonCompileTa
     funcToCodeRange_[func.index()] = funcCodeRangeIndex;
 
     // Merge the compiled results into the whole-module masm.
     mozilla::DebugOnly<size_t> sizeBefore = masm_.size();
     if (!masm_.asmMergeWith(results.masm()))
         return false;
     MOZ_ASSERT(masm_.size() == offsetInWhole + results.masm().size());
 
+    UniqueBytes recycled;
+    task->reset(&recycled);
     freeTasks_.infallibleAppend(task);
-    return true;
+    return freeBytes_.emplaceBack(Move(recycled));
 }
 
 bool
 ModuleGenerator::finishFuncExports()
 {
     // In addition to all the functions that were explicitly exported, any
     // element of an exported table is also exported.
 
@@ -797,25 +799,16 @@ uint32_t
 ModuleGenerator::numFuncImports() const
 {
     // Until all functions have been validated, asm.js doesn't know the total
     // number of imports.
     MOZ_ASSERT_IF(isAsmJS(), finishedFuncDefs_);
     return metadata_->funcImports.length();
 }
 
-uint32_t
-ModuleGenerator::numFuncDefs() const
-{
-    // asm.js overallocates the length of funcSigs and in general does not know
-    // the number of function definitions until it's done compiling.
-    MOZ_ASSERT(!isAsmJS());
-    return env_->funcSigs.length() - numFuncImports();
-}
-
 const SigWithId&
 ModuleGenerator::funcSig(uint32_t funcIndex) const
 {
     MOZ_ASSERT(env_->funcSigs[funcIndex]);
     return *env_->funcSigs[funcIndex];
 }
 
 bool
@@ -859,38 +852,48 @@ ModuleGenerator::startFuncDefs()
     for (size_t i = 0; i < numTasks; i++)
         tasks_.infallibleEmplaceBack(*env_, COMPILATION_LIFO_DEFAULT_CHUNK_SIZE);
 
     if (!freeTasks_.reserve(numTasks))
         return false;
     for (size_t i = 0; i < numTasks; i++)
         freeTasks_.infallibleAppend(&tasks_[i]);
 
+    if (!freeBytes_.reserve(numTasks))
+        return false;
+    for (size_t i = 0; i < numTasks; i++) {
+        auto bytes = js::MakeUnique<Bytes>();
+        if (!bytes)
+            return false;
+        freeBytes_.infallibleAppend(Move(bytes));
+    }
+
     startedFuncDefs_ = true;
     MOZ_ASSERT(!finishedFuncDefs_);
     return true;
 }
 
 bool
 ModuleGenerator::startFuncDef(uint32_t lineOrBytecode, FunctionGenerator* fg)
 {
     MOZ_ASSERT(startedFuncDefs_);
     MOZ_ASSERT(!activeFuncDef_);
     MOZ_ASSERT(!finishedFuncDefs_);
 
-    if (freeTasks_.empty() && !finishOutstandingTask())
-        return false;
+    if (!freeBytes_.empty()) {
+        fg->bytes_ = Move(freeBytes_.back());
+        freeBytes_.popBack();
+    } else {
+        fg->bytes_ = js::MakeUnique<Bytes>();
+        if (!fg->bytes_)
+            return false;
+    }
 
-    IonCompileTask* task = freeTasks_.popCopy();
-
-    task->reset(&fg->bytes_);
-    fg->bytes_.clear();
     fg->lineOrBytecode_ = lineOrBytecode;
     fg->m_ = this;
-    fg->task_ = task;
     activeFuncDef_ = fg;
     return true;
 }
 
 bool
 ModuleGenerator::finishFuncDef(uint32_t funcIndex, FunctionGenerator* fg)
 {
     MOZ_ASSERT(activeFuncDef_ == fg);
@@ -899,34 +902,37 @@ ModuleGenerator::finishFuncDef(uint32_t 
                                           funcIndex,
                                           funcSig(funcIndex),
                                           fg->lineOrBytecode_,
                                           Move(fg->callSiteLineNums_));
     if (!func)
         return false;
 
     auto mode = alwaysBaseline_ && BaselineCanCompile(fg)
-                ? IonCompileTask::CompileMode::Baseline
-                : IonCompileTask::CompileMode::Ion;
+                ? CompileTask::CompileMode::Baseline
+                : CompileTask::CompileMode::Ion;
 
-    fg->task_->init(Move(func), mode);
+    if (freeTasks_.empty() && !finishOutstandingTask())
+        return false;
+
+    CompileTask* task = freeTasks_.popCopy();
+    task->init(Move(func), mode);
 
     if (parallel_) {
-        if (!StartOffThreadWasmCompile(fg->task_))
+        if (!StartOffThreadWasmCompile(task))
             return false;
         outstanding_++;
     } else {
-        if (!CompileFunction(fg->task_))
+        if (!CompileFunction(task))
             return false;
-        if (!finishTask(fg->task_))
+        if (!finishTask(task))
             return false;
     }
 
     fg->m_ = nullptr;
-    fg->task_ = nullptr;
     activeFuncDef_ = nullptr;
     numFinishedFuncDefs_++;
     return true;
 }
 
 bool
 ModuleGenerator::finishFuncDefs()
 {
@@ -970,17 +976,17 @@ ModuleGenerator::finishFuncDefs()
 #ifdef DEBUG
     if (isAsmJS()) {
         MOZ_ASSERT(numFuncImports() < AsmJSFirstDefFuncIndex);
         for (uint32_t i = 0; i < AsmJSFirstDefFuncIndex; i++)
             MOZ_ASSERT(funcToCodeRange_[i] == BAD_CODE_RANGE);
         for (uint32_t i = AsmJSFirstDefFuncIndex; i < numFinishedFuncDefs_; i++)
             MOZ_ASSERT(funcCodeRange(i).funcIndex() == i);
     } else {
-        MOZ_ASSERT(numFinishedFuncDefs_ == numFuncDefs());
+        MOZ_ASSERT(numFinishedFuncDefs_ == env_->numFuncDefs());
         for (uint32_t i = 0; i < env_->numFuncs(); i++)
             MOZ_ASSERT(funcCodeRange(i).funcIndex() == i);
     }
 #endif
 
     // Complete element segments with the code range index of every element, now
     // that all functions have been compiled.
 
@@ -1122,8 +1128,26 @@ ModuleGenerator::finish(const ShareableB
                                        Move(linkData_),
                                        Move(env_->imports),
                                        Move(env_->exports),
                                        Move(dataSegments),
                                        Move(env_->elemSegments),
                                        *metadata_,
                                        bytecode));
 }
+
+bool
+wasm::CompileFunction(CompileTask* task)
+{
+    TraceLoggerThread* logger = TraceLoggerForCurrentThread();
+    AutoTraceLog logCompile(logger, TraceLogger_WasmCompilation);
+
+    switch (task->mode()) {
+      case wasm::CompileTask::CompileMode::Ion:
+        return wasm::IonCompileFunction(task);
+      case wasm::CompileTask::CompileMode::Baseline:
+        return wasm::BaselineCompileFunction(task);
+      case wasm::CompileTask::CompileMode::None:
+        break;
+    }
+
+    MOZ_CRASH("Uninitialized task");
+}
--- a/js/src/wasm/WasmGenerator.h
+++ b/js/src/wasm/WasmGenerator.h
@@ -21,31 +21,158 @@
 
 #include "jit/MacroAssembler.h"
 #include "wasm/WasmModule.h"
 #include "wasm/WasmValidate.h"
 
 namespace js {
 namespace wasm {
 
+struct ModuleEnvironment;
+
+typedef Vector<jit::MIRType, 8, SystemAllocPolicy> MIRTypeVector;
+typedef jit::ABIArgIter<MIRTypeVector> ABIArgMIRTypeIter;
+typedef jit::ABIArgIter<ValTypeVector> ABIArgValTypeIter;
+
 struct CompileArgs;
 
 class FunctionGenerator;
 
+typedef Vector<UniqueBytes, 0, SystemAllocPolicy> UniqueBytesVector;
+
+// The FuncBytes class represents a single, concurrently-compilable function.
+// A FuncBytes object is composed of the wasm function body bytes along with the
+// ambient metadata describing the function necessary to compile it.
+
+class FuncBytes
+{
+    UniqueBytes      bytes_;
+    uint32_t         index_;
+    const SigWithId& sig_;
+    uint32_t         lineOrBytecode_;
+    Uint32Vector     callSiteLineNums_;
+
+  public:
+    FuncBytes(UniqueBytes bytes,
+              uint32_t index,
+              const SigWithId& sig,
+              uint32_t lineOrBytecode,
+              Uint32Vector&& callSiteLineNums)
+      : bytes_(Move(bytes)),
+        index_(index),
+        sig_(sig),
+        lineOrBytecode_(lineOrBytecode),
+        callSiteLineNums_(Move(callSiteLineNums))
+    {}
+
+    Bytes& bytes() { return *bytes_; }
+    const Bytes& bytes() const { return *bytes_; }
+    UniqueBytes recycle() { return Move(bytes_); }
+    uint32_t index() const { return index_; }
+    const SigWithId& sig() const { return sig_; }
+    uint32_t lineOrBytecode() const { return lineOrBytecode_; }
+    const Uint32Vector& callSiteLineNums() const { return callSiteLineNums_; }
+};
+
+typedef UniquePtr<FuncBytes> UniqueFuncBytes;
+
+// The FuncCompileResults class contains the results of compiling a single
+// function body, ready to be merged into the whole-module MacroAssembler.
+
+class FuncCompileResults
+{
+    jit::TempAllocator alloc_;
+    jit::MacroAssembler masm_;
+    FuncOffsets offsets_;
+
+    FuncCompileResults(const FuncCompileResults&) = delete;
+    FuncCompileResults& operator=(const FuncCompileResults&) = delete;
+
+  public:
+    explicit FuncCompileResults(LifoAlloc& lifo)
+      : alloc_(&lifo),
+        masm_(jit::MacroAssembler::WasmToken(), alloc_)
+    {}
+
+    jit::TempAllocator& alloc() { return alloc_; }
+    jit::MacroAssembler& masm() { return masm_; }
+    FuncOffsets& offsets() { return offsets_; }
+};
+
+// A CompileTask represents the task of compiling a single function body. An
+// CompileTask is filled with the wasm code to be compiled on the main
+// validation thread, sent off to a compilation helper thread which creates
+// the FuncCompileResults, and finally sent back to the validation thread. To
+// save time allocating and freeing memory, CompileTasks are reset() and
+// reused.
+
+class CompileTask
+{
+  public:
+    enum class CompileMode { None, Baseline, Ion };
+
+  private:
+    const ModuleEnvironment&  env_;
+    LifoAlloc                 lifo_;
+    UniqueFuncBytes           func_;
+    CompileMode               mode_;
+    Maybe<FuncCompileResults> results_;
+
+    CompileTask(const CompileTask&) = delete;
+    CompileTask& operator=(const CompileTask&) = delete;
+
+  public:
+    CompileTask(const ModuleEnvironment& env, size_t defaultChunkSize)
+      : env_(env), lifo_(defaultChunkSize), func_(nullptr), mode_(CompileMode::None)
+    {}
+    LifoAlloc& lifo() {
+        return lifo_;
+    }
+    const ModuleEnvironment& env() const {
+        return env_;
+    }
+    void init(UniqueFuncBytes func, CompileMode mode) {
+        MOZ_ASSERT(!func_);
+        func_ = Move(func);
+        results_.emplace(lifo_);
+        mode_ = mode;
+    }
+    CompileMode mode() const {
+        return mode_;
+    }
+    const FuncBytes& func() const {
+        MOZ_ASSERT(func_);
+        return *func_;
+    }
+    FuncCompileResults& results() {
+        return *results_;
+    }
+    void reset(UniqueBytes* recycled) {
+        if (func_) {
+            *recycled = Move(func_->recycle());
+            (*recycled)->clear();
+        }
+        func_.reset(nullptr);
+        results_.reset();
+        lifo_.releaseAll();
+        mode_ = CompileMode::None;
+    }
+};
+
 // A ModuleGenerator encapsulates the creation of a wasm module. During the
 // lifetime of a ModuleGenerator, a sequence of FunctionGenerators are created
 // and destroyed to compile the individual function bodies. After generating all
 // functions, ModuleGenerator::finish() must be called to complete the
 // compilation and extract the resulting wasm module.
 
 class MOZ_STACK_CLASS ModuleGenerator
 {
     typedef HashSet<uint32_t, DefaultHasher<uint32_t>, SystemAllocPolicy> Uint32Set;
-    typedef Vector<IonCompileTask, 0, SystemAllocPolicy> IonCompileTaskVector;
-    typedef Vector<IonCompileTask*, 0, SystemAllocPolicy> IonCompileTaskPtrVector;
+    typedef Vector<CompileTask, 0, SystemAllocPolicy> CompileTaskVector;
+    typedef Vector<CompileTask*, 0, SystemAllocPolicy> CompileTaskPtrVector;
     typedef EnumeratedArray<Trap, Trap::Limit, ProfilingOffsets> TrapExitOffsetArray;
 
     // Constant parameters
     bool                            alwaysBaseline_;
 
     // Data that is moved into the result of finish()
     Assumptions                     assumptions_;
     LinkData                        linkData_;
@@ -62,41 +189,45 @@ class MOZ_STACK_CLASS ModuleGenerator
     Uint32Vector                    funcToCodeRange_;
     Uint32Set                       exportedFuncs_;
     uint32_t                        lastPatchedCallsite_;
     uint32_t                        startOfUnpatchedCallsites_;
 
     // Parallel compilation
     bool                            parallel_;
     uint32_t                        outstanding_;
-    IonCompileTaskVector            tasks_;
-    IonCompileTaskPtrVector         freeTasks_;
+    CompileTaskVector               tasks_;
+    CompileTaskPtrVector            freeTasks_;
+    UniqueBytesVector               freeBytes_;
 
     // Assertions
     DebugOnly<FunctionGenerator*>   activeFuncDef_;
     DebugOnly<bool>                 startedFuncDefs_;
     DebugOnly<bool>                 finishedFuncDefs_;
     DebugOnly<uint32_t>             numFinishedFuncDefs_;
 
     bool funcIsCompiled(uint32_t funcIndex) const;
     const CodeRange& funcCodeRange(uint32_t funcIndex) const;
     MOZ_MUST_USE bool patchCallSites(TrapExitOffsetArray* maybeTrapExits = nullptr);
     MOZ_MUST_USE bool patchFarJumps(const TrapExitOffsetArray& trapExits);
-    MOZ_MUST_USE bool finishTask(IonCompileTask* task);
+    MOZ_MUST_USE bool finishTask(CompileTask* task);
     MOZ_MUST_USE bool finishOutstandingTask();
     MOZ_MUST_USE bool finishFuncExports();
     MOZ_MUST_USE bool finishCodegen();
     MOZ_MUST_USE bool finishLinkData(Bytes& code);
     MOZ_MUST_USE bool addFuncImport(const Sig& sig, uint32_t globalDataOffset);
     MOZ_MUST_USE bool allocateGlobalBytes(uint32_t bytes, uint32_t align, uint32_t* globalDataOff);
     MOZ_MUST_USE bool allocateGlobal(GlobalDesc* global);
 
     MOZ_MUST_USE bool initAsmJS(Metadata* asmJSMetadata);
     MOZ_MUST_USE bool initWasm();
 
+    // Functions declarations:
+    uint32_t numFuncImports() const;
+
   public:
     explicit ModuleGenerator();
     ~ModuleGenerator();
 
     MOZ_MUST_USE bool init(UniqueModuleEnvironment env, const CompileArgs& args,
                            Metadata* maybeAsmJSMetadata = nullptr);
 
     const ModuleEnvironment& env() const { return *env_; }
@@ -116,20 +247,16 @@ class MOZ_STACK_CLASS ModuleGenerator
     uint32_t numSigs() const { return numSigs_; }
     const SigWithId& sig(uint32_t sigIndex) const;
     const SigWithId& funcSig(uint32_t funcIndex) const;
     const SigWithIdPtrVector& funcSigs() const { return env_->funcSigs; }
 
     // Globals:
     const GlobalDescVector& globals() const { return env_->globals; }
 
-    // Functions declarations:
-    uint32_t numFuncImports() const;
-    uint32_t numFuncDefs() const;
-
     // Function definitions:
     MOZ_MUST_USE bool startFuncDefs();
     MOZ_MUST_USE bool startFuncDef(uint32_t lineOrBytecode, FunctionGenerator* fg);
     MOZ_MUST_USE bool finishFuncDef(uint32_t funcIndex, FunctionGenerator* fg);
     MOZ_MUST_USE bool finishFuncDefs();
 
     // asm.js lazy initialization:
     void initSig(uint32_t sigIndex, Sig&& sig);
@@ -155,30 +282,29 @@ class MOZ_STACK_CLASS ModuleGenerator
 // be called before the FunctionGenerator is destroyed and the next function is
 // started.
 
 class MOZ_STACK_CLASS FunctionGenerator
 {
     friend class ModuleGenerator;
 
     ModuleGenerator* m_;
-    IonCompileTask*  task_;
     bool             usesSimd_;
     bool             usesAtomics_;
 
     // Data created during function generation, then handed over to the
     // FuncBytes in ModuleGenerator::finishFunc().
-    Bytes            bytes_;
+    UniqueBytes      bytes_;
     Uint32Vector     callSiteLineNums_;
 
     uint32_t lineOrBytecode_;
 
   public:
     FunctionGenerator()
-      : m_(nullptr), task_(nullptr), usesSimd_(false), usesAtomics_(false), lineOrBytecode_(0)
+      : m_(nullptr), usesSimd_(false), usesAtomics_(false), bytes_(nullptr), lineOrBytecode_(0)
     {}
 
     bool usesSimd() const {
         return usesSimd_;
     }
     void setUsesSimd() {
         usesSimd_ = true;
     }
@@ -186,17 +312,17 @@ class MOZ_STACK_CLASS FunctionGenerator
     bool usesAtomics() const {
         return usesAtomics_;
     }
     void setUsesAtomics() {
         usesAtomics_ = true;
     }
 
     Bytes& bytes() {
-        return bytes_;
+        return *bytes_;
     }
     MOZ_MUST_USE bool addCallSiteLineNum(uint32_t lineno) {
         return callSiteLineNums_.append(lineno);
     }
 };
 
 } // namespace wasm
 } // namespace js
--- a/js/src/wasm/WasmIonCompile.cpp
+++ b/js/src/wasm/WasmIonCompile.cpp
@@ -3692,19 +3692,19 @@ EmitExpr(FunctionCompiler& f)
         return EmitCurrentMemory(f);
       case Op::Limit:;
     }
 
     MOZ_CRASH("unexpected wasm opcode");
 }
 
 bool
-wasm::IonCompileFunction(IonCompileTask* task)
+wasm::IonCompileFunction(CompileTask* task)
 {
-    MOZ_ASSERT(task->mode() == IonCompileTask::CompileMode::Ion);
+    MOZ_ASSERT(task->mode() == CompileTask::CompileMode::Ion);
 
     const FuncBytes& func = task->func();
     FuncCompileResults& results = task->results();
 
     Decoder d(func.bytes());
 
     // Build the local types vector.
 
@@ -3774,26 +3774,8 @@ wasm::IonCompileFunction(IonCompileTask*
 
         CodeGenerator codegen(&mir, lir, &results.masm());
         if (!codegen.generateWasm(sigId, prologueTrapOffset, &results.offsets()))
             return false;
     }
 
     return true;
 }
-
-bool
-wasm::CompileFunction(IonCompileTask* task)
-{
-    TraceLoggerThread* logger = TraceLoggerForCurrentThread();
-    AutoTraceLog logCompile(logger, TraceLogger_WasmCompilation);
-
-    switch (task->mode()) {
-      case wasm::IonCompileTask::CompileMode::Ion:
-        return wasm::IonCompileFunction(task);
-      case wasm::IonCompileTask::CompileMode::Baseline:
-        return wasm::BaselineCompileFunction(task);
-      case wasm::IonCompileTask::CompileMode::None:
-        break;
-    }
-
-    MOZ_CRASH("Uninitialized task");
-}
--- a/js/src/wasm/WasmIonCompile.h
+++ b/js/src/wasm/WasmIonCompile.h
@@ -14,146 +14,23 @@
  * 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.
  */
 
 #ifndef wasm_ion_compile_h
 #define wasm_ion_compile_h
 
-#include "jit/MacroAssembler.h"
-#include "wasm/WasmTypes.h"
+#include "mozilla/Attributes.h"
 
 namespace js {
 namespace wasm {
 
-struct ModuleEnvironment;
-
-typedef Vector<jit::MIRType, 8, SystemAllocPolicy> MIRTypeVector;
-typedef jit::ABIArgIter<MIRTypeVector> ABIArgMIRTypeIter;
-typedef jit::ABIArgIter<ValTypeVector> ABIArgValTypeIter;
-
-// The FuncBytes class represents a single, concurrently-compilable function.
-// A FuncBytes object is composed of the wasm function body bytes along with the
-// ambient metadata describing the function necessary to compile it.
-
-class FuncBytes
-{
-    Bytes            bytes_;
-    uint32_t         index_;
-    const SigWithId& sig_;
-    uint32_t         lineOrBytecode_;
-    Uint32Vector     callSiteLineNums_;
-
-  public:
-    FuncBytes(Bytes&& bytes,
-              uint32_t index,
-              const SigWithId& sig,
-              uint32_t lineOrBytecode,
-              Uint32Vector&& callSiteLineNums)
-      : bytes_(Move(bytes)),
-        index_(index),
-        sig_(sig),
-        lineOrBytecode_(lineOrBytecode),
-        callSiteLineNums_(Move(callSiteLineNums))
-    {}
-
-    Bytes& bytes() { return bytes_; }
-    const Bytes& bytes() const { return bytes_; }
-    uint32_t index() const { return index_; }
-    const SigWithId& sig() const { return sig_; }
-    uint32_t lineOrBytecode() const { return lineOrBytecode_; }
-    const Uint32Vector& callSiteLineNums() const { return callSiteLineNums_; }
-};
-
-typedef UniquePtr<FuncBytes> UniqueFuncBytes;
-
-// The FuncCompileResults class contains the results of compiling a single
-// function body, ready to be merged into the whole-module MacroAssembler.
-
-class FuncCompileResults
-{
-    jit::TempAllocator alloc_;
-    jit::MacroAssembler masm_;
-    FuncOffsets offsets_;
-
-    FuncCompileResults(const FuncCompileResults&) = delete;
-    FuncCompileResults& operator=(const FuncCompileResults&) = delete;
-
-  public:
-    explicit FuncCompileResults(LifoAlloc& lifo)
-      : alloc_(&lifo),
-        masm_(jit::MacroAssembler::WasmToken(), alloc_)
-    {}
-
-    jit::TempAllocator& alloc() { return alloc_; }
-    jit::MacroAssembler& masm() { return masm_; }
-    FuncOffsets& offsets() { return offsets_; }
-};
+class CompileTask;
 
-// An IonCompileTask represents the task of compiling a single function body. An
-// IonCompileTask is filled with the wasm code to be compiled on the main
-// validation thread, sent off to an Ion compilation helper thread which creates
-// the FuncCompileResults, and finally sent back to the validation thread. To
-// save time allocating and freeing memory, IonCompileTasks are reset() and
-// reused.
-
-class IonCompileTask
-{
-  public:
-    enum class CompileMode { None, Baseline, Ion };
-
-  private:
-    const ModuleEnvironment&  env_;
-    LifoAlloc                 lifo_;
-    UniqueFuncBytes           func_;
-    CompileMode               mode_;
-    Maybe<FuncCompileResults> results_;
-
-    IonCompileTask(const IonCompileTask&) = delete;
-    IonCompileTask& operator=(const IonCompileTask&) = delete;
-
-  public:
-    IonCompileTask(const ModuleEnvironment& env, size_t defaultChunkSize)
-      : env_(env), lifo_(defaultChunkSize), func_(nullptr), mode_(CompileMode::None)
-    {}
-    LifoAlloc& lifo() {
-        return lifo_;
-    }
-    const ModuleEnvironment& env() const {
-        return env_;
-    }
-    void init(UniqueFuncBytes func, CompileMode mode) {
-        MOZ_ASSERT(!func_);
-        func_ = Move(func);
-        results_.emplace(lifo_);
-        mode_ = mode;
-    }
-    CompileMode mode() const {
-        return mode_;
-    }
-    const FuncBytes& func() const {
-        MOZ_ASSERT(func_);
-        return *func_;
-    }
-    FuncCompileResults& results() {
-        return *results_;
-    }
-    void reset(Bytes* recycled) {
-        if (func_)
-            *recycled = Move(func_->bytes());
-        func_.reset(nullptr);
-        results_.reset();
-        lifo_.releaseAll();
-        mode_ = CompileMode::None;
-    }
-};
-
+// Generates very fast code at the expense of compilation time.
 MOZ_MUST_USE bool
-IonCompileFunction(IonCompileTask* task);
-
-bool
-CompileFunction(IonCompileTask* task);
+IonCompileFunction(CompileTask* task);
 
 } // namespace wasm
 } // namespace js
 
 #endif // wasm_ion_compile_h
--- a/js/src/wasm/WasmJS.cpp
+++ b/js/src/wasm/WasmJS.cpp
@@ -1678,24 +1678,24 @@ ResolveCompilation(JSContext* cx, Module
     RootedObject moduleObj(cx, WasmModuleObject::create(cx, module, proto));
     if (!moduleObj)
         return false;
 
     RootedValue resolutionValue(cx, ObjectValue(*moduleObj));
     return promise->resolve(cx, resolutionValue);
 }
 
-struct CompileTask : PromiseTask
+struct CompilePromiseTask : PromiseTask
 {
     MutableBytes bytecode;
     CompileArgs  compileArgs;
     UniqueChars  error;
     SharedModule module;
 
-    CompileTask(JSContext* cx, Handle<PromiseObject*> promise)
+    CompilePromiseTask(JSContext* cx, Handle<PromiseObject*> promise)
       : PromiseTask(cx, promise)
     {}
 
     void execute() override {
         module = Compile(*bytecode, compileArgs, &error);
     }
 
     bool finishPromise(JSContext* cx, Handle<PromiseObject*> promise) override {
@@ -1753,17 +1753,17 @@ WebAssembly_compile(JSContext* cx, unsig
     RootedFunction nopFun(cx, NewNativeFunction(cx, Nop, 0, nullptr));
     if (!nopFun)
         return false;
 
     Rooted<PromiseObject*> promise(cx, PromiseObject::create(cx, nopFun));
     if (!promise)
         return false;
 
-    auto task = cx->make_unique<CompileTask>(cx, promise);
+    auto task = cx->make_unique<CompilePromiseTask>(cx, promise);
     if (!task)
         return false;
 
     CallArgs callArgs = CallArgsFromVp(argc, vp);
 
     if (!GetBufferSource(cx, callArgs, "WebAssembly.compile", &task->bytecode))
         return RejectWithPendingException(cx, promise, callArgs);
 
@@ -1801,22 +1801,22 @@ ResolveInstantiation(JSContext* cx, Modu
     val = ObjectValue(*instanceObj);
     if (!JS_DefineProperty(cx, resultObj, "instance", val, JSPROP_ENUMERATE))
         return false;
 
     val = ObjectValue(*resultObj);
     return promise->resolve(cx, val);
 }
 
-struct InstantiateTask : CompileTask
+struct InstantiatePromiseTask : CompilePromiseTask
 {
     PersistentRootedObject importObj;
 
-    InstantiateTask(JSContext* cx, Handle<PromiseObject*> promise, HandleObject importObj)
-      : CompileTask(cx, promise),
+    InstantiatePromiseTask(JSContext* cx, Handle<PromiseObject*> promise, HandleObject importObj)
+      : CompilePromiseTask(cx, promise),
         importObj(cx, importObj)
     {}
 
     bool finishPromise(JSContext* cx, Handle<PromiseObject*> promise) override {
         return module
                ? ResolveInstantiation(cx, *module, importObj, promise)
                : Reject(cx, compileArgs, Move(error), promise);
     }
@@ -1873,17 +1873,17 @@ WebAssembly_instantiate(JSContext* cx, u
         RootedWasmInstanceObject instanceObj(cx);
         if (!Instantiate(cx, *module, importObj, &instanceObj))
             return RejectWithPendingException(cx, promise, callArgs);
 
         RootedValue resolutionValue(cx, ObjectValue(*instanceObj));
         if (!promise->resolve(cx, resolutionValue))
             return false;
     } else {
-        auto task = cx->make_unique<InstantiateTask>(cx, promise, importObj);
+        auto task = cx->make_unique<InstantiatePromiseTask>(cx, promise, importObj);
         if (!task)
             return false;
 
         if (!GetBufferSource(cx, firstArg, JSMSG_WASM_BAD_BUF_MOD_ARG, &task->bytecode))
             return RejectWithPendingException(cx, promise, callArgs);
 
         if (!InitCompileArgs(cx, &task->compileArgs))
             return false;
--- a/js/src/wasm/WasmStubs.cpp
+++ b/js/src/wasm/WasmStubs.cpp
@@ -16,17 +16,17 @@
  * limitations under the License.
  */
 
 #include "wasm/WasmStubs.h"
 
 #include "mozilla/ArrayUtils.h"
 
 #include "wasm/WasmCode.h"
-#include "wasm/WasmIonCompile.h"
+#include "wasm/WasmGenerator.h"
 
 #include "jit/MacroAssembler-inl.h"
 
 using namespace js;
 using namespace js::jit;
 using namespace js::wasm;
 
 using mozilla::ArrayLength;
--- a/js/src/wasm/WasmTypes.h
+++ b/js/src/wasm/WasmTypes.h
@@ -78,16 +78,17 @@ using mozilla::PodZero;
 using mozilla::PodCopy;
 using mozilla::PodEqual;
 using mozilla::RefCounted;
 using mozilla::Some;
 using mozilla::Unused;
 
 typedef Vector<uint32_t, 0, SystemAllocPolicy> Uint32Vector;
 typedef Vector<uint8_t, 0, SystemAllocPolicy> Bytes;
+typedef UniquePtr<Bytes> UniqueBytes;
 
 typedef int8_t I8x16[16];
 typedef int16_t I16x8[8];
 typedef int32_t I32x4[4];
 typedef float F32x4[4];
 
 class Code;
 class CodeRange;
--- a/layout/style/ServoBindingList.h
+++ b/layout/style/ServoBindingList.h
@@ -48,16 +48,22 @@ SERVO_BINDING_FUNC(Servo_StyleSet_Insert
                    RawServoStyleSheetBorrowed reference)
 
 // CSSRuleList
 SERVO_BINDING_FUNC(Servo_CssRules_ListTypes, void,
                    ServoCssRulesBorrowed rules,
                    nsTArrayBorrowed_uintptr_t result)
 SERVO_BINDING_FUNC(Servo_CssRules_GetStyleRuleAt, RawServoStyleRuleStrong,
                    ServoCssRulesBorrowed rules, uint32_t index)
+SERVO_BINDING_FUNC(Servo_CssRules_InsertRule, nsresult,
+                   ServoCssRulesBorrowed rules,
+                   RawServoStyleSheetBorrowed sheet, const nsACString* rule,
+                   uint32_t index, bool nested, uint16_t* rule_type)
+SERVO_BINDING_FUNC(Servo_CssRules_DeleteRule, nsresult,
+                   ServoCssRulesBorrowed rules, uint32_t index)
 
 // CSS Rules
 SERVO_BINDING_FUNC(Servo_StyleRule_Debug, void,
                    RawServoStyleRuleBorrowed rule, nsACString* result)
 SERVO_BINDING_FUNC(Servo_StyleRule_GetStyle, RawServoDeclarationBlockStrong,
                    RawServoStyleRuleBorrowed rule)
 SERVO_BINDING_FUNC(Servo_StyleRule_SetStyle, void,
                    RawServoStyleRuleBorrowed rule,
--- a/layout/style/ServoCSSRuleList.cpp
+++ b/layout/style/ServoCSSRuleList.cpp
@@ -19,24 +19,19 @@ ServoCSSRuleList::ServoCSSRuleList(Servo
   , mRawRules(aRawRules)
 {
   Servo_CssRules_ListTypes(mRawRules, &mRules);
   // XXX We may want to eagerly create object for import rule, so that
   //     we don't lose the reference to child stylesheet when our own
   //     stylesheet goes away.
 }
 
-nsIDOMCSSRule*
-ServoCSSRuleList::IndexedGetter(uint32_t aIndex, bool& aFound)
+css::Rule*
+ServoCSSRuleList::GetRule(uint32_t aIndex)
 {
-  if (aIndex >= mRules.Length()) {
-    aFound = false;
-    return nullptr;
-  }
-  aFound = true;
   uintptr_t rule = mRules[aIndex];
   if (rule <= kMaxRuleType) {
     RefPtr<css::Rule> ruleObj = nullptr;
     switch (rule) {
       case nsIDOMCSSRule::STYLE_RULE: {
         ruleObj = new ServoStyleRule(
           Servo_CssRules_GetStyleRuleAt(mRawRules, aIndex).Consume());
         break;
@@ -49,17 +44,31 @@ ServoCSSRuleList::IndexedGetter(uint32_t
       default:
         NS_ERROR("stylo: not implemented yet");
         return nullptr;
     }
     ruleObj->SetStyleSheet(mStyleSheet);
     rule = CastToUint(ruleObj.forget().take());
     mRules[aIndex] = rule;
   }
-  return CastToPtr(rule)->GetDOMRule();
+  return CastToPtr(rule);
+}
+
+nsIDOMCSSRule*
+ServoCSSRuleList::IndexedGetter(uint32_t aIndex, bool& aFound)
+{
+  if (aIndex >= mRules.Length()) {
+    aFound = false;
+    return nullptr;
+  }
+  aFound = true;
+  if (css::Rule* rule = GetRule(aIndex)) {
+    return rule->GetDOMRule();
+  }
+  return nullptr;
 }
 
 template<typename Func>
 void
 ServoCSSRuleList::EnumerateInstantiatedRules(Func aCallback)
 {
   for (uintptr_t rule : mRules) {
     if (rule > kMaxRuleType) {
@@ -72,14 +81,40 @@ void
 ServoCSSRuleList::DropReference()
 {
   mStyleSheet = nullptr;
   EnumerateInstantiatedRules([](css::Rule* rule) {
     rule->SetStyleSheet(nullptr);
   });
 }
 
+nsresult
+ServoCSSRuleList::InsertRule(const nsAString& aRule, uint32_t aIndex)
+{
+  NS_ConvertUTF16toUTF8 rule(aRule);
+  // XXX This needs to actually reflect whether it is nested when we
+  // support using CSSRuleList in CSSGroupingRules.
+  bool nested = false;
+  uint16_t type;
+  nsresult rv = Servo_CssRules_InsertRule(mRawRules, mStyleSheet->RawSheet(),
+                                          &rule, aIndex, nested, &type);
+  if (!NS_FAILED(rv)) {
+    mRules.InsertElementAt(type);
+  }
+  return rv;
+}
+
+nsresult
+ServoCSSRuleList::DeleteRule(uint32_t aIndex)
+{
+  nsresult rv = Servo_CssRules_DeleteRule(mRawRules, aIndex);
+  if (!NS_FAILED(rv)) {
+    mRules.RemoveElementAt(aIndex);
+  }
+  return rv;
+}
+
 ServoCSSRuleList::~ServoCSSRuleList()
 {
   EnumerateInstantiatedRules([](css::Rule* rule) { rule->Release(); });
 }
 
 } // namespace mozilla
--- a/layout/style/ServoCSSRuleList.h
+++ b/layout/style/ServoCSSRuleList.h
@@ -27,16 +27,20 @@ public:
 
   ServoStyleSheet* GetParentObject() final { return mStyleSheet; }
 
   nsIDOMCSSRule* IndexedGetter(uint32_t aIndex, bool& aFound) final;
   uint32_t Length() final { return mRules.Length(); }
 
   void DropReference();
 
+  css::Rule* GetRule(uint32_t aIndex);
+  nsresult InsertRule(const nsAString& aRule, uint32_t aIndex);
+  nsresult DeleteRule(uint32_t aIndex);
+
 private:
   virtual ~ServoCSSRuleList();
 
   // XXX Is it possible to have an address lower than or equal to 255?
   //     Is it possible to have more than 255 CSS rule types?
   static const uintptr_t kMaxRuleType = UINT8_MAX;
 
   static uintptr_t CastToUint(css::Rule* aPtr) {
--- a/layout/style/ServoStyleSheet.cpp
+++ b/layout/style/ServoStyleSheet.cpp
@@ -5,16 +5,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/ServoStyleSheet.h"
 #include "mozilla/StyleBackendType.h"
 #include "mozilla/ServoBindings.h"
 #include "mozilla/ServoCSSRuleList.h"
 #include "mozilla/dom/CSSRuleList.h"
 
+#include "mozAutoDocUpdate.h"
+
 namespace mozilla {
 
 ServoStyleSheet::ServoStyleSheet(css::SheetParsingMode aParsingMode,
                                  CORSMode aCORSMode,
                                  net::ReferrerPolicy aReferrerPolicy,
                                  const dom::SRIMetadata& aIntegrity)
   : StyleSheet(StyleBackendType::Servo, aParsingMode)
   , mSheetInfo(aCORSMode, aReferrerPolicy, aIntegrity)
@@ -134,19 +136,50 @@ ServoStyleSheet::GetCssRulesInternal(Err
   }
   return mRuleList;
 }
 
 uint32_t
 ServoStyleSheet::InsertRuleInternal(const nsAString& aRule,
                                     uint32_t aIndex, ErrorResult& aRv)
 {
-  aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
-  return 0;
+  // Ensure mRuleList is constructed.
+  GetCssRulesInternal(aRv);
+
+  mozAutoDocUpdate updateBatch(mDocument, UPDATE_STYLE, true);
+  aRv = mRuleList->InsertRule(aRule, aIndex);
+  if (aRv.Failed()) {
+    return 0;
+  }
+  if (mDocument) {
+    // XXX When we support @import rules, we should not notify here,
+    // but rather when the sheet the rule is importing is loaded.
+    // XXX We may not want to get the rule when stylesheet change event
+    // is not enabled.
+    mDocument->StyleRuleAdded(this, mRuleList->GetRule(aIndex));
+  }
+  return aIndex;
 }
 
 void
 ServoStyleSheet::DeleteRuleInternal(uint32_t aIndex, ErrorResult& aRv)
 {
-  aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+  // Ensure mRuleList is constructed.
+  GetCssRulesInternal(aRv);
+  if (aIndex > mRuleList->Length()) {
+    aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+    return;
+  }
+
+  mozAutoDocUpdate updateBatch(mDocument, UPDATE_STYLE, true);
+  // Hold a strong ref to the rule so it doesn't die when we remove it
+  // from the list. XXX We may not want to hold it if stylesheet change
+  // event is not enabled.
+  RefPtr<css::Rule> rule = mRuleList->GetRule(aIndex);
+  aRv = mRuleList->DeleteRule(aIndex);
+  MOZ_ASSERT(!aRv.ErrorCodeIs(NS_ERROR_DOM_INDEX_SIZE_ERR),
+             "IndexSizeError should have been handled earlier");
+  if (!aRv.Failed() && mDocument) {
+    mDocument->StyleRuleRemoved(this, rule);
+  }
 }
 
 } // namespace mozilla
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -4395,21 +4395,37 @@ CSSParserImpl::ParseKeyframesRule(RuleAp
 {
   uint32_t linenum, colnum;
   if (!GetNextTokenLocation(true, &linenum, &colnum) ||
       !GetToken(true)) {
     REPORT_UNEXPECTED_EOF(PEKeyframeNameEOF);
     return false;
   }
 
-  if (mToken.mType != eCSSToken_Ident) {
+  if (mToken.mType != eCSSToken_Ident && mToken.mType != eCSSToken_String) {
     REPORT_UNEXPECTED_TOKEN(PEKeyframeBadName);
     UngetToken();
     return false;
   }
+
+  if (mToken.mType == eCSSToken_Ident) {
+    // Check for keywords that are not allowed as custom-ident for the
+    // keyframes-name: standard CSS-wide keywords, plus 'none'.
+    static const nsCSSKeyword excludedKeywords[] = {
+      eCSSKeyword_none,
+      eCSSKeyword_UNKNOWN
+    };
+    nsCSSValue value;
+    if (!ParseCustomIdent(value, mToken.mIdent, excludedKeywords)) {
+      REPORT_UNEXPECTED_TOKEN(PEKeyframeBadName);
+      UngetToken();
+      return false;
+    }
+  }
+
   nsString name(mToken.mIdent);
 
   if (!ExpectSymbol('{', true)) {
     REPORT_UNEXPECTED_TOKEN(PEKeyframeBrace);
     return false;
   }
 
   RefPtr<nsCSSKeyframesRule> rule = new nsCSSKeyframesRule(name,
--- a/layout/style/nsCSSPropList.h
+++ b/layout/style/nsCSSPropList.h
@@ -429,17 +429,17 @@ CSS_PROP_DISPLAY(
     animation-name,
     animation_name,
     AnimationName,
     CSS_PROPERTY_PARSE_VALUE_LIST |
         CSS_PROPERTY_VALUE_LIST_USES_COMMAS,
     "",
     // FIXME: The spec should say something about 'inherit' and 'initial'
     // not being allowed.
-    VARIANT_NONE | VARIANT_IDENTIFIER_NO_INHERIT, // used by list parsing
+    VARIANT_NONE | VARIANT_IDENTIFIER_NO_INHERIT | VARIANT_STRING, // used by list parsing
     nullptr,
     CSS_PROP_NO_OFFSET,
     eStyleAnimType_None)
 CSS_PROP_DISPLAY(
     animation-play-state,
     animation_play_state,
     AnimationPlayState,
     CSS_PROPERTY_PARSE_VALUE_LIST |
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -5736,16 +5736,17 @@ nsRuleNode::ComputeDisplayData(void* aSt
       MOZ_ASSERT(!conditions.Cacheable(),
                  "should have made conditions.Cacheable() false above");
       animation->SetName(parentDisplay->mAnimations[i].GetName());
     } else if (animName.unit == eCSSUnit_Initial ||
                animName.unit == eCSSUnit_Unset) {
       animation->SetName(EmptyString());
     } else if (animName.list) {
       switch (animName.list->mValue.GetUnit()) {
+        case eCSSUnit_String:
         case eCSSUnit_Ident: {
           nsDependentString
             nameStr(animName.list->mValue.GetStringBufferValue());
           animation->SetName(nameStr);
           break;
         }
         case eCSSUnit_None: {
           animation->SetName(EmptyString());
--- a/layout/style/test/test_animations.html
+++ b/layout/style/test/test_animations.html
@@ -158,16 +158,48 @@ https://bugzilla.mozilla.org/show_bug.cg
   @keyframes overridetop {
     0%, 100% { top: 0px }
   }
 
   @keyframes opacitymid {
     0% { opacity: 0.2 }
     100% { opacity: 0.8 }
   }
+
+  @keyframes "string name 1" { /* using string for keyframes name */
+    0%, 100% { left: 1px }
+  }
+
+  @keyframes "string name 2" {
+    0%, 100% { left: 2px }
+  }
+
+  @keyframes custom\ ident\ 1 {
+    0%, 100% { left: 3px }
+  }
+
+  @keyframes custom\ ident\ 2 {
+    0%, 100% { left: 4px }
+  }
+
+  @keyframes "initial" {
+    0%, 100% { left: 5px }
+  }
+
+  @keyframes initial { /* illegal as an identifier, should be dropped */
+    0%, 100% { left: 6px }
+  }
+
+  @keyframes "none" {
+    0%, 100% { left: 7px }
+  }
+
+  @keyframes none { /* illegal as an identifier, should be dropped */
+    0%, 100% { left: 8px }
+  }
   </style>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=435442">Mozilla Bug 435442</a>
 <div id="display"></div>
 <pre id="test">
 <script type="application/javascript">
 "use strict";
@@ -2034,14 +2066,41 @@ is(cs.getPropertyValue("opacity"), "0.8"
 advance_clock(500);
 is(cs.getPropertyValue("opacity"), "0.65", "opacity transition at 0.5s");
 div.style.animation = "opacitymid 2s linear";
 is(cs.getPropertyValue("opacity"), "0.2", "opacity animation overriding transition at 0s");
 advance_clock(500);
 is(cs.getPropertyValue("opacity"), "0.35", "opacity animation overriding transition at 0.5s");
 done_div();
 
+
+/*
+ * Bug 1320474 - keyframes-name may be a string, allows names that would otherwise be excluded
+ */
+new_div("position: relative; animation: \"string name 1\" 1s linear");
+advance_clock(0);
+is(cs.getPropertyValue("left"), "1px", "animation name as a string");
+div.style.animation = "string\\ name\\ 2 1s linear";
+is(cs.getPropertyValue("left"), "2px", "animation name specified as string, referenced using custom ident");
+div.style.animation = "custom\\ ident\\ 1 1s linear";
+is(cs.getPropertyValue("left"), "3px", "animation name specified as custom-ident");
+div.style.animation = "\"custom ident 2\" 1s linear";
+is(cs.getPropertyValue("left"), "4px", "animation name specified as custom-ident, referenced using string");
+div.style.animation = "unset";
+div.style.animation = "initial 1s linear";
+is(cs.getPropertyValue("left"), "0px", "animation name 'initial' as identifier is ignored");
+div.style.animation = "unset";
+div.style.animation = "\"initial\" 1s linear";
+is(cs.getPropertyValue("left"), "5px", "animation name 'initial' as string is accepted");
+div.style.animation = "unset";
+div.style.animation = "none 1s linear";
+is(cs.getPropertyValue("left"), "0px", "animation name 'none' as identifier is ignored");
+div.style.animation = "unset";
+div.style.animation = "\"none\" 1s linear";
+is(cs.getPropertyValue("left"), "7px", "animation name 'none' as string is accepted");
+done_div();
+
 SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
 
 </script>
 </pre>
 </body>
 </html>
--- a/layout/style/test/test_animations_omta.html
+++ b/layout/style/test/test_animations_omta.html
@@ -2232,16 +2232,22 @@ addAsyncAnimTest(function *() {
   omta_is("opacity", 0.2, RunningOn.Compositor,
           "opacity animation overriding transition at 0s");
   advance_clock(500);
   omta_is("opacity", 0.35, RunningOn.Compositor,
           "opacity animation overriding transition at 0.5s");
   done_div();
 });
 
+// Bug 1320474 - keyframes-name may be a string, allows names that would
+// otherwise be excluded.
+// These tests don't need to be duplicated here as they relate purely to
+// the animation setup which is common to both main-thread and compositor
+// animations.
+
 // Bug 847287 - Test that changes of when an animation is dynamically
 // overridden work correctly.
 addAsyncAnimTest(function *() {
   // anim2 and anim3 are both animations from opacity 0 to 1
 
   new_div("animation: anim2 1s linear forwards; opacity: 0.5 ! important");
   yield waitForPaintsFlushed();
   omta_is("opacity", 0.5, RunningOn.MainThread,
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -93,19 +93,21 @@ import org.mozilla.gecko.toolbar.Autocom
 import org.mozilla.gecko.toolbar.BrowserToolbar;
 import org.mozilla.gecko.toolbar.BrowserToolbar.TabEditingState;
 import org.mozilla.gecko.toolbar.ToolbarProgressView;
 import org.mozilla.gecko.trackingprotection.TrackingProtectionPrompt;
 import org.mozilla.gecko.updater.PostUpdateHandler;
 import org.mozilla.gecko.updater.UpdateServiceHelper;
 import org.mozilla.gecko.util.ActivityUtils;
 import org.mozilla.gecko.util.Clipboard;
+import org.mozilla.gecko.util.BundleEventListener;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.FloatUtils;
 import org.mozilla.gecko.util.GamepadUtils;
+import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.IntentUtils;
 import org.mozilla.gecko.util.MenuUtils;
 import org.mozilla.gecko.util.NativeEventListener;
 import org.mozilla.gecko.util.NativeJSObject;
 import org.mozilla.gecko.util.PrefUtils;
 import org.mozilla.gecko.util.StringUtils;
@@ -195,17 +197,18 @@ public class BrowserApp extends GeckoApp
                                    View.OnKeyListener,
                                    LayerView.DynamicToolbarListener,
                                    BrowserSearch.OnSearchListener,
                                    BrowserSearch.OnEditSuggestionListener,
                                    OnUrlOpenListener,
                                    OnUrlOpenInBackgroundListener,
                                    AnchoredPopup.OnVisibilityChangeListener,
                                    ActionModeCompat.Presenter,
-                                   LayoutInflater.Factory {
+                                   LayoutInflater.Factory,
+                                   BundleEventListener {
     private static final String LOGTAG = "GeckoBrowserApp";
 
     private static final int TABS_ANIMATION_DURATION = 450;
 
     // Intent String extras used to specify custom Switchboard configurations.
     private static final String INTENT_KEY_SWITCHBOARD_SERVER = "switchboard-server";
 
     // TODO: Replace with kinto endpoint.
@@ -724,17 +727,16 @@ public class BrowserApp extends GeckoApp
         mDoorhangerOverlay = findViewById(R.id.doorhanger_overlay);
 
         EventDispatcher.getInstance().registerGeckoThreadListener((GeckoEventListener)this,
             "Gecko:DelayedStartup",
             "Menu:Open",
             "Menu:Update",
             "LightweightTheme:Update",
             "Search:Keyword",
-            "Prompt:ShowTop",
             "Tab:Added",
             "Video:Play");
 
         EventDispatcher.getInstance().registerGeckoThreadListener((NativeEventListener)this,
             "CharEncoding:Data",
             "CharEncoding:State",
             "Download:AndroidDownloadManager",
             "Experiments:GetActive",
@@ -746,16 +748,18 @@ public class BrowserApp extends GeckoApp
             "Menu:Remove",
             "Sanitize:ClearHistory",
             "Sanitize:ClearSyncedTabs",
             "Settings:Show",
             "Telemetry:Gather",
             "Updater:Launch",
             "Website:Metadata");
 
+        getAppEventDispatcher().registerUiThreadListener(this, "Prompt:ShowTop");
+
         final GeckoProfile profile = getProfile();
 
         // We want to upload the telemetry core ping as soon after startup as possible. It relies on the
         // Distribution being initialized. If you move this initialization, ensure it plays well with telemetry.
         final Distribution distribution = Distribution.init(getApplicationContext());
         distribution.addOnDistributionReadyCallback(
                 new DistributionStoreCallback(getApplicationContext(), profile.getName()));
 
@@ -1055,18 +1059,17 @@ public class BrowserApp extends GeckoApp
     @Override
     public void onResume() {
         super.onResume();
         if (mIsAbortingAppLaunch) {
             return;
         }
 
         if (!mHasResumed) {
-            EventDispatcher.getInstance().unregisterGeckoThreadListener((GeckoEventListener) this,
-                    "Prompt:ShowTop");
+            getAppEventDispatcher().unregisterUiThreadListener(this, "Prompt:ShowTop");
             mHasResumed = true;
         }
 
         processTabQueue();
 
         for (BrowserAppDelegate delegate : delegates) {
             delegate.onResume(this);
         }
@@ -1076,18 +1079,17 @@ public class BrowserApp extends GeckoApp
     public void onPause() {
         super.onPause();
         if (mIsAbortingAppLaunch) {
             return;
         }
 
         if (mHasResumed) {
             // Register for Prompt:ShowTop so we can foreground this activity even if it's hidden.
-            EventDispatcher.getInstance().registerGeckoThreadListener((GeckoEventListener) this,
-                "Prompt:ShowTop");
+            getAppEventDispatcher().registerUiThreadListener(this, "Prompt:ShowTop");
             mHasResumed = false;
         }
 
         for (BrowserAppDelegate delegate : delegates) {
             delegate.onPause(this);
         }
     }
 
@@ -1429,17 +1431,16 @@ public class BrowserApp extends GeckoApp
         mSearchEngineManager.unregisterListeners();
 
         EventDispatcher.getInstance().unregisterGeckoThreadListener((GeckoEventListener) this,
             "Gecko:DelayedStartup",
             "Menu:Open",
             "Menu:Update",
             "LightweightTheme:Update",
             "Search:Keyword",
-            "Prompt:ShowTop",
             "Tab:Added",
             "Video:Play");
 
         EventDispatcher.getInstance().unregisterGeckoThreadListener((NativeEventListener) this,
             "CharEncoding:Data",
             "CharEncoding:State",
             "Download:AndroidDownloadManager",
             "Experiments:GetActive",
@@ -1451,16 +1452,18 @@ public class BrowserApp extends GeckoApp
             "Menu:Remove",
             "Sanitize:ClearHistory",
             "Sanitize:ClearSyncedTabs",
             "Settings:Show",
             "Telemetry:Gather",
             "Updater:Launch",
             "Website:Metadata");
 
+        getAppEventDispatcher().unregisterUiThreadListener(this, "Prompt:ShowTop");
+
         if (AppConstants.MOZ_ANDROID_BEAM) {
             NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this);
             if (nfc != null) {
                 // null this out even though the docs say it's not needed,
                 // because the source code looks like it will only do this
                 // automatically on API 14+
                 nfc.setNdefPushMessageCallback(null, this);
             }
@@ -1695,16 +1698,31 @@ public class BrowserApp extends GeckoApp
 
         if (mTabStrip != null) {
             mTabStrip.refresh();
         }
 
         mBrowserToolbar.refresh();
     }
 
+    @Override // BundleEventListener
+    public void handleMessage(final String event, final GeckoBundle message,
+                              final EventCallback callback) {
+        switch (event) {
+            case "Prompt:ShowTop":
+                // Bring this activity to front so the prompt is visible..
+                Intent bringToFrontIntent = new Intent();
+                bringToFrontIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME,
+                                                AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
+                bringToFrontIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+                startActivity(bringToFrontIntent);
+                break;
+        }
+    }
+
     @Override
     public void handleMessage(final String event, final NativeJSObject message,
                               final EventCallback callback) {
         switch (event) {
             case "CharEncoding:Data":
                 final NativeJSObject[] charsets = message.getObjectArray("charsets");
                 final int selected = message.getInt("selected");
 
@@ -2106,24 +2124,16 @@ public class BrowserApp extends GeckoApp
                             public void run() {
                                 mVideoPlayer.start(Uri.parse(uri));
                                 Telemetry.sendUIEvent(TelemetryContract.Event.SHOW, TelemetryContract.Method.CONTENT, "playhls");
                             }
                         });
                     }
                     break;
 
-                case "Prompt:ShowTop":
-                    // Bring this activity to front so the prompt is visible..
-                    Intent bringToFrontIntent = new Intent();
-                    bringToFrontIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
-                    bringToFrontIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
-                    startActivity(bringToFrontIntent);
-                    break;
-
                 case "Tab:Added":
                     if (message.getBoolean("cancelEditMode")) {
                         ThreadUtils.postToUiThread(new Runnable() {
                             @Override
                             public void run() {
                                 // Set the target tab to null so it does not get selected (on editing
                                 // mode exit) in lieu of the tab that we're going to open and select.
                                 mTargetTabForEditingMode = null;
@@ -3818,34 +3828,32 @@ public class BrowserApp extends GeckoApp
     public void showGuestModeDialog(final GuestModeDialog type) {
         if ((type == GuestModeDialog.ENTERING) == getProfile().inGuestMode()) {
             // Don't show enter dialog if we are already in guest mode; same with leaving.
             return;
         }
 
         final Prompt ps = new Prompt(this, new Prompt.PromptCallback() {
             @Override
-            public void onPromptFinished(String result) {
-                try {
-                    int itemId = new JSONObject(result).getInt("button");
-                    if (itemId == 0) {
-                        final Context context = GeckoAppShell.getApplicationContext();
-                        if (type == GuestModeDialog.ENTERING) {
-                            GeckoProfile.enterGuestMode(context);
-                        } else {
-                            GeckoProfile.leaveGuestMode(context);
-                            // Now's a good time to make sure we're not displaying the
-                            // Guest Browsing notification.
-                            GuestSession.hideNotification(context);
-                        }
-                        doRestart();
-                    }
-                } catch (JSONException ex) {
-                    Log.e(LOGTAG, "Exception reading guest mode prompt result", ex);
+            public void onPromptFinished(final GeckoBundle result) {
+                final int itemId = result.getInt("button", -1);
+                if (itemId != 0) {
+                    return;
                 }
+
+                final Context context = GeckoAppShell.getApplicationContext();
+                if (type == GuestModeDialog.ENTERING) {
+                    GeckoProfile.enterGuestMode(context);
+                } else {
+                    GeckoProfile.leaveGuestMode(context);
+                    // Now's a good time to make sure we're not displaying the
+                    // Guest Browsing notification.
+                    GuestSession.hideNotification(context);
+                }
+                doRestart();
             }
         });
 
         Resources res = getResources();
         ps.setButtons(new String[] {
             res.getString(R.string.guest_session_dialog_continue),
             res.getString(R.string.guest_session_dialog_cancel)
         });
--- a/mobile/android/base/java/org/mozilla/gecko/MediaPlayerManager.java
+++ b/mobile/android/base/java/org/mozilla/gecko/MediaPlayerManager.java
@@ -184,24 +184,23 @@ public class MediaPlayerManager extends 
                 GeckoAppShell.notifyObservers("MediaPlayer:Removed", route.getId());
                 updatePresentation();
 
                 // Remove from presentation display list.
                 displays.remove(route.getId());
                 GeckoAppShell.notifyObservers("AndroidCastDevice:Removed", route.getId());
             }
 
-            @SuppressWarnings("unused")
-            public void onRouteSelected(MediaRouter router, int type, MediaRouter.RouteInfo route) {
+            @Override
+            public void onRouteSelected(MediaRouter router, MediaRouter.RouteInfo route) {
                 updatePresentation();
             }
 
-            // These methods aren't used by the support version Media Router
-            @SuppressWarnings("unused")
-            public void onRouteUnselected(MediaRouter router, int type, RouteInfo route) {
+            @Override
+            public void onRouteUnselected(MediaRouter router, MediaRouter.RouteInfo route) {
                 updatePresentation();
             }
 
             @Override
             public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo route) {
                 updatePresentation();
             }
 
--- a/mobile/android/base/java/org/mozilla/gecko/delegates/BookmarkStateChangeDelegate.java
+++ b/mobile/android/base/java/org/mozilla/gecko/delegates/BookmarkStateChangeDelegate.java
@@ -30,16 +30,17 @@ import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.home.HomeConfig;
 import org.mozilla.gecko.promotion.SimpleHelperUI;
 import org.mozilla.gecko.prompts.Prompt;
 import org.mozilla.gecko.prompts.PromptListItem;
 import org.mozilla.gecko.util.DrawableUtil;
+import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import java.lang.ref.WeakReference;
 
 /**
  * Delegate to watch for bookmark state changes.
  *
  * This is responsible for showing snackbars and helper UIs related to the addition/removal
@@ -157,36 +158,32 @@ public class BookmarkStateChangeDelegate
                 .duration(Snackbar.LENGTH_LONG)
                 .buildAndShow();
     }
 
     private static void showBookmarkDialog(final BrowserApp browserApp) {
         final Resources res = browserApp.getResources();
         final Tab tab = Tabs.getInstance().getSelectedTab();
 
+        if (tab == null) {
+            return;
+        }
+
         final Prompt ps = new Prompt(browserApp, new Prompt.PromptCallback() {
             @Override
-            public void onPromptFinished(String result) {
-                int itemId = -1;
-                try {
-                    itemId = new JSONObject(result).getInt("button");
-                } catch (JSONException ex) {
-                    Log.e(LOGTAG, "Exception reading bookmark prompt result", ex);
-                }
-
-                if (tab == null) {
-                    return;
-                }
+            public void onPromptFinished(final GeckoBundle result) {
+                final int itemId = result.getInt("button", -1);
 
                 if (itemId == 0) {
                     final String extrasId = res.getResourceEntryName(R.string.contextmenu_edit_bookmark);
                     Telemetry.sendUIEvent(TelemetryContract.Event.ACTION,
                             TelemetryContract.Method.DIALOG, extrasId);
 
                     new EditBookmarkDialog(browserApp).show(tab.getURL());
+
                 } else if (itemId == 1) {
                     final String extrasId = res.getResourceEntryName(R.string.contextmenu_add_to_launcher);
                     Telemetry.sendUIEvent(TelemetryContract.Event.ACTION,
                             TelemetryContract.Method.DIALOG, extrasId);
 
                     final String url = tab.getURL();
                     final String title = tab.getDisplayTitle();
 
--- a/mobile/android/base/java/org/mozilla/gecko/prompts/ColorPickerInput.java
+++ b/mobile/android/base/java/org/mozilla/gecko/prompts/ColorPickerInput.java
@@ -1,34 +1,34 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.prompts;
 
-import org.json.JSONObject;
 import org.mozilla.gecko.R;
+import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.widget.BasicColorPicker;
 
 import android.content.Context;
 import android.graphics.Color;
 import android.view.LayoutInflater;
 import android.view.View;
 
 public class ColorPickerInput extends PromptInput {
     public static final String INPUT_TYPE = "color";
     public static final String LOGTAG = "GeckoColorPickerInput";
 
     private final boolean mShowAdvancedButton = true;
     private final int mInitialColor;
 
-    public ColorPickerInput(JSONObject obj) {
+    public ColorPickerInput(GeckoBundle obj) {
         super(obj);
-        String init = obj.optString("value");
+        String init = obj.getString("value");
         mInitialColor = Color.rgb(Integer.parseInt(init.substring(1, 3), 16),
                                   Integer.parseInt(init.substring(3, 5), 16),
                                   Integer.parseInt(init.substring(5, 7), 16));
     }
 
     @Override
     public View getView(Context context) throws UnsupportedOperationException {
         LayoutInflater inflater = LayoutInflater.from(context);
--- a/mobile/android/base/java/org/mozilla/gecko/prompts/IconGridInput.java
+++ b/mobile/android/base/java/org/mozilla/gecko/prompts/IconGridInput.java
@@ -3,20 +3,19 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.prompts;
 
 import java.util.ArrayList;
 import java.util.List;
 
-import org.json.JSONArray;
-import org.json.JSONObject;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.R;
+import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.ResourceDrawableUtils;
 
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.text.TextUtils;
 import android.view.Display;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -34,21 +33,22 @@ public class IconGridInput extends Promp
     public static final String LOGTAG = "GeckoIconGridInput";
 
     private ArrayAdapter<IconGridItem> mAdapter; // An adapter holding a list of items to show in the grid
 
     private static int mColumnWidth = -1;  // The maximum width of columns
     private static int mMaxColumns = -1;  // The maximum number of columns to show
     private static int mIconSize = -1;    // Size of icons in the grid
     private int mSelected;                // Current selection
-    private final JSONArray mArray;
+    private final GeckoBundle[] mArray;
 
-    public IconGridInput(JSONObject obj) {
+    public IconGridInput(GeckoBundle obj) {
         super(obj);
-        mArray = obj.optJSONArray("items");
+        final GeckoBundle[] array = obj.getBundleArray("items");
+        mArray = array != null ? array : new GeckoBundle[0];
     }
 
     @Override
     public View getView(Context context) throws UnsupportedOperationException {
         if (mColumnWidth < 0) {
             // getColumnWidth isn't available on pre-ICS, so we pull it out and assign it here
             mColumnWidth = context.getResources().getDimensionPixelSize(R.dimen.icongrid_columnwidth);
         }
@@ -65,19 +65,19 @@ public class IconGridInput extends Promp
         final WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
         final Display display = wm.getDefaultDisplay();
         final int screenWidth = display.getWidth();
         int maxColumns = Math.min(mMaxColumns, screenWidth / mColumnWidth);
 
         final GridView view = (GridView) LayoutInflater.from(context).inflate(R.layout.icon_grid, null, false);
         view.setColumnWidth(mColumnWidth);
 
-        final ArrayList<IconGridItem> items = new ArrayList<IconGridItem>(mArray.length());
-        for (int i = 0; i < mArray.length(); i++) {
-            IconGridItem item = new IconGridItem(context, mArray.optJSONObject(i));
+        final ArrayList<IconGridItem> items = new ArrayList<IconGridItem>(mArray.length);
+        for (int i = 0; i < mArray.length; i++) {
+            IconGridItem item = new IconGridItem(context, mArray[i]);
             items.add(item);
             if (item.selected) {
                 mSelected = i;
             }
         }
 
         view.setNumColumns(Math.min(items.size(), maxColumns));
         view.setOnItemClickListener(this);
@@ -146,21 +146,21 @@ public class IconGridInput extends Promp
     }
 
     private class IconGridItem {
         final String label;
         final String description;
         final boolean selected;
         Drawable icon;
 
-        public IconGridItem(final Context context, final JSONObject obj) {
-            label = obj.optString("name");
-            final String iconUrl = obj.optString("iconUri");
-            description = obj.optString("description");
-            selected = obj.optBoolean("selected");
+        public IconGridItem(final Context context, final GeckoBundle obj) {
+            label = obj.getString("name");
+            final String iconUrl = obj.getString("iconUri");
+            description = obj.getString("description");
+            selected = obj.getBoolean("selected");
 
             ResourceDrawableUtils.getDrawable(context, iconUrl, new ResourceDrawableUtils.BitmapLoader() {
                 @Override
                 public void onBitmapFound(Drawable d) {
                     icon = d;
                     if (mAdapter != null) {
                         mAdapter.notifyDataSetChanged();
                     }
--- a/mobile/android/base/java/org/mozilla/gecko/prompts/IntentChooserPrompt.java
+++ b/mobile/android/base/java/org/mozilla/gecko/prompts/IntentChooserPrompt.java
@@ -1,29 +1,27 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.prompts;
 
 import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.widget.GeckoActionProvider;
 
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.widget.ListView;
 import android.util.Log;
 
-import org.json.JSONException;
-import org.json.JSONObject;
-
 import java.util.ArrayList;
 import java.util.List;
 
 /**
  * Shows a prompt letting the user pick from a list of intent handlers for a set of Intents or
  * for a GeckoActionProvider.  Basic usage:
  *   IntentChooserPrompt prompt = new IntentChooserPrompt(context, new Intent[] {
  *      ... // some intents
@@ -61,27 +59,22 @@ public class IntentChooserPrompt {
         // If there's only one item in the intent list, just return it
         if (mItems.size() == 1) {
             handler.onIntentSelected(mItems.get(0).getIntent(), 0);
             return;
         }
 
         final Prompt prompt = new Prompt(context, new Prompt.PromptCallback() {
             @Override
-            public void onPromptFinished(String promptServiceResult) {
+            public void onPromptFinished(final GeckoBundle result) {
                 if (handler == null) {
                     return;
                 }
 
-                int itemId = -1;
-                try {
-                    itemId = new JSONObject(promptServiceResult).getInt("button");
-                } catch (JSONException e) {
-                    Log.e(LOGTAG, "result from promptservice was invalid: ", e);
-                }
+                final int itemId = result.getInt("button", -1);
 
                 if (itemId == -1) {
                     handler.onCancelled();
                 } else {
                     handler.onIntentSelected(mItems.get(itemId).getIntent(), itemId);
                 }
             }
         });
--- a/mobile/android/base/java/org/mozilla/gecko/prompts/Prompt.java
+++ b/mobile/android/base/java/org/mozilla/gecko/prompts/Prompt.java
@@ -1,21 +1,19 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.prompts;
 
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.gfx.LayerView;
+import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 
 import android.app.AlertDialog;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.DialogInterface.OnCancelListener;
@@ -73,48 +71,44 @@ public class Prompt implements OnClickLi
     private View applyInputStyle(View view, PromptInput input) {
         // Don't add padding to color picker views
         if (input.canApplyInputStyle()) {
             view.setPadding(mInputPaddingSize, 0, mInputPaddingSize, 0);
         }
         return view;
     }
 
-    public void show(JSONObject message) {
-        String title = message.optString("title");
-        String text = message.optString("text");
-        mGuid = message.optString("guid");
+    public void show(GeckoBundle message) {
+        String title = message.getString("title");
+        String text = message.getString("text");
+        mGuid = message.getString("guid");
 
-        mButtons = getStringArray(message, "buttons");
+        mButtons = message.getStringArray("buttons");
         final int buttonCount = mButtons == null ? 0 : mButtons.length;
-        mDoubleTapButtonType = convertIndexToButtonType(message.optInt("doubleTapButton", -1), buttonCount);
+        mDoubleTapButtonType = convertIndexToButtonType(message.getInt("doubleTapButton", -1), buttonCount);
         mPreviousInputValue = null;
 
-        JSONArray inputs = getSafeArray(message, "inputs");
-        mInputs = new PromptInput[inputs.length()];
+        GeckoBundle[] inputs = message.getBundleArray("inputs");
+        mInputs = new PromptInput[inputs != null ? inputs.length : 0];
         for (int i = 0; i < mInputs.length; i++) {
-            try {
-                mInputs[i] = PromptInput.getInput(inputs.getJSONObject(i));
-                mInputs[i].setListener(this);
-            } catch (Exception ex) { }
+            mInputs[i] = PromptInput.getInput(inputs[i]);
+            mInputs[i].setListener(this);
         }
 
-        PromptListItem[] menuitems = PromptListItem.getArray(message.optJSONArray("listitems"));
-        String selected = message.optString("choiceMode");
+        PromptListItem[] menuitems = PromptListItem.getArray(message.getBundleArray("listitems"));
+        String selected = message.getString("choiceMode");
 
         int choiceMode = ListView.CHOICE_MODE_NONE;
         if ("single".equals(selected)) {
             choiceMode = ListView.CHOICE_MODE_SINGLE;
         } else if ("multiple".equals(selected)) {
             choiceMode = ListView.CHOICE_MODE_MULTIPLE;
         }
 
-        if (message.has("tabId")) {
-            mTabId = message.optInt("tabId", Tabs.INVALID_TAB_ID);
-        }
+        mTabId = message.getInt("tabId", Tabs.INVALID_TAB_ID);
 
         show(title, text, menuitems, choiceMode);
     }
 
     private int convertIndexToButtonType(final int buttonIndex, final int buttonCount) {
         if (buttonIndex < 0 || buttonIndex >= buttonCount) {
             // All valid DialogInterface button values are < 0,
             // so we return 0 as an invalid value.
@@ -221,73 +215,81 @@ public class Prompt implements OnClickLi
         mInputs = inputs;
     }
 
     /* Adds to a result value from the lists that can be shown in dialogs.
      *  Will set the selected value(s) to the button attribute of the
      *  object that's passed in. If this is a multi-select dialog, sets a
      *  selected attribute to an array of booleans.
      */
-    private void addListResult(final JSONObject result, int which) {
+    private void addListResult(final GeckoBundle result, int which) {
         if (mAdapter == null) {
             return;
         }
 
-        try {
-            JSONArray selected = new JSONArray();
+        // If the button has already been filled in
+        final ArrayList<Integer> selected = mAdapter.getSelected();
 
-            // If the button has already been filled in
-            ArrayList<Integer> selectedItems = mAdapter.getSelected();
-            for (Integer item : selectedItems) {
-                selected.put(item);
+        // If we haven't assigned a button yet, or we assigned it to -1, assign the which
+        // parameter to both selected and the button.
+        if (result.getInt("button", -1) == -1) {
+            if (!selected.contains(which)) {
+                selected.add(which);
             }
 
-            // If we haven't assigned a button yet, or we assigned it to -1, assign the which
-            // parameter to both selected and the button.
-            if (!result.has("button") || result.optInt("button") == -1) {
-                if (!selectedItems.contains(which)) {
-                    selected.put(which);
-                }
+            result.putInt("button", which);
+        }
 
-                result.put("button", which);
-            }
-
-            result.put("list", selected);
-        } catch (JSONException ex) { }
+        result.putIntArray("list", selected);
     }
 
     /* Adds to a result value from the inputs that can be shown in dialogs.
      * Each input will set its own value in the result.
      */
-    private void addInputValues(final JSONObject result) {
-        try {
-            if (mInputs != null) {
-                for (int i = 0; i < mInputs.length; i++) {
-                    if (mInputs[i] != null) {
-                        result.put(mInputs[i].getId(), mInputs[i].getValue());
-                    }
-                }
+    private void addInputValues(final GeckoBundle result) {
+        if (mInputs == null) {
+            return;
+        }
+
+        for (final PromptInput input : mInputs) {
+            if (input == null) {
+                continue;
             }
-        } catch (JSONException ex) { }
+
+            final String id = input.getId();
+            final Object value = input.getValue();
+
+            if (value instanceof Boolean) {
+                result.putBoolean(id, (Boolean) value);
+            } else if (value instanceof Double) {
+                result.putDouble(id, (Double) value);
+            } else if (value instanceof Integer) {
+                result.putInt(id, (Integer) value);
+            } else if (value instanceof String) {
+                result.putString(id, (String) value);
+            } else if (value instanceof GeckoBundle) {
+                result.putBundle(id, (GeckoBundle) value);
+            } else {
+                throw new UnsupportedOperationException();
+            }
+        }
     }
 
     /* Adds the selected button to a result. This should only be called if there
      * are no lists shown on the dialog, since they also write their results to the button
      * attribute.
      */
-    private void addButtonResult(final JSONObject result, int which) {
+    private void addButtonResult(final GeckoBundle result, int which) {
         int button = -1;
         switch (which) {
             case DialogInterface.BUTTON_POSITIVE : button = 0; break;
             case DialogInterface.BUTTON_NEUTRAL  : button = 1; break;
             case DialogInterface.BUTTON_NEGATIVE : button = 2; break;
         }
-        try {
-            result.put("button", button);
-        } catch (JSONException ex) { }
+        result.putInt("button", button);
     }
 
     @Override
     public void onClick(DialogInterface dialog, int which) {
         ThreadUtils.assertOnUiThread();
         closeDialog(which);
     }
 
@@ -470,55 +472,52 @@ public class Prompt implements OnClickLi
      */
     @Override
     public void onCancel(DialogInterface aDialog) {
         ThreadUtils.assertOnUiThread();
         cancelDialog();
     }
 
     /* Called in situations where we want to cancel the dialog . This can happen if the user hits back,
-     * or if the dialog can't be created because of invalid JSON.
+     * or if the dialog can't be created because of invalid input.
      */
     private void cancelDialog() {
-        JSONObject ret = new JSONObject();
-        try {
-            ret.put("button", -1);
-        } catch (Exception ex) { }
+        final GeckoBundle ret = new GeckoBundle();
+        ret.putInt("button", -1);
         addInputValues(ret);
+
         notifyClosing(ret);
     }
 
     /* Called any time we're closing the dialog to cleanup and notify listeners that the dialog
      * is closing.
      */
     private void closeDialog(int which) {
-        JSONObject ret = new JSONObject();
+        final GeckoBundle ret = new GeckoBundle();
         mDialog.dismiss();
 
         addButtonResult(ret, which);
         addListResult(ret, which);
         addInputValues(ret);
 
         notifyClosing(ret);
     }
 
     /* Called any time we're closing the dialog to cleanup and notify listeners that the dialog
      * is closing.
      */
-    private void notifyClosing(JSONObject aReturn) {
-        try {
-            aReturn.put("guid", mGuid);
-        } catch (JSONException ex) { }
+    private void notifyClosing(final GeckoBundle ret) {
+        ret.putString("guid", mGuid);
 
         if (mTabId != Tabs.INVALID_TAB_ID) {
             Tabs.unregisterOnTabsChangedListener(this);
         }
 
         if (mCallback != null) {
-            mCallback.onPromptFinished(aReturn.toString());
+            mCallback.onPromptFinished(ret);
         }
     }
 
     // Called when the prompt inputs on the dialog change
     @Override
     public void onChange(PromptInput input) {
         // If there are no buttons on this dialog, assuming that "changing" an input
         // means something was selected and we can close. This provides a way to tap
@@ -535,52 +534,16 @@ public class Prompt implements OnClickLi
         if (mDoubleTapButtonType != 0 && inputValue == mPreviousInputValue) {
             closeDialog(mDoubleTapButtonType);
             return true;
         }
         mPreviousInputValue = inputValue;
         return false;
     }
 
-    private static JSONArray getSafeArray(JSONObject json, String key) {
-        try {
-            return json.getJSONArray(key);
-        } catch (Exception e) {
-            return new JSONArray();
-        }
-    }
-
-    public static String[] getStringArray(JSONObject aObject, String aName) {
-        JSONArray items = getSafeArray(aObject, aName);
-        int length = items.length();
-        String[] list = new String[length];
-        for (int i = 0; i < length; i++) {
-            try {
-                list[i] = items.getString(i);
-            } catch (Exception ex) { }
-        }
-        return list;
-    }
-
-    private static boolean[] getBooleanArray(JSONObject aObject, String aName) {
-        JSONArray items = new JSONArray();
-        try {
-            items = aObject.getJSONArray(aName);
-        } catch (Exception ex) { return null; }
-        int length = items.length();
-        boolean[] list = new boolean[length];
-        for (int i = 0; i < length; i++) {
-            try {
-                list[i] = items.getBoolean(i);
-            } catch (Exception ex) { }
-        }
-        return list;
-    }
-
     public interface PromptCallback {
-
         /**
          * Called when the Prompt has been completed (i.e. when the user has selected an item or action in the Prompt).
          * This callback is run on the UI thread.
          */
-        public void onPromptFinished(String jsonResult);
+        public void onPromptFinished(GeckoBundle result);
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/prompts/PromptInput.java
+++ b/mobile/android/base/java/org/mozilla/gecko/prompts/PromptInput.java
@@ -4,18 +4,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.prompts;
 
 import java.text.SimpleDateFormat;
 import java.util.Calendar;
 import java.util.GregorianCalendar;
 
-import org.json.JSONObject;
 import org.mozilla.gecko.AppConstants.Versions;
+import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.widget.AllCapsTextView;
 import org.mozilla.gecko.widget.DateTimePicker;
 
 import android.content.Context;
 import android.content.res.Configuration;
 import android.support.design.widget.TextInputLayout;
 import android.support.v7.widget.AppCompatCheckBox;
 import android.text.Html;
@@ -54,20 +54,20 @@ public abstract class PromptInput {
         mListener = listener;
     }
 
     public static class EditInput extends PromptInput {
         protected final String mHint;
         protected final boolean mAutofocus;
         public static final String INPUT_TYPE = "textbox";
 
-        public EditInput(JSONObject object) {
+        public EditInput(GeckoBundle object) {
             super(object);
-            mHint = object.optString("hint");
-            mAutofocus = object.optBoolean("autofocus");
+            mHint = object.getString("hint");
+            mAutofocus = object.getBoolean("autofocus");
         }
 
         @Override
         public View getView(final Context context) throws UnsupportedOperationException {
             EditText input = new EditText(context);
             input.setInputType(InputType.TYPE_CLASS_TEXT);
             input.setText(mValue);
 
@@ -98,34 +98,34 @@ public abstract class PromptInput {
         public Object getValue() {
             final TextInputLayout inputLayout = (TextInputLayout) mView;
             return inputLayout.getEditText().getText();
         }
     }
 
     public static class NumberInput extends EditInput {
         public static final String INPUT_TYPE = "number";
-        public NumberInput(JSONObject obj) {
+        public NumberInput(GeckoBundle obj) {
             super(obj);
         }
 
         @Override
         public View getView(final Context context) throws UnsupportedOperationException {
             final TextInputLayout inputLayout = (TextInputLayout) super.getView(context);
             final EditText input = inputLayout.getEditText();
             input.setRawInputType(Configuration.KEYBOARD_12KEY);
             input.setInputType(InputType.TYPE_CLASS_NUMBER |
                                InputType.TYPE_NUMBER_FLAG_SIGNED);
             return input;
         }
     }
 
     public static class PasswordInput extends EditInput {
         public static final String INPUT_TYPE = "password";
-        public PasswordInput(JSONObject obj) {
+        public PasswordInput(GeckoBundle obj) {
             super(obj);
         }
 
         @Override
         public View getView(Context context) throws UnsupportedOperationException {
             final TextInputLayout inputLayout = (TextInputLayout) super.getView(context);
             inputLayout.getEditText().setInputType(InputType.TYPE_CLASS_TEXT |
                                InputType.TYPE_TEXT_VARIATION_PASSWORD |
@@ -133,19 +133,19 @@ public abstract class PromptInput {
             return inputLayout;
         }
     }
 
     public static class CheckboxInput extends PromptInput {
         public static final String INPUT_TYPE = "checkbox";
         private final boolean mChecked;
 
-        public CheckboxInput(JSONObject obj) {
+        public CheckboxInput(GeckoBundle obj) {
             super(obj);
-            mChecked = obj.optBoolean("checked");
+            mChecked = obj.getBoolean("checked");
         }
 
         @Override
         public View getView(Context context) throws UnsupportedOperationException {
             final CheckBox checkbox = new AppCompatCheckBox(context);
             checkbox.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
             checkbox.setText(mLabel);
             checkbox.setChecked(mChecked);
@@ -165,17 +165,17 @@ public abstract class PromptInput {
             "date",
             "week",
             "time",
             "datetime-local",
             "datetime",
             "month"
         };
 
-        public DateTimeInput(JSONObject obj) {
+        public DateTimeInput(GeckoBundle obj) {
             super(obj);
         }
 
         @Override
         public View getView(Context context) throws UnsupportedOperationException {
             if (mType.equals("date")) {
                 try {
                     DateTimePicker input = new DateTimePicker(context, "yyyy-MM-dd", mValue,
@@ -260,26 +260,27 @@ public abstract class PromptInput {
                 }
             }
             return super.getValue();
         }
     }
 
     public static class MenulistInput extends PromptInput {
         public static final String INPUT_TYPE = "menulist";
-        private static String[] mListitems;
-        private static int mSelected;
+        private final String[] mListitems;
+        private final int mSelected;
 
         public Spinner spinner;
         public AllCapsTextView textView;
 
-        public MenulistInput(JSONObject obj) {
+        public MenulistInput(GeckoBundle obj) {
             super(obj);
-            mListitems = Prompt.getStringArray(obj, "values");
-            mSelected = obj.optInt("selected");
+            final String[] listitems = obj.getStringArray("values");
+            mListitems = listitems != null ? listitems : new String[0];
+            mSelected = obj.getInt("selected");
         }
 
         @Override
         public View getView(final Context context) throws UnsupportedOperationException {
             spinner = new Spinner(context, Spinner.MODE_DIALOG);
             try {
                 if (mListitems.length > 0) {
                     ArrayAdapter<String> adapter = new ArrayAdapter<String>(context, android.R.layout.simple_spinner_item, mListitems);
@@ -309,42 +310,42 @@ public abstract class PromptInput {
         @Override
         public Object getValue() {
             return spinner.getSelectedItemPosition();
         }
     }
 
     public static class LabelInput extends PromptInput {
         public static final String INPUT_TYPE = "label";
-        public LabelInput(JSONObject obj) {
+        public LabelInput(GeckoBundle obj) {
             super(obj);
         }
 
         @Override
         public View getView(Context context) throws UnsupportedOperationException {
             // not really an input, but a way to add labels and such to the dialog
             TextView view = new TextView(context);
             view.setText(Html.fromHtml(mLabel));
             mView = view;
             return mView;
         }
     }
 
-    public PromptInput(JSONObject obj) {
-        mLabel = obj.optString("label");
-        mType = obj.optString("type");
-        String id = obj.optString("id");
+    public PromptInput(GeckoBundle obj) {
+        mLabel = obj.getString("label");
+        mType = obj.getString("type");
+        String id = obj.getString("id");
         mId = TextUtils.isEmpty(id) ? mType : id;
-        mValue = obj.optString("value");
-        mMaxValue = obj.optString("max");
-        mMinValue = obj.optString("min");
+        mValue = obj.getString("value");
+        mMaxValue = obj.getString("max");
+        mMinValue = obj.getString("min");
     }
 
-    public static PromptInput getInput(JSONObject obj) {
-        String type = obj.optString("type");
+    public static PromptInput getInput(GeckoBundle obj) {
+        String type = obj.getString("type");
         switch (type) {
             case EditInput.INPUT_TYPE:
                 return new EditInput(obj);
             case NumberInput.INPUT_TYPE:
                 return new NumberInput(obj);
             case PasswordInput.INPUT_TYPE:
                 return new PasswordInput(obj);
             case CheckboxInput.INPUT_TYPE:
--- a/mobile/android/base/java/org/mozilla/gecko/prompts/PromptListItem.java
+++ b/mobile/android/base/java/org/mozilla/gecko/prompts/PromptListItem.java
@@ -1,20 +1,17 @@
 package org.mozilla.gecko.prompts;
 
-import org.json.JSONException;
 import org.mozilla.gecko.IntentHelper;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.ThumbnailHelper;
+import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.ResourceDrawableUtils;
 import org.mozilla.gecko.widget.GeckoActionProvider;
 
-import org.json.JSONArray;
-import org.json.JSONObject;
-
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
 
 import java.util.List;
 import java.util.ArrayList;
 
 // This class should die and be replaced with normal menu items
@@ -27,42 +24,42 @@ public class PromptListItem {
     public final int id;
     public final boolean showAsActions;
     public final boolean isParent;
 
     public Intent mIntent;
     public boolean mSelected;
     public Drawable mIcon;
 
-    PromptListItem(JSONObject aObject) {
+    PromptListItem(GeckoBundle aObject) {
         Context context = GeckoAppShell.getContext();
-        label = aObject.isNull("label") ? "" : aObject.optString("label");
-        isGroup = aObject.optBoolean("isGroup");
-        inGroup = aObject.optBoolean("inGroup");
-        disabled = aObject.optBoolean("disabled");
-        id = aObject.optInt("id");
-        mSelected = aObject.optBoolean("selected");
+        label = aObject.getString("label", "");
+        isGroup = aObject.getBoolean("isGroup");
+        inGroup = aObject.getBoolean("inGroup");
+        disabled = aObject.getBoolean("disabled");
+        id = aObject.getInt("id");
+        mSelected = aObject.getBoolean("selected");
 
-        JSONObject obj = aObject.optJSONObject("showAsActions");
+        GeckoBundle obj = aObject.getBundle("showAsActions");
         if (obj != null) {
             showAsActions = true;
-            String uri = obj.isNull("uri") ? "" : obj.optString("uri");
-            String type = obj.isNull("type") ? GeckoActionProvider.DEFAULT_MIME_TYPE :
-                                               obj.optString("type", GeckoActionProvider.DEFAULT_MIME_TYPE);
+            String uri = obj.getString("uri", "");
+            String type = obj.getString("type", GeckoActionProvider.DEFAULT_MIME_TYPE);
 
             mIntent = IntentHelper.getShareIntent(context, uri, type, "");
             isParent = true;
         } else {
             mIntent = null;
             showAsActions = false;
-            // Support both "isParent" (backwards compat for older consumers), and "menu" for the new Tabbed prompt ui.
-            isParent = aObject.optBoolean("isParent") || aObject.optBoolean("menu");
+            // Support both "isParent" (backwards compat for older consumers), and "menu"
+            // for the new Tabbed prompt ui.
+            isParent = aObject.getBoolean("isParent") || aObject.getBoolean("menu");
         }
 
-        final String iconStr = aObject.optString("icon");
+        final String iconStr = aObject.getString("icon");
         if (iconStr != null) {
             final ResourceDrawableUtils.BitmapLoader loader = new ResourceDrawableUtils.BitmapLoader() {
                     @Override
                     public void onBitmapFound(Drawable d) {
                         mIcon = d;
                     }
                 };
 
@@ -104,25 +101,23 @@ public class PromptListItem {
         isGroup = false;
         inGroup = false;
         isParent = false;
         disabled = false;
         id = 0;
         showAsActions = false;
     }
 
-    static PromptListItem[] getArray(JSONArray items) {
+    static PromptListItem[] getArray(GeckoBundle[] items) {
         if (items == null) {
             return new PromptListItem[0];
         }
 
-        int length = items.length();
+        int length = items.length;
         List<PromptListItem> list = new ArrayList<>(length);
         for (int i = 0; i < length; i++) {
-            try {
-                PromptListItem item = new PromptListItem(items.getJSONObject(i));
-                list.add(item);
-            } catch (JSONException ex) { }
+            PromptListItem item = new PromptListItem(items[i]);
+            list.add(item);
         }
 
         return list.toArray(new PromptListItem[length]);
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/prompts/PromptService.java
+++ b/mobile/android/base/java/org/mozilla/gecko/prompts/PromptService.java
@@ -1,72 +1,50 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.prompts;
 
-import org.json.JSONException;
-import org.json.JSONObject;
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.util.GeckoEventListener;
+import org.mozilla.gecko.util.BundleEventListener;
+import org.mozilla.gecko.util.EventCallback;
+import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.content.Context;
 import android.util.Log;
 
-public class PromptService implements GeckoEventListener {
+public class PromptService implements BundleEventListener {
     private static final String LOGTAG = "GeckoPromptService";
 
     private final Context mContext;
 
     public PromptService(Context context) {
-        GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
+        GeckoApp.getEventDispatcher().registerUiThreadListener(this,
             "Prompt:Show",
             "Prompt:ShowTop");
         mContext = context;
     }
 
     public void destroy() {
-        GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
+        GeckoApp.getEventDispatcher().unregisterUiThreadListener(this,
             "Prompt:Show",
             "Prompt:ShowTop");
     }
 
-    public void show(final String aTitle, final String aText, final PromptListItem[] aMenuList,
-                     final int aChoiceMode, final Prompt.PromptCallback callback) {
-        // The dialog must be created on the UI thread.
-        ThreadUtils.postToUiThread(new Runnable() {
+    // BundleEventListener implementation
+    @Override
+    public void handleMessage(final String event, final GeckoBundle message,
+                              final EventCallback callback) {
+        Prompt p;
+        p = new Prompt(mContext, new Prompt.PromptCallback() {
             @Override
-            public void run() {
-                Prompt p;
-                p = new Prompt(mContext, callback);
-                p.show(aTitle, aText, aMenuList, aChoiceMode);
+            public void onPromptFinished(final GeckoBundle result) {
+                callback.sendSuccess(result);
             }
         });
-    }
-
-    // GeckoEventListener implementation
-    @Override
-    public void handleMessage(String event, final JSONObject message) {
-        // The dialog must be created on the UI thread.
-        ThreadUtils.postToUiThread(new Runnable() {
-            @Override
-            public void run() {
-                Prompt p;
-                p = new Prompt(mContext, new Prompt.PromptCallback() {
-                    @Override
-                    public void onPromptFinished(String jsonResult) {
-                        try {
-                            EventDispatcher.sendResponse(message, new JSONObject(jsonResult));
-                        } catch (JSONException ex) {
-                            Log.i(LOGTAG, "Error building json response", ex);
-                        }
-                    }
-                });
-                p.show(message);
-            }
-        });
+        p.show(message);
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/prompts/TabInput.java
+++ b/mobile/android/base/java/org/mozilla/gecko/prompts/TabInput.java
@@ -2,55 +2,49 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.prompts;
 
 import java.util.LinkedHashMap;
 
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.R;
+import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.content.Context;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.AdapterView;
 import android.widget.ListView;
 import android.widget.TabHost;
 import android.widget.TextView;
 
 public class TabInput extends PromptInput implements AdapterView.OnItemClickListener {
     public static final String INPUT_TYPE = "tabs";
     public static final String LOGTAG = "GeckoTabInput";
 
-    /* Keeping the order of this in sync with the JSON is important. */
+    /* Keeping the order of this in sync with the input is important. */
     final private LinkedHashMap<String, PromptListItem[]> mTabs;
 
     private TabHost mHost;
     private int mPosition;
 
-    public TabInput(JSONObject obj) {
+    public TabInput(GeckoBundle obj) {
         super(obj);
         mTabs = new LinkedHashMap<String, PromptListItem[]>();
-        try {
-            JSONArray tabs = obj.getJSONArray("items");
-            for (int i = 0; i < tabs.length(); i++) {
-                JSONObject tab = tabs.getJSONObject(i);
-                String title = tab.getString("label");
-                JSONArray items = tab.getJSONArray("items");
-                mTabs.put(title, PromptListItem.getArray(items));
-            }
-        } catch (JSONException ex) {
-            Log.e(LOGTAG, "Exception", ex);
+        GeckoBundle[] tabs = obj.getBundleArray("items");
+        for (int i = 0; i < (tabs != null ? tabs.length : 0); i++) {
+            GeckoBundle tab = tabs[i];
+            String title = tab.getString("label");
+            GeckoBundle[] items = tab.getBundleArray("items");
+            mTabs.put(title, PromptListItem.getArray(items));
         }
     }
 
     @Override
     public View getView(final Context context) throws UnsupportedOperationException {
         final LayoutInflater inflater = LayoutInflater.from(context);
         mHost = (TabHost) inflater.inflate(R.layout.tab_prompt_input, null);
         mHost.setup();
@@ -73,22 +67,19 @@ public class TabInput extends PromptInpu
             mHost.addTab(spec);
         }
         mView = mHost;
         return mHost;
     }
 
     @Override
     public Object getValue() {
-        JSONObject obj = new JSONObject();
-        try {
-            obj.put("tab", mHost.getCurrentTab());
-            obj.put("item", mPosition);
-        } catch (JSONException ex) { }
-
+        final GeckoBundle obj = new GeckoBundle(2);
+        obj.putInt("tab", mHost.getCurrentTab());
+        obj.putInt("item", mPosition);
         return obj;
     }
 
     @Override
     public boolean getScrollable() {
         return true;
     }
 
--- a/mobile/android/base/java/org/mozilla/gecko/widget/DefaultDoorHanger.java
+++ b/mobile/android/base/java/org/mozilla/gecko/widget/DefaultDoorHanger.java
@@ -10,16 +10,17 @@ import android.text.Html;
 import android.text.Spanned;
 import android.util.Log;
 import android.widget.Button;
 import android.widget.TextView;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.prompts.PromptInput;
+import org.mozilla.gecko.util.GeckoBundle;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.content.Context;
 import android.text.TextUtils;
 import android.view.View;
@@ -104,17 +105,18 @@ public class DefaultDoorHanger extends D
         if (inputs != null) {
             mInputs = new ArrayList<PromptInput>();
 
             final ViewGroup group = (ViewGroup) findViewById(R.id.doorhanger_inputs);
             group.setVisibility(VISIBLE);
 
             for (int i = 0; i < inputs.length(); i++) {
                 try {
-                    PromptInput input = PromptInput.getInput(inputs.getJSONObject(i));
+                    PromptInput input = PromptInput.getInput(
+                            GeckoBundle.fromJSONObject(inputs.getJSONObject(i)));
                     mInputs.add(input);
 
                     final int padding = mResources.getDimensionPixelSize(R.dimen.doorhanger_section_padding_medium);
                     View v = input.getView(getContext());
                     styleInput(input, v);
                     v.setPadding(0, 0, 0, padding);
                     group.addView(v);
                 } catch (JSONException ex) { }
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -6882,20 +6882,25 @@ var Tabs = {
     let tabs = BrowserApp.tabs;
     for (let i = 0; i < tabs.length; i++) {
       dump(aPrefix + " | " + "Tab [" + tabs[i].browser.contentWindow.location.href + "]: lastTouchedAt:" + tabs[i].lastTouchedAt + ", zombie:" + tabs[i].browser.__SS_restore);
     }
   },
 };
 
 function ContextMenuItem(args) {
-  this.id = uuidgen.generateUUID().toString();
+  this.id = ContextMenuItem._nextId;
   this.args = args;
+
+  // Limit to Java int range.
+  ContextMenuItem._nextId = (ContextMenuItem._nextId + 1) & 0x7fffffff;
 }
 
+ContextMenuItem._nextId = 1;
+
 ContextMenuItem.prototype = {
   get order() {
     return this.args.order || 0;
   },
 
   matches: function(elt, x, y) {
     return this.args.selector.matches(elt, x, y);
   },
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/EventDispatcher.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/EventDispatcher.java
@@ -361,17 +361,17 @@ public final class EventDispatcher exten
             geckoListeners = mGeckoThreadListeners.get(type);
         }
         if (geckoListeners != null && !geckoListeners.isEmpty()) {
             final boolean onGeckoThread = ThreadUtils.isOnGeckoThread();
             final EventCallback wrappedCallback = JavaCallbackDelegate.wrap(callback);
             final GeckoBundle messageAsBundle;
             try {
                 messageAsBundle = jsMessage != null ?
-                        convertBundle(jsMessage.toBundle()) : bundleMessage;
+                        convertBundle(jsMessage.toBundle(), jsMessage) : bundleMessage;
             } catch (final NativeJSObject.InvalidPropertyException e) {
                 Log.e(LOGTAG, "Exception occurred while handling " + type, e);
                 return true;
             }
 
             for (final BundleEventListener listener : geckoListeners) {
                 // For other threads, we always dispatch asynchronously. However, for
                 // Gecko listeners only, we dispatch synchronously if we're already on
@@ -420,37 +420,47 @@ public final class EventDispatcher exten
                         "Dispatching Bundle message to Gecko listener " + type);
             }
         }
 
         return false;
     }
 
     // XXX: temporary helper for converting Bundle to GeckoBundle.
-    private GeckoBundle convertBundle(final Bundle bundle) {
+    private GeckoBundle convertBundle(final Bundle bundle, final NativeJSObject jsObj) {
         if (bundle == null) {
             return null;
         }
 
         final Set<String> keys = bundle.keySet();
         final GeckoBundle out = new GeckoBundle(keys.size());
 
         for (final String key : keys) {
             final Object value = bundle.get(key);
 
             if (value instanceof Bundle) {
-                out.putBundle(key, convertBundle((Bundle) value));
+                final Bundle bundleValue = (Bundle) value;
+                try {
+                    // XXX: NativeJSObject.toBundle doesn't support object arrays, and
+                    // instead converts it to a Bundle with integer members; correct that.
+                    final NativeJSObject[] objs = jsObj.getObjectArray(key);
+                    final GeckoBundle[] outArray = new GeckoBundle[objs.length];
+                    for (int i = 0; i < objs.length; i++) {
+                        outArray[i] = convertBundle(
+                                bundleValue.getBundle(String.valueOf(i)), objs[i]);
+                    }
+                    out.putBundleArray(key, outArray);
+
+                } catch (final Exception e) {
+                    // Not an array
+                    out.putBundle(key, convertBundle(bundleValue, jsObj.getObject(key)));
+                }
 
             } else if (value instanceof Bundle[]) {
-                final Bundle[] inArray = (Bundle[]) value;
-                final GeckoBundle[] outArray = new GeckoBundle[inArray.length];
-                for (int i = 0; i < inArray.length; i++) {
-                    outArray[i] = convertBundle(inArray[i]);
-                }
-                out.putBundleArray(key, outArray);
+                throw new IllegalStateException("toBundle should not have generated Bundle[] values");
 
             } else {
                 out.put(key, value);
             }
         }
         return out;
     }
 
@@ -475,17 +485,17 @@ public final class EventDispatcher exten
 
                 // There were native listeners, and they're gone.
                 return false;
             }
 
             final GeckoBundle messageAsBundle;
             try {
                 messageAsBundle = jsMessage != null ?
-                        convertBundle(jsMessage.toBundle()) : bundleMessage;
+                        convertBundle(jsMessage.toBundle(), jsMessage) : bundleMessage;
             } catch (final NativeJSObject.InvalidPropertyException e) {
                 Log.e(LOGTAG, "Exception occurred while handling " + type, e);
                 return true;
             }
 
             // Use a delegate to make sure callbacks happen on a specific thread.
             final EventCallback wrappedCallback = JavaCallbackDelegate.wrap(callback);
 
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/GeckoBundle.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/GeckoBundle.java
@@ -3,19 +3,25 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.util;
 
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.annotation.WrapForJNI;
 
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
 import android.support.v4.util.SimpleArrayMap;
 
 import java.lang.reflect.Array;
+import java.util.Collection;
+import java.util.Iterator;
 import java.util.Set;
 
 /**
  * A lighter-weight version of Bundle that adds support for type coercion (e.g.
  * int to double) in order to better cooperate with JS objects.
  */
 @RobocopTarget
 public final class GeckoBundle {
@@ -133,17 +139,18 @@ public final class GeckoBundle {
      * Returns the value associated with a boolean array mapping, or null if the mapping
      * does not exist.
      *
      * @param key Key to look for.
      * @return Boolean array value
      */
     public boolean[] getBooleanArray(final String key) {
         final Object value = mMap.get(key);
-        return Array.getLength(value) == 0 ? EMPTY_BOOLEAN_ARRAY : (boolean[]) value;
+        return value == null ? null :
+                Array.getLength(value) == 0 ? EMPTY_BOOLEAN_ARRAY : (boolean[]) value;
     }
 
     /**
      * Returns the value associated with a double mapping, or defaultValue if the mapping
      * does not exist.
      *
      * @param key Key to look for.
      * @param defaultValue Value to return if mapping does not exist.
@@ -178,17 +185,17 @@ public final class GeckoBundle {
      * Returns the value associated with a double array mapping, or null if the mapping
      * does not exist.
      *
      * @param key Key to look for.
      * @return Double array value
      */
     public double[] getDoubleArray(final String key) {
         final Object value = mMap.get(key);
-        return Array.getLength(value) == 0 ? EMPTY_DOUBLE_ARRAY :
+        return value == null ? null : Array.getLength(value) == 0 ? EMPTY_DOUBLE_ARRAY :
                value instanceof int[] ? getDoubleArray((int[]) value) : (double[]) value;
     }
 
     /**
      * Returns the value associated with an int mapping, or defaultValue if the mapping
      * does not exist.
      *
      * @param key Key to look for.
@@ -224,17 +231,17 @@ public final class GeckoBundle {
      * Returns the value associated with an int array mapping, or null if the mapping does
      * not exist.
      *
      * @param key Key to look for.
      * @return Int array value
      */
     public int[] getIntArray(final String key) {
         final Object value = mMap.get(key);
-        return Array.getLength(value) == 0 ? EMPTY_INT_ARRAY :
+        return value == null ? null : Array.getLength(value) == 0 ? EMPTY_INT_ARRAY :
                value instanceof double[] ? getIntArray((double[]) value) : (int[]) value;
     }
 
     /**
      * Returns the value associated with a String mapping, or defaultValue if the mapping
      * does not exist.
      *
      * @param key Key to look for.
@@ -273,17 +280,17 @@ public final class GeckoBundle {
      * Returns the value associated with a String array mapping, or null if the mapping
      * does not exist.
      *
      * @param key Key to look for.
      * @return String array value
      */
     public String[] getStringArray(final String key) {
         final Object value = mMap.get(key);
-        return Array.getLength(value) == 0 ? EMPTY_STRING_ARRAY :
+        return value == null ? null : Array.getLength(value) == 0 ? EMPTY_STRING_ARRAY :
                !(value instanceof String[]) ? new String[getNullArrayLength(value)] :
                                               (String[]) value;
     }
 
     /**
      * Returns the value associated with a GeckoBundle mapping, or null if the mapping
      * does not exist.
      *
@@ -298,17 +305,17 @@ public final class GeckoBundle {
      * Returns the value associated with a GeckoBundle array mapping, or null if the
      * mapping does not exist.
      *
      * @param key Key to look for.
      * @return GeckoBundle array value
      */
     public GeckoBundle[] getBundleArray(final String key) {
         final Object value = mMap.get(key);
-        return Array.getLength(value) == 0 ? EMPTY_BUNDLE_ARRAY :
+        return value == null ? null : Array.getLength(value) == 0 ? EMPTY_BUNDLE_ARRAY :
                !(value instanceof GeckoBundle[]) ? new GeckoBundle[getNullArrayLength(value)] :
                                                    (GeckoBundle[]) value;
     }
 
     /**
      * Returns whether this GeckoBundle has no mappings.
      *
      * @return True if no mapping exists.
@@ -366,16 +373,53 @@ public final class GeckoBundle {
      * @param key Key to map.
      * @param value Value to map to.
      */
     public void putBooleanArray(final String key, final boolean[] value) {
         mMap.put(key, value);
     }
 
     /**
+     * Map a key to a boolean array value.
+     *
+     * @param key Key to map.
+     * @param value Value to map to.
+     */
+    public void putBooleanArray(final String key, final Boolean[] value) {
+        if (value == null) {
+            mMap.put(key, null);
+            return;
+        }
+        final boolean[] array = new boolean[value.length];
+        for (int i = 0; i < value.length; i++) {
+            array[i] = value[i];
+        }
+        mMap.put(key, array);
+    }
+
+    /**
+     * Map a key to a boolean array value.
+     *
+     * @param key Key to map.
+     * @param value Value to map to.
+     */
+    public void putBooleanArray(final String key, final Collection<Boolean> value) {
+        if (value == null) {
+            mMap.put(key, null);
+            return;
+        }
+        final boolean[] array = new boolean[value.size()];
+        int i = 0;
+        for (final Boolean element : value) {
+            array[i++] = element;
+        }
+        mMap.put(key, array);
+    }
+
+    /**
      * Map a key to a double value.
      *
      * @param key Key to map.
      * @param value Value to map to.
      */
     public void putDouble(final String key, final double value) {
         mMap.put(key, value);
     }
@@ -386,16 +430,53 @@ public final class GeckoBundle {
      * @param key Key to map.
      * @param value Value to map to.
      */
     public void putDoubleArray(final String key, final double[] value) {
         mMap.put(key, value);
     }
 
     /**
+     * Map a key to a double array value.
+     *
+     * @param key Key to map.
+     * @param value Value to map to.
+     */
+    public void putDoubleArray(final String key, final Double[] value) {
+        if (value == null) {
+            mMap.put(key, null);
+            return;
+        }
+        final double[] array = new double[value.length];
+        for (int i = 0; i < value.length; i++) {
+            array[i] = value[i];
+        }
+        mMap.put(key, array);
+    }
+
+    /**
+     * Map a key to a double array value.
+     *
+     * @param key Key to map.
+     * @param value Value to map to.
+     */
+    public void putDoubleArray(final String key, final Collection<Double> value) {
+        if (value == null) {
+            mMap.put(key, null);
+            return;
+        }
+        final double[] array = new double[value.size()];
+        int i = 0;
+        for (final Double element : value) {
+            array[i++] = element;
+        }
+        mMap.put(key, array);
+    }
+
+    /**
      * Map a key to an int value.
      *
      * @param key Key to map.
      * @param value Value to map to.
      */
     public void putInt(final String key, final int value) {
         mMap.put(key, value);
     }
@@ -406,16 +487,53 @@ public final class GeckoBundle {
      * @param key Key to map.
      * @param value Value to map to.
      */
     public void putIntArray(final String key, final int[] value) {
         mMap.put(key, value);
     }
 
     /**
+     * Map a key to a int array value.
+     *
+     * @param key Key to map.
+     * @param value Value to map to.
+     */
+    public void putIntArray(final String key, final Integer[] value) {
+        if (value == null) {
+            mMap.put(key, null);
+            return;
+        }
+        final int[] array = new int[value.length];
+        for (int i = 0; i < value.length; i++) {
+            array[i] = value[i];
+        }
+        mMap.put(key, array);
+    }
+
+    /**
+     * Map a key to a int array value.
+     *
+     * @param key Key to map.
+     * @param value Value to map to.
+     */
+    public void putIntArray(final String key, final Collection<Integer> value) {
+        if (value == null) {
+            mMap.put(key, null);
+            return;
+        }
+        final int[] array = new int[value.size()];
+        int i = 0;
+        for (final Integer element : value) {
+            array[i++] = element;
+        }
+        mMap.put(key, array);
+    }
+
+    /**
      * Map a key to a String value.
      *
      * @param key Key to map.
      * @param value Value to map to.
      */
     public void putString(final String key, final String value) {
         mMap.put(key, value);
     }
@@ -426,16 +544,35 @@ public final class GeckoBundle {
      * @param key Key to map.
      * @param value Value to map to.
      */
     public void putStringArray(final String key, final String[] value) {
         mMap.put(key, value);
     }
 
     /**
+     * Map a key to a String array value.
+     *
+     * @param key Key to map.
+     * @param value Value to map to.
+     */
+    public void putStringArray(final String key, final Collection<String> value) {
+        if (value == null) {
+            mMap.put(key, null);
+            return;
+        }
+        final String[] array = new String[value.size()];
+        int i = 0;
+        for (final String element : value) {
+            array[i++] = element;
+        }
+        mMap.put(key, array);
+    }
+
+    /**
      * Map a key to a GeckoBundle value.
      *
      * @param key Key to map.
      * @param value Value to map to.
      */
     public void putBundle(final String key, final GeckoBundle value) {
         mMap.put(key, value);
     }
@@ -446,25 +583,108 @@ public final class GeckoBundle {
      * @param key Key to map.
      * @param value Value to map to.
      */
     public void putBundleArray(final String key, final GeckoBundle[] value) {
         mMap.put(key, value);
     }
 
     /**
+     * Map a key to a GeckoBundle array value.
+     *
+     * @param key Key to map.
+     * @param value Value to map to.
+     */
+    public void putBundleArray(final String key, final Collection<GeckoBundle> value) {
+        if (value == null) {
+            mMap.put(key, null);
+            return;
+        }
+        final GeckoBundle[] array = new GeckoBundle[value.size()];
+        int i = 0;
+        for (final GeckoBundle element : value) {
+            array[i++] = element;
+        }
+        mMap.put(key, array);
+    }
+
+    /**
      * Remove a mapping.
      *
      * @param key Key to remove.
      */
     public void remove(final String key) {
         mMap.remove(key);
     }
 
     /**
      * Returns number of mappings in this GeckoBundle.
      *
      * @return Number of mappings.
      */
     public int size() {
         return mMap.size();
     }
+
+    private static Object fromJSONValue(Object value) throws JSONException {
+        if (value instanceof JSONObject || value == JSONObject.NULL) {
+            return fromJSONObject((JSONObject) value);
+        }
+        if (value instanceof JSONArray) {
+            final JSONArray array = (JSONArray) value;
+            final int len = array.length();
+            if (len == 0) {
+                return EMPTY_BOOLEAN_ARRAY;
+            }
+            Object out = null;
+            for (int i = 0; i < len; i++) {
+                final Object element = fromJSONValue(array.opt(0));
+                if (element == null) {
+                    continue;
+                }
+                if (out == null) {
+                    Class<?> type = element.getClass();
+                    if (type == Boolean.class) {
+                        type = boolean.class;
+                    } else if (type == Integer.class) {
+                        type = int.class;
+                    } else if (type == Double.class) {
+                        type = double.class;
+                    }
+                    out = Array.newInstance(type, len);
+                }
+                Array.set(out, i, element);
+            }
+            if (out == null) {
+                // Treat all-null arrays as String arrays.
+                return new String[len];
+            }
+            return out;
+        }
+        if (value instanceof Boolean) {
+            return value;
+        }
+        if (value instanceof Byte || value instanceof Short || value instanceof Integer) {
+            return ((Number) value).intValue();
+        }
+        if (value instanceof Float || value instanceof Double || value instanceof Long) {
+            return ((Number) value).doubleValue();
+        }
+        return value != null ? value.toString() : null;
+    }
+
+    public static GeckoBundle fromJSONObject(final JSONObject obj) throws JSONException {
+        if (obj == null || obj == JSONObject.NULL) {
+            return null;
+        }
+
+        final String[] keys = new String[obj.length()];
+        final Object[] values = new String[obj.length()];
+
+        final Iterator<String> iter = obj.keys();
+        for (int i = 0; iter.hasNext(); i++) {
+            final String key = iter.next();
+            keys[i] = key;
+            values[i] = fromJSONValue(obj.opt(key));
+        }
+        return new GeckoBundle(keys, values);
+    }
 }
--- a/mobile/android/modules/Prompt.jsm
+++ b/mobile/android/modules/Prompt.jsm
@@ -3,17 +3,16 @@
  * 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"
 
 var Cc = Components.classes;
 var Ci = Components.interfaces;
 
 Components.utils.import("resource://gre/modules/Services.jsm");
-Components.utils.import("resource://gre/modules/Messaging.jsm");
 
 this.EXPORTED_SYMBOLS = ["Prompt"];
 
 function log(msg) {
   Services.console.logStringMessage(msg);
 }
 
 function Prompt(aOptions) {
@@ -171,17 +170,18 @@ Prompt.prototype = {
 
   show: function(callback) {
     this.callback = callback;
     log("Sending message");
     this._innerShow();
   },
 
   _innerShow: function() {
-    Messaging.sendRequestForResult(this.msg).then((data) => {
+    let window = Services.wm.getMostRecentWindow("navigator:browser");
+    window.WindowEventDispatcher.sendRequestForResult(this.msg).then((data) => {
       if (this.callback)
         this.callback(data);
     });
   },
 
   _setListItems: function(aItems) {
     let hasSelected = false;
     this.msg.listitems = [];
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testEventDispatcher.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testEventDispatcher.java
@@ -206,18 +206,42 @@ public class testEventDispatcher extends
         } else {
             fFail("Event type should be valid: " + event);
         }
 
         if (UI_EVENT.equals(event) || BACKGROUND_EVENT.equals(event)) {
             checkBundle(message);
             checkBundle(message.getBundle("object"));
             fAssertSame("Bundle null object has correct value", null, message.getBundle("nullObject"));
+            fAssertSame("Bundle nonexistent object returns null",
+                    null, message.getBundle("nonexistentObject"));
 
-            // XXX add objectArray check when we remove NativeJSObject
+            final GeckoBundle[] objectArray = message.getBundleArray("objectArray");
+            fAssertNotNull("Bundle object array should exist", objectArray);
+            fAssertEquals("Bundle object array has correct length", 2, objectArray.length);
+            fAssertSame("Bundle object array index 0 has correct value", null, objectArray[0]);
+            checkBundle(objectArray[1]);
+
+            final GeckoBundle[] objectArrayOfNull = message.getBundleArray("objectArrayOfNull");
+            fAssertNotNull("Bundle object array of null should exist", objectArrayOfNull);
+            fAssertEquals("Bundle object array of null has correct length", 2, objectArrayOfNull.length);
+            fAssertSame("Bundle object array of null index 0 has correct value",
+                    null, objectArrayOfNull[0]);
+            fAssertSame("Bundle object array of null index 1 has correct value",
+                    null, objectArrayOfNull[1]);
+
+            final GeckoBundle[] emptyObjectArray = message.getBundleArray("emptyObjectArray");
+            fAssertNotNull("Bundle empty object array should exist", emptyObjectArray);
+            fAssertEquals("Bundle empty object array has correct length", 0, emptyObjectArray.length);
+
+            fAssertSame("Bundle null object array exists",
+                    null, message.getBundleArray("nullObjectArray"));
+
+            fAssertSame("Bundle nonexistent object array returns null",
+                    null, message.getBundleArray("nonexistentObjectArray"));
 
         } else if (UI_RESPONSE_EVENT.equals(event) || BACKGROUND_RESPONSE_EVENT.equals(event)) {
             final String response = message.getString("response");
             if ("success".equals(response)) {
                 callback.sendSuccess(response);
             } else if ("error".equals(response)) {
                 callback.sendError(response);
             } else {
@@ -385,16 +409,34 @@ public class testEventDispatcher extends
     }
 
     private void checkBundle(final GeckoBundle bundle) {
         fAssertEquals("Bundle boolean has correct value", true, bundle.getBoolean("boolean"));
         fAssertEquals("Bundle int has correct value", 1, bundle.getInt("int"));
         fAssertEquals("Bundle double has correct value", 0.5, bundle.getDouble("double"));
         fAssertEquals("Bundle string has correct value", "foo", bundle.getString("string"));
 
+        fAssertEquals("Bundle default boolean has correct value",
+                true, bundle.getBoolean("nonexistentBoolean", true));
+        fAssertEquals("Bundle default int has correct value",
+                1, bundle.getInt("nonexistentInt", 1));
+        fAssertEquals("Bundle default double has correct value",
+                0.5, bundle.getDouble("nonexistentDouble", 0.5));
+        fAssertEquals("Bundle default string has correct value",
+                "foo", bundle.getString("nonexistentString", "foo"));
+
+        fAssertEquals("Bundle nonexistent boolean returns false",
+                false, bundle.getBoolean("nonexistentBoolean"));
+        fAssertEquals("Bundle nonexistent int returns 0",
+                0, bundle.getInt("nonexistentInt"));
+        fAssertEquals("Bundle nonexistent double returns 0.0",
+                0.0, bundle.getDouble("nonexistentDouble"));
+        fAssertSame("Bundle nonexistent string returns null",
+                null, bundle.getString("nonexistentString"));
+
         fAssertSame("Bundle null string has correct value", null, bundle.getString("nullString"));
         fAssertEquals("Bundle empty string has correct value", "", bundle.getString("emptyString"));
         fAssertEquals("Bundle default null string is correct", "foo",
                       bundle.getString("nullString", "foo"));
         fAssertEquals("Bundle default empty string is correct", "",
                       bundle.getString("emptyString", "foo"));
 
         final boolean[] booleanArray = bundle.getBooleanArray("booleanArray");
@@ -416,32 +458,58 @@ public class testEventDispatcher extends
         fAssertEquals("Bundle double array index 1 has correct value", 2.5, doubleArray[1]);
 
         final String[] stringArray = bundle.getStringArray("stringArray");
         fAssertNotNull("Bundle string array should exist", stringArray);
         fAssertEquals("Bundle string array has correct length", 2, stringArray.length);
         fAssertEquals("Bundle string array index 0 has correct value", "bar", stringArray[0]);
         fAssertEquals("Bundle string array index 1 has correct value", "baz", stringArray[1]);
 
+        final String[] stringArrayOfNull = bundle.getStringArray("stringArrayOfNull");
+        fAssertNotNull("Bundle string array of null should exist", stringArrayOfNull);
+        fAssertEquals("Bundle string array of null has correct length", 2, stringArrayOfNull.length);
+        fAssertSame("Bundle string array of null index 0 has correct value",
+                null, stringArrayOfNull[0]);
+        fAssertSame("Bundle string array of null index 1 has correct value",
+                null, stringArrayOfNull[1]);
+
         final boolean[] emptyBooleanArray = bundle.getBooleanArray("emptyBooleanArray");
         fAssertNotNull("Bundle empty boolean array should exist", emptyBooleanArray);
         fAssertEquals("Bundle empty boolean array has correct length", 0, emptyBooleanArray.length);
 
         final int[] emptyIntArray = bundle.getIntArray("emptyIntArray");
         fAssertNotNull("Bundle empty int array should exist", emptyIntArray);
         fAssertEquals("Bundle empty int array has correct length", 0, emptyIntArray.length);
 
         final double[] emptyDoubleArray = bundle.getDoubleArray("emptyDoubleArray");
         fAssertNotNull("Bundle empty double array should exist", emptyDoubleArray);
         fAssertEquals("Bundle empty double array has correct length", 0, emptyDoubleArray.length);
 
         final String[] emptyStringArray = bundle.getStringArray("emptyStringArray");
         fAssertNotNull("Bundle empty String array should exist", emptyStringArray);
         fAssertEquals("Bundle empty String array has correct length", 0, emptyStringArray.length);
 
+        fAssertSame("Bundle null boolean array exists",
+                null, bundle.getBooleanArray("nullBooleanArray"));
+        fAssertSame("Bundle null int array exists",
+                null, bundle.getIntArray("nullIntArray"));
+        fAssertSame("Bundle null double array exists",
+                null, bundle.getDoubleArray("nullDoubleArray"));
+        fAssertSame("Bundle null string array exists",
+                null, bundle.getStringArray("nullStringArray"));
+
+        fAssertSame("Bundle nonexistent boolean array returns null",
+                null, bundle.getBooleanArray("nonexistentBooleanArray"));
+        fAssertSame("Bundle nonexistent int array returns null",
+                null, bundle.getIntArray("nonexistentIntArray"));
+        fAssertSame("Bundle nonexistent double array returns null",
+                null, bundle.getDoubleArray("nonexistentDoubleArray"));
+        fAssertSame("Bundle nonexistent string array returns null",
+                null, bundle.getStringArray("nonexistentStringArray"));
+
         // XXX add mixedArray check when we remove NativeJSObject
     }
 
     private void checkJSONObject(final JSONObject object) throws JSONException {
         fAssertEquals("JSON boolean has correct value", true, object.getBoolean("boolean"));
         fAssertEquals("JSON int has correct value", 1, object.getInt("int"));
         fAssertEquals("JSON double has correct value", 0.5, object.getDouble("double"));
         fAssertEquals("JSON string has correct value", "foo", object.getString("string"));
@@ -575,33 +643,41 @@ public class testEventDispatcher extends
 
         bundle.putDouble("double", 0.5);
         bundle.putDoubleArray("doubleArray", new double[] {1.5, 2.5});
 
         bundle.putString("string", "foo");
         bundle.putString("nullString", null);
         bundle.putString("emptyString", "");
         bundle.putStringArray("stringArray", new String[] {"bar", "baz"});
+        bundle.putStringArray("stringArrayOfNull", new String[2]);
 
         bundle.putBooleanArray("emptyBooleanArray", new boolean[0]);
         bundle.putIntArray("emptyIntArray", new int[0]);
         bundle.putDoubleArray("emptyDoubleArray", new double[0]);
         bundle.putStringArray("emptyStringArray", new String[0]);
 
+        bundle.putBooleanArray("nullBooleanArray", (boolean[]) null);
+        bundle.putIntArray("nullIntArray", (int[]) null);
+        bundle.putDoubleArray("nullDoubleArray", (double[]) null);
+        bundle.putStringArray("nullStringArray", (String[]) null);
+
         return bundle;
     }
 
     private static GeckoBundle createBundle() {
         final GeckoBundle outer = createInnerBundle();
         final GeckoBundle inner = createInnerBundle();
 
         outer.putBundle("object", inner);
         outer.putBundle("nullObject", null);
         outer.putBundleArray("objectArray", new GeckoBundle[] {null, inner});
+        outer.putBundleArray("objectArrayOfNull", new GeckoBundle[2]);
         outer.putBundleArray("emptyObjectArray", new GeckoBundle[0]);
+        outer.putBundleArray("nullObjectArray", (GeckoBundle[]) null);
 
         return outer;
     }
 
     public void dispatchMessage(final String scope, final String type) {
         getDispatcher(scope).dispatch(type, createBundle());
     }
 
--- a/mobile/android/tests/browser/robocop/testEventDispatcher.js
+++ b/mobile/android/tests/browser/robocop/testEventDispatcher.js
@@ -16,31 +16,38 @@ function get_test_message() {
     intArray: [2, 3],
     double: 0.5,
     doubleArray: [1.5, 2.5],
     null: null,
     string: "foo",
     nullString: null,
     emptyString: "",
     stringArray: ["bar", "baz"],
+    stringArrayOfNull: [null, null],
     emptyBooleanArray: [],
     emptyIntArray: [],
     emptyDoubleArray: [],
     emptyStringArray: [],
+    nullBooleanArray: null,
+    nullIntArray: null,
+    nullDoubleArray: null,
+    nullStringArray: null,
     // XXX enable when we remove NativeJSObject
     // mixedArray: [1, 1.5],
   };
 
   // Make a copy
   let outerObject = JSON.parse(JSON.stringify(innerObject));
 
   outerObject.object = innerObject;
   outerObject.nullObject = null;
   outerObject.objectArray = [null, innerObject];
+  outerObject.objectArrayOfNull = [null, null];
   outerObject.emptyObjectArray = [];
+  outerObject.nullObjectArray = null;
   return outerObject;
 }
 
 function send_test_message(type) {
   let outerObject = get_test_message();
   outerObject.type = type;
 
   Messaging.sendRequest(outerObject);
@@ -99,35 +106,50 @@ let listener = {
     do_check_eq(obj.string, "foo");
     do_check_eq(obj.nullString, null);
     do_check_eq(obj.emptyString, "");
 
     do_check_eq(obj.stringArray.length, 2);
     do_check_eq(obj.stringArray[0], "bar");
     do_check_eq(obj.stringArray[1], "baz");
 
+    do_check_eq(obj.stringArrayOfNull.length, 2);
+    do_check_eq(obj.stringArrayOfNull[0], null);
+    do_check_eq(obj.stringArrayOfNull[1], null);
+
     do_check_eq(obj.emptyBooleanArray.length, 0);
     do_check_eq(obj.emptyIntArray.length, 0);
     do_check_eq(obj.emptyDoubleArray.length, 0);
     do_check_eq(obj.emptyStringArray.length, 0);
+
+    do_check_eq(obj.nullBooleanArray, null);
+    do_check_eq(obj.nullIntArray, null);
+    do_check_eq(obj.nullDoubleArray, null);
+    do_check_eq(obj.nullStringArray, null);
   },
 
   onEvent: function (event, data, callback) {
     do_check_eq(event, this._type);
     this._callCount++;
 
     this._checkObject(data);
 
     this._checkObject(data.object);
     do_check_eq(data.nullObject, null);
 
     do_check_eq(data.objectArray.length, 2);
     do_check_eq(data.objectArray[0], null);
     this._checkObject(data.objectArray[1]);
+
+    do_check_eq(data.objectArrayOfNull.length, 2);
+    do_check_eq(data.objectArrayOfNull[0], null);
+    do_check_eq(data.objectArrayOfNull[1], null);
+
     do_check_eq(data.emptyObjectArray.length, 0);
+    do_check_eq(data.nullObjectArray, null);
   }
 };
 
 let callbackListener = {
   onEvent: function (event, data, callback) {
     do_check_eq(event, this._type);
     this._callCount++;
 
--- a/netwerk/base/ArrayBufferInputStream.cpp
+++ b/netwerk/base/ArrayBufferInputStream.cpp
@@ -8,17 +8,16 @@
 #include "nsStreamUtils.h"
 #include "jsapi.h"
 #include "jsfriendapi.h"
 
 NS_IMPL_ISUPPORTS(ArrayBufferInputStream, nsIArrayBufferInputStream, nsIInputStream);
 
 ArrayBufferInputStream::ArrayBufferInputStream()
 : mBufferLength(0)
-, mOffset(0)
 , mPos(0)
 , mClosed(false)
 {
 }
 
 NS_IMETHODIMP
 ArrayBufferInputStream::SetData(JS::Handle<JS::Value> aBuffer,
                                 uint32_t aByteOffset,
@@ -28,21 +27,26 @@ ArrayBufferInputStream::SetData(JS::Hand
   if (!aBuffer.isObject()) {
     return NS_ERROR_FAILURE;
   }
   JS::RootedObject arrayBuffer(aCx, &aBuffer.toObject());
   if (!JS_IsArrayBufferObject(arrayBuffer)) {
     return NS_ERROR_FAILURE;
   }
 
-  mArrayBuffer.emplace(aCx, arrayBuffer);
+  uint32_t buflen = JS_GetArrayBufferByteLength(arrayBuffer);
+  uint32_t offset = std::min(buflen, aByteOffset);
+  mBufferLength = std::min(buflen - offset, aLength);
 
-  uint32_t buflen = JS_GetArrayBufferByteLength(arrayBuffer);
-  mOffset = std::min(buflen, aByteOffset);
-  mBufferLength = std::min(buflen - mOffset, aLength);
+  mArrayBuffer = mozilla::MakeUnique<char[]>(mBufferLength);
+
+  JS::AutoCheckCannotGC nogc;
+  bool isShared;
+  char* src = (char*) JS_GetArrayBufferData(arrayBuffer, &isShared, nogc) + offset;
+  memcpy(&mArrayBuffer[0], src, mBufferLength);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 ArrayBufferInputStream::Close()
 {
   mClosed = true;
   return NS_OK;
@@ -50,18 +54,17 @@ ArrayBufferInputStream::Close()
 
 NS_IMETHODIMP
 ArrayBufferInputStream::Available(uint64_t* aCount)
 {
   if (mClosed) {
     return NS_BASE_STREAM_CLOSED;
   }
   if (mArrayBuffer) {
-    uint32_t buflen = JS_GetArrayBufferByteLength(mArrayBuffer->get());
-    *aCount = buflen ? buflen - mPos : 0;
+    *aCount = mBufferLength ? mBufferLength - mPos : 0;
   } else {
     *aCount = 0;
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 ArrayBufferInputStream::Read(char* aBuf, uint32_t aCount, uint32_t *aReadCount)
@@ -81,44 +84,24 @@ ArrayBufferInputStream::ReadSegments(nsW
   }
 
   MOZ_ASSERT(mArrayBuffer || (mPos == mBufferLength), "stream inited incorrectly");
 
   *result = 0;
   while (mPos < mBufferLength) {
     uint32_t remaining = mBufferLength - mPos;
     MOZ_ASSERT(mArrayBuffer);
-    uint32_t byteLength = JS_GetArrayBufferByteLength(mArrayBuffer->get());
-    if (byteLength == 0) {
-      mClosed = true;
-      return NS_BASE_STREAM_CLOSED;
-    }
 
-    // If you change the size of this buffer, please also remember to
-    // update test_arraybufferinputstream.html.
-    char buffer[8192];
-    uint32_t count = std::min(std::min(aCount, remaining), uint32_t(mozilla::ArrayLength(buffer)));
+    uint32_t count = std::min(aCount, remaining);
     if (count == 0) {
       break;
     }
 
-    // It is just barely possible that writer() will detach the ArrayBuffer's
-    // data, setting its length to zero. Or move the data to a different memory
-    // area. (This would only happen in a subclass that passed something other
-    // than NS_CopySegmentToBuffer as 'writer'). So copy the data out into a
-    // holding area before passing it to writer().
-    {
-      JS::AutoCheckCannotGC nogc;
-      bool isShared;
-      char* src = (char*) JS_GetArrayBufferData(mArrayBuffer->get(), &isShared, nogc) + mOffset + mPos;
-      MOZ_ASSERT(!isShared);    // Because ArrayBuffer
-      memcpy(buffer, src, count);
-    }
     uint32_t written;
-    nsresult rv = writer(this, closure, buffer, *result, count, &written);
+    nsresult rv = writer(this, closure, &mArrayBuffer[0] + mPos, *result, count, &written);
     if (NS_FAILED(rv)) {
       // InputStreams do not propagate errors to caller.
       return NS_OK;
     }
 
     NS_ASSERTION(written <= count,
                  "writer should not write more than we asked it to write");
     mPos += written;
--- a/netwerk/base/ArrayBufferInputStream.h
+++ b/netwerk/base/ArrayBufferInputStream.h
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef ArrayBufferInputStream_h
 #define ArrayBufferInputStream_h
 
 #include "nsIArrayBufferInputStream.h"
 #include "js/Value.h"
 #include "mozilla/Maybe.h"
+#include "mozilla/UniquePtr.h"
 
 #define NS_ARRAYBUFFERINPUTSTREAM_CONTRACTID "@mozilla.org/io/arraybuffer-input-stream;1"
 #define NS_ARRAYBUFFERINPUTSTREAM_CID                \
 { /* 3014dde6-aa1c-41db-87d0-48764a3710f6 */         \
     0x3014dde6,                                      \
     0xaa1c,                                          \
     0x41db,                                          \
     {0x87, 0xd0, 0x48, 0x76, 0x4a, 0x37, 0x10, 0xf6} \
@@ -23,16 +24,15 @@ class ArrayBufferInputStream : public ns
 public:
   ArrayBufferInputStream();
   NS_DECL_ISUPPORTS
   NS_DECL_NSIARRAYBUFFERINPUTSTREAM
   NS_DECL_NSIINPUTSTREAM
 
 private:
   virtual ~ArrayBufferInputStream() {}
-  mozilla::Maybe<JS::PersistentRooted<JSObject*> > mArrayBuffer;
-  uint32_t mBufferLength; // length of slice
-  uint32_t mOffset; // permanent offset from start of actual buffer
-  uint32_t mPos; // offset from start of slice
+  mozilla::UniquePtr<char[]> mArrayBuffer;
+  uint32_t mBufferLength;
+  uint32_t mPos;
   bool mClosed;
 };
 
 #endif // ArrayBufferInputStream_h
--- a/netwerk/protocol/http/nsHttpConnection.cpp
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -1709,16 +1709,19 @@ nsHttpConnection::OnSocketWritable()
             rv = mProxyConnectStream->ReadSegments(ReadFromStream, this,
                                                    nsIOService::gDefaultSegmentSize,
                                                    &transactionBytes);
         } else if (!EnsureNPNComplete(rv, transactionBytes)) {
             if (NS_SUCCEEDED(rv) && !transactionBytes &&
                 NS_SUCCEEDED(mSocketOutCondition)) {
                 mSocketOutCondition = NS_BASE_STREAM_WOULD_BLOCK;
             }
+        } else if (!mTransaction) {
+            rv = NS_ERROR_FAILURE;
+            LOG(("  No Transaction In OnSocketWritable\n"));
         } else {
 
             // for non spdy sessions let the connection manager know
             if (!mReportedSpdy) {
                 mReportedSpdy = true;
                 MOZ_ASSERT(!mEverUsedSpdy);
                 gHttpHandler->ConnMgr()->ReportSpdyConnection(this, false);
             }
--- a/netwerk/test/mochitests/test_arraybufferinputstream.html
+++ b/netwerk/test/mochitests/test_arraybufferinputstream.html
@@ -14,16 +14,17 @@ function detachArrayBuffer(ab)
   w.postMessage(ab, [ab]);
 }
 
 function test()
 {
   var ab = new ArrayBuffer(4000);
   var ta = new Uint8Array(ab);
   ta[0] = 'a'.charCodeAt(0);
+  ta[1] = 'b'.charCodeAt(0);
 
   const Cc = SpecialPowers.Cc, Ci = SpecialPowers.Ci, Cr = SpecialPowers.Cr;
   var abis = Cc["@mozilla.org/io/arraybuffer-input-stream;1"]
                .createInstance(Ci.nsIArrayBufferInputStream);
 
   var sis = Cc["@mozilla.org/scriptableinputstream;1"]
               .createInstance(Ci.nsIScriptableInputStream);
   sis.init(abis);
@@ -36,23 +37,21 @@ function test()
 
   detachArrayBuffer(ab);
 
   SpecialPowers.forceGC();
   SpecialPowers.forceGC();
 
   try
   {
-    sis.read(1);
-    ok(false, "reading from stream shouldn't have worked");
+    is(sis.read(1), "b", "should read 'b' after detaching buffer");
   }
   catch (e)
   {
-    ok(e.result === Cr.NS_BASE_STREAM_CLOSED,
-       "detaching underneath an input stream should close it");
+    ok(false, "reading from stream should have worked");
   }
 
   // A regression test for bug 1265076.  Previously, overflowing
   // the internal buffer from readSegments would cause incorrect
   // copying.  The constant mirrors the value in
   // ArrayBufferInputStream::readSegments.
   var size = 8192;
   ab = new ArrayBuffer(2 * size);
--- a/security/manager/tools/PreloadedHPKPins.json
+++ b/security/manager/tools/PreloadedHPKPins.json
@@ -15,23 +15,20 @@
 // Subject Public Key Infos (SPKIs) is found in the chain.  SPKIs are specified
 // as names, which must match up with the name given in the Mozilla root store.
 //
 // "entries" is a list of objects. Each object has the following members:
 //   name: (string) the DNS name of the host in question
 //   include_subdomains: (optional bool) whether subdomains of |name| are also covered
 //   pins: (string) the |name| member of an object in |pinsets|
 //
-// "extra_certs" is a list of base64-encoded certificates. These are used in
+// "extra_certificates" is a list of base64-encoded certificates. These are used in
 // pinsets that reference certificates not in our root program (for example,
-// Facebook).
+// Facebook or intermediate CA certs).
 
-// equifax -> aus3
-// Geotrust Primary -> www.mozilla.org
-// Geotrust Global -> *. addons.mozilla.org
 {
   "chromium_data" : {
     "cert_file_url": "https://chromium.googlesource.com/chromium/src/net/+/master/http/transport_security_state_static.pins?format=TEXT",
     "json_file_url": "https://chromium.googlesource.com/chromium/src/net/+/master/http/transport_security_state_static.json?format=TEXT",
     "substitute_pinsets": {
       // Use the larger google_root_pems pinset instead of google
       "google": "google_root_pems"
     },
@@ -67,49 +64,24 @@
     "exclude_domains" : [
       // Chrome's entry for twitter.com doesn't include subdomains, so replace
       // it with our own entry below which also uses an expanded pinset.
       "twitter.com"
     ]
    },
   "pinsets": [
     {
-      // From bug 772756, mozilla uses GeoTrust, Digicert and Thawte.  Our
-      // cdn sites use Verisign and Baltimore. We exclude 1024-bit root certs
-      // from all providers. geotrust ca info:
-      // http://www.geotrust.com/resources/root-certificates/index.html
-      "name": "mozilla",
+      "name": "mozilla_services",
       "sha256_hashes": [
-        "Baltimore CyberTrust Root",
-        "DigiCert Assured ID Root CA",
         "DigiCert Global Root CA",
         "DigiCert High Assurance EV Root CA",
-        "GeoTrust Global CA",
-        "GeoTrust Global CA 2",
-        "GeoTrust Primary Certification Authority",
-        "GeoTrust Primary Certification Authority - G2",
-        "GeoTrust Primary Certification Authority - G3",
-        "GeoTrust Universal CA",
-        "GeoTrust Universal CA 2",
-        "thawte Primary Root CA",
-        "thawte Primary Root CA - G2",
-        "thawte Primary Root CA - G3",
-        "Verisign Class 1 Public Primary Certification Authority - G3",
-        "Verisign Class 2 Public Primary Certification Authority - G3",
-        "Verisign Class 3 Public Primary Certification Authority - G3",
-        "VeriSign Class 3 Public Primary Certification Authority - G4",
-        "VeriSign Class 3 Public Primary Certification Authority - G5",
-        // "Verisign Class 4 Public Primary Certification Authority - G3",
-        "VeriSign Universal Root Certification Authority"
-      ]
-    },
-    {
-      "name": "mozilla_services",
-      "sha256_hashes": [
-        "DigiCert Global Root CA"
+        // Backup intermediates with Let's Encrypt are not normally
+        // in use and require disabling Mozilla's sites blacklisting
+        "Let's Encrypt Authority X3",
+        "Let's Encrypt Authority X4"
       ]
     },
     // For pinning tests on pinning.example.com, the certificate must be 'End
     // Entity Test Cert'
     {
       "name": "mozilla_test",
       "sha256_hashes": [
         "End Entity Test Cert"
@@ -180,43 +152,75 @@
       ]
     }
   ],
 
   "entries": [
     // Only domains that are operationally crucial to Firefox can have per-host
     // telemetry reporting (the "id") field
     { "name": "addons.mozilla.org", "include_subdomains": true,
-      "pins": "mozilla", "test_mode": false, "id": 1 },
+      "pins": "mozilla_services", "test_mode": false, "id": 1 },
     { "name": "addons.mozilla.net", "include_subdomains": true,
-      "pins": "mozilla", "test_mode": false, "id": 2 },
+      "pins": "mozilla_services", "test_mode": false, "id": 2 },
+    // AUS servers MUST remain in test mode
+    // see: https://bugzilla.mozilla.org/show_bug.cgi?id=1301956#c23
     { "name": "aus4.mozilla.org", "include_subdomains": true,
-      "pins": "mozilla", "test_mode": true, "id": 3 },
+      "pins": "mozilla_services", "test_mode": true, "id": 3 },
+    { "name": "aus5.mozilla.org", "include_subdomains": true,
+      "pins": "mozilla_services", "test_mode": true, "id": 7 },
+    // Firefox Accounts & sync
     { "name": "accounts.firefox.com", "include_subdomains": true,
       "pins": "mozilla_services", "test_mode": false, "id": 4 },
     { "name": "api.accounts.firefox.com", "include_subdomains": true,
       "pins": "mozilla_services", "test_mode": false, "id": 5 },
+    { "name": "sync.services.mozilla.com", "include_subdomains": true,
+      "pins": "mozilla_services", "test_mode": false, "id": 13 },
+    // Catch-all for all CDN resources, including product delivery
     { "name": "cdn.mozilla.net", "include_subdomains": true,
-      "pins": "mozilla", "test_mode": false },
+      "pins": "mozilla_services", "test_mode": false },
     { "name": "cdn.mozilla.org", "include_subdomains": true,
-      "pins": "mozilla", "test_mode": false },
+      "pins": "mozilla_services", "test_mode": false },
+    { "name": "download.mozilla.org", "include_subdomains": false,
+      "pins": "mozilla_services", "test_mode": false, "id": 14 },
+    // Catch-all for everything hosted under services.mozilla.com
     { "name": "services.mozilla.com", "include_subdomains": true,
       "pins": "mozilla_services", "test_mode": false, "id": 6 },
+    // Catch-all for everything hosted under telemetry.mozilla.org
+    // MUST remain in test mode in order to receive telemetry on broken pins
+    { "name": "telemetry.mozilla.org", "include_subdomains": true,
+      "pins": "mozilla_services", "test_mode": true, "id": 8 },
+    // Test Pilot
+    { "name": "testpilot.firefox.com", "include_subdomains": false,
+      "pins": "mozilla_services", "test_mode": false, "id": 9 },
+    // Crash report sites
+    { "name": "crash-reports.mozilla.com", "include_subdomains": false,
+      "pins": "mozilla_services", "test_mode": false, "id": 10 },
+    { "name": "crash-reports-xpsp2.mozilla.com", "include_subdomains": false,
+      "pins": "mozilla_services", "test_mode": false, "id": 11 },
+    { "name": "crash-stats.mozilla.com", "include_subdomains": false,
+      "pins": "mozilla_services", "test_mode": false, "id": 12 },
     { "name": "include-subdomains.pinning.example.com",
       "include_subdomains": true, "pins": "mozilla_test",
       "test_mode": false },
     // Example domain to collect per-host stats for telemetry tests.
     { "name": "exclude-subdomains.pinning.example.com",
       "include_subdomains": false, "pins": "mozilla_test",
       "test_mode": false, "id": 0 },
     { "name": "test-mode.pinning.example.com", "include_subdomains": true,
       "pins": "mozilla_test", "test_mode": true },
     // Expand twitter's pinset to include all of *.twitter.com and use
     // twitterCDN. More specific rules take precedence because we search for
     // exact domain name first.
     { "name": "twitter.com", "include_subdomains": true,
-      "pins": "twitterCDN", "test_mode": false },
-    { "name": "aus5.mozilla.org", "include_subdomains": true,
-      "pins": "mozilla", "test_mode": true, "id": 7 }
+      "pins": "twitterCDN", "test_mode": false }
   ],
-
-  "extra_certificates": []
+  // When pinning to non-root certs, like intermediates,
+  // place the PEM of the pinned certificate in this array
+  // so Firefox can find the subject DN and public key
+  "extra_certificates": [
+    // Subject: C=US, O=Let's Encrypt, CN=Let's Encrypt Authority X3
+    // Issuer: C=US, O=Internet Security Research Group, CN=ISRG Root X1
+    "MIIFjTCCA3WgAwIBAgIRANOxciY0IzLc9AUoUSrsnGowDQYJKoZIhvcNAQELBQAwTzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTYxMDA2MTU0MzU1WhcNMjExMDA2MTU0MzU1WjBKMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3MgRW5jcnlwdDEjMCEGA1UEAxMaTGV0J3MgRW5jcnlwdCBBdXRob3JpdHkgWDMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCc0wzwWuUuR7dyXTeDs2hjMOrXNSYZJeG9vjXxcJIvt7hLQQWrqZ41CFjssSrEaIcLo+N15Obzp2JxunmBYB/XkZqf89B4Z3HIaQ6Vkc/+5pnpYDxIzH7KTXcSJJ1HG1rrueweNwAcnKx7pwXqzkrrvUHlNpi5y/1tPJZo3yMqQpAMhnRnyH+lmrhSYRQTP2XpgofL2/oOVvaGifOFP5eGr7DcGu9rDZUWfcQroGWymQQ2dYBrrErzG5BJeC+ilk8qICUpBMZ0wNAxzY8xOJUWuqgzuEPxsR/DMH+ieTETPS02+OP88jNquTkxxa/EjQ0dZBYzqvqEKbbUC8DYfcOTAgMBAAGjggFnMIIBYzAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADBUBgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEBATAwMC4GCCsGAQUFBwIBFiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQub3JnMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3JsLnJvb3QteDEubGV0c2VuY3J5cHQub3JnMHIGCCsGAQUFBwEBBGYwZDAwBggrBgEFBQcwAYYkaHR0cDovL29jc3Aucm9vdC14MS5sZXRzZW5jcnlwdC5vcmcvMDAGCCsGAQUFBzAChiRodHRwOi8vY2VydC5yb290LXgxLmxldHNlbmNyeXB0Lm9yZy8wHwYDVR0jBBgwFoAUebRZ5nu25eQBc4AIiMgaWPbpm24wDQYJKoZIhvcNAQELBQADggIBABnPdSA0LTqmRf/Q1eaM2jLonG4bQdEnqOJQ8nCqxOeTRrToEKtwT++36gTSlBGxA/5dut82jJQ2jxN8RI8L9QFXrWi4xXnA2EqA10yjHiR6H9cj6MFiOnb5In1eWsRMUM2v3e9tNsCAgBukPHAg1lQh07rvFKm/Bz9BCjaxorALINUfZ9DD64j2igLIxle2DPxW8dI/F2loHMjXZjqG8RkqZUdoxtID5+90FgsGIfkMpqgRS05f4zPbCEHqCXl1eO5HyELTgcVlLXXQDgAWnRzut1hFJeczY1tjQQno6f6s+nMydLN26WuU4s3UYvOuOsUxRlJu7TSRHqDC3lSE5XggVkzdaPkuKGQbGpny+01/47hfXXNB7HntWNZ6N2Vwp7G6OfY+YQrZwIaQmhrIqJZuigsrbe3W+gdn5ykE9+Ky0VgVUsfxo52mwFYs1JKY2PGDuWx8M6DlS6qQkvHaRUo0FMd8TsSlbF0/v965qGFKhSDeQoMpYnwcmQilRh/0ayLThlHLN81gSkJjVrPI0Y8xCVPB4twb1PFUd2fPM3sA1tJ83sZ5v8vgFv2yofKRPB0t6JzUA81mSqM3kxl5e+IZwhYAyO0OTg3/fs8HqGTNKd9BqoUwSRBzp06JMg5brUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt",
+    // Subject: C=US, O=Let's Encrypt, CN=Let's Encrypt Authority X4
+    // Issuer: C=US, O=Internet Security Research Group, CN=ISRG Root X1
+    "MIIFjTCCA3WgAwIBAgIRAJObmZ6kjhYNW0JZtD0gE9owDQYJKoZIhvcNAQELBQAwTzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTYxMDA2MTU0NDM0WhcNMjExMDA2MTU0NDM0WjBKMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3MgRW5jcnlwdDEjMCEGA1UEAxMaTGV0J3MgRW5jcnlwdCBBdXRob3JpdHkgWDQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDhJHRCe7eRMdlz/ziq2M5EXLc5CtxErg29RbmXN2evvVBPX9MQVGv3QdqOY+ZtW8DoQKmMQfzRA4n/YmEJYNYHBXiakL0aZD5P3M93L4lry2evQU3FjQDAa/6NhNy18pUxqOj2kKBDSpN0XLM+Q2lLiSJHdFE+mWTDzSQB+YQvKHcXIqfdw2wITGYvN3TFb5OOsEY3FmHRUJjIsA9PWFN8rPbaLZZhUK1D3AqmT561Urmcju9O30azMdwg/GnCoyB1Puw4GzZOZmbS3/VmpJMve6YOlD5gPUpLHG+6tE0cPJFYbi9NxNpw2+0BOXbASefpNbUUBpDB5ZLiEP1rubSFAgMBAAGjggFnMIIBYzAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADBUBgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEBATAwMC4GCCsGAQUFBwIBFiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQub3JnMB0GA1UdDgQWBBTFsatOTLHNZDCTfsGEmQWr5gPiJTAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3JsLnJvb3QteDEubGV0c2VuY3J5cHQub3JnMHIGCCsGAQUFBwEBBGYwZDAwBggrBgEFBQcwAYYkaHR0cDovL29jc3Aucm9vdC14MS5sZXRzZW5jcnlwdC5vcmcvMDAGCCsGAQUFBzAChiRodHRwOi8vY2VydC5yb290LXgxLmxldHNlbmNyeXB0Lm9yZy8wHwYDVR0jBBgwFoAUebRZ5nu25eQBc4AIiMgaWPbpm24wDQYJKoZIhvcNAQELBQADggIBAF4tI1yGjZgld9lP01+zftU3aSV0un0d2GKUMO7GxvwTLWAKQz/eT+u3J4+GvpD+BMfopIxkJcDCzMChjjZtZZwJpIY7BatVrO6OkEmaRNITtbZ/hCwNkUnbk3C7EG3OGJZlo9b2wzA8v9WBsPzHpTvLfOr+dS57LLPZBhp3ArHaLbdk33lIONRPt9sseDEkmdHnVmGmBRf4+J0Wy67mddOvz5rHH8uzY94raOayf20gzzcmqmot4hPXtDG4Y49MoFMMT2kcWck3EOTAH6QiGWkGJ7cxMfSL3S0niA6wgFJtfETETOZu8AVDgENgCJ3DS0bz/dhVKvs3WRkaKuuR/W0nnC2VDdaFj4+CRF8LGtn/8ERaH48TktH5BDyDVcF9zfJ75Scxcy23jAL2N6w3n/t3nnqoXt9Im4FprDr+mP1g2Z6Lf2YA0jE3kZalgZ6lNHu4CmvJYoOTSJw9X2qlGl1K+B4U327rG1tRxgjM76pN6lIS02PMECoyKJigpOSBu4V8+LVaUMezCJH9Qf4EKeZTHddQ1t96zvNd2s9ewSKx/DblXbKsBDzIdHJ+qi6+F9DIVM5/ICdtDdulOO+dr/BXB+pBZ3uVxjRANvJKKpdxkePyluITSNZHbanWRN07gMvwBWOL060i4VrL9er1sBQrRjU9iNpZQGTnLVAxQVFu"
+  ]
 }
--- a/toolkit/components/telemetry/Telemetry.cpp
+++ b/toolkit/components/telemetry/Telemetry.cpp
@@ -68,21 +68,25 @@
 #include "mozilla/Mutex.h"
 #include "mozilla/FileUtils.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/IOInterposer.h"
 #include "mozilla/PoisonIOInterposer.h"
 #include "mozilla/StartupTimeline.h"
 #include "mozilla/HangMonitor.h"
+
 #if defined(MOZ_ENABLE_PROFILER_SPS)
 #include "shared-libraries.h"
+#if defined(MOZ_STACKWALKING)
+#define ENABLE_STACK_CAPTURE
 #include "mozilla/StackWalk.h"
 #include "nsPrintfCString.h"
-#endif
+#endif // MOZ_STACKWALKING
+#endif // MOZ_ENABLE_PROFILER_SPS
 
 namespace {
 
 using namespace mozilla;
 using namespace mozilla::HangMonitor;
 using Telemetry::Common::AutoHashtable;
 
 // The maximum number of chrome hangs stacks that we're keeping.
@@ -390,17 +394,17 @@ HangReports::GetFirefoxUptime(unsigned a
   return mHangInfo[aIndex].mFirefoxUptime;
 }
 
 const nsClassHashtable<nsStringHashKey, HangReports::AnnotationInfo>&
 HangReports::GetAnnotationInfo() const {
   return mAnnotationInfo;
 }
 
-#if defined(MOZ_ENABLE_PROFILER_SPS)
+#if defined(ENABLE_STACK_CAPTURE)
 
 const uint8_t kMaxKeyLength = 50;
 
 /**
  * Checks if a single character of the key string is valid.
  *
  * @param aChar a character to validate.
  * @return True, if the char is valid, False - otherwise.
@@ -878,16 +882,18 @@ public:
   static void RecordSlowStatement(const nsACString &sql, const nsACString &dbName,
                                   uint32_t delay);
 #if defined(MOZ_ENABLE_PROFILER_SPS)
   static void RecordChromeHang(uint32_t aDuration,
                                Telemetry::ProcessedStack &aStack,
                                int32_t aSystemUptime,
                                int32_t aFirefoxUptime,
                                HangAnnotationsPtr aAnnotations);
+#endif
+#if defined(ENABLE_STACK_CAPTURE)
   static void DoStackCapture(const nsACString& aKey);
 #endif
   static void RecordThreadHangStats(Telemetry::ThreadHangStats& aStats);
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
   struct Stat {
     uint32_t hitCount;
     uint32_t totalTime;
   };
@@ -926,17 +932,17 @@ private:
 
   static TelemetryImpl *sTelemetry;
   AutoHashtable<SlowSQLEntryType> mPrivateSQL;
   AutoHashtable<SlowSQLEntryType> mSanitizedSQL;
   Mutex mHashMutex;
   HangReports mHangReports;
   Mutex mHangReportsMutex;
 
-#if defined(MOZ_ENABLE_PROFILER_SPS)
+#if defined(ENABLE_STACK_CAPTURE)
   // Stores data about stacks captured on demand.
   KeyedStackCapturer mStackCapturer;
 #endif
 
   // mThreadHangStats stores recorded, inactive thread hang stats
   Vector<Telemetry::ThreadHangStats> mThreadHangStats;
   Mutex mThreadHangStatsMutex;
 
@@ -1523,17 +1529,17 @@ TelemetryImpl::GetChromeHangs(JSContext 
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TelemetryImpl::SnapshotCapturedStacks(bool clear, JSContext *cx, JS::MutableHandle<JS::Value> ret)
 {
-#if defined(MOZ_ENABLE_PROFILER_SPS)
+#if defined(ENABLE_STACK_CAPTURE)
   nsresult rv = mStackCapturer.ReflectCapturedStacks(cx, ret);
   if (clear) {
     mStackCapturer.Clear();
   }
   return rv;
 #else
   return NS_OK;
 #endif
@@ -2456,27 +2462,29 @@ TelemetryImpl::RecordChromeHang(uint32_t
 
   MutexAutoLock hangReportMutex(sTelemetry->mHangReportsMutex);
 
   sTelemetry->mHangReports.AddHang(aStack, aDuration,
                                    aSystemUptime, aFirefoxUptime,
                                    Move(annotations));
 }
 
+#if defined(ENABLE_STACK_CAPTURE)
 void
 TelemetryImpl::DoStackCapture(const nsACString& aKey) {
   if (Telemetry::CanRecordExtended() && XRE_IsParentProcess()) {
     sTelemetry->mStackCapturer.Capture(aKey);
   }
 }
 #endif
+#endif
 
 nsresult
 TelemetryImpl::CaptureStack(const nsACString& aKey) {
-#if defined(MOZ_ENABLE_PROFILER_SPS)
+#if defined(ENABLE_STACK_CAPTURE)
   TelemetryImpl::DoStackCapture(aKey);
 #endif
   return NS_OK;
 }
 
 void
 TelemetryImpl::RecordThreadHangStats(Telemetry::ThreadHangStats& aStats)
 {
@@ -3143,17 +3151,19 @@ void RecordChromeHang(uint32_t duration,
 {
   TelemetryImpl::RecordChromeHang(duration, aStack,
                                   aSystemUptime, aFirefoxUptime,
                                   Move(aAnnotations));
 }
 
 void CaptureStack(const nsACString& aKey)
 {
+#if defined(ENABLE_STACK_CAPTURE)
   TelemetryImpl::DoStackCapture(aKey);
+#endif
 }
 #endif
 
 void RecordThreadHangStats(ThreadHangStats& aStats)
 {
   TelemetryImpl::RecordThreadHangStats(aStats);
 }
 
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryCaptureStack.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryCaptureStack.js
@@ -1,14 +1,18 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 Cu.import("resource://gre/modules/TelemetryController.jsm", this);
 Cu.import("resource://gre/modules/AppConstants.jsm", this);
 
+// We need both in order to capture stacks.
+const ENABLE_TESTS = AppConstants.MOZ_ENABLE_PROFILER_SPS &&
+                     AppConstants.MOZ_STACKWALKING;
+
 /**
  * Ensures that the sctucture of the javascript object used for capturing stacks
  * is as intended. The structure is expected to be as in this example:
  *
  * {
  *  "memoryMap": [
  *      [String, String],
  *      ...
@@ -61,34 +65,34 @@ function captureStacks(key, clear = true
 }
 
 const TEST_STACK_KEYS = ["TEST-KEY1", "TEST-KEY2"];
 
 /**
  * Ensures that captured stacks appear in pings, if any were captured.
  */
 add_task({
-  skip_if: () => !AppConstants.MOZ_ENABLE_PROFILER_SPS
+  skip_if: () => !ENABLE_TESTS
 }, function* test_capturedStacksAppearInPings() {
   yield TelemetryController.testSetup();
   captureStacks("DOES-NOT-MATTER", false);
 
   let ping = TelemetryController.getCurrentPingData();
   Assert.ok("capturedStacks" in ping.payload.processes.parent);
 
   let capturedStacks = ping.payload.processes.parent.capturedStacks;
   Assert.ok(checkObjectStructure(capturedStacks));
 });
 
 /**
  * Ensures that capturing a stack for a new key increases the number
  * of captured stacks and adds a new entry to captures.
  */
 add_task({
-  skip_if: () => !AppConstants.MOZ_ENABLE_PROFILER_SPS
+  skip_if: () => !ENABLE_TESTS
 }, function* test_CaptureStacksIncreasesNumberOfCapturedStacks() {
   // Construct a unique key for this test.
   let key = TEST_STACK_KEYS[0] + "-UNIQUE-KEY-1";
 
   // Ensure that no captures for the key exist.
   let original = Telemetry.snapshotCapturedStacks();
   Assert.equal(undefined, original.captures.find(capture => capture[0] === key));
 
@@ -106,17 +110,17 @@ add_task({
   );
 });
 
 /**
  * Ensures that stacks are grouped by the key. If a stack is captured
  * more than once for the key, the length of stacks does not increase.
  */
  add_task({
-   skip_if: () => !AppConstants.MOZ_ENABLE_PROFILER_SPS
+   skip_if: () => !ENABLE_TESTS
  }, function* test_CaptureStacksGroupsDuplicateStacks() {
   // Make sure that there are initial captures for TEST_STACK_KEYS[0].
   let stacks = captureStacks(TEST_STACK_KEYS[0], false);
   let original = {
     captures: stacks.captures.find(capture => capture[0] === TEST_STACK_KEYS[0]),
     stacks: stacks.stacks
   };
 
@@ -137,17 +141,17 @@ add_task({
   Assert.deepEqual(expectedCaptures, updated.captures);
 });
 
 /**
  * Ensure that capturing the stack for a key does not affect info
  * for other keys.
  */
 add_task({
-  skip_if: () => !AppConstants.MOZ_ENABLE_PROFILER_SPS
+  skip_if: () => !ENABLE_TESTS
 }, function* test_CaptureStacksSeparatesInformationByKeys() {
   // Make sure that there are initial captures for TEST_STACK_KEYS[0].
   let stacks = captureStacks(TEST_STACK_KEYS[0], false);
   let original = {
     captures: stacks.captures.find(capture => capture[0] === TEST_STACK_KEYS[0]),
     stacks: stacks.stacks
   };
 
@@ -164,17 +168,17 @@ add_task({
     updated.captures.find(capture => capture[0] === TEST_STACK_KEYS[0])
   );
 });
 
 /**
  * Ensure that Telemetry does not allow weird keys.
  */
 add_task({
-  skip_if: () => !AppConstants.MOZ_ENABLE_PROFILER_SPS
+  skip_if: () => !ENABLE_TESTS
 }, function* test_CaptureStacksDoesNotAllowBadKey() {
   for (let badKey of [null, "KEY-!@\"#$%^&*()_"]) {
     let stacks = captureStacks(badKey);
     let captureData = stacks.captures.find(capture => capture[0] === badKey)
     Assert.ok(!captureData, `"${badKey}" should not be allowed as a key`);
   }
 });
 
--- a/toolkit/modules/AppConstants.jsm
+++ b/toolkit/modules/AppConstants.jsm
@@ -290,16 +290,23 @@ this.AppConstants = Object.freeze({
 
   MOZ_ENABLE_PROFILER_SPS:
 #ifdef MOZ_ENABLE_PROFILER_SPS
   true,
 #else
   false,
 #endif
 
+  MOZ_STACKWALKING:
+#ifdef MOZ_STACKWALKING
+  true,
+#else
+  false,
+#endif
+
   MOZ_ANDROID_ACTIVITY_STREAM:
 #ifdef MOZ_ANDROID_ACTIVITY_STREAM
   true,
 #else
   false,
 #endif
 
   DLL_PREFIX: "@DLL_PREFIX@",
--- a/toolkit/modules/addons/.eslintrc.js
+++ b/toolkit/modules/addons/.eslintrc.js
@@ -3,12 +3,13 @@
 module.exports = { // eslint-disable-line no-undef
   "extends": "../../components/extensions/.eslintrc.js",
 
   "globals": {
     "addEventListener": false,
     "addMessageListener": false,
     "removeEventListener": false,
     "sendAsyncMessage": false,
+    "AddonManagerPermissions": false,
 
     "initialProcessData": true,
   },
 };
--- a/toolkit/modules/addons/WebRequest.jsm
+++ b/toolkit/modules/addons/WebRequest.jsm
@@ -709,17 +709,20 @@ HttpObserverManager = {
 
         if (includeStatus) {
           mergeStatus(data, channel, kind);
         }
 
         try {
           let result = callback(data);
 
-          if (result && typeof result === "object" && opts.blocking) {
+          if (result && typeof result === "object" && opts.blocking
+              && !AddonManagerPermissions.isHostPermitted(uri.host)
+              && loadInfo && loadInfo.loadingPrincipal && loadInfo.loadingPrincipal.URI
+              && !AddonManagerPermissions.isHostPermitted(loadInfo.loadingPrincipal.URI.host)) {
             handlerResults.push({opts, result});
           }
         } catch (e) {
           Cu.reportError(e);
         }
       }
     } catch (e) {
       Cu.reportError(e);
--- a/toolkit/mozapps/extensions/AddonManagerWebAPI.cpp
+++ b/toolkit/mozapps/extensions/AddonManagerWebAPI.cpp
@@ -13,57 +13,62 @@
 #include "nsGlobalWindow.h"
 
 #include "nsIDocShell.h"
 #include "nsIScriptObjectPrincipal.h"
 
 namespace mozilla {
 using namespace mozilla::dom;
 
+static bool
+IsValidHost(const nsACString& host) {
+  if (host.Equals("addons.mozilla.org") ||
+      host.Equals("discovery.addons.mozilla.org") ||
+      host.Equals("testpilot.firefox.com")) {
+    return true;
+  }
+
+  // When testing allow access to the developer sites.
+  if (Preferences::GetBool("extensions.webapi.testing", false)) {
+    if (host.LowerCaseEqualsLiteral("addons.allizom.org") ||
+        host.LowerCaseEqualsLiteral("discovery.addons.allizom.org") ||
+        host.LowerCaseEqualsLiteral("addons-dev.allizom.org") ||
+        host.LowerCaseEqualsLiteral("discovery.addons-dev.allizom.org") ||
+        host.LowerCaseEqualsLiteral("testpilot.stage.mozaws.net") ||
+        host.LowerCaseEqualsLiteral("testpilot.dev.mozaws.net") ||
+        host.LowerCaseEqualsLiteral("example.com")) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
 // Checks if the given uri is secure and matches one of the hosts allowed to
 // access the API.
 bool
 AddonManagerWebAPI::IsValidSite(nsIURI* uri)
 {
   if (!uri) {
     return false;
   }
 
   bool isSecure;
   nsresult rv = uri->SchemeIs("https", &isSecure);
   if (NS_FAILED(rv) || !isSecure) {
     return false;
   }
 
-  nsCString host;
+  nsAutoCString host;
   rv = uri->GetHost(host);
   if (NS_FAILED(rv)) {
     return false;
   }
 
-  if (host.Equals("addons.mozilla.org") ||
-      host.Equals("discovery.addons.mozilla.org") ||
-      host.Equals("testpilot.firefox.com")) {
-    return true;
-  }
-
-  // When testing allow access to the developer sites.
-  if (Preferences::GetBool("extensions.webapi.testing", false)) {
-    if (host.Equals("addons.allizom.org") ||
-        host.Equals("discovery.addons.allizom.org") ||
-        host.Equals("addons-dev.allizom.org") ||
-        host.Equals("discovery.addons-dev.allizom.org") ||
-        host.Equals("testpilot.stage.mozaws.net") ||
-        host.Equals("testpilot.dev.mozaws.net") ||
-        host.Equals("example.com")) {
-      return true;
-    }
-  }
-
-  return false;
+  return IsValidHost(host);
 }
 
 bool
 AddonManagerWebAPI::IsAPIEnabled(JSContext* cx, JSObject* obj)
 {
   nsGlobalWindow* global = xpc::WindowGlobalOrNull(obj);
   if (!global) {
     return false;
@@ -131,9 +136,20 @@ AddonManagerWebAPI::IsAPIEnabled(JSConte
 
     win = doc->GetInnerWindow();
   }
 
   // Found a document with no inner window, don't grant access to the API.
   return false;
 }
 
+namespace dom {
+
+bool
+AddonManagerPermissions::IsHostPermitted(const GlobalObject& /*unused*/, const nsAString& host)
+{
+  return IsValidHost(NS_ConvertUTF16toUTF8(host));
+}
+
+} // namespace mozilla::dom
+
+
 } // namespace mozilla
--- a/toolkit/mozapps/extensions/AddonManagerWebAPI.h
+++ b/toolkit/mozapps/extensions/AddonManagerWebAPI.h
@@ -14,11 +14,20 @@ namespace mozilla {
 class AddonManagerWebAPI {
 public:
   static bool IsAPIEnabled(JSContext* cx, JSObject* obj);
 
 private:
   static bool IsValidSite(nsIURI* uri);
 };
 
+namespace dom {
+
+class AddonManagerPermissions {
+public:
+  static bool IsHostPermitted(const GlobalObject&, const nsAString& host);
+};
+
+} // namespace mozilla::dom
+
 } // namespace mozilla
 
 #endif // addonmanagerwebapi_h_
--- a/tools/fuzzing/libfuzzer/harness/LibFuzzerRegistry.h
+++ b/tools/fuzzing/libfuzzer/harness/LibFuzzerRegistry.h
@@ -7,16 +7,17 @@
 #define _LibFuzzerRegistry_h__
 
 #include <cstdint>
 #include <map>
 #include <string>
 #include <utility>
 
 #include "mozilla/Attributes.h"
+#include "mozilla/Types.h"
 
 typedef int(*LibFuzzerMain)(int, char**);
 typedef int(*LibFuzzerInitFunc)(int*, char***);
 typedef int(*LibFuzzerTestingFunc)(const uint8_t*, size_t);
 
 namespace mozilla {
 
 typedef std::pair<LibFuzzerInitFunc, LibFuzzerTestingFunc> LibFuzzerFunctions;
--- a/widget/android/EventDispatcher.cpp
+++ b/widget/android/EventDispatcher.cpp
@@ -90,36 +90,37 @@ BoxArrayPrimitive(JSContext* aCx, JS::Ha
         NS_ENSURE_TRUE((element.get().*IsType)(), NS_ERROR_INVALID_ARG);
 
         data[i] = (element.get().*ToType)();
     }
     aOut = (*NewArray)(data.get(), aLength);
     return NS_OK;
 }
 
-template<bool (JS::Value::*IsType)() const,
-         class Type,
-         nsresult (*Box)(JSContext*, JS::HandleValue, jni::Object::LocalRef&)>
+template<class Type,
+         nsresult (*Box)(JSContext*, JS::HandleValue, jni::Object::LocalRef&),
+         typename IsType>
 nsresult
 BoxArrayObject(JSContext* aCx, JS::HandleObject aData,
                jni::Object::LocalRef& aOut, size_t aLength,
-               JS::HandleValue aElement)
+               JS::HandleValue aElement,
+               IsType&& aIsType)
 {
     auto out = jni::ObjectArray::New<Type>(aLength);
     JS::RootedValue element(aCx);
     jni::Object::LocalRef jniElement(aOut.Env());
 
     nsresult rv = (*Box)(aCx, aElement, jniElement);
     NS_ENSURE_SUCCESS(rv, rv);
     out->SetElement(0, jniElement);
 
     for (size_t i = 1; i < aLength; i++) {
         NS_ENSURE_TRUE(CheckJS(aCx, JS_GetElement(aCx, aData, i, &element)),
                        NS_ERROR_FAILURE);
-        NS_ENSURE_TRUE((element.get().*IsType)() || element.isNullOrUndefined(),
+        NS_ENSURE_TRUE(element.isNullOrUndefined() || aIsType(element),
                        NS_ERROR_INVALID_ARG);
 
         rv = (*Box)(aCx, element, jniElement);
         NS_ENSURE_SUCCESS(rv, rv);
         out->SetElement(i, jniElement);
     }
     aOut = out;
     return NS_OK;
@@ -163,29 +164,41 @@ BoxArray(JSContext* aCx, JS::HandleObjec
 
     if (element.isNumber()) {
         return BoxArrayPrimitive<
                 double, &JS::Value::isNumber, &JS::Value::toNumber,
                 jni::DoubleArray, &jni::DoubleArray::New>(aCx, aData, aOut,
                                                           length, element);
     }
 
-    if (element.isString() || element.isNullOrUndefined()) {
-        nsresult rv = BoxArrayObject<&JS::Value::isString,
-                                     jni::String, &BoxString>(
-                aCx, aData, aOut, length, element);
+    if (element.isNullOrUndefined() || element.isString()) {
+        const auto isString = [] (JS::HandleValue val) -> bool {
+            return val.isString();
+        };
+        nsresult rv = BoxArrayObject<jni::String, &BoxString>(
+                aCx, aData, aOut, length, element, isString);
         if (element.isString() || rv != NS_ERROR_INVALID_ARG) {
             return rv;
         }
         // First element was null/undefined, so it may still be an object array.
     }
 
-    if (element.isObject() || element.isNullOrUndefined()) {
-        return BoxArrayObject<&JS::Value::isObject, jni::Object, &BoxObject>(
-                aCx, aData, aOut, length, element);
+    const auto isObject = [aCx] (JS::HandleValue val) -> bool {
+        if (!val.isObject()) {
+            return false;
+        }
+        bool array = false;
+        JS::RootedObject obj(aCx, &val.toObject());
+        // We don't support array of arrays.
+        return CheckJS(aCx, JS_IsArrayObject(aCx, obj, &array)) && !array;
+    };
+
+    if (element.isNullOrUndefined() || isObject(element)) {
+        return BoxArrayObject<java::GeckoBundle, &BoxObject>(
+                aCx, aData, aOut, length, element, isObject);
     }
 
     NS_WARNING("Unknown type");
     return NS_ERROR_INVALID_ARG;
 }
 
 nsresult
 BoxValue(JSContext* aCx, JS::HandleValue aData, jni::Object::LocalRef& aOut);