Merge inbound to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Wed, 15 Mar 2017 14:24:58 -0700
changeset 347833 ff04d410e74b69acfab17ef7e73e7397602d5a68
parent 347770 1c4d97d1bc61468c82e55a09bab660e52bb2e9b5 (current diff)
parent 347832 f44a3f36c9eeea0456dfb80274ed80311a1df249 (diff)
child 347834 dd2f2db1f87aa31e98d204a707dcfd6457a6c696
child 347874 0b51e2a9d7810cc12363afe41aef50a089cbca10
child 347932 dd0376458483b07b48676442a29022591148136b
push id31505
push userkwierso@gmail.com
push dateWed, 15 Mar 2017 21:25:15 +0000
treeherdermozilla-central@ff04d410e74b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone55.0a1
first release with
nightly linux32
ff04d410e74b / 55.0a1 / 20170316110253 / files
nightly linux64
ff04d410e74b / 55.0a1 / 20170316110253 / files
nightly mac
ff04d410e74b / 55.0a1 / 20170316030211 / files
nightly win32
ff04d410e74b / 55.0a1 / 20170316030211 / files
nightly win64
ff04d410e74b / 55.0a1 / 20170316030211 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to central, a=merge MozReview-Commit-ID: 4p4SK3EhvJ2
browser/app/profile/firefox.js
testing/web-platform/meta/presentation-api/receiving-ua/idlharness.html.ini
testing/web-platform/meta/webvtt/webvtt-file-format-parsing/webvtt-cue-text-parsing-rules/tests/entities.html.ini
testing/web-platform/meta/webvtt/webvtt-file-format-parsing/webvtt-cue-text-parsing-rules/tests/tags.html.ini
testing/web-platform/meta/webvtt/webvtt-file-format-parsing/webvtt-cue-text-parsing-rules/tests/timestamps.html.ini
testing/web-platform/meta/webvtt/webvtt-file-format-parsing/webvtt-cue-text-parsing-rules/tests/tree-building.html.ini
testing/web-platform/tests/XMLHttpRequest/open-url-bogus.htm
testing/web-platform/tests/html/browsers/windows/browsing-context-first-created.xhtml
testing/web-platform/tests/html/browsers/windows/support-close.html
testing/web-platform/tests/html/browsers/windows/support-named-null-opener.html
testing/web-platform/tests/html/browsers/windows/support-nested-browsing-contexts.html
testing/web-platform/tests/html/browsers/windows/support-opener-null.html
testing/web-platform/tests/html/browsers/windows/support-post-to-opener.html
testing/web-platform/tests/html/browsers/windows/support-window-name-echo.html
testing/web-platform/tests/html/syntax/parsing-html-fragments/the-input-byte-stream-015.html
testing/web-platform/tests/mediacapture-streams/MediaStreamTrack-end.https.html
testing/web-platform/tests/notifications/shownotification-resolve-manual.html
testing/web-platform/tests/presentation-api/receiving-ua/idlharness.html
testing/web-platform/tests/service-workers/service-worker/fetch-event-within-sw-manual.html
testing/web-platform/tests/service-workers/service-worker/fetch-event-within-sw.html
testing/web-platform/tests/streams/readable-streams/readable-stream-reader.js
testing/web-platform/tests/webvtt/webvtt-api-for-browsers/vttcue-interface/align.html
testing/web-platform/tests/webvtt/webvtt-api-for-browsers/vttcue-interface/getCueAsHTML.html
testing/web-platform/tests/webvtt/webvtt-api-for-browsers/vttcue-interface/line.html
testing/web-platform/tests/webvtt/webvtt-api-for-browsers/vttcue-interface/vertical.html
testing/web-platform/tests/webvtt/webvtt-file-format-parsing/webvtt-cue-text-parsing-rules/buildtests.py
testing/web-platform/tests/webvtt/webvtt-file-format-parsing/webvtt-cue-text-parsing-rules/common.js
testing/web-platform/tests/webvtt/webvtt-file-format-parsing/webvtt-cue-text-parsing-rules/dat/entities.dat
testing/web-platform/tests/webvtt/webvtt-file-format-parsing/webvtt-cue-text-parsing-rules/dat/tags.dat
testing/web-platform/tests/webvtt/webvtt-file-format-parsing/webvtt-cue-text-parsing-rules/dat/timestamps.dat
testing/web-platform/tests/webvtt/webvtt-file-format-parsing/webvtt-cue-text-parsing-rules/dat/tree-building.dat
testing/web-platform/tests/webvtt/webvtt-file-format-parsing/webvtt-cue-text-parsing-rules/tests/entities.html
testing/web-platform/tests/webvtt/webvtt-file-format-parsing/webvtt-cue-text-parsing-rules/tests/tags.html
testing/web-platform/tests/webvtt/webvtt-file-format-parsing/webvtt-cue-text-parsing-rules/tests/timestamps.html
testing/web-platform/tests/webvtt/webvtt-file-format-parsing/webvtt-cue-text-parsing-rules/tests/tree-building.html
testing/web-platform/tests/webvtt/webvtt-file-format-parsing/webvtt-file-parsing/001.html
testing/web-platform/tests/webvtt/webvtt-file-format-parsing/webvtt-file-parsing/support/newlines.vtt
testing/web-platform/tests/webvtt/webvtt-file-format-parsing/webvtt-file-parsing/support/settings-align.vtt
testing/web-platform/tests/webvtt/webvtt-file-format-parsing/webvtt-file-parsing/support/settings-line.vtt
testing/web-platform/tests/webvtt/webvtt-file-format-parsing/webvtt-file-parsing/support/settings-multiple.vtt
testing/web-platform/tests/webvtt/webvtt-file-format-parsing/webvtt-file-parsing/support/settings-position.vtt
testing/web-platform/tests/webvtt/webvtt-file-format-parsing/webvtt-file-parsing/support/signature-bom.vtt
testing/web-platform/tests/webvtt/webvtt-file-format-parsing/webvtt-file-parsing/support/signature-formfeed.vtt
testing/web-platform/tests/webvtt/webvtt-file-format-parsing/webvtt-file-parsing/support/signature-lowercase.vtt
testing/web-platform/tests/webvtt/webvtt-file-format-parsing/webvtt-file-parsing/support/signature-no-newline.vtt
testing/web-platform/tests/webvtt/webvtt-file-format-parsing/webvtt-file-parsing/support/signature-null.vtt
testing/web-platform/tests/webvtt/webvtt-file-format-parsing/webvtt-file-parsing/support/signature-space-no-newline.vtt
testing/web-platform/tests/webvtt/webvtt-file-format-parsing/webvtt-file-parsing/support/signature-space.vtt
testing/web-platform/tests/webvtt/webvtt-file-format-parsing/webvtt-file-parsing/support/signature-tab-no-newline.vtt
testing/web-platform/tests/webvtt/webvtt-file-format-parsing/webvtt-file-parsing/support/signature-tab.vtt
testing/web-platform/tests/webvtt/webvtt-file-format-parsing/webvtt-file-parsing/support/signature-too-short.vtt
testing/web-platform/tests/webvtt/webvtt-file-format-parsing/webvtt-file-parsing/support/signature-two-boms.vtt
testing/web-platform/tests/webvtt/webvtt-file-format-parsing/webvtt-file-parsing/support/signature-websrt.vtt
--- a/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_bad.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_bad.js
@@ -137,16 +137,86 @@ add_task(function* testBadPermissions() 
       await browser.pageAction.show(tab.id);
     },
   });
 
   yield BrowserTestUtils.removeTab(tab2);
   yield BrowserTestUtils.removeTab(tab1);
 });
 
+add_task(function* testMatchDataURI() {
+  const target = ExtensionTestUtils.loadExtension({
+    files: {
+      "page.html": `<!DOCTYPE html>
+        <meta charset="utf-8">
+        <script src="page.js"></script>
+        <iframe id="inherited" src="data:text/html;charset=utf-8,inherited"></iframe>
+      `,
+      "page.js": function() {
+        browser.test.onMessage.addListener((msg, url) => {
+          window.location.href = url;
+        });
+      },
+    },
+    background() {
+      browser.tabs.create({active: true, url: browser.runtime.getURL("page.html")});
+    },
+  });
+
+  const scripts = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["<all_urls>", "webNavigation"],
+    },
+    background() {
+      browser.webNavigation.onCompleted.addListener(({url, frameId}) => {
+        browser.test.log(`Document loading complete: ${url}`);
+        if (frameId === 0) {
+          browser.test.sendMessage("tab-ready", url);
+        }
+      });
+
+      browser.test.onMessage.addListener(async msg => {
+        browser.test.assertRejects(
+          browser.tabs.executeScript({
+            code: "location.href;",
+            allFrames: true,
+          }),
+          /No window matching/,
+          "Should not execute in `data:` frame");
+
+        browser.test.sendMessage("done");
+      });
+    },
+  });
+
+  yield scripts.startup();
+  yield target.startup();
+
+  // Test extension page with a data: iframe.
+  const page = yield scripts.awaitMessage("tab-ready");
+  ok(page.endsWith("page.html"), "Extension page loaded into a tab");
+
+  scripts.sendMessage("execute");
+  yield scripts.awaitMessage("done");
+
+  // Test extension tab navigated to a data: URI.
+  const data = "data:text/html;charset=utf-8,also-inherits";
+  target.sendMessage("navigate", data);
+
+  const url = yield scripts.awaitMessage("tab-ready");
+  is(url, data, "Extension tab navigated to a data: URI");
+
+  scripts.sendMessage("execute");
+  yield scripts.awaitMessage("done");
+
+  yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+  yield scripts.unload();
+  yield target.unload();
+});
+
 add_task(function* testBadURL() {
   async function background() {
     let promises = [
       new Promise(resolve => {
         browser.tabs.executeScript({
           file: "http://example.com/script.js",
         }, result => {
           browser.test.assertEq(undefined, result, "Result value");
--- a/devtools/client/inspector/boxmodel/box-model.js
+++ b/devtools/client/inspector/boxmodel/box-model.js
@@ -293,16 +293,20 @@ BoxModel.prototype = {
         if (property.substring(0, 7) == "border-") {
           let bprop = property.substring(0, property.length - 5) + "style";
           let style = session.getProperty(bprop);
           if (!style || style == "none" || style == "hidden") {
             properties.push({ name: bprop, value: "solid" });
           }
         }
 
+        if (property.substring(0, 9) == "position-") {
+          properties[0].name = property.substring(9);
+        }
+
         session.setProperties(properties).catch(e => console.error(e));
       },
       done: (value, commit) => {
         editor.elt.parentNode.classList.remove("boxmodel-editing");
         if (!commit) {
           session.revert().then(() => {
             session.destroy();
           }, e => console.error(e));
--- a/devtools/client/inspector/boxmodel/components/BoxModelMain.js
+++ b/devtools/client/inspector/boxmodel/components/BoxModelMain.js
@@ -78,108 +78,179 @@ module.exports = createClass({
       value = "auto";
     } else if (layout[property]) {
       value = parseFloat(layout[property]);
     }
 
     return value;
   },
 
+  getPositionValue(property) {
+    let { layout } = this.props.boxModel;
+
+    if (layout.position === "static") {
+      return "-";
+    }
+    return layout[property] ? parseFloat(layout[property]) : "-";
+  },
+
   onHighlightMouseOver(event) {
     let region = event.target.getAttribute("data-box");
     if (!region) {
       this.props.onHideBoxModelHighlighter();
     }
 
     this.props.onShowBoxModelHighlighter({
       region,
       showOnly: region,
       onlyRegionArea: true,
     });
   },
 
   render() {
     let { boxModel, onShowBoxModelEditor } = this.props;
     let { layout } = boxModel;
-    let { height, width } = layout;
+    let { height, width, position } = layout;
 
     let borderTop = this.getBorderOrPaddingValue("border-top-width");
     let borderRight = this.getBorderOrPaddingValue("border-right-width");
     let borderBottom = this.getBorderOrPaddingValue("border-bottom-width");
     let borderLeft = this.getBorderOrPaddingValue("border-left-width");
 
     let paddingTop = this.getBorderOrPaddingValue("padding-top");
     let paddingRight = this.getBorderOrPaddingValue("padding-right");
     let paddingBottom = this.getBorderOrPaddingValue("padding-bottom");
     let paddingLeft = this.getBorderOrPaddingValue("padding-left");
 
+    let displayPosition = layout.position && layout.position != "static";
+    let positionTop = this.getPositionValue("top");
+    let positionRight = this.getPositionValue("right");
+    let positionBottom = this.getPositionValue("bottom");
+    let positionLeft = this.getPositionValue("left");
+
     let marginTop = this.getMarginValue("margin-top", "top");
     let marginRight = this.getMarginValue("margin-right", "right");
     let marginBottom = this.getMarginValue("margin-bottom", "bottom");
     let marginLeft = this.getMarginValue("margin-left", "left");
 
     height = this.getHeightValue(height);
     width = this.getWidthValue(width);
 
     return dom.div(
       {
         className: "boxmodel-main",
         onMouseOver: this.onHighlightMouseOver,
         onMouseOut: this.props.onHideBoxModelHighlighter,
       },
-      dom.span(
-        {
-          className: "boxmodel-legend",
-          "data-box": "margin",
-          title: BOXMODEL_L10N.getStr("boxmodel.margin"),
-        },
-        BOXMODEL_L10N.getStr("boxmodel.margin")
-      ),
+      displayPosition ?
+        dom.span(
+          {
+            className: "boxmodel-legend",
+            "data-box": "position",
+            title: BOXMODEL_L10N.getFormatStr("boxmodel.position", position),
+          },
+          BOXMODEL_L10N.getFormatStr("boxmodel.position", position)
+        )
+        :
+        null,
       dom.div(
         {
-          className: "boxmodel-margins",
-          "data-box": "margin",
-          title: BOXMODEL_L10N.getStr("boxmodel.margin"),
+          className: "boxmodel-box"
         },
         dom.span(
           {
             className: "boxmodel-legend",
-            "data-box": "border",
-            title: BOXMODEL_L10N.getStr("boxmodel.border"),
+            "data-box": "margin",
+            title: BOXMODEL_L10N.getStr("boxmodel.margin"),
           },
-          BOXMODEL_L10N.getStr("boxmodel.border")
+          BOXMODEL_L10N.getStr("boxmodel.margin")
         ),
         dom.div(
           {
-            className: "boxmodel-borders",
-            "data-box": "border",
-            title: BOXMODEL_L10N.getStr("boxmodel.border"),
+            className: "boxmodel-margins",
+            "data-box": "margin",
+            title: BOXMODEL_L10N.getStr("boxmodel.margin"),
           },
           dom.span(
             {
               className: "boxmodel-legend",
-              "data-box": "padding",
-              title: BOXMODEL_L10N.getStr("boxmodel.padding"),
+              "data-box": "border",
+              title: BOXMODEL_L10N.getStr("boxmodel.border"),
             },
-            BOXMODEL_L10N.getStr("boxmodel.padding")
+            BOXMODEL_L10N.getStr("boxmodel.border")
           ),
           dom.div(
             {
-              className: "boxmodel-paddings",
-              "data-box": "padding",
-              title: BOXMODEL_L10N.getStr("boxmodel.padding"),
+              className: "boxmodel-borders",
+              "data-box": "border",
+              title: BOXMODEL_L10N.getStr("boxmodel.border"),
             },
-            dom.div({
-              className: "boxmodel-content",
-              "data-box": "content",
-              title: BOXMODEL_L10N.getStr("boxmodel.content"),
-            })
+            dom.span(
+              {
+                className: "boxmodel-legend",
+                "data-box": "padding",
+                title: BOXMODEL_L10N.getStr("boxmodel.padding"),
+              },
+              BOXMODEL_L10N.getStr("boxmodel.padding")
+            ),
+            dom.div(
+              {
+                className: "boxmodel-paddings",
+                "data-box": "padding",
+                title: BOXMODEL_L10N.getStr("boxmodel.padding"),
+              },
+              dom.div({
+                className: "boxmodel-content",
+                "data-box": "content",
+                title: BOXMODEL_L10N.getStr("boxmodel.content"),
+              })
+            )
           )
         )
       ),
+      displayPosition ?
+        BoxModelEditable({
+          box: "position",
+          direction: "top",
+          property: "position-top",
+          textContent: positionTop,
+          onShowBoxModelEditor,
+        })
+        :
+        null,
+      displayPosition ?
+        BoxModelEditable({
+          box: "position",
+          direction: "right",
+          property: "position-right",
+          textContent: positionRight,
+          onShowBoxModelEditor,
+        })
+        :
+        null,
+      displayPosition ?
+        BoxModelEditable({
+          box: "position",
+          direction: "bottom",
+          property: "position-bottom",
+          textContent: positionBottom,
+          onShowBoxModelEditor,
+        })
+        :
+        null,
+      displayPosition ?
+        BoxModelEditable({
+          box: "position",
+          direction: "left",
+          property: "position-left",
+          textContent: positionLeft,
+          onShowBoxModelEditor,
+        })
+        :
+        null,
       BoxModelEditable({
         box: "margin",
         direction: "top",
         property: "margin-top",
         textContent: marginTop,
         onShowBoxModelEditor,
       }),
       BoxModelEditable({
--- a/devtools/client/locales/en-US/boxmodel.properties
+++ b/devtools/client/locales/en-US/boxmodel.properties
@@ -10,16 +10,20 @@
 # You want to make that choice consistent across the developer tools.
 # A good criteria is the language in which you'd find the best
 # documentation on web development on the web.
 
 # LOCALIZATION NOTE (boxmodel.title) This is the title of the box model panel and is
 # displayed as a label.
 boxmodel.title=Box Model
 
+# LOCALIZATION NOTE (boxmodel.position) This refers to the position in the box model and
+# might be displayed as a label or as a tooltip.
+boxmodel.position=position: %S
+
 # LOCALIZATION NOTE (boxmodel.margin) This refers to the margin in the box model and
 # might be displayed as a label or as a tooltip.
 boxmodel.margin=margin
 
 # LOCALIZATION NOTE (boxmodel.border) This refers to the border in the box model and
 # might be displayed as a label or as a tooltip.
 boxmodel.border=border
 
--- a/devtools/client/themes/boxmodel.css
+++ b/devtools/client/themes/boxmodel.css
@@ -6,16 +6,17 @@
  * This is the stylesheet of the Box Model view implemented in the layout panel.
  */
 
 .boxmodel-container {
   /* The view will grow bigger as the window gets resized, until 400px */
   max-width: 400px;
   margin: 0px auto;
   padding: 0;
+  overflow: auto;
 }
 
 /* Header */
 
 .boxmodel-header,
 .boxmodel-info {
   display: flex;
   align-items: center;
@@ -25,24 +26,28 @@
 .layout-geometry-editor::before {
   background: url(images/geometry-editor.svg) no-repeat center center / 16px 16px;
 }
 
 /* Main: contains the box-model regions */
 
 .boxmodel-main {
   position: relative;
-  box-sizing: border-box;
-  /* The regions are semi-transparent, so the white background is partly
-     visible */
-  background-color: white;
   color: var(--theme-selection-color);
   /* Make sure there is some space between the window's edges and the regions */
   margin: 14px 14px 4px 14px;
   width: calc(100% - 2 * 14px);
+  min-width: 240px;
+}
+
+.boxmodel-box {
+  margin: 25px;
+  /* The regions are semi-transparent, so the white background is partly
+     visible */
+  background-color: white;
 }
 
 .boxmodel-margin,
 .boxmodel-size {
   color: var(--theme-highlight-blue);
 }
 
 /* Regions are 3 nested elements with wide borders and outlines */
@@ -79,22 +84,16 @@
   border-color: #6a5acd;
 }
 
 .boxmodel-content {
   background-color: #87ceeb;
 }
 
 .theme-firebug .boxmodel-main,
-.theme-firebug .boxmodel-borders,
-.theme-firebug .boxmodel-content {
-  border-style: solid;
-}
-
-.theme-firebug .boxmodel-main,
 .theme-firebug .boxmodel-header {
   font-family: var(--proportional-font-family);
 }
 
 .theme-firebug .boxmodel-main {
   color: var(--theme-body-color);
   font-size: var(--theme-toolbar-font-size);
 }
@@ -146,83 +145,145 @@
   top: 1px;
 }
 
 .boxmodel-margin.boxmodel-bottom {
   bottom: 2px;
 }
 
 .boxmodel-size,
+.boxmodel-position.boxmodel-left,
+.boxmodel-position.boxmodel-right,
 .boxmodel-margin.boxmodel-left,
 .boxmodel-margin.boxmodel-right,
 .boxmodel-border.boxmodel-left,
 .boxmodel-border.boxmodel-right,
 .boxmodel-padding.boxmodel-left,
 .boxmodel-padding.boxmodel-right {
   top: 22px;
   line-height: 80px;
 }
 
 .boxmodel-size {
   width: calc(100% - 2px);
 }
 
+.boxmodel-position.boxmodel-top,
+.boxmodel-position.boxmodel-bottom,
+.boxmodel-position.boxmodel-left,
+.boxmodel-position.boxmodel-right,
 .boxmodel-margin.boxmodel-right,
 .boxmodel-margin.boxmodel-left,
 .boxmodel-border.boxmodel-left,
 .boxmodel-border.boxmodel-right,
 .boxmodel-padding.boxmodel-right,
 .boxmodel-padding.boxmodel-left {
   width: 21px;
 }
 
 .boxmodel-padding.boxmodel-left {
-  left: 35px;
+  left: 60px;
 }
 
 .boxmodel-padding.boxmodel-right {
-  right: 35px;
+  right: 60px;
 }
 
 .boxmodel-border.boxmodel-left {
-  left: 16px;
+  left: 41px;
 }
 
 .boxmodel-border.boxmodel-right {
-  right: 17px;
+  right: 42px;
 }
 
 .boxmodel-margin.boxmodel-right {
-  right: 0;
+  right: 25px;
 }
 
 .boxmodel-margin.boxmodel-left {
-  left: 0;
+  left: 25px;
 }
 
 .boxmodel-rotate.boxmodel-left:not(.boxmodel-editing) {
   transform: rotate(-90deg);
 }
 
 .boxmodel-rotate.boxmodel-right:not(.boxmodel-editing) {
   transform: rotate(90deg);
 }
 
+.boxmodel-rotate.boxmodel-left.boxmodel-position:not(.boxmodel-editing) {
+  border-top: none;
+  border-right: 1px solid var(--theme-highlight-purple);
+  width: auto;
+  height: 30px;
+}
+
+.boxmodel-rotate.boxmodel-right.boxmodel-position:not(.boxmodel-editing) {
+  border-top: none;
+  border-left: 1px solid var(--theme-highlight-purple);
+  width: auto;
+  height: 30px;
+}
+
+/* Box Model Positioning: contains top, right, bottom, left */
+
+.boxmodel-position {
+  color: var(--theme-highlight-purple);
+}
+
+.boxmodel-position.boxmodel-top,
+.boxmodel-position.boxmodel-bottom {
+  border-left: 1px solid var(--theme-highlight-purple);
+  left: 49.5%;
+  padding-left: 1px;
+}
+
+.boxmodel-position.boxmodel-right,
+.boxmodel-position.boxmodel-left {
+  border-top: 1px solid var(--theme-highlight-purple);
+  line-height: 15px;
+  top: 49.5%;
+  width: 30px;
+}
+
+.boxmodel-position.boxmodel-top {
+  top: -18px;
+}
+
+.boxmodel-position.boxmodel-right {
+  right: -9px;
+}
+
+.boxmodel-position.boxmodel-bottom {
+  bottom: -18px;
+}
+
+.boxmodel-position.boxmodel-left {
+  left: -9px;
+}
+
 /* Legend: displayed inside regions */
 
 .boxmodel-legend {
   position: absolute;
   margin: 2px 6px;
   z-index: 1;
 }
 
 .boxmodel-legend[data-box="margin"] {
   color: var(--theme-highlight-blue);
 }
 
+.boxmodel-legend[data-box="position"] {
+  color: var(--theme-highlight-purple);
+  margin: -18px -9px;
+}
+
 /* Editable fields */
 
 .boxmodel-editable {
   border: 1px dashed transparent;
   -moz-user-select: none;
 }
 
 .boxmodel-editable:hover {
--- a/devtools/client/themes/layout.css
+++ b/devtools/client/themes/layout.css
@@ -1,16 +1,17 @@
 /* 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;
+  min-width: 200px;
 }
 
 /**
  * Common styles for shared components
  */
 
 .grid-container {
   display: flex;
--- a/devtools/server/actors/styles.js
+++ b/devtools/server/actors/styles.js
@@ -766,16 +766,20 @@ var PageStyleActor = protocol.ActorClass
     let clientRect = node.rawNode.getBoundingClientRect();
     layout.width = parseFloat(clientRect.width.toPrecision(6));
     layout.height = parseFloat(clientRect.height.toPrecision(6));
 
     // We compute and update the values of margins & co.
     let style = CssLogic.getComputedStyle(node.rawNode);
     for (let prop of [
       "position",
+      "top",
+      "right",
+      "bottom",
+      "left",
       "margin-top",
       "margin-right",
       "margin-bottom",
       "margin-left",
       "padding-top",
       "padding-right",
       "padding-bottom",
       "padding-left",
--- a/dom/base/IntlUtils.cpp
+++ b/dom/base/IntlUtils.cpp
@@ -81,10 +81,57 @@ IntlUtils::GetDisplayNames(const Sequenc
 
   // Return the result as DisplayNameResult.
   JSAutoCompartment ac(cx, &retVal.toObject());
   if (!aResult.Init(cx, retVal)) {
     aError.Throw(NS_ERROR_FAILURE);
   }
 }
 
+void
+IntlUtils::GetLocaleInfo(const Sequence<nsString>& aLocales,
+                         LocaleInfo& aResult, ErrorResult& aError)
+{
+  MOZ_ASSERT(nsContentUtils::IsCallerChrome() ||
+             nsContentUtils::IsCallerContentXBL());
+
+  nsCOMPtr<mozIMozIntl> mozIntl = do_GetService("@mozilla.org/mozintl;1");
+  if (!mozIntl) {
+    aError.Throw(NS_ERROR_UNEXPECTED);
+    return;
+  }
+
+  AutoJSAPI jsapi;
+  if (!jsapi.Init(xpc::PrivilegedJunkScope())) {
+    aError.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+  JSContext* cx = jsapi.cx();
+
+  // Prepare parameter for getLocaleInfo().
+  JS::Rooted<JS::Value> locales(cx);
+  if (!ToJSValue(cx, aLocales, &locales)) {
+    aError.StealExceptionFromJSContext(cx);
+    return;
+  }
+
+  // Now call the method.
+  JS::Rooted<JS::Value> retVal(cx);
+  nsresult rv = mozIntl->GetLocaleInfo(locales, &retVal);
+  if (NS_FAILED(rv)) {
+    aError.Throw(rv);
+    return;
+  }
+
+  if (!retVal.isObject()) {
+    aError.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
+  // Return the result as LocaleInfo.
+  JSAutoCompartment ac(cx, &retVal.toObject());
+  if (!aResult.Init(cx, retVal)) {
+    aError.Throw(NS_ERROR_FAILURE);
+  }
+}
+
 } // dom namespace
 } // mozilla namespace
--- a/dom/base/IntlUtils.h
+++ b/dom/base/IntlUtils.h
@@ -31,16 +31,21 @@ public:
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   void
   GetDisplayNames(const Sequence<nsString>& aLocales,
                   const mozilla::dom::DisplayNameOptions& aOptions,
                   mozilla::dom::DisplayNameResult& aResult,
                   mozilla::ErrorResult& aError);
 
+  void
+  GetLocaleInfo(const Sequence<nsString>& aLocales,
+                mozilla::dom::LocaleInfo& aResult,
+                mozilla::ErrorResult& aError);
+
 private:
   ~IntlUtils();
 
   nsCOMPtr<nsPIDOMWindowInner> mWindow;
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -1139,17 +1139,17 @@ nsFocusManager::EnsureCurrentWidgetFocus
     }
   }
 }
 
 bool
 ActivateOrDeactivateChild(TabParent* aParent, void* aArg)
 {
   bool active = static_cast<bool>(aArg);
-  Unused << aParent->Manager()->AsContentParent()->SendParentActivated(aParent, active);
+  Unused << aParent->Manager()->SendParentActivated(aParent, active);
   return false;
 }
 
 void
 nsFocusManager::ActivateOrDeactivate(nsPIDOMWindowOuter* aWindow, bool aActive)
 {
   if (!aWindow) {
     return;
--- a/dom/ipc/ContentBridgeChild.cpp
+++ b/dom/ipc/ContentBridgeChild.cpp
@@ -199,10 +199,31 @@ ContentBridgeChild::AllocPFileDescriptor
 }
 
 bool
 ContentBridgeChild::DeallocPFileDescriptorSetChild(PFileDescriptorSetChild* aActor)
 {
   return nsIContentChild::DeallocPFileDescriptorSetChild(aActor);
 }
 
+mozilla::ipc::IPCResult
+ContentBridgeChild::RecvActivate(PBrowserChild* aTab)
+{
+  TabChild* tab = static_cast<TabChild*>(aTab);
+  return tab->RecvActivate();
+}
+
+mozilla::ipc::IPCResult
+ContentBridgeChild::RecvDeactivate(PBrowserChild* aTab)
+{
+  TabChild* tab = static_cast<TabChild*>(aTab);
+  return tab->RecvDeactivate();
+}
+
+mozilla::ipc::IPCResult
+ContentBridgeChild::RecvParentActivated(PBrowserChild* aTab, const bool& aActivated)
+{
+  TabChild* tab = static_cast<TabChild*>(aTab);
+  return tab->RecvParentActivated(aActivated);
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/ipc/ContentBridgeChild.h
+++ b/dom/ipc/ContentBridgeChild.h
@@ -46,16 +46,23 @@ public:
                                        const bool& aIsForBrowser) override;
 
   virtual mozilla::ipc::PFileDescriptorSetChild*
   SendPFileDescriptorSetConstructor(const mozilla::ipc::FileDescriptor&) override;
 
   virtual mozilla::ipc::PChildToParentStreamChild*
   SendPChildToParentStreamConstructor(mozilla::ipc::PChildToParentStreamChild*) override;
 
+  virtual mozilla::ipc::IPCResult RecvActivate(PBrowserChild* aTab) override;
+
+  virtual mozilla::ipc::IPCResult RecvDeactivate(PBrowserChild* aTab) override;
+
+  virtual mozilla::ipc::IPCResult RecvParentActivated(PBrowserChild* aTab,
+                                                      const bool& aActivated) override;
+
   FORWARD_SHMEM_ALLOCATOR_TO(PContentBridgeChild)
 
 protected:
   virtual ~ContentBridgeChild();
 
   virtual PBrowserChild* AllocPBrowserChild(const TabId& aTabId,
                                             const IPCTabContext& aContext,
                                             const uint32_t& aChromeFlags,
--- a/dom/ipc/ContentBridgeParent.h
+++ b/dom/ipc/ContentBridgeParent.h
@@ -64,16 +64,32 @@ public:
   {
     // XXX: do we need this for ContentBridgeParent?
     return -1;
   }
 
   virtual mozilla::ipc::PParentToChildStreamParent*
   SendPParentToChildStreamConstructor(mozilla::ipc::PParentToChildStreamParent*) override;
 
+  virtual bool SendActivate(PBrowserParent* aTab) override
+  {
+    return PContentBridgeParent::SendActivate(aTab);
+  }
+
+  virtual bool SendDeactivate(PBrowserParent* aTab) override
+  {
+    return PContentBridgeParent::SendDeactivate(aTab);
+  }
+
+  virtual bool SendParentActivated(PBrowserParent* aTab,
+                                   const bool& aActivated) override
+  {
+    return PContentBridgeParent::SendParentActivated(aTab, aActivated);
+  }
+
 protected:
   virtual ~ContentBridgeParent();
 
   void SetChildID(ContentParentId aId)
   {
     mChildID = aId;
   }
 
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -1961,51 +1961,55 @@ ContentParent::LaunchSubprocess(ProcessP
 
   std::vector<std::string> extraArgs;
   extraArgs.push_back("-childID");
   char idStr[21];
   SprintfLiteral(idStr, "%" PRId64, static_cast<uint64_t>(mChildID));
   extraArgs.push_back(idStr);
   extraArgs.push_back(IsForBrowser() ? "-isForBrowser" : "-notForBrowser");
 
-  std::stringstream boolPrefs;
-  std::stringstream intPrefs;
-  std::stringstream stringPrefs;
+  char boolBuf[1024];
+  char intBuf[1024];
+  char strBuf[1024];
+  nsFixedCString boolPrefs(boolBuf, 1024, 0);
+  nsFixedCString intPrefs(intBuf, 1024, 0);
+  nsFixedCString stringPrefs(strBuf, 1024, 0);
 
   size_t prefsLen;
   ContentPrefs::GetContentPrefs(&prefsLen);
 
   for (unsigned int i = 0; i < prefsLen; i++) {
     MOZ_ASSERT(i == 0 || strcmp(ContentPrefs::GetContentPref(i), ContentPrefs::GetContentPref(i - 1)) > 0);
     switch (Preferences::GetType(ContentPrefs::GetContentPref(i))) {
     case nsIPrefBranch::PREF_INT:
-      intPrefs << i << ':' << Preferences::GetInt(ContentPrefs::GetContentPref(i)) << '|';
+      intPrefs.Append(nsPrintfCString("%u:%d|", i, Preferences::GetInt(ContentPrefs::GetContentPref(i))));
       break;
     case nsIPrefBranch::PREF_BOOL:
-      boolPrefs << i << ':' << Preferences::GetBool(ContentPrefs::GetContentPref(i)) << '|';
+      boolPrefs.Append(nsPrintfCString("%u:%d|", i, Preferences::GetBool(ContentPrefs::GetContentPref(i))));
       break;
     case nsIPrefBranch::PREF_STRING: {
-      std::string value(Preferences::GetCString(ContentPrefs::GetContentPref(i)).get());
-      stringPrefs << i << ':' << value.length() << ':' << value << '|';
+      nsAdoptingCString value(Preferences::GetCString(ContentPrefs::GetContentPref(i)));
+      stringPrefs.Append(nsPrintfCString("%u:%d;%s|", i, value.Length(), value.get()));
+
     }
       break;
     case nsIPrefBranch::PREF_INVALID:
       break;
     default:
       printf("preference type: %x\n", Preferences::GetType(ContentPrefs::GetContentPref(i)));
       MOZ_CRASH();
     }
   }
 
   extraArgs.push_back("-intPrefs");
-  extraArgs.push_back(intPrefs.str());
+  extraArgs.push_back(intPrefs.get());
   extraArgs.push_back("-boolPrefs");
-  extraArgs.push_back(boolPrefs.str());
+  extraArgs.push_back(boolPrefs.get());
   extraArgs.push_back("-stringPrefs");
-  extraArgs.push_back(stringPrefs.str());
+  extraArgs.push_back(stringPrefs.get());
 
   if (gSafeMode) {
     extraArgs.push_back("-safeMode");
   }
 
   if (!mSubprocess->LaunchAndWaitForProcessHandle(extraArgs)) {
     MarkAsDead();
     return false;
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -606,16 +606,33 @@ public:
   AllocPURLClassifierParent(const Principal& aPrincipal,
                             const bool& aUseTrackingProtection,
                             bool* aSuccess) override;
   virtual mozilla::ipc::IPCResult
   RecvPURLClassifierConstructor(PURLClassifierParent* aActor,
                                 const Principal& aPrincipal,
                                 const bool& aUseTrackingProtection,
                                 bool* aSuccess) override;
+
+  virtual bool SendActivate(PBrowserParent* aTab) override
+  {
+    return PContentParent::SendActivate(aTab);
+  }
+
+  virtual bool SendDeactivate(PBrowserParent* aTab) override
+  {
+    return PContentParent::SendDeactivate(aTab);
+  }
+
+  virtual bool SendParentActivated(PBrowserParent* aTab,
+                                   const bool& aActivated) override
+  {
+    return PContentParent::SendParentActivated(aTab, aActivated);
+  }
+
   virtual bool
   DeallocPURLClassifierParent(PURLClassifierParent* aActor) override;
 
   virtual mozilla::ipc::IPCResult
   RecvClassifyLocal(const URIParams& aURI,
                     const nsCString& aTables,
                     nsresult* aRv,
                     nsTArray<nsCString>* aResults) override;
--- a/dom/ipc/ContentProcess.cpp
+++ b/dom/ipc/ContentProcess.cpp
@@ -155,49 +155,56 @@ ContentProcess::Init(int aArgc, char* aA
       }
       isForBrowser = strcmp(aArgv[idx], "-notForBrowser");
       foundIsForBrowser = true;
     } else if (!strcmp(aArgv[idx], "-intPrefs")) {
       SET_PREF_PHASE(BEGIN_INIT_PREFS);
       char* str = aArgv[idx + 1];
       while (*str) {
         int32_t index = strtol(str, &str, 10);
+        MOZ_ASSERT(str[0] == ':');
         str++;
         MaybePrefValue value(PrefValue(static_cast<int32_t>(strtol(str, &str, 10))));
+        MOZ_ASSERT(str[0] == '|');
         str++;
         PrefSetting pref(nsCString(ContentPrefs::GetContentPref(index)), value, MaybePrefValue());
         prefsArray.AppendElement(pref);
       }
       SET_PREF_PHASE(END_INIT_PREFS);
       foundIntPrefs = true;
     } else if (!strcmp(aArgv[idx], "-boolPrefs")) {
       SET_PREF_PHASE(BEGIN_INIT_PREFS);
       char* str = aArgv[idx + 1];
       while (*str) {
         int32_t index = strtol(str, &str, 10);
+        MOZ_ASSERT(str[0] == ':');
         str++;
         MaybePrefValue value(PrefValue(!!strtol(str, &str, 10)));
+        MOZ_ASSERT(str[0] == '|');
         str++;
         PrefSetting pref(nsCString(ContentPrefs::GetContentPref(index)), value, MaybePrefValue());
         prefsArray.AppendElement(pref);
       }
       SET_PREF_PHASE(END_INIT_PREFS);
       foundBoolPrefs = true;
     } else if (!strcmp(aArgv[idx], "-stringPrefs")) {
       SET_PREF_PHASE(BEGIN_INIT_PREFS);
       char* str = aArgv[idx + 1];
       while (*str) {
         int32_t index = strtol(str, &str, 10);
+        MOZ_ASSERT(str[0] == ':');
         str++;
         int32_t length = strtol(str, &str, 10);
+        MOZ_ASSERT(str[0] == ';');
         str++;
         MaybePrefValue value(PrefValue(nsCString(str, length)));
         PrefSetting pref(nsCString(ContentPrefs::GetContentPref(index)), value, MaybePrefValue());
         prefsArray.AppendElement(pref);
         str += length + 1;
+        MOZ_ASSERT(*(str - 1) == '|');
       }
       SET_PREF_PHASE(END_INIT_PREFS);
       foundStringPrefs = true;
     }
     else if (!strcmp(aArgv[idx], "-safeMode")) {
       gSafeMode = true;
     }
 
--- a/dom/ipc/PContentBridge.ipdl
+++ b/dom/ipc/PContentBridge.ipdl
@@ -40,16 +40,26 @@ nested(upto inside_cpow) sync protocol P
     manages PFileDescriptorSet;
     manages PJavaScript;
     manages PChildToParentStream;
     manages PParentToChildStream;
 
 child:
     async PParentToChildStream();
 
+child:
+   /**
+     * Sending an activate message moves focus to the child.
+     */
+    async Activate(PBrowser aTab);
+
+    async Deactivate(PBrowser aTab);
+
+    async ParentActivated(PBrowser aTab, bool aActivated);
+
 parent:
     sync SyncMessage(nsString aMessage, ClonedMessageData aData,
                      CpowEntry[] aCpows, Principal aPrincipal)
       returns (StructuredCloneData[] retval);
 
     async PJavaScript();
 
     async PChildToParentStream();
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -353,16 +353,18 @@ public:
                        const LayoutDeviceIntPoint& aChromeDisp) override;
   virtual mozilla::ipc::IPCResult
   RecvSizeModeChanged(const nsSizeMode& aSizeMode) override;
 
   mozilla::ipc::IPCResult RecvActivate();
 
   mozilla::ipc::IPCResult RecvDeactivate();
 
+  mozilla::ipc::IPCResult RecvParentActivated(const bool& aActivated);
+
   virtual mozilla::ipc::IPCResult RecvMouseEvent(const nsString& aType,
                                                  const float& aX,
                                                  const float& aY,
                                                  const int32_t& aButton,
                                                  const int32_t& aClickCount,
                                                  const int32_t& aModifiers,
                                                  const bool& aIgnoreRootScrollFrame) override;
 
@@ -687,18 +689,16 @@ protected:
 
   virtual mozilla::ipc::IPCResult RecvNavigateByKey(const bool& aForward,
                                                     const bool& aForDocumentNavigation) override;
 
   virtual mozilla::ipc::IPCResult RecvRequestNotifyAfterRemotePaint() override;
 
   virtual mozilla::ipc::IPCResult RecvSuppressDisplayport(const bool& aEnabled) override;
 
-  mozilla::ipc::IPCResult RecvParentActivated(const bool& aActivated);
-
   virtual mozilla::ipc::IPCResult RecvSetKeyboardIndicators(const UIStateChangeType& aShowAccelerators,
                                                             const UIStateChangeType& aShowFocusRings) override;
 
   virtual mozilla::ipc::IPCResult RecvStopIMEStateManagement() override;
 
   virtual mozilla::ipc::IPCResult RecvMenuKeyboardListenerInstalled(
     const bool& aInstalled) override;
 
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -854,25 +854,25 @@ TabParent::HandleAccessKey(const WidgetK
     Unused << SendHandleAccessKey(aEvent, aCharCodes, aModifierMask);
   }
 }
 
 void
 TabParent::Activate()
 {
   if (!mIsDestroyed) {
-    Unused << Manager()->AsContentParent()->SendActivate(this);
+    Unused << Manager()->SendActivate(this);
   }
 }
 
 void
 TabParent::Deactivate()
 {
   if (!mIsDestroyed) {
-    Unused << Manager()->AsContentParent()->SendDeactivate(this);
+    Unused << Manager()->SendDeactivate(this);
   }
 }
 
 NS_IMETHODIMP
 TabParent::Init(mozIDOMWindowProxy *window)
 {
   return NS_OK;
 }
--- a/dom/ipc/nsIContentParent.h
+++ b/dom/ipc/nsIContentParent.h
@@ -86,16 +86,23 @@ public:
   ContentParent* AsContentParent();
 
   virtual bool IsContentBridgeParent() const { return false; }
 
   ContentBridgeParent* AsContentBridgeParent();
 
   nsFrameMessageManager* GetMessageManager() const { return mMessageManager; }
 
+  virtual bool SendActivate(PBrowserParent* aTab) = 0;
+
+  virtual bool SendDeactivate(PBrowserParent* aTab) = 0;
+
+  virtual bool SendParentActivated(PBrowserParent* aTab,
+                                   const bool& aActivated) = 0;
+
   virtual int32_t Pid() const = 0;
 
   virtual mozilla::ipc::PParentToChildStreamParent*
   SendPParentToChildStreamConstructor(mozilla::ipc::PParentToChildStreamParent*) = 0;
 
 protected: // methods
   bool CanOpenBrowser(const IPCTabContext& aContext);
 
--- a/dom/manifest/Manifest.jsm
+++ b/dom/manifest/Manifest.jsm
@@ -85,18 +85,25 @@ class Manifest {
       installed: true,
       manifest: manifestData
     };
     Manifests.manifestInstalled(this);
     this._store.saveSoon();
   }
 
   async icon(expectedSize) {
-    return await ManifestIcons
+    if ('cached_icon' in this._store.data) {
+      return this._store.data.cached_icon;
+    }
+    const icon = await ManifestIcons
       .browserFetchIcon(this._browser, this._store.data.manifest, expectedSize);
+    // Cache the icon so future requests do not go over the network
+    this._store.data.cached_icon = icon;
+    this._store.saveSoon();
+    return icon;
   }
 
   get scope() {
     const scope = this._store.data.manifest.scope ||
       this._store.data.manifest.start_url;
     return stripQuery(scope);
   }
 
@@ -111,16 +118,20 @@ class Manifest {
 
   get installed() {
     return this._store.data && this._store.data.installed || false;
   }
 
   get start_url() {
     return this._store.data.manifest.start_url;
   }
+
+  get path() {
+    return this._path;
+  }
 }
 
 /*
  * Manifests maintains the list of installed manifests
  */
 var Manifests = {
 
   async initialise () {
--- a/dom/manifest/ManifestIcons.jsm
+++ b/dom/manifest/ManifestIcons.jsm
@@ -58,17 +58,17 @@ async function getIcon(aWindow, icons, e
   let index = icons.findIndex(icon => icon.size >= expectedSize);
   if (index === -1) {
     index = icons.length - 1;
   }
 
   return fetchIcon(aWindow, icons[index].src).catch(err => {
     // Remove all icons with the failed source, the same source
     // may have been used for multiple sizes
-    icons = icons.filter(x => x.src === icons[index].src);
+    icons = icons.filter(x => x.src !== icons[index].src);
     return getIcon(aWindow, icons, expectedSize);
   });
 }
 
 function fetchIcon(aWindow, src) {
   const manifestURL = new aWindow.URL(src);
   const request = new aWindow.Request(manifestURL, {mode: "cors"});
   request.overrideContentPolicyType(Ci.nsIContentPolicy.TYPE_WEB_MANIFEST);
--- a/dom/tests/mochitest/chrome/chrome.ini
+++ b/dom/tests/mochitest/chrome/chrome.ini
@@ -60,16 +60,17 @@ skip-if = os == 'linux' && !debug # bug 
 [test_focused_link_scroll.xul]
 [test_fullscreen.xul]
 tags = fullscreen
 # disabled on linux for timeouts--bug-867745
 skip-if = os == 'linux'
 [test_geolocation.xul]
 [test_indexedSetter.html]
 [test_intlUtils_getDisplayNames.html]
+[test_intlUtils_getLocaleInfo.html]
 [test_moving_nodeList.xul]
 [test_moving_xhr.xul]
 [test_MozDomFullscreen_event.xul]
 tags = fullscreen
 # disabled on OS X for intermittent failures--bug-798848
 skip-if = toolkit == 'cocoa'
 [test_nodesFromRect.html]
 [test_parsingMode.html]
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/chrome/test_intlUtils_getLocaleInfo.html
@@ -0,0 +1,69 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1346084
+-->
+<head>
+  <title>Test for Bug 1346084 </title>
+  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1346084 ">Mozilla Bug 1346084</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<script>
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const localeService =
+  Cc["@mozilla.org/intl/localeservice;1"].getService(Ci.mozILocaleService);
+const mozIntl = Cc["@mozilla.org/mozintl;1"].getService(Ci.mozIMozIntl);
+
+let appLocale = localeService.getAppLocalesAsBCP47()[0];
+
+let testData = [
+  {
+    locales: ["en-US"],
+    expected: {
+      locale: "en-US",
+      direction: "ltr",
+    }
+  },
+  {
+    locales: ["fr"],
+    expected: {
+      locale: "fr",
+      direction: "ltr",
+    }
+  },
+  {
+    locales: ["ar"],
+    expected: {
+      locale: "ar",
+      direction: "rtl",
+    }
+  },
+  // IntlUtils uses current app locales if locales is not provided.
+  {
+    locales: [],
+    expected: {
+      locale: appLocale,
+      direction: mozIntl.getLocaleInfo(appLocale).direction,
+    }
+  },
+];
+
+let intlUtils = window.intlUtils;
+ok(intlUtils, "window.intlUtils should exist");
+
+for (let { locales, expected } of testData) {
+  let result = intlUtils.getLocaleInfo(locales);
+
+  is(result.locale, expected.locale, "locale is " + expected.locale);
+  is(result.direction, expected.direction, "direction is " + expected.direction);
+}
+
+</script>
+</body>
+</html>
--- a/dom/webidl/IntlUtils.webidl
+++ b/dom/webidl/IntlUtils.webidl
@@ -8,16 +8,21 @@ dictionary DisplayNameOptions {
 };
 
 dictionary DisplayNameResult {
   DOMString locale;
   DOMString style;
   record<DOMString, DOMString> values;
 };
 
+dictionary LocaleInfo {
+  DOMString locale;
+  DOMString direction;
+};
+
 /**
  * The IntlUtils interface provides helper functions for localization.
  */
 [NoInterfaceObject]
 interface IntlUtils {
   /**
    * Helper function to retrieve the localized values for a list of requested
    * keys.
@@ -42,9 +47,27 @@ interface IntlUtils {
    *   values:
    *     a key-value pair list of requested keys and corresponding translated
    *     values
    *
    */
   [Throws]
   DisplayNameResult getDisplayNames(sequence<DOMString> locales,
                                     optional DisplayNameOptions options);
+
+  /**
+   * Helper function to retrieve useful information about a locale.
+   *
+   * The function takes one argument - locales which is a list of locale
+   * strings.
+   *
+   * It returns an object with properties:
+   *
+   *   locale:
+   *     a negotiated locale string
+   *
+   *   direction:
+   *     text direction, "ltr" or "rtl"
+   *
+   */
+  [Throws]
+  LocaleInfo getLocaleInfo(sequence<DOMString> locales);
 };
--- a/gfx/skia/skia/src/gpu/SkGpuDevice.cpp
+++ b/gfx/skia/skia/src/gpu/SkGpuDevice.cpp
@@ -1029,17 +1029,17 @@ void SkGpuDevice::drawBitmapTile(const S
     sk_sp<GrColorSpaceXform> colorSpaceXform =
         GrColorSpaceXform::Make(bitmap.colorSpace(), fDrawContext->getColorSpace());
 
     SkScalar iw = 1.f / texture->width();
     SkScalar ih = 1.f / texture->height();
 
     SkMatrix texMatrix;
     // Compute a matrix that maps the rect we will draw to the src rect.
-    texMatrix.setRectToRect(dstRect, srcRect, SkMatrix::kStart_ScaleToFit);
+    texMatrix.setRectToRect(dstRect, srcRect, SkMatrix::kFill_ScaleToFit);
     texMatrix.postScale(iw, ih);
 
     // Construct a GrPaint by setting the bitmap texture as the first effect and then configuring
     // the rest from the SkPaint.
     sk_sp<GrFragmentProcessor> fp;
 
     if (needsTextureDomain && (SkCanvas::kStrict_SrcRectConstraint == constraint)) {
         // Use a constrained texture domain to avoid color bleeding
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/bug1342882.js
@@ -0,0 +1,3 @@
+// |jit-test| error: ReferenceError
+
+for (let [k, map = send.log += "" + map] of map) {}
--- a/js/src/jit/CacheIR.cpp
+++ b/js/src/jit/CacheIR.cpp
@@ -1212,20 +1212,36 @@ GetPropIRGenerator::tryAttachStringLengt
 bool
 GetPropIRGenerator::tryAttachStringChar(ValOperandId valId, ValOperandId indexId)
 {
     MOZ_ASSERT(idVal_.isInt32());
 
     if (!val_.isString())
         return false;
 
+    int32_t index = idVal_.toInt32();
+    if (index < 0)
+        return false;
+
     JSString* str = val_.toString();
-    int32_t index = idVal_.toInt32();
-    if (size_t(index) >= str->length() ||
-        !str->isLinear() ||
+    if (size_t(index) >= str->length())
+        return false;
+
+    // This follows JSString::getChar, otherwise we fail to attach getChar in a lot of cases.
+    if (str->isRope()) {
+        JSRope* rope = &str->asRope();
+
+        // Make sure the left side contains the index.
+        if (size_t(index) >= rope->leftChild()->length())
+            return false;
+
+        str = rope->leftChild();
+    }
+
+    if (!str->isLinear() ||
         str->asLinear().latin1OrTwoByteChar(index) >= StaticStrings::UNIT_STATIC_LIMIT)
     {
         return false;
     }
 
     StringOperandId strId = writer.guardIsString(valId);
     Int32OperandId int32IndexId = writer.guardIsInt32Index(indexId);
     writer.loadStringCharResult(strId, int32IndexId);
--- a/js/src/jit/CacheIRCompiler.cpp
+++ b/js/src/jit/CacheIRCompiler.cpp
@@ -1753,22 +1753,20 @@ CacheIRCompiler::emitLoadStringCharResul
     Register index = allocator.useRegister(masm, reader.int32OperandId());
     AutoScratchRegisterMaybeOutput scratch1(allocator, masm, output);
     AutoScratchRegister scratch2(allocator, masm);
 
     FailurePath* failure;
     if (!addFailurePath(&failure))
         return false;
 
-    masm.branchIfRope(str, failure->label());
-
     // Bounds check, load string char.
     masm.branch32(Assembler::BelowOrEqual, Address(str, JSString::offsetOfLength()),
                   index, failure->label());
-    masm.loadStringChar(str, index, scratch1);
+    masm.loadStringChar(str, index, scratch1, failure->label());
 
     // Load StaticString for this char.
     masm.branch32(Assembler::AboveOrEqual, scratch1, Imm32(StaticStrings::UNIT_STATIC_LIMIT),
                   failure->label());
     masm.movePtr(ImmPtr(&cx_->staticStrings().unitStaticTable), scratch2);
     masm.loadPtr(BaseIndex(scratch2, scratch1, ScalePointer), scratch2);
 
     EmitStoreResult(masm, scratch2, JSVAL_TYPE_STRING, output);
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -7961,20 +7961,17 @@ static const VMFunction CharCodeAtInfo =
 void
 CodeGenerator::visitCharCodeAt(LCharCodeAt* lir)
 {
     Register str = ToRegister(lir->str());
     Register index = ToRegister(lir->index());
     Register output = ToRegister(lir->output());
 
     OutOfLineCode* ool = oolCallVM(CharCodeAtInfo, lir, ArgList(str, index), StoreRegisterTo(output));
-
-    masm.branchIfRope(str, ool->entry());
-    masm.loadStringChar(str, index, output);
-
+    masm.loadStringChar(str, index, output, ool->entry());
     masm.bind(ool->rejoin());
 }
 
 typedef JSFlatString* (*StringFromCharCodeFn)(JSContext*, int32_t);
 static const VMFunction StringFromCharCodeInfo =
     FunctionInfo<StringFromCharCodeFn>(jit::StringFromCharCode, "StringFromCharCode");
 
 void
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -3223,23 +3223,25 @@ IonBuilder::binaryArithTryConcat(bool* e
     // Make sure one of the inputs is a string.
     if (left->type() != MIRType::String && right->type() != MIRType::String) {
         trackOptimizationOutcome(TrackedOutcome::OperandNotString);
         return Ok();
     }
 
     // The non-string input (if present) should be atleast easily coercible to string.
     if (right->type() != MIRType::String &&
-        (right->mightBeType(MIRType::Symbol) || right->mightBeType(MIRType::Object)))
+        (right->mightBeType(MIRType::Symbol) || right->mightBeType(MIRType::Object) ||
+         right->mightBeMagicType()))
     {
         trackOptimizationOutcome(TrackedOutcome::OperandNotEasilyCoercibleToString);
         return Ok();
     }
     if (left->type() != MIRType::String &&
-        (left->mightBeType(MIRType::Symbol) || left->mightBeType(MIRType::Object)))
+        (left->mightBeType(MIRType::Symbol) || left->mightBeType(MIRType::Object) ||
+         left->mightBeMagicType()))
     {
         trackOptimizationOutcome(TrackedOutcome::OperandNotEasilyCoercibleToString);
         return Ok();
     }
 
     MConcat* ins = MConcat::New(alloc(), left, right);
     current->add(ins);
     current->push(ins);
--- a/js/src/jit/MacroAssembler-inl.h
+++ b/js/src/jit/MacroAssembler-inl.h
@@ -391,16 +391,24 @@ void
 MacroAssembler::branchIfRope(Register str, Label* label)
 {
     Address flags(str, JSString::offsetOfFlags());
     static_assert(JSString::ROPE_FLAGS == 0, "Rope type flags must be 0");
     branchTest32(Assembler::Zero, flags, Imm32(JSString::TYPE_FLAGS_MASK), label);
 }
 
 void
+MacroAssembler::branchIfNotRope(Register str, Label* label)
+{
+    Address flags(str, JSString::offsetOfFlags());
+    static_assert(JSString::ROPE_FLAGS == 0, "Rope type flags must be 0");
+    branchTest32(Assembler::NonZero, flags, Imm32(JSString::TYPE_FLAGS_MASK), label);
+}
+
+void
 MacroAssembler::branchLatin1String(Register string, Label* label)
 {
     branchTest32(Assembler::NonZero, Address(string, JSString::offsetOfFlags()),
                  Imm32(JSString::LATIN1_CHARS_BIT), label);
 }
 
 void
 MacroAssembler::branchTwoByteString(Register string, Label* label)
--- a/js/src/jit/MacroAssembler.cpp
+++ b/js/src/jit/MacroAssembler.cpp
@@ -1378,22 +1378,39 @@ MacroAssembler::loadStringChars(Register
 
     bind(&isInline);
     computeEffectiveAddress(Address(str, JSInlineString::offsetOfInlineStorage()), dest);
 
     bind(&done);
 }
 
 void
-MacroAssembler::loadStringChar(Register str, Register index, Register output)
+MacroAssembler::loadStringChar(Register str, Register index, Register output, Label* fail)
 {
     MOZ_ASSERT(str != output);
     MOZ_ASSERT(index != output);
 
-    loadStringChars(str, output);
+    movePtr(str, output);
+
+    // This follows JSString::getChar.
+    Label notRope;
+    branchIfNotRope(str, &notRope);
+
+    // Load leftChild.
+    loadPtr(Address(str, JSRope::offsetOfLeft()), output);
+
+    // Check if the index is contained in the leftChild.
+    // Todo: Handle index in the rightChild.
+    branch32(Assembler::BelowOrEqual, Address(output, JSString::offsetOfLength()), index, fail);
+
+    // If the left side is another rope, give up.
+    branchIfRope(output, fail);
+
+    bind(&notRope);
+    loadStringChars(output, output);
 
     Label isLatin1, done;
     branchLatin1String(str, &isLatin1);
     load16ZeroExtend(BaseIndex(output, index, TimesTwo), output);
     jump(&done);
 
     bind(&isLatin1);
     load8ZeroExtend(BaseIndex(output, index, TimesOne), output);
--- a/js/src/jit/MacroAssembler.h
+++ b/js/src/jit/MacroAssembler.h
@@ -1108,16 +1108,17 @@ class MacroAssembler : public MacroAssem
     // Branches to |label| if |reg| is false. |reg| should be a C++ bool.
     template <class L>
     inline void branchIfFalseBool(Register reg, L label);
 
     // Branches to |label| if |reg| is true. |reg| should be a C++ bool.
     inline void branchIfTrueBool(Register reg, Label* label);
 
     inline void branchIfRope(Register str, Label* label);
+    inline void branchIfNotRope(Register str, Label* label);
 
     inline void branchLatin1String(Register string, Label* label);
     inline void branchTwoByteString(Register string, Label* label);
 
     inline void branchIfFunctionHasNoScript(Register fun, Label* label);
     inline void branchIfInterpreted(Register fun, Label* label);
 
     inline void branchFunctionKind(Condition cond, JSFunction::FunctionKind kind, Register fun,
@@ -1488,17 +1489,17 @@ class MacroAssembler : public MacroAssem
         loadPtr(Address(dest, ObjectGroup::offsetOfProto()), dest);
     }
 
     void loadStringLength(Register str, Register dest) {
         load32(Address(str, JSString::offsetOfLength()), dest);
     }
 
     void loadStringChars(Register str, Register dest);
-    void loadStringChar(Register str, Register index, Register output);
+    void loadStringChar(Register str, Register index, Register output, Label* fail);
 
     void loadJSContext(Register dest);
     void loadJitActivation(Register dest) {
         loadJSContext(dest);
         loadPtr(Address(dest, offsetof(JSContext, activation_)), dest);
     }
     void loadWasmActivationFromTls(Register dest) {
         loadPtr(Address(WasmTlsReg, offsetof(wasm::TlsData, cx)), dest);
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -1455,33 +1455,34 @@ js::StringFindPattern(JSLinearString* te
 
 // When an algorithm does not need a string represented as a single linear
 // array of characters, this range utility may be used to traverse the string a
 // sequence of linear arrays of characters. This avoids flattening ropes.
 class StringSegmentRange
 {
     // If malloc() shows up in any profiles from this vector, we can add a new
     // StackAllocPolicy which stashes a reusable freed-at-gc buffer in the cx.
-    Rooted<StringVector> stack;
+    using StackVector = JS::GCVector<JSString*, 16>;
+    Rooted<StackVector> stack;
     RootedLinearString cur;
 
     bool settle(JSString* str) {
         while (str->isRope()) {
             JSRope& rope = str->asRope();
             if (!stack.append(rope.rightChild()))
                 return false;
             str = rope.leftChild();
         }
         cur = &str->asLinear();
         return true;
     }
 
   public:
     explicit StringSegmentRange(JSContext* cx)
-      : stack(cx, StringVector(cx)), cur(cx)
+      : stack(cx, StackVector(cx)), cur(cx)
     {}
 
     MOZ_MUST_USE bool init(JSString* str) {
         MOZ_ASSERT(stack.empty());
         return settle(str);
     }
 
     bool empty() const {
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -63,16 +63,17 @@
 #include "gfxPrefs.h"
 #include "gfxTypes.h"
 #include "nsTArray.h"
 #include "mozilla/dom/HTMLCanvasElement.h"
 #include "nsICanvasRenderingContextInternal.h"
 #include "gfxPlatform.h"
 #include <algorithm>
 #include <limits>
+#include "mozilla/dom/AnonymousContent.h"
 #include "mozilla/dom/HTMLVideoElement.h"
 #include "mozilla/dom/HTMLImageElement.h"
 #include "mozilla/dom/DOMRect.h"
 #include "mozilla/dom/DOMStringList.h"
 #include "mozilla/dom/KeyframeEffectReadOnly.h"
 #include "mozilla/layers/APZCCallbackHelper.h"
 #include "imgIRequest.h"
 #include "nsIImageLoadingContent.h"
@@ -1152,25 +1153,65 @@ GetDisplayPortFromMarginsData(nsIContent
 
   // Make sure the displayport remains within the scrollable rect.
   result = result.MoveInsideAndClamp(expandedScrollableRect - scrollPos);
 
   return result;
 }
 
 static bool
-ShouldDisableApzForElement(nsIContent* aContent)
-{
-  if (gfxPrefs::APZDisableForScrollLinkedEffects() && aContent) {
-    nsIDocument* doc = aContent->GetComposedDoc();
-    return (doc && doc->HasScrollLinkedEffect());
+HasVisibleAnonymousContents(nsIDocument* aDoc)
+{
+  for (RefPtr<AnonymousContent>& ac : aDoc->GetAnonymousContents()) {
+    Element* elem = ac->GetContentNode();
+    // We check to see if the anonymous content node has a frame. If it doesn't,
+    // that means that's not visible to the user because e.g. it's display:none.
+    // For now we assume that if it has a frame, it is visible. We might be able
+    // to refine this further by adding complexity if it turns out this condition
+    // results in a lot of false positives.
+    if (elem && elem->GetPrimaryFrame()) {
+      return true;
+    }
   }
   return false;
 }
 
+bool
+nsLayoutUtils::ShouldDisableApzForElement(nsIContent* aContent)
+{
+  if (!aContent) {
+    return false;
+  }
+
+  nsIDocument* doc = aContent->GetComposedDoc();
+  nsIPresShell* rootShell = APZCCallbackHelper::GetRootContentDocumentPresShellForContent(aContent);
+  if (rootShell) {
+    if (nsIDocument* rootDoc = rootShell->GetDocument()) {
+      nsIContent* rootContent = rootShell->GetRootScrollFrame()
+          ? rootShell->GetRootScrollFrame()->GetContent()
+          : rootDoc->GetDocumentElement();
+      // For the AccessibleCaret: disable APZ on any scrollable subframes that
+      // are not the root scrollframe of a document, if the document has any
+      // visible anonymous contents.
+      // If we find this is triggering in too many scenarios then we might
+      // want to tighten this check further. The main use cases for which we want
+      // to disable APZ as of this writing are listed in bug 1316318.
+      if (aContent != rootContent && HasVisibleAnonymousContents(rootDoc)) {
+        return true;
+      }
+    }
+  }
+
+  if (!doc) {
+    return false;
+  }
+  return gfxPrefs::APZDisableForScrollLinkedEffects() &&
+         doc->HasScrollLinkedEffect();
+}
+
 static bool
 GetDisplayPortData(nsIContent* aContent,
                    DisplayPortPropertyData** aOutRectData,
                    DisplayPortMarginsPropertyData** aOutMarginsData)
 {
   MOZ_ASSERT(aOutRectData && aOutMarginsData);
 
   *aOutRectData =
@@ -1225,17 +1266,18 @@ GetDisplayPortImpl(nsIContent* aContent,
     // We have displayport data, but the caller doesn't want the actual
     // rect, so we don't need to actually compute it.
     return true;
   }
 
   nsRect result;
   if (rectData) {
     result = GetDisplayPortFromRectData(aContent, rectData, aMultiplier);
-  } else if (APZCCallbackHelper::IsDisplayportSuppressed() || ShouldDisableApzForElement(aContent)) {
+  } else if (APZCCallbackHelper::IsDisplayportSuppressed() ||
+      nsLayoutUtils::ShouldDisableApzForElement(aContent)) {
     DisplayPortMarginsPropertyData noMargins(ScreenMargin(), 1);
     result = GetDisplayPortFromMarginsData(aContent, &noMargins, aMultiplier);
   } else {
     result = GetDisplayPortFromMarginsData(aContent, marginsData, aMultiplier);
   }
 
   if (!gfxPrefs::LayersTilesEnabled()) {
     // Either we should have gotten a valid rect directly from the displayport
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -2680,16 +2680,24 @@ public:
 
   /**
    * Returns the current APZ Resolution Scale. When Java Pan/Zoom is
    * enabled in Fennec it will always return 1.0.
    */
   static float GetCurrentAPZResolutionScale(nsIPresShell* aShell);
 
   /**
+   * Returns true if we need to disable async scrolling for this particular
+   * element. Note that this does a partial disabling - the displayport still
+   * exists but uses a very small margin, and the compositor doesn't apply the
+   * async transform. However, the content may still be layerized.
+   */
+  static bool ShouldDisableApzForElement(nsIContent* aContent);
+
+  /**
    * Log a key/value pair for APZ testing during a paint.
    * @param aManager   The data will be written to the APZTestData associated
    *                   with this layer manager.
    * @param aScrollId Identifies the scroll frame to which the data pertains.
    * @param aKey The key under which to log the data.
    * @param aValue The value of the data to be logged.
    */
   static void LogTestDataForPaint(mozilla::layers::LayerManager* aManager,
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -2813,17 +2813,19 @@ ScrollFrameHelper::ScrollToImpl(nsPoint 
     mAllowScrollOriginDowngrade = false;
   }
   mLastSmoothScrollOrigin = nullptr;
   mScrollGeneration = ++sScrollGenerationCounter;
 
   ScrollVisual();
 
   bool schedulePaint = true;
-  if (nsLayoutUtils::AsyncPanZoomEnabled(mOuter) && gfxPrefs::APZPaintSkipping()) {
+  if (nsLayoutUtils::AsyncPanZoomEnabled(mOuter) &&
+      !nsLayoutUtils::ShouldDisableApzForElement(content) &&
+      gfxPrefs::APZPaintSkipping()) {
     // If APZ is enabled with paint-skipping, there are certain conditions in
     // which we can skip paints:
     // 1) If APZ triggered this scroll, and the tile-aligned displayport is
     //    unchanged.
     // 2) If non-APZ triggered this scroll, but we can handle it by just asking
     //    APZ to update the scroll position. Again we make this conditional on
     //    the tile-aligned displayport being unchanged.
     // We do the displayport check first since it's common to all scenarios,
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -49,16 +49,17 @@
 
 #endif
 
         <!-- This activity handles all incoming Intents and dispatches them to other activities. -->
         <activity android:name="org.mozilla.gecko.LauncherActivity"
             android:theme="@android:style/Theme.Translucent.NoTitleBar"
             android:relinquishTaskIdentity="true"
             android:taskAffinity=""
+            android:exported="true"
             android:excludeFromRecents="true" />
 
         <!-- Fennec is shipped as the Android package named
              org.mozilla.{fennec,firefox,firefox_beta}.  The internal Java
              package hierarchy inside the Android package used to have an
              org.mozilla.{fennec,firefox,firefox_beta} subtree *and* an
              org.mozilla.gecko subtree; it now only has org.mozilla.gecko. -->
         <activity android:name="@MOZ_ANDROID_BROWSER_INTENT_CLASS@"
@@ -311,16 +312,18 @@
             </intent-filter>
 
         </activity>
 
 #ifdef MOZ_ANDROID_CUSTOM_TABS
         <activity android:name="org.mozilla.gecko.customtabs.CustomTabsActivity"
             android:theme="@style/GeckoCustomTabs" />
 #endif
+        <activity android:name="org.mozilla.gecko.webapps.WebAppActivity"
+            android:theme="@style/Theme.AppCompat.NoActionBar" />
 
         <!-- Service to handle requests from overlays. -->
         <service android:name="org.mozilla.gecko.overlays.service.OverlayActionService" />
 
         <!--
           Ensure that passwords provider runs in its own process. (Bug 718760.)
           Process name is per-application to avoid loading CPs from multiple
           Fennec versions into the same process. (Bug 749727.)
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -2032,20 +2032,21 @@ public class BrowserApp extends GeckoApp
 
                 Telemetry.addToHistogram("FENNEC_ORBOT_INSTALLED",
                     ContextUtils.isPackageInstalled(getContext(), "org.torproject.android") ? 1 : 0);
                 break;
 
             case "Website:AppInstalled":
                 final String name = message.getString("name");
                 final String startUrl = message.getString("start_url");
+                final String manifestPath = message.getString("manifest_path");
                 final Bitmap icon = FaviconDecoder
                     .decodeDataURI(getContext(), message.getString("icon"))
                     .getBestBitmap(GeckoAppShell.getPreferredIconSize());
-                createShortcut(name, startUrl, icon);
+                createAppShortcut(name, startUrl, manifestPath, icon);
                 break;
 
             case "Updater:Launch":
                 /**
                  * Launch UI that lets the user update Firefox.
                  *
                  * This depends on the current channel: Release and Beta both direct to
                  * the Google Play Store. If updating is enabled, Aurora, Nightly, and
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -35,22 +35,24 @@ import org.mozilla.gecko.prompts.PromptS
 import org.mozilla.gecko.restrictions.Restrictions;
 import org.mozilla.gecko.tabqueue.TabQueueHelper;
 import org.mozilla.gecko.text.FloatingToolbarTextSelection;
 import org.mozilla.gecko.text.TextSelection;
 import org.mozilla.gecko.updater.UpdateServiceHelper;
 import org.mozilla.gecko.util.ActivityResultHandler;
 import org.mozilla.gecko.util.ActivityUtils;
 import org.mozilla.gecko.util.BundleEventListener;
+import org.mozilla.gecko.util.ColorUtil;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.FileUtils;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.PrefUtils;
 import org.mozilla.gecko.util.ThreadUtils;
+import org.mozilla.gecko.webapps.WebAppActivity;
 
 import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
@@ -140,16 +142,17 @@ public abstract class GeckoApp
     Tabs.OnTabsChangedListener,
     ViewTreeObserver.OnGlobalLayoutListener {
 
     private static final String LOGTAG = "GeckoApp";
     private static final long ONE_DAY_MS = TimeUnit.MILLISECONDS.convert(1, TimeUnit.DAYS);
 
     public static final String ACTION_ALERT_CALLBACK       = "org.mozilla.gecko.ALERT_CALLBACK";
     public static final String ACTION_HOMESCREEN_SHORTCUT  = "org.mozilla.gecko.BOOKMARK";
+    public static final String ACTION_WEBAPP               = "org.mozilla.gecko.WEBAPP";
     public static final String ACTION_DEBUG                = "org.mozilla.gecko.DEBUG";
     public static final String ACTION_LAUNCH_SETTINGS      = "org.mozilla.gecko.SETTINGS";
     public static final String ACTION_LOAD                 = "org.mozilla.gecko.LOAD";
     public static final String ACTION_INIT_PW              = "org.mozilla.gecko.INIT_PW";
     public static final String ACTION_SWITCH_TAB           = "org.mozilla.gecko.SWITCH_TAB";
 
     public static final String INTENT_REGISTER_STUMBLER_LISTENER = "org.mozilla.gecko.STUMBLER_REGISTER_LOCAL_LISTENER";
 
@@ -2017,23 +2020,35 @@ public abstract class GeckoApp
                     @Override
                     public void onIconResponse(IconResponse response) {
                         createShortcut(title, url, response.getBitmap());
                     }
                 });
     }
 
     public void createShortcut(final String aTitle, final String aURI, final Bitmap aIcon) {
-        // The intent to be launched by the shortcut.
         Intent shortcutIntent = new Intent();
         shortcutIntent.setAction(GeckoApp.ACTION_HOMESCREEN_SHORTCUT);
         shortcutIntent.setData(Uri.parse(aURI));
         shortcutIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME,
                 AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
-
+        createHomescreenIcon(shortcutIntent, aTitle, aURI, aIcon);
+    }
+
+    public void createAppShortcut(final String aTitle, final String aURI, final String manifestPath, final Bitmap aIcon) {
+        Intent shortcutIntent = new Intent();
+        shortcutIntent.setAction(GeckoApp.ACTION_WEBAPP);
+        shortcutIntent.setData(Uri.parse(aURI));
+        shortcutIntent.putExtra("MANIFEST_PATH", manifestPath);
+        shortcutIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, LauncherActivity.class.getName());
+        createHomescreenIcon(shortcutIntent, aTitle, aURI, aIcon);
+    }
+
+    public void createHomescreenIcon(final Intent shortcutIntent, final String aTitle,
+                                     final String aURI, final Bitmap aIcon) {
         Intent intent = new Intent();
         intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
         intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, getLauncherIcon(aIcon, GeckoAppShell.getPreferredIconSize()));
 
         if (aTitle != null) {
             intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, aTitle);
         } else {
             intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, aURI);
--- a/mobile/android/base/java/org/mozilla/gecko/LauncherActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/LauncherActivity.java
@@ -6,16 +6,17 @@
 package org.mozilla.gecko;
 
 import android.app.Activity;
 import android.content.Intent;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
 import android.support.customtabs.CustomTabsIntent;
 
+import org.mozilla.gecko.webapps.WebAppActivity;
 import org.mozilla.gecko.customtabs.CustomTabsActivity;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.mozglue.SafeIntent;
 import org.mozilla.gecko.preferences.GeckoPreferences;
 import org.mozilla.gecko.tabqueue.TabQueueHelper;
 import org.mozilla.gecko.tabqueue.TabQueueService;
 
 /**
@@ -25,18 +26,22 @@ public class LauncherActivity extends Ac
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
         GeckoAppShell.ensureCrashHandling();
 
         final SafeIntent safeIntent = new SafeIntent(getIntent());
 
+        // Is this web app?
+        if (isWebAppIntent(safeIntent)) {
+            dispatchWebAppIntent();
+
         // If it's not a view intent, it won't be a custom tabs intent either. Just launch!
-        if (!isViewIntentWithURL(safeIntent)) {
+        } else if (!isViewIntentWithURL(safeIntent)) {
             dispatchNormalIntent();
 
         // Is this a custom tabs intent, and are custom tabs enabled?
         } else if (AppConstants.MOZ_ANDROID_CUSTOM_TABS && isCustomTabsIntent(safeIntent)
                 && isCustomTabsEnabled()) {
             dispatchCustomTabsIntent();
 
         // Can we dispatch this VIEW action intent to the tab queue service?
@@ -78,16 +83,25 @@ public class LauncherActivity extends Ac
         Intent intent = new Intent(getIntent());
         intent.setClassName(getApplicationContext(), CustomTabsActivity.class.getName());
 
         filterFlags(intent);
 
         startActivity(intent);
     }
 
+    private void dispatchWebAppIntent() {
+        Intent intent = new Intent(getIntent());
+        intent.setClassName(getApplicationContext(), WebAppActivity.class.getName());
+
+        filterFlags(intent);
+
+        startActivity(intent);
+    }
+
     private static void filterFlags(Intent intent) {
         // Explicitly remove the new task and clear task flags (Our browser activity is a single
         // task activity and we never want to start a second task here). See bug 1280112.
         intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK);
         intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_CLEAR_TASK);
 
         // LauncherActivity is started with the "exclude from recents" flag (set in manifest). We do
         // not want to propagate this flag from the launcher activity to the browser.
@@ -99,12 +113,16 @@ public class LauncherActivity extends Ac
                 && safeIntent.getDataString() != null;
     }
 
     private static boolean isCustomTabsIntent(@NonNull final SafeIntent safeIntent) {
         return isViewIntentWithURL(safeIntent)
                 && safeIntent.hasExtra(CustomTabsIntent.EXTRA_SESSION);
     }
 
+    private static boolean isWebAppIntent(@NonNull final SafeIntent safeIntent) {
+        return GeckoApp.ACTION_WEBAPP.equals(safeIntent.getAction());
+    }
+
     private boolean isCustomTabsEnabled() {
         return GeckoSharedPrefs.forApp(this).getBoolean(GeckoPreferences.PREFS_CUSTOM_TABS, false);
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/search/SearchEngineManager.java
+++ b/mobile/android/base/java/org/mozilla/gecko/search/SearchEngineManager.java
@@ -17,16 +17,17 @@ import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.GeckoSharedPrefs;
 import org.mozilla.gecko.Locales;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.distribution.Distribution;
 import org.mozilla.gecko.util.FileUtils;
 import org.mozilla.gecko.util.GeckoJarReader;
 import org.mozilla.gecko.util.HardwareUtils;
+import org.mozilla.gecko.util.IOUtils;
 import org.mozilla.gecko.util.RawResource;
 import org.mozilla.gecko.util.StringUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.BufferedInputStream;
 import java.io.BufferedReader;
 import java.io.File;
@@ -37,16 +38,18 @@ import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.io.UnsupportedEncodingException;
 import java.lang.ref.WeakReference;
 import java.net.HttpURLConnection;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Locale;
+import org.json.JSONObject;
+import org.json.JSONArray;
 
 /**
  * This class is not thread-safe, except where otherwise noted.
  *
  * This class contains a reference to {@link Context} - DO NOT LEAK!
  */
 public class SearchEngineManager implements SharedPreferences.OnSharedPreferenceChangeListener {
     private static final String LOG_TAG = "GeckoSearchEngineManager";
@@ -61,16 +64,19 @@ public class SearchEngineManager impleme
     private static final String PREF_DEFAULT_ENGINE_KEY = "search.engines.defaultname";
 
     // Key for shared preference that stores search region.
     private static final String PREF_REGION_KEY = "search.region";
 
     // URL for the geo-ip location service. Keep in sync with "browser.search.geoip.url" perference in Gecko.
     private static final String GEOIP_LOCATION_URL = "https://location.services.mozilla.com/v1/country?key=" + AppConstants.MOZ_MOZILLA_API_KEY;
 
+    // The API we're using requires a file size, so set an arbitrarily large one
+    public static final int MAX_LISTJSON_SIZE = 8192;
+
     // This should go through GeckoInterface to get the UA, but the search activity
     // doesn't use a GeckoView yet. Until it does, get the UA directly.
     private static final String USER_AGENT = HardwareUtils.isTablet() ?
         AppConstants.USER_AGENT_FENNEC_TABLET : AppConstants.USER_AGENT_FENNEC_MOBILE;
 
     private final Context context;
     private final Distribution distribution;
     @Nullable private volatile SearchEngineCallback changeCallback;
@@ -554,52 +560,61 @@ public class SearchEngineManager impleme
         }
 
         return createEngineFromFileList(files.toArray(new File[files.size()]), name);
     }
 
     /**
      * Creates a SearchEngine instance for a search plugin shipped in the locale.
      *
-     * This method reads the list of search plugin file names from list.txt, then
+     * This method reads the list of search plugin file names from list.json, then
      * iterates through the files, creating SearchEngine instances until it finds one
      * with the right name. Unfortunately, we need to do this because there is no
      * other way to map the search engine "name" to the file for the search plugin.
      *
      * @param name Search engine name.
      * @return SearchEngine instance for name.
      */
     private SearchEngine createEngineFromLocale(String name) {
-        final InputStream in = getInputStreamFromSearchPluginsJar("list.txt");
+        final InputStream in = getInputStreamFromSearchPluginsJar("list.json");
         if (in == null) {
             return null;
         }
-        final BufferedReader br = getBufferedReader(in);
-
+        JSONObject json;
+        try {
+            json = new JSONObject(FileUtils.readStringFromInputStreamAndCloseStream(in, MAX_LISTJSON_SIZE));
+        } catch (IOException e) {
+            Log.e(LOG_TAG, "Error reading list.json", e);
+            return null;
+        } catch (JSONException e) {
+            Log.e(LOG_TAG, "Error parsing list.json", e);
+            return null;
+        } finally {
+            IOUtils.safeStreamClose(in);
+        }
         try {
-            String identifier;
-            while ((identifier = br.readLine()) != null) {
-                final InputStream pluginIn = getInputStreamFromSearchPluginsJar(identifier + ".xml");
-                // pluginIn can be null if the xml file doesn't exist which
-                // can happen with :hidden plugins
+            String region = GeckoSharedPrefs.forApp(context).getString(PREF_REGION_KEY, null);
+
+            JSONArray engines;
+            if (json.has(region)) {
+                engines = json.getJSONObject(region).getJSONArray("visibleDefaultEngines");
+            } else {
+                engines = json.getJSONObject("default").getJSONArray("visibleDefaultEngines");
+            }
+            for (int i = 0; i < engines.length(); i++) {
+                final InputStream pluginIn = getInputStreamFromSearchPluginsJar(engines.getString(i) + ".xml");
                 if (pluginIn != null) {
-                  final SearchEngine engine = createEngineFromInputStream(identifier, pluginIn);
-                  if (engine != null && engine.getName().equals(name)) {
-                      return engine;
-                  }
+                    final SearchEngine engine = createEngineFromInputStream(engines.getString(i), pluginIn);
+                    if (engine != null && engine.getName().equals(name)) {
+                        return engine;
+                    }
                 }
             }
         } catch (Throwable e) {
             Log.e(LOG_TAG, "Error creating shipped search engine from name: " + name, e);
-        } finally {
-            try {
-                br.close();
-            } catch (IOException e) {
-                // Ignore.
-            }
         }
         return null;
     }
 
     /**
      * Creates a SearchEngine instance for a search plugin in the profile directory.
      *
      * This method iterates through the profile searchplugins directory, creating
--- a/mobile/android/base/java/org/mozilla/gecko/util/ColorUtil.java
+++ b/mobile/android/base/java/org/mozilla/gecko/util/ColorUtil.java
@@ -24,16 +24,29 @@ public class ColorUtil {
         // 186 chosen rather than the seemingly obvious 128 because of gamma.
         if (greyValue < 186) {
             return Color.WHITE;
         } else {
             return Color.BLACK;
         }
     }
 
+    public static Integer parseStringColor(final String color) {
+        try {
+            if (color.length() < 7) {
+                return null;
+            }
+            return Color.argb(255,
+                    Integer.valueOf(color.substring(1, 3), 16),
+                    Integer.valueOf(color.substring(3, 5), 16),
+                    Integer.valueOf(color.substring(5, 7), 16));
+        } catch (NumberFormatException e) { }
+        return null;
+    }
+
     private static int darkenColor(final int color, final double fraction) {
         return (int) Math.max(color - (color * fraction), 0);
     }
 
     private static int grayscaleFromRGB(final int color) {
         final int red = Color.red(color);
         final int green = Color.green(color);
         final int blue = Color.blue(color);
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/webapps/WebAppActivity.java
@@ -0,0 +1,112 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; 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.webapps;
+
+import java.io.File;
+import java.io.IOException;
+
+import android.app.ActivityManager;
+import android.graphics.Bitmap;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.Window;
+import android.view.WindowManager;
+import android.util.Log;
+
+import org.json.JSONObject;
+import org.json.JSONException;
+
+import org.mozilla.gecko.AppConstants;
+import org.mozilla.gecko.EventDispatcher;
+import org.mozilla.gecko.GeckoApp;
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.GeckoProfile;
+import org.mozilla.gecko.icons.decoders.FaviconDecoder;
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.util.ColorUtil;
+import org.mozilla.gecko.util.EventCallback;
+import org.mozilla.gecko.util.FileUtils;
+import org.mozilla.gecko.util.GeckoBundle;
+
+public class WebAppActivity extends GeckoApp {
+
+    public static final String INTENT_KEY = "IS_A_WEBAPP";
+    public static final String MANIFEST_PATH = "MANIFEST_PATH";
+
+    private static final String LOGTAG = "WebAppActivity";
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        String manifestPath = getIntent().getStringExtra(WebAppActivity.MANIFEST_PATH);
+        if (manifestPath != null) {
+            updateFromManifest(manifestPath);
+        }
+    }
+
+    @Override
+    public int getLayout() {
+        return R.layout.webapp_activity;
+    }
+
+    private void updateFromManifest(String manifestPath) {
+        try {
+            final File manifestFile = new File(manifestPath);
+            final JSONObject manifest = FileUtils.readJSONObjectFromFile(manifestFile);
+            final JSONObject manifestField = (JSONObject) manifest.get("manifest");
+            final Integer color = readColorFromManifest(manifestField);
+            final String name = readNameFromManifest(manifestField);
+            final Bitmap icon = readIconFromManifest(manifest);
+            final ActivityManager.TaskDescription taskDescription = (color == null)
+                ? new ActivityManager.TaskDescription(name, icon)
+                : new ActivityManager.TaskDescription(name, icon, color);
+
+            updateStatusBarColor(color);
+            setTaskDescription(taskDescription);
+
+        } catch (IOException | JSONException e) {
+            Log.e(LOGTAG, "Failed to read manifest", e);
+        }
+    }
+
+    private void updateStatusBarColor(final Integer themeColor) {
+        if (themeColor != null && !AppConstants.Versions.preLollipop) {
+            final Window window = getWindow();
+            window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+            window.setStatusBarColor(ColorUtil.darken(themeColor, 0.25));
+        }
+    }
+
+    private Integer readColorFromManifest(JSONObject manifest) throws JSONException {
+        final String colorStr = (String) manifest.get("theme_color");
+        if (colorStr != null) {
+            return ColorUtil.parseStringColor(colorStr);
+        }
+        return null;
+    }
+
+    private String readNameFromManifest(JSONObject manifest) throws JSONException {
+        String name = (String) manifest.get("name");
+        if (name == null) {
+            name = (String) manifest.get("short_name");
+        }
+        if (name == null) {
+            name = (String) manifest.get("start_url");
+        }
+        return name;
+    }
+
+    private Bitmap readIconFromManifest(JSONObject manifest) throws JSONException {
+        final String iconStr = (String) manifest.get("cached_icon");
+        if (iconStr != null) {
+            return FaviconDecoder
+                .decodeDataURI(getContext(), iconStr)
+                .getBestBitmap(GeckoAppShell.getPreferredIconSize());
+        }
+        return null;
+    }
+}
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -784,16 +784,17 @@ gbjar.sources += ['java/org/mozilla/geck
     'updater/UpdateService.java',
     'updater/UpdateServiceHelper.java',
     'util/ColorUtil.java',
     'util/DrawableUtil.java',
     'util/JavaUtil.java',
     'util/ResourceDrawableUtils.java',
     'util/TouchTargetUtil.java',
     'util/ViewUtil.java',
+    'webapps/WebAppActivity.java',
     'widget/ActivityChooserModel.java',
     'widget/AllCapsTextView.java',
     'widget/AnchoredPopup.java',
     'widget/AnimatedHeightLayout.java',
     'widget/BasicColorPicker.java',
     'widget/CheckableLinearLayout.java',
     'widget/ClickableWhenDisabledEditText.java',
     'widget/ContentSecurityDoorHanger.java',
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/layout/webapp_activity.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-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/. -->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/root_layout"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <!--
+        This layout is quite complex because GeckoApp accesses all view groups
+        in this tree. In a perfect world this should just include a GeckoView.
+    -->
+
+    <view class="org.mozilla.gecko.GeckoApp$MainLayout"
+        android:id="@+id/main_layout"
+        android:layout_width="match_parent"
+        android:layout_below="@+id/toolbar"
+        android:layout_height="match_parent"
+        android:background="@android:color/transparent">
+
+        <RelativeLayout android:id="@+id/gecko_layout"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_below="@+id/tablet_tab_strip"
+            android:layout_above="@+id/find_in_page">
+
+            <fragment class="org.mozilla.gecko.GeckoViewFragment"
+                android:id="@+id/layer_view"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:scrollbars="none"/>
+
+        </RelativeLayout>
+
+    </view>
+
+</RelativeLayout>
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -2203,17 +2203,18 @@ async function installManifest(browser, 
   try {
     const manifest = await Manifests.getManifest(browser, manifestUrl);
     await manifest.install();
     const icon = await manifest.icon(iconSize);
     GlobalEventDispatcher.sendRequest({
       type: "Website:AppInstalled",
       icon,
       name: manifest.name,
-      start_url: manifest.start_url
+      start_url: manifest.start_url,
+      manifest_path: manifest.path
     });
   } catch (err) {
     Cu.reportError("Failed to install: " + err.message);
   }
 }
 
 var NativeWindow = {
   init: function() {
--- a/mobile/locales/Makefile.in
+++ b/mobile/locales/Makefile.in
@@ -41,18 +41,17 @@ SUBMAKEFILES += \
 #    gmake -C $obj/mobile/locales
 ###########################################################################
 search-jar-default: search-jar
 
 
 ###########################################################################
 ## Searchlist plugin config
 plugin-file-array = \
-  $(wildcard $(LOCALE_SRCDIR)/searchplugins/list.txt) \
-  $(srcdir)/en-US/searchplugins/list.txt \
+  $(srcdir)/search/list.json \
   $(NULL)
 
 ###########################################################################
 plugin_file    = $(firstword $(plugin-file-array))
 plugin-file-ts = $(tgt-gendir)/$(subst $(topsrcdir)/,$(NULL),$(plugin_file)).ts
 
 GARBAGE += $(plugin-file-ts)
 # ---------------------------------------------------------------------------
@@ -75,32 +74,32 @@ plugin-file-ts-preqs = \
 search-jar-common = tmp-search.jar.mn
 search-jar        = $(tgt-gendir)/$(search-jar-common)
 search-jar-ts     = $(search-jar).ts
 
 GARBAGE += $(search-jar) $(search-jar-ts) $(search-jar-common)
 # ---------------------------------------------------------------------------
 # search-jar contains a list of providers for the search plugin
 ###########################################################################
-SEARCH_PLUGINS = $(shell cat $(plugin_file))
-SEARCH_PLUGINS := $(subst :hidden,,$(SEARCH_PLUGINS))
+SEARCH_PLUGINS := $(shell $(call py_action,output_searchplugins_list,$(srcdir)/search/list.json $(AB_CD)))
 $(call errorIfEmpty,SEARCH_PLUGINS)
 
 search-jar-preqs = \
   $(plugin-file-ts) \
   $(if $(IS_LANGUAGE_REPACK),FORCE) \
   $(NULL)
 
 .PHONY: search-jar
 search-jar: $(search-jar)
 $(search-jar): $(search-jar-preqs)
 	@echo '\nGenerating: search-jar'
 	printf '$(AB_CD).jar:' > $@
 	ln -fn $@ .
 	printf '$(foreach plugin,$(SEARCH_PLUGINS),$(subst __PLUGIN_SUBST__,$(plugin), \n locale/$(AB_CD)/browser/searchplugins/__PLUGIN_SUBST__.xml (__PLUGIN_SUBST__.xml)))' >>  $@
+	printf '\n locale/$(AB_CD)/browser/searchplugins/list.json (list.json)' >> $@
 	@echo   >> $@
 
 ###################
 search-dir-deps = \
   $(plugin-file) \
   $(dir-chrome) \
   $(NULL)
 
@@ -110,20 +109,22 @@ search-preqs =\
   $(search-jar) \
   $(search-dir-deps) \
   $(if $(IS_LANGUAGE_REPACK),FORCE) \
   $(GLOBAL_DEPS) \
   $(NULL)
 
 .PHONY: searchplugins
 searchplugins: $(search-preqs)
+	$(call py_action,generate_searchjson,$(srcdir)/search/list.json $(AB_CD) $(tgt-gendir)/list.json)
 	$(call py_action,jar_maker,\
           $(QUIET) -d $(FINAL_TARGET) \
           -s $(topsrcdir)/$(relativesrcdir)/en-US/searchplugins \
           -s $(LOCALE_SRCDIR)/searchplugins \
+          -s $(tgt-gendir) \
           $(MAKE_JARS_FLAGS) $(search-jar))
 	$(TOUCH) $@
 
 include $(topsrcdir)/config/rules.mk
 
 
 #############
 libs-preqs =\
--- a/mobile/locales/jar.mn
+++ b/mobile/locales/jar.mn
@@ -2,17 +2,16 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 
 @AB_CD@.jar:
 % locale browser @AB_CD@ %locale/@AB_CD@/browser/
   locale/@AB_CD@/browser/region.properties        (%chrome/region.properties)
-  locale/@AB_CD@/browser/searchplugins/list.txt   (%searchplugins/list.txt)
 
 # Fennec-specific overrides of generic strings
   locale/@AB_CD@/browser/netError.dtd             (%overrides/netError.dtd)
 % override chrome://global/locale/netError.dtd    chrome://browser/locale/netError.dtd
   locale/@AB_CD@/browser/appstrings.properties    (%overrides/appstrings.properties)
 % override chrome://global/locale/appstrings.properties chrome://browser/locale/appstrings.properties
   locale/@AB_CD@/browser/passwordmgr.properties    (%overrides/passwordmgr.properties)
 % override chrome://passwordmgr/locale/passwordmgr.properties chrome://browser/locale/passwordmgr.properties
new file mode 100644
--- /dev/null
+++ b/mobile/locales/search/list.json
@@ -0,0 +1,696 @@
+{
+  "default": {
+    "visibleDefaultEngines": [
+      "google", "yahoo", "bing", "amazondotcom", "duckduckgo", "twitter", "wikipedia"
+    ]
+  },
+  "regionOverrides": {
+    "US": {
+      "google": "google-nocodes"
+    },
+    "CA": {
+      "google": "google-nocodes"
+    },
+    "KZ": {
+      "google": "google-nocodes"
+    },
+    "BY": {
+      "google": "google-nocodes"
+    },
+    "RU": {
+      "google": "google-nocodes"
+    },
+    "TR": {
+      "google": "google-nocodes"
+    },
+    "UA": {
+      "google": "google-nocodes"
+    },
+    "CN": {
+      "google": "google-nocodes"
+    },
+    "TW": {
+      "google": "google-nocodes"
+    },
+    "HK": {
+      "google": "google-nocodes"
+    }
+  },
+  "locales": {
+    "an": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-es", "bing", "twitter", "wikipedia-an"
+        ]
+      }
+    },
+    "ar": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo", "bing", "amazondotcom", "twitter", "wikipedia-ar"
+        ]
+      }
+    },
+    "as": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-in", "bing", "amazon-in", "wikipedia-as"
+        ]
+      }
+    },
+    "ast": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-es", "bing", "amazondotcom", "twitter", "wikipedia-ast"
+        ]
+      }
+    },
+    "az": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "bing", "amazondotcom", "azerdict", "duckduckgo", "wikipedia-az"
+        ]
+      }
+    },
+    "be": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "be.wikipedia.org", "bing", "yahoo", "yandex.by"
+        ]
+      }
+    },
+    "bg": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo", "bing", "wikipedia-bg"
+        ]
+      }
+    },
+    "bn-IN": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-in", "bing", "amazondotcom", "rediff", "wikipedia-bn"
+        ]
+      }
+    },
+    "br": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-france", "bing", "twitter", "wikipedia-br"
+        ]
+      }
+    },
+    "ca": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "twitter", "wikipedia-ca", "diec2"
+        ]
+      }
+    },
+    "cak": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-espanol", "amazondotcom", "duckduckgo", "wikipedia-es"
+        ]
+      }
+    },
+    "cs": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "duckduckgo", "heureka-cz", "mapy-cz", "seznam-cz", "twitter", "wikipedia-cz"
+        ]
+      }
+    },
+    "cy": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-en-GB", "bing", "amazon-en-GB", "duckduckgo", "wikipedia-cy"
+        ]
+      }
+    },
+    "da": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "amazon-co-uk", "twitter", "wikipedia-da"
+        ]
+      }
+    },
+    "de": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-de", "bing", "amazon-de", "duckduckgo", "qwant", "twitter", "wikipedia-de"
+        ]
+      }
+    },
+    "dsb": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-de", "bing", "amazon-de", "twitter", "wikipedia-dsb"
+        ]
+      }
+    },
+    "el": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "skroutz", "twitter", "wikipedia-el"
+        ]
+      }
+    },
+    "en-GB": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-en-GB",  "bing", "amazon-en-GB", "duckduckgo", "qwant", "twitter", "wikipedia"
+        ]
+      }
+    },
+    "en-US": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo", "bing", "amazondotcom", "duckduckgo", "twitter", "wikipedia"
+        ]
+      },
+      "US": {
+        "visibleDefaultEngines": [
+          "yahoo", "google-nocodes", "bing", "amazondotcom", "duckduckgo", "twitter", "wikipedia"
+        ]
+      },
+      "CA": {
+        "visibleDefaultEngines": [
+          "google-nocodes", "yahoo", "bing", "amazondotcom", "duckduckgo", "twitter", "wikipedia"
+        ]
+      }
+    },
+    "en-ZA": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "twitter", "wikipedia"
+        ]
+      }
+    },
+    "eo": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo", "bing", "duckduckgo", "reta-vortaro", "twitter", "wikipedia-eo"
+        ]
+      }
+    },
+    "es-AR": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "mercadolibre-ar", "twitter", "wikipedia-es"
+        ]
+      }
+    },
+    "es-CL": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-cl", "bing", "drae", "mercadolibre-cl", "twitter", "wikipedia-es"
+        ]
+      }
+    },
+    "es-ES": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-es", "bing", "duckduckgo", "twitter", "wikipedia-es"
+        ]
+      }
+    },
+    "es-MX": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-mx", "bing", "amazondotcom", "duckduckgo", "mercadolibre-mx", "twitter", "wikipedia-es"
+        ]
+      }
+    },
+    "et": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "amazon-co-uk", "twitter", "wikipedia-et"
+        ]
+      }
+    },
+    "eu": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-es", "bing", "elebila", "wikipedia-eu"
+        ]
+      }
+    },
+    "fa": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo", "bing", "wikipedia-fa"
+        ]
+      }
+    },
+    "ff": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-france", "bing", "amazon-france", "wikipedia-fr"
+        ]
+      }
+    },
+    "fi": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "amazondotcom", "twitter", "wikipedia-fi", "yahoo-fi"
+        ]
+      }
+    },
+    "fr": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-france", "bing", "duckduckgo", "qwant", "twitter", "wikipedia-fr"
+        ]
+      }
+    },
+    "fy-NL": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "wikipedia-fy-NL", "bolcom-fy-NL"
+        ]
+      }
+    },
+    "ga-IE": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "amazon-en-GB", "tearma", "twitter", "wikipedia-ga-IE"
+        ]
+      }
+    },
+    "gd": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-en-GB", "bing", "duckduckgo", "faclair-beag", "wikipedia-gd"
+        ]
+      }
+    },
+    "gl": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-es", "bing", "amazondotcom", "twitter", "wikipedia-gl"
+        ]
+      }
+    },
+    "gn": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-espanol", "bing", "amazondotcom", "twitter", "wikipedia-gn"
+        ]
+      }
+    },
+    "gu-IN": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-in", "bing", "amazon-in", "wikipedia-gu"
+        ]
+      }
+    },
+    "he": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo", "bing", "amazondotcom", "twitter", "wikipedia-he"
+        ]
+      }
+    },
+    "hi-IN": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-in", "bing", "amazon-in", "twitter", "wikipedia-hi"
+        ]
+      }
+    },
+    "hr": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo", "bing", "amazon-en-GB", "duckduckgo", "twitter", "wikipedia-hr"
+        ]
+      }
+    },
+    "hsb": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-de", "bing", "amazon-de", "twitter", "wikipedia-hsb"
+        ]
+      }
+    },
+    "hu": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "sztaki-en-hu", "vatera", "twitter", "wikipedia-hu"
+        ]
+      }
+    },
+    "hy-AM": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo", "bing", "amazondotcom", "list-am", "wikipedia-hy-AM"
+        ]
+      }
+    },
+    "id": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-id", "bing", "twitter", "wikipedia-id"
+        ]
+      }
+    },
+    "is": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo", "bing", "amazondotcom", "leit-is", "wikipedia-is"
+        ]
+      }
+    },
+    "it": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "twitter", "wikipedia-it"
+        ]
+      }
+    },
+    "ja": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "amazon-jp", "bing", "twitter-ja", "wikipedia-ja", "yahoo-jp"
+        ]
+      }
+    },
+    "ka": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo", "bing", "amazondotcom", "duckduckgo", "wikipedia-ka"
+        ]
+      }
+    },
+    "kab": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-france", "bing", "wikipedia-kab"
+        ]
+      }
+    },
+    "kk": {
+      "default": {
+        "visibleDefaultEngines": [
+          "yandex", "google", "yahoo", "bing", "twitter", "wikipedia-kk"
+        ]
+      }
+    },
+    "km": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo", "bing", "amazondotcom", "twitter", "wikipedia-km"
+        ]
+      }
+    },
+    "kn": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-in", "bing", "amazon-in", "twitter", "wikipedia-kn", "wiktionary-kn"
+        ]
+      }
+    },
+    "ko": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "danawa-kr", "twitter", "daum-kr", "naver-kr"
+        ]
+      }
+    },
+    "lij": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "amazondotcom", "twitter", "wikipedia"
+        ]
+      }
+    },
+    "lo": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo", "bing", "twitter", "wikipedia-lo"
+        ]
+      }
+    },
+    "lt": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "twitter", "wikipedia-lt"
+        ]
+      }
+    },
+    "lv": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "salidzinilv", "sslv", "twitter", "wikipedia-lv"
+        ]
+      }
+    },
+    "mai": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-in", "bing", "amazon-in", "twitter", "wikipedia-hi"
+        ]
+      }
+    },
+    "ml": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-in", "bing", "duckduckgo", "twitter", "wikipedia-ml"
+        ]
+      }
+    },
+    "mr": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-in", "bing", "amazon-in", "rediff", "wikipedia-mr"
+        ]
+      }
+    },
+    "ms": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo", "bing", "amazondotcom", "twitter", "wikipedia-ms"
+        ]
+      }
+    },
+    "my": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo", "bing", "amazondotcom", "twitter", "wikipedia-my"
+        ]
+      }
+    },
+    "nb-NO": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "duckduckgo", "gulesider-mobile-NO", "twitter", "wikipedia-NO"
+        ]
+      }
+    },
+    "ne-NP": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo", "bing", "twitter", "wikipedia-ne"
+        ]
+      }
+    },
+    "nl": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "wikipedia-nl", "bolcom-nl"
+        ]
+      }
+    },
+    "nn-NO": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "duckduckgo", "gulesider-mobile-NO", "twitter", "wikipedia-NN"
+        ]
+      }
+    },
+    "or": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-in", "bing", "amazon-in", "wikipedia-or", "wiktionary-or"
+        ]
+      }
+    },
+    "pa-IN": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-in", "bing", "wikipedia-pa"
+        ]
+      }
+    },
+    "pl": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "bing", "duckduckgo", "twitter", "wikipedia-pl"
+        ]
+      }
+    },
+    "pt-BR": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-br", "bing", "twitter", "wikipedia-br"
+        ]
+      }
+    },
+    "pt-PT": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "wikipedia-ptpt"
+        ]
+      }
+    },
+    "rm": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-ch", "bing", "duckduckgo", "leo_ende_de", "pledarigrond", "wikipedia-rm"
+        ]
+      }
+    },
+    "ro": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "twitter", "wikipedia-ro"
+        ]
+      }
+    },
+    "ru": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yandex-ru", "twitter", "wikipedia-ru"
+        ]
+      }
+    },
+    "sk": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "azet-sk", "slovnik-sk", "twitter", "wikipedia-sk"
+        ]
+      }
+    },
+    "sl": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "ceneje", "odpiralni", "twitter", "wikipedia-sl"
+        ]
+      }
+    },
+    "son": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-france", "bing", "amazon-fr", "twitter", "wikipedia-fr"
+        ]
+      }
+    },
+    "sq": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo", "bing", "amazon-en-GB", "twitter", "wikipedia-sq"
+        ]
+      }
+    },
+    "sr": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo", "bing", "duckduckgo", "twitter", "wikipedia-sr"
+        ]
+      }
+    },
+    "sv-SE": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "prisjakt-sv-SE", "twitter", "wikipedia-sv-SE"
+        ]
+      }
+    },
+    "ta": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-in", "bing", "amazon-in", "duckduckgo", "wikipedia-ta", "wiktionary-ta"
+        ]
+      }
+    },
+    "te": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-in", "bing", "amazon-in", "wikipedia-te", "wiktionary-te"
+        ]
+      }
+    },
+    "th": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "twitter", "wikipedia-th"
+        ]
+      }
+    },
+    "tr": {
+      "default": {
+        "visibleDefaultEngines": [
+          "yandex-tr", "google", "twitter", "wikipedia-tr"
+        ]
+      }
+    },
+    "trs": {
+      "default": {
+        "searchDefault": "Google",
+        "visibleDefaultEngines": [
+          "amazondotcom", "bing", "google", "twitter", "wikipedia-es", "yahoo-espanol"
+        ]
+      }
+    },
+    "uk": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "twitter", "wikipedia-uk", "yandex-market"
+        ]
+      }
+    },
+    "ur": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo-in", "bing", "amazon-in", "duckduckgo", "twitter", "wikipedia-ur"
+        ]
+      }
+    },
+    "uz": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo", "bing", "amazondotcom", "twitter", "wikipedia-uz"
+        ]
+      }
+    },
+    "vi": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "amazondotcom", "twitter", "wikipedia-vi"
+        ]
+      }
+    },
+    "xh": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "yahoo", "bing", "twitter", "wikipedia"
+        ]
+      }
+    },
+    "zh-CN": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "baidu", "bing", "taobao", "wikipedia-zh-CN"
+        ]
+      }
+    },
+    "zh-TW": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "bing", "duckduckgo", "wikipedia-zh-TW"
+        ]
+      }
+    }
+  }
+}
--- a/mozglue/android/APKOpen.cpp
+++ b/mozglue/android/APKOpen.cpp
@@ -472,10 +472,14 @@ ChildProcessInit(int argc, char* argv[])
 
   XREChildData childData;
   return NS_FAILED(gBootstrap->XRE_InitChildProcess(argc, argv, &childData));
 }
 
 extern "C" APKOPEN_EXPORT jboolean MOZ_JNICALL
 Java_org_mozilla_gecko_mozglue_GeckoLoader_neonCompatible(JNIEnv *jenv, jclass jc)
 {
+#ifdef __ARM_EABI__
   return mozilla::supports_neon();
+#else
+  return true;
+#endif // __ARM_EABI__
 }
--- a/testing/web-platform/tests/webvtt/webvtt-file-format-parsing/webvtt-file-parsing/support/newlines.vtt
+++ b/testing/web-platform/tests/webvtt/webvtt-file-format-parsing/webvtt-file-parsing/support/newlines.vtt
@@ -4,9 +4,8 @@ lf
 00:00:00.000 --> 00:00:01.000
 {"id":"lf"}
 
 crlf
 00:00:00.000 --> 00:00:01.000
 {"id":"crlf"}
 
lfcr
00:00:00.000 --> 00:00:01.000
 {"id":"lfcr"}
-
\ No newline at end of file
--- a/toolkit/components/extensions/test/mochitest/chrome.ini
+++ b/toolkit/components/extensions/test/mochitest/chrome.ini
@@ -13,16 +13,17 @@ support-files =
   redirect_auto.sjs
 tags = webextensions in-process-webextensions
 
 [test_chrome_ext_background_debug_global.html]
 skip-if = (os == 'android') # android doesn't have devtools
 [test_chrome_ext_background_page.html]
 skip-if = (toolkit == 'android') # android doesn't have devtools
 [test_chrome_ext_eventpage_warning.html]
+[test_chrome_ext_contentscript_data_uri.html]
 [test_chrome_ext_contentscript_unrecognizedprop_warning.html]
 [test_chrome_ext_hybrid_addons.html]
 [test_chrome_ext_trustworthy_origin.html]
 [test_chrome_ext_webnavigation_resolved_urls.html]
 [test_chrome_native_messaging_paths.html]
 skip-if = os != "mac" && os != "linux"
 [test_ext_cookies_expiry.html]
 [test_ext_cookies_permissions_bad.html]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_contentscript_data_uri.html
@@ -0,0 +1,98 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Test content script matching a data: URI</title>
+  <script src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
+  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script src="chrome://mochikit/content/tests/SimpleTest/ExtensionTestUtils.js"></script>
+  <script src="head.js"></script>
+  <link rel="stylesheet" href="chrome://mochikit/contents/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script>
+"use strict";
+
+add_task(function* test_contentscript_data_uri() {
+  const target = ExtensionTestUtils.loadExtension({
+    files: {
+      "page.html": `<!DOCTYPE html>
+        <meta charset="utf-8">
+        <iframe id="inherited" src="data:text/html;charset=utf-8,inherited"></iframe>
+      `,
+    },
+    background() {
+      browser.test.sendMessage("page", browser.runtime.getURL("page.html"));
+    },
+  });
+
+  const scripts = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["webNavigation"],
+      content_scripts: [{
+        all_frames: true,
+        matches: ["<all_urls>"],
+        run_at: "document_start",
+        css: ["all_urls.css"],
+        js: ["all_urls.js"],
+      }],
+    },
+    files: {
+      "all_urls.css": `
+        body { background: yellow; }
+      `,
+      "all_urls.js": function() {
+        document.body.style.color = "red";
+        browser.test.assertTrue(location.protocol !== "data:",
+                                `Matched document not a data URI: ${location.href}`);
+      },
+    },
+    background() {
+      browser.webNavigation.onCompleted.addListener(({url, frameId}) => {
+        browser.test.log(`Document loading complete: ${url}`);
+        if (frameId === 0) {
+          browser.test.sendMessage("tab-ready", url);
+        }
+      });
+    },
+  });
+
+  yield target.startup();
+  yield scripts.startup();
+
+  // Test extension page with a data: iframe.
+  const page = yield target.awaitMessage("page");
+  const win = window.open(page);
+
+  yield scripts.awaitMessage("tab-ready");
+  is(win.location.href, page, "Extension page loaded into a tab");
+  is(win.document.readyState, "complete", "Page finished loading");
+
+  const iframe = win.document.getElementById("inherited").contentWindow;
+  is(iframe.document.readyState, "complete", "iframe finished loading");
+
+  const style1 = iframe.getComputedStyle(iframe.document.body);
+  is(style1.color, "rgb(0, 0, 0)", "iframe text color is unmodified");
+  is(style1.backgroundColor, "rgba(0, 0, 0, 0)", "iframe background unmodified");
+
+  // Test extension tab navigated to a data: URI.
+  const data = "data:text/html;charset=utf-8,also-inherits";
+  win.location.href = data;
+
+  yield scripts.awaitMessage("tab-ready");
+  is(win.location.href, data, "Extension tab navigated to a data: URI");
+  is(win.document.readyState, "complete", "Tab finished loading");
+
+  const style2 = win.getComputedStyle(win.document.body);
+  is(style2.color, "rgb(0, 0, 0)", "Tab text color is unmodified");
+  is(style2.backgroundColor, "rgba(0, 0, 0, 0)", "Tab background unmodified");
+
+  win.close();
+  yield target.unload();
+  yield scripts.unload();
+});
+
+</script>
+
+</body>
+</html>
--- a/toolkit/content/aboutSupport.js
+++ b/toolkit/content/aboutSupport.js
@@ -147,16 +147,26 @@ var snapshotFormatters = {
         $.new("td", extension.name),
         $.new("td", extension.version),
         $.new("td", extension.isActive),
         $.new("td", extension.id),
       ]);
     }));
   },
 
+  features: function features(data) {
+    $.append($("features-tbody"), data.map(function(feature) {
+      return $.new("tr", [
+        $.new("td", feature.name),
+        $.new("td", feature.version),
+        $.new("td", feature.id),
+      ]);
+    }));
+  },
+
   experiments: function experiments(data) {
     $.append($("experiments-tbody"), data.map(function(experiment) {
       return $.new("tr", [
         $.new("td", experiment.name),
         $.new("td", experiment.id),
         $.new("td", experiment.description),
         $.new("td", experiment.active),
         $.new("td", experiment.endDate),
--- a/toolkit/content/aboutSupport.xhtml
+++ b/toolkit/content/aboutSupport.xhtml
@@ -295,16 +295,40 @@
         <a href="about:crashes" id="crashes-allReportsWithPending" class="block">&aboutSupport.crashes.allReports;</a>
       </p>
       <p id="crashes-noConfig" class="hidden no-copy">&aboutSupport.crashes.noConfig;</p>
 
 #endif
       <!-- - - - - - - - - - - - - - - - - - - - - -->
 
       <h2 class="major-section">
+        &aboutSupport.featuresTitle;
+      </h2>
+
+      <table>
+        <thead>
+          <tr>
+            <th>
+              &aboutSupport.featureName;
+            </th>
+            <th>
+              &aboutSupport.featureVersion;
+            </th>
+            <th>
+              &aboutSupport.featureId;
+            </th>
+          </tr>
+        </thead>
+        <tbody id="features-tbody">
+        </tbody>
+      </table>
+
+      <!-- - - - - - - - - - - - - - - - - - - - - -->
+
+      <h2 class="major-section">
         &aboutSupport.extensionsTitle;
       </h2>
 
       <table>
         <thead>
           <tr>
             <th>
               &aboutSupport.extensionName;
--- a/toolkit/locales/en-US/chrome/global/aboutSupport.dtd
+++ b/toolkit/locales/en-US/chrome/global/aboutSupport.dtd
@@ -20,16 +20,21 @@ This is likely the same like id.heading 
 <!ENTITY aboutSupport.crashes.noConfig "This application has not been configured to display crash reports.">
 
 <!ENTITY aboutSupport.extensionsTitle "Extensions">
 <!ENTITY aboutSupport.extensionName "Name">
 <!ENTITY aboutSupport.extensionEnabled "Enabled">
 <!ENTITY aboutSupport.extensionVersion "Version">
 <!ENTITY aboutSupport.extensionId "ID">
 
+<!ENTITY aboutSupport.featuresTitle "&brandShortName; Features">
+<!ENTITY aboutSupport.featureName "Name">
+<!ENTITY aboutSupport.featureVersion "Version">
+<!ENTITY aboutSupport.featureId "ID">
+
 <!ENTITY aboutSupport.experimentsTitle "Experimental Features">
 <!ENTITY aboutSupport.experimentName "Name">
 <!ENTITY aboutSupport.experimentId "ID">
 <!ENTITY aboutSupport.experimentDescription "Description">
 <!ENTITY aboutSupport.experimentActive "Active">
 <!ENTITY aboutSupport.experimentEndDate "End Date">
 <!ENTITY aboutSupport.experimentHomepage "Homepage">
 <!ENTITY aboutSupport.experimentBranch "Branch">
--- a/toolkit/modules/Troubleshoot.jsm
+++ b/toolkit/modules/Troubleshoot.jsm
@@ -237,16 +237,17 @@ var dataProviders = {
     const keyMozilla = Services.urlFormatter.formatURL("%MOZILLA_API_KEY%").trim();
     data.keyMozillaFound = keyMozilla != "no-mozilla-api-key" && keyMozilla.length > 0;
 
     done(data);
   },
 
   extensions: function extensions(done) {
     AddonManager.getAddonsByTypes(["extension"], function(extensions) {
+      extensions = extensions.filter(e => !e.isSystem);
       extensions.sort(function(a, b) {
         if (a.isActive != b.isActive)
           return b.isActive ? 1 : -1;
 
         // In some unfortunate cases addon names can be null.
         let aname = a.name || null;
         let bname = b.name || null;
         let lc = aname.localeCompare(bname);
@@ -261,16 +262,40 @@ var dataProviders = {
         return props.reduce(function(extData, prop) {
           extData[prop] = ext[prop];
           return extData;
         }, {});
       }));
     });
   },
 
+  features: function features(done) {
+    AddonManager.getAddonsByTypes(["extension"], function(features) {
+      features = features.filter(f => f.isSystem);
+      features.sort(function(a, b) {
+        // In some unfortunate cases addon names can be null.
+        let aname = a.name || null;
+        let bname = b.name || null;
+        let lc = aname.localeCompare(bname);
+        if (lc != 0)
+          return lc;
+        if (a.version != b.version)
+          return a.version > b.version ? 1 : -1;
+        return 0;
+      });
+      let props = ["name", "version", "id"];
+      done(features.map(function(f) {
+        return props.reduce(function(fData, prop) {
+          fData[prop] = f[prop];
+          return fData;
+        }, {});
+      }));
+    });
+  },
+
   experiments: function experiments(done) {
     if (Experiments === undefined) {
       done([]);
       return;
     }
 
     // getExperiments promises experiment history
     Experiments.instance().getExperiments().then(
--- a/toolkit/modules/tests/browser/browser_Troubleshoot.js
+++ b/toolkit/modules/tests/browser/browser_Troubleshoot.js
@@ -200,16 +200,37 @@ const SNAPSHOT_SCHEMA = {
           },
           isActive: {
             required: true,
             type: "boolean",
           },
         },
       },
     },
+    features: {
+      required: true,
+      type: "array",
+      items: {
+        type: "object",
+        properties: {
+          name: {
+            required: true,
+            type: "string",
+          },
+          version: {
+            required: true,
+            type: "string",
+          },
+          id: {
+            required: true,
+            type: "string",
+          },
+        },
+      },
+    },
     modifiedPreferences: {
       required: true,
       type: "object",
     },
     lockedPreferences: {
       required: true,
       type: "object",
     },