Merge inbound to mozilla-central. a=merge
authorNarcis Beleuzu <nbeleuzu@mozilla.com>
Fri, 12 Oct 2018 00:48:03 +0300
changeset 496455 0de23e038872
parent 496443 8d8c2a0e01b4 (current diff)
parent 496454 3a34d4624a1a (diff)
child 496503 f8d429e8d630
child 496558 e642ac929fe4
push id9984
push userffxbld-merge
push dateMon, 15 Oct 2018 21:07:35 +0000
treeherdermozilla-beta@183d27ea8570 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone64.0a1
first release with
nightly linux32
0de23e038872 / 64.0a1 / 20181011220118 / files
nightly linux64
0de23e038872 / 64.0a1 / 20181011220118 / files
nightly mac
0de23e038872 / 64.0a1 / 20181011220118 / files
nightly win32
0de23e038872 / 64.0a1 / 20181011220118 / files
nightly win64
0de23e038872 / 64.0a1 / 20181011220118 / 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 mozilla-central. a=merge
--- a/browser/base/content/test/siteIdentity/browser.ini
+++ b/browser/base/content/test/siteIdentity/browser.ini
@@ -103,8 +103,9 @@ support-files =
 tags = mcb
 support-files =
   test_no_mcb_for_onions.html
 [browser_check_identity_state.js]
 [browser_iframe_navigation.js]
 support-files =
   iframe_navigation.html
 [browser_navigation_failures.js]
+[browser_secure_transport_insecure_scheme.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/siteIdentity/browser_secure_transport_insecure_scheme.js
@@ -0,0 +1,136 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that an insecure resource routed over a secure transport is considered
+// insecure in terms of the site identity panel. We achieve this by running an
+// HTTP-over-TLS "proxy" and having Firefox request an http:// URI over it.
+
+// But first, a quick test that we don't incorrectly treat a
+// blob:https://example.com URI as secure.
+add_task(async function() {
+  let uri = getRootDirectory(gTestPath).replace("chrome://mochitests/content",
+                                                "https://example.com") + "dummy_page.html";
+  await BrowserTestUtils.withNewTab(uri, async (browser) => {
+    await ContentTask.spawn(browser, null, async () => {
+      Cu.importGlobalProperties(["Blob", "URL"]);
+      let debug = {hello: "world"};
+      let blob = new Blob([JSON.stringify(debug, null, 2)], {type: "application/json"});
+      let blobUri = URL.createObjectURL(blob);
+      content.document.location = blobUri;
+    });
+    await BrowserTestUtils.browserLoaded(browser);
+    let identityMode = window.document.getElementById("identity-box").className;
+    is(identityMode, "unknownIdentity", "identity should be 'unknown'");
+  });
+});
+
+// This server pretends to be a HTTP over TLS proxy. It isn't really, but this
+// is sufficient for the purposes of this test.
+function startServer(cert) {
+  let tlsServer = Cc["@mozilla.org/network/tls-server-socket;1"]
+                    .createInstance(Ci.nsITLSServerSocket);
+  tlsServer.init(-1, true, -1);
+  tlsServer.serverCert = cert;
+
+  let input, output;
+
+  let listener = {
+    onSocketAccepted(socket, transport) {
+      let connectionInfo = transport.securityInfo
+                           .QueryInterface(Ci.nsITLSServerConnectionInfo);
+      connectionInfo.setSecurityObserver(listener);
+      input = transport.openInputStream(0, 0, 0);
+      output = transport.openOutputStream(0, 0, 0);
+    },
+
+    onHandshakeDone(socket, status) {
+      input.asyncWait({
+        onInputStreamReady(readyInput) {
+          try {
+            let request = NetUtil.readInputStreamToString(readyInput,
+                                                          readyInput.available());
+            ok(request.startsWith("GET ") && request.includes("HTTP/1.1"),
+               "expecting an HTTP/1.1 GET request");
+            let response = "HTTP/1.1 200 OK\r\nContent-Type:text/plain\r\n" +
+                           "Connection:Close\r\nContent-Length:2\r\n\r\nOK";
+            output.write(response, response.length);
+          } catch (e) {
+            info(e);
+          }
+        },
+      }, 0, 0, Services.tm.currentThread);
+    },
+
+    onStopListening() {
+      input.close();
+      output.close();
+    },
+  };
+
+  tlsServer.setSessionTickets(false);
+  tlsServer.asyncListen(listener);
+
+  return tlsServer;
+}
+
+add_task(async function() {
+  await SpecialPowers.pushPrefEnv({
+    // This test fails on some platforms if we leave IPv6 enabled.
+    set: [["network.dns.disableIPv6", true]],
+  });
+
+  let certService = Cc["@mozilla.org/security/local-cert-service;1"]
+                      .getService(Ci.nsILocalCertService);
+  let certOverrideService = Cc["@mozilla.org/security/certoverride;1"]
+                              .getService(Ci.nsICertOverrideService);
+
+  let cert = await new Promise((resolve, reject) => {
+    certService.getOrCreateCert("http-over-https-proxy", {
+      handleCert(c, rv) {
+        if (!Components.isSuccessCode(rv)) {
+          reject(rv);
+          return;
+        }
+        resolve(c);
+      },
+    });
+  });
+  // Start the proxy and configure Firefox to trust its certificate.
+  let server = startServer(cert);
+  let overrideBits = Ci.nsICertOverrideService.ERROR_UNTRUSTED |
+                     Ci.nsICertOverrideService.ERROR_MISMATCH;
+  certOverrideService.rememberValidityOverride("localhost", server.port, cert,
+                                               overrideBits, true);
+  // Configure Firefox to use the proxy.
+  let systemProxySettings = {
+    QueryInterface: ChromeUtils.generateQI([Ci.nsISystemProxySettings]),
+    mainThreadOnly: true,
+    PACURI: null,
+    getProxyForURI: (aSpec, aScheme, aHost, aPort) => {
+      return `HTTPS localhost:${server.port}`;
+    },
+  };
+  let oldProxyType = Services.prefs.getIntPref("network.proxy.type");
+  Services.prefs.setIntPref("network.proxy.type", Ci.nsIProtocolProxyService.PROXYCONFIG_SYSTEM);
+  let { MockRegistrar } = ChromeUtils.import("resource://testing-common/MockRegistrar.jsm", {});
+  let mockProxy = MockRegistrar.register("@mozilla.org/system-proxy-settings;1",
+                                         systemProxySettings);
+  // Register cleanup to undo the configuration changes we've made.
+  registerCleanupFunction(() => {
+    certOverrideService.clearValidityOverride("localhost", server.port);
+    Services.prefs.setIntPref("network.proxy.type", oldProxyType);
+    MockRegistrar.unregister(mockProxy);
+    server.close();
+  });
+
+  // Navigate to 'http://example.com'. Our proxy settings will route this via
+  // the "proxy" we just started. Even though our connection to the proxy is
+  // secure, in a real situation the connection from the proxy to
+  // http://example.com won't be secure, so we treat it as not secure.
+  await BrowserTestUtils.withNewTab("http://example.com/", async (browser) => {
+    let identityMode = window.document.getElementById("identity-box").className;
+    is(identityMode, "unknownIdentity", "identity should be 'unknown'");
+  });
+});
--- a/devtools/client/inspector/flexbox/components/FlexItem.js
+++ b/devtools/client/inspector/flexbox/components/FlexItem.js
@@ -2,48 +2,57 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { PureComponent } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const { translateNodeFrontToGrip } = require("devtools/client/inspector/shared/utils");
 
-loader.lazyGetter(this, "Rep", function() {
-  return require("devtools/client/shared/components/reps/reps").REPS.Rep;
-});
-loader.lazyGetter(this, "MODE", function() {
-  return require("devtools/client/shared/components/reps/reps").MODE;
-});
-
-loader.lazyRequireGetter(this, "translateNodeFrontToGrip", "devtools/client/inspector/shared/utils", true);
+const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
+const { Rep } = REPS;
+const ElementNode = REPS.ElementNode;
 
 const Types = require("../types");
 
 class FlexItem extends PureComponent {
   static get propTypes() {
     return {
       flexItem: PropTypes.shape(Types.flexItem).isRequired,
+      onHideBoxModelHighlighter: PropTypes.func.isRequired,
+      onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
       setSelectedNode: PropTypes.func.isRequired,
     };
   }
 
   render() {
-    const { nodeFront } = this.props.flexItem;
+    const {
+      flexItem,
+      onHideBoxModelHighlighter,
+      onShowBoxModelHighlighterForNode,
+      setSelectedNode,
+    } = this.props;
+    const { nodeFront } = flexItem;
 
     return (
       dom.li({},
         dom.button(
           {
             className: "devtools-button devtools-monospace",
-            onClick: () => this.props.setSelectedNode(nodeFront),
+            onClick: () => {
+              setSelectedNode(nodeFront);
+              onHideBoxModelHighlighter();
+            },
+            onMouseOut: () => onHideBoxModelHighlighter(),
+            onMouseOver: () => onShowBoxModelHighlighterForNode(nodeFront),
           },
           Rep({
-            defaultRep: Rep.ElementNode,
+            defaultRep: ElementNode,
             mode: MODE.TINY,
             object: translateNodeFrontToGrip(nodeFront),
           })
         )
       )
     );
   }
 }
--- a/devtools/client/inspector/flexbox/components/FlexItemList.js
+++ b/devtools/client/inspector/flexbox/components/FlexItemList.js
@@ -11,32 +11,38 @@ const PropTypes = require("devtools/clie
 const FlexItem = createFactory(require(("./FlexItem")));
 
 const Types = require("../types");
 
 class FlexItemList extends PureComponent {
   static get propTypes() {
     return {
       flexItems: PropTypes.arrayOf(PropTypes.shape(Types.flexItem)).isRequired,
+      onHideBoxModelHighlighter: PropTypes.func.isRequired,
+      onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
       setSelectedNode: PropTypes.func.isRequired,
     };
   }
 
   render() {
     const {
       flexItems,
+      onHideBoxModelHighlighter,
+      onShowBoxModelHighlighterForNode,
       setSelectedNode,
     } = this.props;
 
     return (
       dom.ol(
         { className: "flex-item-list" },
         flexItems.map(flexItem => FlexItem({
           key: flexItem.actorID,
           flexItem,
+          onHideBoxModelHighlighter,
+          onShowBoxModelHighlighterForNode,
           setSelectedNode,
         }))
       )
     );
   }
 }
 
 module.exports = FlexItemList;
--- a/devtools/client/inspector/flexbox/components/Flexbox.js
+++ b/devtools/client/inspector/flexbox/components/Flexbox.js
@@ -42,33 +42,33 @@ class Flexbox extends PureComponent {
       onSetFlexboxOverlayColor: PropTypes.func.isRequired,
       onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
       onToggleFlexboxHighlighter: PropTypes.func.isRequired,
       setSelectedNode: PropTypes.func.isRequired,
     };
   }
 
   renderFlexContainerProperties() {
-    // Don't show the flex container properties for the parent flex container of the
-    // selected element.
-    if (this.props.flexContainer.isFlexItemContainer) {
-      return null;
-    }
-
     return FlexContainerProperties({
       properties: this.props.flexContainer.properties,
     });
   }
 
   renderFlexItemList() {
-    const { setSelectedNode } = this.props;
+    const {
+      onHideBoxModelHighlighter,
+      onShowBoxModelHighlighterForNode,
+      setSelectedNode,
+    } = this.props;
     const { flexItems } = this.props.flexContainer;
 
     return FlexItemList({
       flexItems,
+      onHideBoxModelHighlighter,
+      onShowBoxModelHighlighterForNode,
       setSelectedNode,
     });
   }
 
   renderFlexItemSizing() {
     const {
       color,
     } = this.props.flexbox;
@@ -128,15 +128,15 @@ class Flexbox extends PureComponent {
           onHideBoxModelHighlighter,
           onSetFlexboxOverlayColor,
           onShowBoxModelHighlighterForNode,
           onToggleFlexboxHighlighter,
           setSelectedNode,
         }),
         !flexItemShown && flexItems.length > 0 ? this.renderFlexItemList() : null,
         flexItemShown ? this.renderFlexItemSizing() : null,
-        this.renderFlexContainerProperties()
+        !flexItemShown ? this.renderFlexContainerProperties() : null
       )
     );
   }
 }
 
 module.exports = Flexbox;
--- a/devtools/client/inspector/flexbox/components/Header.js
+++ b/devtools/client/inspector/flexbox/components/Header.js
@@ -92,19 +92,24 @@ class Header extends PureComponent {
     const {
       flexContainer,
       setSelectedNode,
     } = this.props;
     const {
       flexItems,
       flexItemShown,
     } = flexContainer;
+    const flexItem = flexItems.find(item => item.nodeFront.actorID === flexItemShown);
+
+    if (!flexItem) {
+      return null;
+    }
 
     return FlexItemSelector({
-      flexItem: flexItems.find(item => item.nodeFront.actorID === flexItemShown),
+      flexItem,
       flexItems,
       setSelectedNode,
     });
   }
 
   render() {
     const {
       flexContainer,
--- a/devtools/client/themes/layout.css
+++ b/devtools/client/themes/layout.css
@@ -92,17 +92,16 @@
 /**
  * Header
  */
 
 .flex-header {
   display: flex;
   align-items: center;
   height: 32px;
-  border-bottom: 1px solid var(--theme-splitter-color);
   padding: 0 3px;
 }
 
 .flex-header-button-prev::before {
   background-image: url("chrome://devtools/skin/images/arrowhead-left.svg");
   background-size: 16px;
 }
 
@@ -118,18 +117,21 @@
   justify-content: center;
   padding: 0;
 }
 
 /**
  * Flex Item List
  */
 
+.flex-header + .flex-item-list {
+  border-block-start: 1px solid var(--theme-splitter-color);
+}
+
 .flex-item-list {
-  border-bottom: 1px solid var(--theme-splitter-color);
   font-size: 12px;
   margin: 0;
   padding-inline-start: 34px;
   padding-top: 5px;
   padding-bottom: 5px;
 }
 
 .flex-item-list .devtools-button {
@@ -158,16 +160,17 @@
   width: 85%;
 }
 
 /**
  * Flex Item Sizing Outline
  */
 
 .flex-outline-container {
+  border-block-start: 1px solid var(--theme-splitter-color);
   display: flex;
   justify-content: center;
 }
 
 .flex-outline {
   display: grid;
   margin: 2em 0;
   grid-auto-rows: 35px;
@@ -343,16 +346,17 @@
  font-weight: 600;
 }
 
 /**
  * Flex Container Properties
  */
 
 #flex-container-properties {
+  border-block-start: 1px solid var(--theme-splitter-color);
   margin-bottom: 5px;
 }
 
 #flex-container-properties .layout-properties-header {
   padding: 5px 0;
   padding-inline-start: 20px;
 }
 
--- a/dom/base/domerr.msg
+++ b/dom/base/domerr.msg
@@ -53,31 +53,32 @@ DOM4_MSG_DEF(InvalidStateError, "The mat
 /* XPath errors from http://www.w3.org/TR/DOM-Level-3-XPath/ */
 
 DOM4_MSG_DEF(SyntaxError, "The expression is not a legal expression.", NS_ERROR_DOM_INVALID_EXPRESSION_ERR)
 DOM4_MSG_DEF(TypeError, "The expression cannot be converted to return the specified type.", NS_ERROR_DOM_TYPE_ERR)
 
 /* IndexedDB errors http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#exceptions */
 
 DOM4_MSG_DEF(UnknownError, "The operation failed for reasons unrelated to the database itself and not covered by any other error code.", NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR)
-DOM4_MSG_DEF(ConstraintError, "A mutation operation in the transaction failed because a constraint was not satisfied. For example, an object such as an object store or index already exists and a new one was being attempted to be created.", NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR)
+DOM4_MSG_DEF(ConstraintError, "A mutation operation in the transaction failed because a constraint was not satisfied.", NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR)
+DOM4_MSG_DEF(ConstraintError, "Object store renamed to an already existing object store.", NS_ERROR_DOM_INDEXEDDB_RENAME_OBJECT_STORE_ERR)
+DOM4_MSG_DEF(ConstraintError, "Index already exists and a new one was being attempted to be created.", NS_ERROR_DOM_INDEXEDDB_RENAME_INDEX_ERR)
 DOM4_MSG_DEF(DataError, "Data provided to an operation does not meet requirements.", NS_ERROR_DOM_INDEXEDDB_DATA_ERR)
 DOM4_MSG_DEF(DataError, "No key or key range specified.", NS_ERROR_DOM_INDEXEDDB_KEY_ERR)
 DOM4_MSG_DEF(TransactionInactiveError, "A request was placed against a transaction which is currently not active, or which is finished.", NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR)
 DOM4_MSG_DEF(ReadOnlyError, "A mutation operation was attempted in a READ_ONLY transaction.", NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR)
 DOM4_MSG_DEF(VersionError, "The operation failed because the stored database is a higher version than the version requested.", NS_ERROR_DOM_INDEXEDDB_VERSION_ERR)
 
 DOM4_MSG_DEF(NotFoundError, "The operation failed because the requested database object could not be found. For example, an object store did not exist but was being opened.", NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR)
 DOM4_MSG_DEF(InvalidStateError, "A mutation operation was attempted on a database that did not allow mutations.", NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR)
 DOM4_MSG_DEF(AbortError, "A request was aborted, for example through a call to IDBTransaction.abort.", NS_ERROR_DOM_INDEXEDDB_ABORT_ERR)
 DOM4_MSG_DEF(TimeoutError, "A lock for the transaction could not be obtained in a reasonable time.", NS_ERROR_DOM_INDEXEDDB_TIMEOUT_ERR)
 DOM4_MSG_DEF(QuotaExceededError, "The current transaction exceeded its quota limitations.", NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR)
 
 /* A Non-standard IndexedDB error */
-
 DOM_MSG_DEF(NS_ERROR_DOM_INDEXEDDB_RECOVERABLE_ERR, "The operation failed because the database was prevented from taking an action. The operation might be able to succeed if the application performs some recovery steps and retries the entire transaction. For example, there was not enough remaining storage space, or the storage quota was reached and the user declined to give more space to the database.")
 
 /* FileSystem DOM errors. */
 DOM4_MSG_DEF(InvalidAccessError, "Invalid file system path.", NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR)
 DOM4_MSG_DEF(InvalidModificationError, "Failed to modify the file.", NS_ERROR_DOM_FILESYSTEM_INVALID_MODIFICATION_ERR)
 DOM4_MSG_DEF(NoModificationAllowedError, "Modifications are not allowed for this file", NS_ERROR_DOM_FILESYSTEM_NO_MODIFICATION_ALLOWED_ERR)
 DOM4_MSG_DEF(AbortError, "File already exists.", NS_ERROR_DOM_FILESYSTEM_PATH_EXISTS_ERR)
 DOM4_MSG_DEF(TypeMismatchError, "The type of the file is incompatible with the expected type.", NS_ERROR_DOM_FILESYSTEM_TYPE_MISMATCH_ERR)
--- a/dom/indexedDB/IDBDatabase.cpp
+++ b/dom/indexedDB/IDBDatabase.cpp
@@ -444,17 +444,20 @@ IDBDatabase::CreateObjectStore(
     return nullptr;
   }
 
   nsTArray<ObjectStoreSpec>& objectStores = mSpec->objectStores();
   for (uint32_t count = objectStores.Length(), index = 0;
        index < count;
        index++) {
     if (aName == objectStores[index].metadata().name()) {
-      aRv.Throw(NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR);
+      aRv.ThrowDOMException(NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR,
+        nsPrintfCString(
+          "Object store named '%s' already exists at index '%u'",
+          NS_ConvertUTF16toUTF8(aName).get(), index));
       return nullptr;
     }
   }
 
   if (!keyPath.IsAllowedForObjectStore(aOptionalParameters.mAutoIncrement)) {
     aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
     return nullptr;
   }
@@ -1318,17 +1321,17 @@ IDBDatabase::RenameObjectStore(int64_t a
        objIndex++) {
     const ObjectStoreSpec& objSpec = objectStores[objIndex];
     if (objSpec.metadata().id() == aObjectStoreId) {
       MOZ_ASSERT(!foundObjectStoreSpec);
       foundObjectStoreSpec = &objectStores[objIndex];
       continue;
     }
     if (aName == objSpec.metadata().name()) {
-      return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR;
+      return NS_ERROR_DOM_INDEXEDDB_RENAME_OBJECT_STORE_ERR;
     }
   }
 
   MOZ_ASSERT(foundObjectStoreSpec);
 
   // Update the name of the matched object store.
   foundObjectStoreSpec->metadata().name() = nsString(aName);
 
@@ -1366,17 +1369,17 @@ IDBDatabase::RenameIndex(int64_t aObject
        idxIndex++) {
     const IndexMetadata& metadata = indexes[idxIndex];
     if (metadata.id() == aIndexId) {
       MOZ_ASSERT(!foundIndexMetadata);
       foundIndexMetadata = &indexes[idxIndex];
       continue;
     }
     if (aName == metadata.name()) {
-      return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR;
+      return NS_ERROR_DOM_INDEXEDDB_RENAME_INDEX_ERR;
     }
   }
 
   MOZ_ASSERT(foundIndexMetadata);
 
   // Update the name of the matched object store.
   foundIndexMetadata->name() = nsString(aName);
 
--- a/dom/indexedDB/IDBObjectStore.cpp
+++ b/dom/indexedDB/IDBObjectStore.cpp
@@ -2234,17 +2234,19 @@ IDBObjectStore::CreateIndex(const nsAStr
     return nullptr;
   }
 
   auto& indexes = const_cast<nsTArray<IndexMetadata>&>(mSpec->indexes());
   for (uint32_t count = indexes.Length(), index = 0;
        index < count;
        index++) {
     if (aName == indexes[index].name()) {
-      aRv.Throw(NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR);
+      aRv.ThrowDOMException(NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR,
+        nsPrintfCString("Index named '%s' already exists at index '%u'",
+                        NS_ConvertUTF16toUTF8(aName).get(), index));
       return nullptr;
     }
   }
 
   KeyPath keyPath(0);
   if (aKeyPath.IsString()) {
     if (NS_FAILED(KeyPath::Parse(aKeyPath.GetAsString(), &keyPath)) ||
         !keyPath.IsValid()) {
--- a/dom/indexedDB/test/mochitest.ini
+++ b/dom/indexedDB/test/mochitest.ini
@@ -24,16 +24,17 @@ support-files =
   unit/test_add_twice_failure.js
   unit/test_advance.js
   unit/test_autoIncrement.js
   unit/test_autoIncrement_indexes.js
   unit/test_blob_file_backed.js
   unit/test_blocked_order.js
   unit/test_clear.js
   unit/test_complex_keyPaths.js
+  unit/test_constraint_error_messages.js
   unit/test_count.js
   unit/test_create_index.js
   unit/test_create_index_with_integer_keys.js
   unit/test_create_locale_aware_index.js
   unit/test_create_objectStore.js
   unit/test_cursor_mutation.js
   unit/test_cursor_update_updates_indexes.js
   unit/test_cursors.js
@@ -133,16 +134,17 @@ skip-if = (e10s && os == 'win' && os_ver
 [test_blob_worker_xhr_post_multifile.html]
 skip-if = e10s && os == 'win' && os_version == '6.1' # Bug 1342415
 [test_blob_worker_xhr_read.html]
 [test_blob_worker_xhr_read_slice.html]
 [test_blocked_order.html]
 [test_bug937006.html]
 [test_clear.html]
 [test_complex_keyPaths.html]
+[test_constraint_error_messages.html]
 [test_count.html]
 [test_create_index.html]
 [test_create_index_with_integer_keys.html]
 [test_create_objectStore.html]
 [test_cursor_mutation.html]
 [test_cursor_update_updates_indexes.html]
 [test_cursors.html]
 [test_database_onclose.html]
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_constraint_error_messages.html
@@ -0,0 +1,18 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>Indexed Database Property Test</title>
+
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+  <script type="text/javascript" src="unit/test_constraint_error_messages.js"></script>
+  <script type="text/javascript" src="helpers.js"></script>
+</head>
+
+<body onload="runTest();"></body>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/unit/test_constraint_error_messages.js
@@ -0,0 +1,64 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var testGenerator = testSteps();
+
+function* testSteps()
+{
+  const name = this.window ? window.location.pathname : "Splendid Test";
+  const objectStoreName = "foo";
+  const indexName = "bar", keyPath = "bar";
+
+  info("Opening database");
+
+  let request = indexedDB.open(name);
+  request.onerror = errorHandler;
+  request.onupgradeneeded = grabEventAndContinueHandler;
+  request.onsuccess = unexpectedSuccessHandler;
+
+  let event = yield undefined;
+  let db = event.target.result;
+
+  info("Creating objectStore");
+
+  let objectStore = db.createObjectStore(objectStoreName);
+
+  info("Creating a duplicated object store to get an error");
+
+  try {
+    db.createObjectStore(objectStoreName);
+    ok(false,
+       "ConstraintError should be thrown if object store already exists");
+  } catch (e) {
+    ok(true, "ConstraintError should be thrown if object store already exists");
+    is(e.message,
+       "Object store named '" + objectStoreName +
+       "' already exists at index '0'",
+       "Threw with correct error message");
+  }
+
+  info("Creating an index");
+
+  objectStore.createIndex(indexName, keyPath);
+
+  info("Creating a duplicated indexes to verify the error message");
+
+  try {
+    objectStore.createIndex(indexName, keyPath);
+
+    ok(false, "ConstraintError should be thrown if index already exists");
+  } catch (e) {
+    ok(true, "ConstraintError should be thrown if index already exists");
+    is(e.message,
+       "Index named '" + indexName + "' already exists at index '0'",
+       "Threw with correct error message");
+  }
+
+  request.onsuccess = grabEventAndContinueHandler;
+  yield undefined;
+  db.close();
+
+  finishTest();
+}
--- a/gfx/thebes/gfxDWriteFontList.cpp
+++ b/gfx/thebes/gfxDWriteFontList.cpp
@@ -670,47 +670,40 @@ gfxDWriteFontEntry::GetVariationInstance
 }
 
 gfxFont *
 gfxDWriteFontEntry::CreateFontInstance(const gfxFontStyle* aFontStyle)
 {
     bool needsBold = aFontStyle->NeedsSyntheticBold(this);
     DWRITE_FONT_SIMULATIONS sims =
         needsBold ? DWRITE_FONT_SIMULATIONS_BOLD : DWRITE_FONT_SIMULATIONS_NONE;
-    if (HasVariations() && !aFontStyle->variationSettings.IsEmpty()) {
-        // If we need to apply variations, we can't use the cached mUnscaledFont
-        // or mUnscaledFontBold here.
-        // XXX todo: consider caching a small number of variation instances?
-        RefPtr<IDWriteFontFace> fontFace;
-        nsresult rv = CreateFontFace(getter_AddRefs(fontFace),
-                                     aFontStyle,
-                                     sims);
-        if (NS_FAILED(rv)) {
-            return nullptr;
-        }
-        RefPtr<UnscaledFontDWrite> unscaledFont =
-            new UnscaledFontDWrite(fontFace, mIsSystemFont ? mFont : nullptr, sims);
-        return new gfxDWriteFont(unscaledFont, this, aFontStyle);
-    }
-
     ThreadSafeWeakPtr<UnscaledFontDWrite>& unscaledFontPtr =
         needsBold ? mUnscaledFontBold : mUnscaledFont;
     RefPtr<UnscaledFontDWrite> unscaledFont(unscaledFontPtr);
     if (!unscaledFont) {
         RefPtr<IDWriteFontFace> fontFace;
         nsresult rv = CreateFontFace(getter_AddRefs(fontFace), aFontStyle, sims);
         if (NS_FAILED(rv)) {
             return nullptr;
         }
         unscaledFont =
             new UnscaledFontDWrite(fontFace,
                                    mIsSystemFont ? mFont : nullptr, sims);
         unscaledFontPtr = unscaledFont;
     }
-    return new gfxDWriteFont(unscaledFont, this, aFontStyle);
+    RefPtr<IDWriteFontFace> fontFace;
+    if (HasVariations() && !aFontStyle->variationSettings.IsEmpty()) {
+        nsresult rv = CreateFontFace(getter_AddRefs(fontFace),
+                                     aFontStyle,
+                                     sims);
+        if (NS_FAILED(rv)) {
+            return nullptr;
+        }
+    }
+    return new gfxDWriteFont(unscaledFont, this, aFontStyle, fontFace);
 }
 
 nsresult
 gfxDWriteFontEntry::CreateFontFace(IDWriteFontFace **aFontFace,
                                    const gfxFontStyle* aFontStyle,
                                    DWRITE_FONT_SIMULATIONS aSimulations)
 {
     // Convert an OpenType font tag from our uint32_t representation
--- a/gfx/thebes/gfxDWriteFonts.cpp
+++ b/gfx/thebes/gfxDWriteFonts.cpp
@@ -76,27 +76,27 @@ GetSystemTextQuality()
   return DEFAULT_QUALITY;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // gfxDWriteFont
 gfxDWriteFont::gfxDWriteFont(const RefPtr<UnscaledFontDWrite>& aUnscaledFont,
                              gfxFontEntry *aFontEntry,
                              const gfxFontStyle *aFontStyle,
+                             RefPtr<IDWriteFontFace> aFontFace,
                              AntialiasOption anAAOption)
     : gfxFont(aUnscaledFont, aFontEntry, aFontStyle, anAAOption)
+    , mFontFace(aFontFace ? aFontFace : aUnscaledFont->GetFontFace())
     , mCairoFontFace(nullptr)
     , mMetrics(nullptr)
     , mSpaceGlyph(0)
     , mUseSubpixelPositions(false)
     , mAllowManualShowGlyphs(true)
     , mAzureScaledFontUsedClearType(false)
 {
-    mFontFace = aUnscaledFont->GetFontFace();
-
     // If the IDWriteFontFace1 interface is available, we can use that for
     // faster glyph width retrieval.
     mFontFace->QueryInterface(__uuidof(IDWriteFontFace1),
                               (void**)getter_AddRefs(mFontFace1));
 
     ComputeMetrics(anAAOption);
 }
 
@@ -145,17 +145,17 @@ gfxDWriteFont::SystemTextQualityChanged(
   gfxPlatform::ForceGlobalReflow();
 }
 
 UniquePtr<gfxFont>
 gfxDWriteFont::CopyWithAntialiasOption(AntialiasOption anAAOption)
 {
     auto entry = static_cast<gfxDWriteFontEntry*>(mFontEntry.get());
     RefPtr<UnscaledFontDWrite> unscaledFont = static_cast<UnscaledFontDWrite*>(mUnscaledFont.get());
-    return MakeUnique<gfxDWriteFont>(unscaledFont, entry, &mStyle, anAAOption);
+    return MakeUnique<gfxDWriteFont>(unscaledFont, entry, &mStyle, mFontFace, anAAOption);
 }
 
 const gfxFont::Metrics&
 gfxDWriteFont::GetHorizontalMetrics()
 {
     return *mMetrics;
 }
 
--- a/gfx/thebes/gfxDWriteFonts.h
+++ b/gfx/thebes/gfxDWriteFonts.h
@@ -25,16 +25,17 @@ typedef _cairo_font_face cairo_font_face
  * \brief Class representing a font face for a font entry.
  */
 class gfxDWriteFont : public gfxFont
 {
 public:
     gfxDWriteFont(const RefPtr<mozilla::gfx::UnscaledFontDWrite>& aUnscaledFont,
                   gfxFontEntry *aFontEntry,
                   const gfxFontStyle *aFontStyle,
+                  RefPtr<IDWriteFontFace> aFontFace = nullptr,
                   AntialiasOption = kAntialiasDefault);
     ~gfxDWriteFont();
 
     static void UpdateSystemTextQuality();
     static void SystemTextQualityChanged();
 
     mozilla::UniquePtr<gfxFont>
     CopyWithAntialiasOption(AntialiasOption anAAOption) override;
--- a/security/manager/ssl/nsSecureBrowserUIImpl.cpp
+++ b/security/manager/ssl/nsSecureBrowserUIImpl.cpp
@@ -3,16 +3,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsSecureBrowserUIImpl.h"
 
 #include "mozilla/Assertions.h"
 #include "mozilla/Logging.h"
 #include "mozilla/Unused.h"
+#include "nsContentUtils.h"
 #include "nsIChannel.h"
 #include "nsIDocShell.h"
 #include "nsIDocShellTreeItem.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsISecurityEventSink.h"
 #include "nsITransportSecurityInfo.h"
 #include "nsIWebProgress.h"
 
@@ -201,16 +202,81 @@ nsSecureBrowserUIImpl::CheckForBlockedCo
     mState |= STATE_COOKIES_BLOCKED_FOREIGN;
   }
 
   if (docShell->GetHasAllCookiesBeenBlocked()) {
     mState |= STATE_COOKIES_BLOCKED_ALL;
   }
 }
 
+// Helper function to determine if the given URI can be considered secure.
+// Essentially, only "https" URIs can be considered secure. However, the URI we
+// have may be e.g. view-source:https://example.com or
+// wyciwyg://https://example.com, in which case we have to evaluate the
+// innermost URI.
+static nsresult
+URICanBeConsideredSecure(nsIURI* uri, /* out */ bool& canBeConsideredSecure)
+{
+  MOZ_ASSERT(uri);
+  NS_ENSURE_ARG(uri);
+
+  canBeConsideredSecure = false;
+
+  nsCOMPtr<nsIURI> innermostURI = NS_GetInnermostURI(uri);
+  if (!innermostURI) {
+    MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
+            ("  couldn't get innermost URI"));
+    return NS_ERROR_FAILURE;
+  }
+  MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
+          ("  innermost URI is '%s'", innermostURI->GetSpecOrDefault().get()));
+
+  // Unfortunately, wyciwyg URIs don't know about innermost URIs, so we have to
+  // manually get the innermost URI if we have such a URI.
+  bool isWyciwyg;
+  nsresult rv = innermostURI->SchemeIs("wyciwyg", &isWyciwyg);
+  if (NS_FAILED(rv)) {
+    MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
+            ("  nsIURI->SchemeIs failed"));
+    return rv;
+  }
+
+  if (isWyciwyg) {
+    nsCOMPtr<nsIURI> nonWyciwygURI;
+    rv = nsContentUtils::RemoveWyciwygScheme(innermostURI,
+                                             getter_AddRefs(nonWyciwygURI));
+    if (NS_FAILED(rv)) {
+      MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
+              ("  nsContentUtils::RemoveWyciwygScheme failed"));
+      return rv;
+    }
+    if (!nonWyciwygURI) {
+      MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
+              ("  apparently that wasn't a valid wyciwyg URI"));
+      return NS_ERROR_FAILURE;
+    }
+    innermostURI = nonWyciwygURI;
+    MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
+            ("  innermost URI is now '%s'",
+             innermostURI->GetSpecOrDefault().get()));
+  }
+
+  bool isHttps;
+  rv = innermostURI->SchemeIs("https", &isHttps);
+  if (NS_FAILED(rv)) {
+    MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
+            ("  nsIURI->SchemeIs failed"));
+    return rv;
+  }
+
+  canBeConsideredSecure = isHttps;
+
+  return NS_OK;
+}
+
 // Helper function to get the securityInfo from a channel as a
 // nsITransportSecurityInfo. The out parameter will be set to null if there is
 // no securityInfo set.
 static void
 GetSecurityInfoFromChannel(nsIChannel* channel,
                            nsITransportSecurityInfo** securityInfoOut)
 {
   MOZ_ASSERT(channel);
@@ -241,23 +307,37 @@ nsSecureBrowserUIImpl::UpdateStateAndSec
   MOZ_ASSERT(uri);
 
   NS_ENSURE_ARG(channel);
   NS_ENSURE_ARG(uri);
 
   mState = STATE_IS_INSECURE;
   mTopLevelSecurityInfo = nullptr;
 
+  // Only https is considered secure (it is possible to have e.g. an http URI
+  // with a channel that has a securityInfo that indicates the connection is
+  // secure - e.g. h2/alt-svc or by visiting an http URI over an https proxy).
+  bool canBeConsideredSecure;
+  nsresult rv = URICanBeConsideredSecure(uri, canBeConsideredSecure);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  if (!canBeConsideredSecure) {
+    MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
+            ("  URI can't be considered secure"));
+    return NS_OK;
+  }
+
   nsCOMPtr<nsITransportSecurityInfo> securityInfo;
   GetSecurityInfoFromChannel(channel, getter_AddRefs(securityInfo));
   if (securityInfo) {
     MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
             ("  we have a security info %p", securityInfo.get()));
 
-    nsresult rv = securityInfo->GetSecurityState(&mState);
+    rv = securityInfo->GetSecurityState(&mState);
     if (NS_FAILED(rv)) {
       return rv;
     }
     // If the security state is STATE_IS_INSECURE, the TLS handshake never
     // completed. Don't set any further state.
     if (mState == STATE_IS_INSECURE) {
       return NS_OK;
     }
@@ -324,18 +404,25 @@ nsSecureBrowserUIImpl::OnLocationChange(
   }
 
   // NB: aRequest may be null. It may also not be QI-able to nsIChannel.
   nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
   if (channel) {
     MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
             ("  we have a channel %p", channel.get()));
     nsresult rv = UpdateStateAndSecurityInfo(channel, aLocation);
+    // Even if this failed, we still want to notify downstream so that we don't
+    // leave a stale security indicator. We set everything to "not secure" to be
+    // safe.
     if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
+      MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
+              ("  Failed to update security info. "
+               "Setting everything to 'not secure' to be safe."));
+      mState = STATE_IS_INSECURE;
+      mTopLevelSecurityInfo = nullptr;
     }
   }
 
   mozilla::dom::ContentBlockingLog* contentBlockingLog = nullptr;
   nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(mDocShell);
   if (docShell) {
     nsIDocument* doc = docShell->GetDocument();
     if (doc) {
--- a/security/manager/ssl/tests/mochitest/mixedcontent/bug329869.js
+++ b/security/manager/ssl/tests/mochitest/mixedcontent/bug329869.js
@@ -1,8 +1,9 @@
 /* import-globals-from mixedContentTest.js */
 "use strict";
 
 document.open();
+// This test depends on nsSecureBrowserUIImpl handling wyciwyg:https:// URIs properly.
 // eslint-disable-next-line no-unsanitized/method
 document.write("This is insecure XSS script " + document.cookie);
 isSecurityState("broken", "security broken after document write from unsecure script");
 finish();
--- a/testing/marionette/client/marionette_driver/geckoinstance.py
+++ b/testing/marionette/client/marionette_driver/geckoinstance.py
@@ -438,26 +438,16 @@ class FennecInstance(GeckoInstance):
         try:
             if self.connect_to_running_emulator:
                 self.runner.device.connect()
             self.runner.start()
         except Exception as e:
             exc, val, tb = sys.exc_info()
             message = "Error possibly due to runner or device args: {}"
             reraise(exc, message.format(e.message), tb)
-        # gecko_log comes from logcat when running with device/emulator
-        logcat_args = {
-            "filterspec": "Gecko",
-            "serial": self.runner.device.app_ctx.device_serial
-        }
-        if self.gecko_log == "-":
-            logcat_args["stream"] = sys.stdout
-        else:
-            logcat_args["logfile"] = self.gecko_log
-        self.runner.device.start_logcat(**logcat_args)
 
         # forward marionette port
         self.runner.device.device.forward(
             local="tcp:{}".format(self.marionette_port),
             remote="tcp:{}".format(self.marionette_port))
 
     def _get_runner_args(self):
         process_args = {
--- a/testing/mozbase/mozrunner/mozrunner/devices/base.py
+++ b/testing/mozbase/mozrunner/mozrunner/devices/base.py
@@ -8,22 +8,20 @@ from ConfigParser import (
 import datetime
 import os
 import posixpath
 import shutil
 import tempfile
 import time
 
 from mozdevice import ADBHost, ADBError
-from mozprocess import ProcessHandler
 
 
 class Device(object):
     connected = False
-    logcat_proc = None
 
     def __init__(self, app_ctx, logdir=None, serial=None, restore=True):
         self.app_ctx = app_ctx
         self.device = self.app_ctx.device
         self.restore = restore
         self.serial = serial
         self.logdir = os.path.abspath(os.path.expanduser(logdir))
         self.added_files = set()
@@ -136,38 +134,16 @@ class Device(object):
         online_devices = self._get_online_devices()
         if not online_devices:
             raise IOError("No devices connected. Ensure the device is on and "
                           "remote debugging via adb is enabled in the settings.")
         self.serial = online_devices[0]
 
         self.connected = True
 
-        if self.logdir:
-            # save logcat
-            logcat_log = os.path.join(self.logdir, '%s.log' % self.serial)
-            if os.path.isfile(logcat_log):
-                self._rotate_log(logcat_log)
-            self.logcat_proc = self.start_logcat(self.serial, logfile=logcat_log)
-
-    def start_logcat(self, serial, logfile=None, stream=None, filterspec=None):
-        logcat_args = [self.app_ctx.adb, '-s', '%s' % serial,
-                       'logcat', '-v', 'time', '-b', 'main', '-b', 'radio']
-        # only log filterspec
-        if filterspec:
-            logcat_args.extend(['-s', filterspec])
-        process_args = {}
-        if logfile:
-            process_args['logfile'] = logfile
-        elif stream:
-            process_args['stream'] = stream
-        proc = ProcessHandler(logcat_args, **process_args)
-        proc.run()
-        return proc
-
     def reboot(self):
         """
         Reboots the device via adb.
         """
         self.device.reboot()
 
     def wait_for_net(self):
         active = False
--- a/testing/mozbase/mozrunner/mozrunner/runners.py
+++ b/testing/mozbase/mozrunner/mozrunner/runners.py
@@ -96,17 +96,17 @@ def FennecEmulatorRunner(avd='mozemulato
     """
     Create a Fennec emulator runner. This can either start a new emulator
     (which will use an avd), or connect to  an already-running emulator.
 
     :param avd: name of an AVD available in your environment.
         Typically obtained via tooltool: either 'mozemulator-4.3' or 'mozemulator-x86'.
         Defaults to 'mozemulator-4.3'
     :param avd_home: Path to avd parent directory
-    :param logdir: Path to save logfiles such as logcat and qemu output.
+    :param logdir: Path to save logfiles such as qemu output.
     :param serial: Serial of emulator to connect to as seen in `adb devices`.
         Defaults to the first entry in `adb devices`.
     :param binary: Path to emulator binary.
         Defaults to None, which causes the device_class to guess based on PATH.
     :param app: Name of Fennec app (often org.mozilla.fennec_$USER)
         Defaults to 'org.mozilla.fennec'
     :param cmdargs: Arguments to pass into binary.
     :returns: A DeviceRunner for Android emulators.
new file mode 100644
--- /dev/null
+++ b/testing/mozharness/configs/web_platform_tests/prod_config_android.py
@@ -0,0 +1,50 @@
+# ***** BEGIN LICENSE BLOCK *****
+# 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/.
+# ***** END LICENSE BLOCK *****
+import os
+
+config = {
+    "options": [
+        "--prefs-root=%(test_path)s/prefs",
+        "--processes=1",
+        "--config=%(test_path)s/wptrunner.ini",
+        "--ca-cert-path=%(test_path)s/tests/tools/certs/cacert.pem",
+        "--host-key-path=%(test_path)s/tests/tools/certs/web-platform.test.key",
+        "--host-cert-path=%(test_path)s/tests/tools/certs/web-platform.test.pem",
+        "--certutil-binary=%(xre_path)s/certutil",
+        "--product=fennec",
+    ],
+    "avds_dir": "/builds/worker/workspace/build/.android",
+    "binary_path": "/tmp",
+    "download_minidump_stackwalk": False,
+    "emulator_avd_name": "test-1",
+    "emulator_extra_args": "-gpu swiftshader_indirect -skip-adb-auth -verbose -show-kernel -use-system-libs -ranchu -selinux permissive -memory 3072 -cores 4",
+    "emulator_manifest": """
+        [
+        {
+        "size": 135064025,
+        "digest": "125678c5b0d93ead8bbf01ba94253e532909417b40637460624cfca34e92f431534fc77a0225e9c4728dcbcf2884a8f7fa1ee059efdfa82d827ca20477d41705",
+        "algorithm": "sha512",
+        "filename": "android-sdk_r27.1.12-linux-x86emu.tar.gz",
+        "unpack": "True"
+        }
+        ] """,
+    "emulator_process_name": "emulator64-x86",
+    "env": {
+        "PATH": "%(PATH)s:%(abs_work_dir)s/android-sdk-linux/emulator:%(abs_work_dir)s/android-sdk-linux/tools:%(abs_work_dir)s/android-sdk-linux/platform-tools",
+    },
+    "exes": {
+        'adb': '%(abs_work_dir)s/android-sdk-linux/platform-tools/adb',
+    },
+    "geckodriver": "%(abs_test_bin_dir)s/geckodriver",
+    "hostutils_manifest_path": "testing/config/tooltool-manifests/linux64/hostutils.manifest",
+    "log_tbpl_level": "info",
+    "log_raw_level": "info",
+    "minidump_stackwalk_path": "/usr/local/bin/linux64-minidump_stackwalk",
+    "per_test_category": "web-platform",
+    "tooltool_cache": os.environ.get("TOOLTOOL_CACHE"),
+    "tooltool_manifest_path": "testing/config/tooltool-manifests/androidx86_7_0/releng.manifest",
+    "tooltool_servers": ['http://relengapi/tooltool/'],
+}
--- a/testing/mozharness/mozharness/mozilla/testing/android.py
+++ b/testing/mozharness/mozharness/mozilla/testing/android.py
@@ -1,31 +1,36 @@
 #!/usr/bin/env python
 # ***** BEGIN LICENSE BLOCK *****
 # 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/.
 # ***** END LICENSE BLOCK *****
 
+import datetime
 import glob
 import os
 import re
+import signal
 import subprocess
+import time
 import tempfile
 from mozharness.mozilla.automation import TBPL_RETRY, EXIT_STATUS_DICT
+from mozharness.base.script import PostScriptAction
 
 
 class AndroidMixin(object):
     """
        Mixin class used by Android test scripts.
     """
 
     def __init__(self, **kwargs):
         self._adb_path = None
         self._device = None
+        self.app_name = None
         self.device_name = os.environ.get('DEVICE_NAME', None)
         self.device_serial = os.environ.get('DEVICE_SERIAL', None)
         self.device_ip = os.environ.get('DEVICE_IP', None)
         self.logcat_proc = None
         self.logcat_file = None
         super(AndroidMixin, self).__init__(**kwargs)
 
     @property
@@ -58,16 +63,34 @@ class AndroidMixin(object):
                                                     verbose=True)
                 self.info("New mozdevice with adb=%s, device=%s" %
                           (self.adb_path, self.device_serial))
             except Exception:
                 # As in adb_path, above.
                 pass
         return self._device
 
+    @property
+    def is_android(self):
+        try:
+            c = self.config
+            installer_url = c.get('installer_url', None)
+            return self.device_serial is not None or self.is_emulator or \
+                (installer_url is not None and installer_url.endswith(".apk"))
+        except AttributeError:
+            return False
+
+    @property
+    def is_emulator(self):
+        try:
+            c = self.config
+            return True if c.get('emulator_manifest') else False
+        except AttributeError:
+            return False
+
     def _get_repo_url(self, path):
         """
            Return a url for a file (typically a tooltool manifest) in this hg repo
            and using this revision (or mozilla-central/default if repo/rev cannot
            be determined).
 
            :param path specifies the directory path to the file of interest.
         """
@@ -97,16 +120,122 @@ class AndroidMixin(object):
         if not os.path.exists(manifest_path):
             self.fatal("Could not retrieve manifest needed to retrieve "
                        "artifacts from %s" % manifest_path)
         # from TooltoolMixin, included in TestingMixin
         self.tooltool_fetch(manifest_path,
                             output_dir=dir,
                             cache=c.get("tooltool_cache", None))
 
+    def _launch_emulator(self):
+        env = self.query_env()
+
+        # Write a default ddms.cfg to avoid unwanted prompts
+        avd_home_dir = self.abs_dirs['abs_avds_dir']
+        DDMS_FILE = os.path.join(avd_home_dir, "ddms.cfg")
+        with open(DDMS_FILE, 'w') as f:
+            f.write("pingOptIn=false\npingId=0\n")
+        self.info("wrote dummy %s" % DDMS_FILE)
+
+        # Delete emulator auth file, so it doesn't prompt
+        AUTH_FILE = os.path.join(os.path.expanduser('~'), '.emulator_console_auth_token')
+        if os.path.exists(AUTH_FILE):
+            try:
+                os.remove(AUTH_FILE)
+                self.info("deleted %s" % AUTH_FILE)
+            except Exception:
+                self.warning("failed to remove %s" % AUTH_FILE)
+
+        avd_path = os.path.join(avd_home_dir, 'avd')
+        if os.path.exists(avd_path):
+            env['ANDROID_AVD_HOME'] = avd_path
+            self.info("Found avds at %s" % avd_path)
+        else:
+            self.warning("AVDs missing? Not found at %s" % avd_path)
+
+        if "deprecated_sdk_path" in self.config:
+            sdk_path = os.path.abspath(os.path.join(avd_home_dir, '..'))
+        else:
+            sdk_path = os.path.join(self.abs_dirs['abs_work_dir'], 'android-sdk-linux')
+        if os.path.exists(sdk_path):
+            env['ANDROID_SDK_HOME'] = sdk_path
+            self.info("Found sdk at %s" % sdk_path)
+        else:
+            self.warning("Android sdk missing? Not found at %s" % sdk_path)
+
+        # extra diagnostics for kvm acceleration
+        emu = self.config.get('emulator_process_name')
+        if os.path.exists('/dev/kvm') and emu and 'x86' in emu:
+            try:
+                self.run_command(['ls', '-l', '/dev/kvm'])
+                self.run_command(['kvm-ok'])
+                self.run_command(["emulator", "-accel-check"], env=env)
+            except Exception as e:
+                self.warning("Extra kvm diagnostics failed: %s" % str(e))
+
+        command = ["emulator", "-avd", self.config["emulator_avd_name"]]
+        if "emulator_extra_args" in self.config:
+            command += self.config["emulator_extra_args"].split()
+
+        dir = self.query_abs_dirs()['abs_blob_upload_dir']
+        tmp_file = tempfile.NamedTemporaryFile(mode='w', prefix='emulator-',
+                                               suffix='.log', dir=dir, delete=False)
+        self.info("Launching the emulator with: %s" % ' '.join(command))
+        self.info("Writing log to %s" % tmp_file.name)
+        proc = subprocess.Popen(command, stdout=tmp_file, stderr=tmp_file, env=env, bufsize=0)
+        return proc
+
+    def _verify_emulator(self):
+        boot_ok = self._retry(30, 10, self.is_boot_completed, "Verify Android boot completed",
+                              max_time=330)
+        if not boot_ok:
+            self.warning('Unable to verify Android boot completion')
+            return False
+        return True
+
+    def _verify_emulator_and_restart_on_fail(self):
+        emulator_ok = self._verify_emulator()
+        if not emulator_ok:
+            self.screenshot("emulator-startup-screenshot-")
+            self.kill_processes(self.config["emulator_process_name"])
+            subprocess.check_call(['ps', '-ef'])
+            # remove emulator tmp files
+            for dir in glob.glob("/tmp/android-*"):
+                self.rmtree(dir)
+            time.sleep(5)
+            self.emulator_proc = self._launch_emulator()
+        return emulator_ok
+
+    def _retry(self, max_attempts, interval, func, description, max_time=0):
+        '''
+        Execute func until it returns True, up to max_attempts times, waiting for
+        interval seconds between each attempt. description is logged on each attempt.
+        If max_time is specified, no further attempts will be made once max_time
+        seconds have elapsed; this provides some protection for the case where
+        the run-time for func is long or highly variable.
+        '''
+        status = False
+        attempts = 0
+        if max_time > 0:
+            end_time = datetime.datetime.now() + datetime.timedelta(seconds=max_time)
+        else:
+            end_time = None
+        while attempts < max_attempts and not status:
+            if (end_time is not None) and (datetime.datetime.now() > end_time):
+                self.info("Maximum retry run-time of %d seconds exceeded; "
+                          "remaining attempts abandoned" % max_time)
+                break
+            if attempts != 0:
+                self.info("Sleeping %d seconds" % interval)
+                time.sleep(interval)
+            attempts += 1
+            self.info(">> %s: Attempt #%d of %d" % (description, attempts, max_attempts))
+            status = func()
+        return status
+
     def dump_perf_info(self):
         '''
         Dump some host and android device performance-related information
         to an artifact file, to help understand task performance.
         '''
         dir = self.query_abs_dirs()['abs_blob_upload_dir']
         perf_path = os.path.join(dir, "android-performance.log")
         with open(perf_path, "w") as f:
@@ -156,17 +285,16 @@ class AndroidMixin(object):
                                EXIT_STATUS_DICT[TBPL_RETRY])
                 self.info("Found Android bogomips: %d" % bogomips)
                 break
 
     def logcat_start(self):
         """
            Start recording logcat. Writes logcat to the upload directory.
         """
-        self.mkdir_p(self.query_abs_dirs()['abs_blob_upload_dir'])
         # Start logcat for the device. The adb process runs until the
         # corresponding device is stopped. Output is written directly to
         # the blobber upload directory so that it is uploaded automatically
         # at the end of the job.
         logcat_filename = 'logcat-%s.log' % self.device_serial
         logcat_path = os.path.join(self.abs_dirs['abs_blob_upload_dir'],
                                    logcat_filename)
         self.logcat_file = open(logcat_path, 'w')
@@ -263,8 +391,117 @@ class AndroidMixin(object):
             apk_dir = self.abs_dirs['abs_work_dir']
             self.apk_path = os.path.join(apk_dir, self.installer_path)
             unzip = self.query_exe("unzip")
             package_path = os.path.join(apk_dir, 'package-name.txt')
             unzip_cmd = [unzip, '-q', '-o', self.apk_path]
             self.run_command(unzip_cmd, cwd=apk_dir, halt_on_failure=True)
             self.app_name = str(self.read_from_file(package_path, verbose=True)).rstrip()
         return self.app_name
+
+    def kill_processes(self, process_name):
+        self.info("Killing every process called %s" % process_name)
+        out = subprocess.check_output(['ps', '-A'])
+        for line in out.splitlines():
+            if process_name in line:
+                pid = int(line.split(None, 1)[0])
+                self.info("Killing pid %d." % pid)
+                os.kill(pid, signal.SIGKILL)
+
+    # Script actions
+
+    def setup_avds(self):
+        """
+        If tooltool cache mechanism is enabled, the cached version is used by
+        the fetch command. If the manifest includes an "unpack" field, tooltool
+        will unpack all compressed archives mentioned in the manifest.
+        """
+        if not self.is_emulator:
+            return
+
+        c = self.config
+        dirs = self.query_abs_dirs()
+        self.mkdir_p(dirs['abs_work_dir'])
+        self.mkdir_p(dirs['abs_blob_upload_dir'])
+
+        # Always start with a clean AVD: AVD includes Android images
+        # which can be stateful.
+        self.rmtree(dirs['abs_avds_dir'])
+        self.mkdir_p(dirs['abs_avds_dir'])
+        if 'avd_url' in c:
+            # Intended for experimental setups to evaluate an avd prior to
+            # tooltool deployment.
+            url = c['avd_url']
+            self.download_unpack(url, dirs['abs_avds_dir'])
+        else:
+            url = self._get_repo_url(c["tooltool_manifest_path"])
+            self._tooltool_fetch(url, dirs['abs_avds_dir'])
+
+        avd_home_dir = self.abs_dirs['abs_avds_dir']
+        if avd_home_dir != "/home/cltbld/.android":
+            # Modify the downloaded avds to point to the right directory.
+            cmd = [
+                'bash', '-c',
+                'sed -i "s|/home/cltbld/.android|%s|" %s/test-*.ini' %
+                (avd_home_dir, os.path.join(avd_home_dir, 'avd'))
+            ]
+            subprocess.check_call(cmd)
+
+    def start_emulator(self):
+        """
+        Starts the emulator
+        """
+        if not self.is_emulator:
+            return
+
+        if 'emulator_url' in self.config or 'emulator_manifest' in self.config:
+            dirs = self.query_abs_dirs()
+            if self.config.get('emulator_url'):
+                self.download_unpack(self.config['emulator_url'], dirs['abs_work_dir'])
+            elif self.config.get('emulator_manifest'):
+                manifest_path = self.create_tooltool_manifest(self.config['emulator_manifest'])
+                dirs = self.query_abs_dirs()
+                cache = self.config.get("tooltool_cache", None)
+                if self.tooltool_fetch(manifest_path,
+                                       output_dir=dirs['abs_work_dir'],
+                                       cache=cache):
+                    self.fatal("Unable to download emulator via tooltool!")
+        else:
+            self.fatal("Cannot get emulator: configure emulator_url or emulator_manifest")
+        if not os.path.isfile(self.adb_path):
+            self.fatal("The adb binary '%s' is not a valid file!" % self.adb_path)
+        self.kill_processes("xpcshell")
+        self.emulator_proc = self._launch_emulator()
+
+    def verify_device(self):
+        """
+        Check to see if the emulator can be contacted via adb.
+        If any communication attempt fails, kill the emulator, re-launch, and re-check.
+        """
+        if not self.is_android:
+            return
+
+        if self.is_emulator:
+            max_restarts = 5
+            emulator_ok = self._retry(max_restarts, 10,
+                                      self._verify_emulator_and_restart_on_fail,
+                                      "Check emulator")
+            if not emulator_ok:
+                self.fatal('INFRA-ERROR: Unable to start emulator after %d attempts'
+                           % max_restarts, EXIT_STATUS_DICT[TBPL_RETRY])
+
+        self.mkdir_p(self.query_abs_dirs()['abs_blob_upload_dir'])
+        self.dump_perf_info()
+        self.logcat_start()
+        # Get a post-boot device process list for diagnostics
+        self.info(self.shell_output('ps'))
+
+    @PostScriptAction('run-tests')
+    def stop_device(self, action, success=None):
+        """
+        Stop logcat and kill the emulator, if necessary.
+        """
+        if not self.is_android:
+            return
+
+        self.logcat_stop()
+        if self.is_emulator:
+            self.kill_processes(self.config["emulator_process_name"])
--- a/testing/mozharness/scripts/android_emulator_unittest.py
+++ b/testing/mozharness/scripts/android_emulator_unittest.py
@@ -2,33 +2,26 @@
 # ***** BEGIN LICENSE BLOCK *****
 # 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/.
 # ***** END LICENSE BLOCK *****
 
 import copy
 import datetime
-import glob
 import os
 import re
 import sys
-import signal
 import subprocess
-import time
-import tempfile
 
 # load modules from parent dir
 sys.path.insert(1, os.path.dirname(sys.path[0]))
 
-from mozprocess import ProcessHandler
-
 from mozharness.base.log import FATAL
-from mozharness.base.script import BaseScript, PreScriptAction, PostScriptAction
-from mozharness.mozilla.automation import TBPL_RETRY, EXIT_STATUS_DICT
+from mozharness.base.script import BaseScript, PreScriptAction
 from mozharness.mozilla.mozbase import MozbaseMixin
 from mozharness.mozilla.testing.android import AndroidMixin
 from mozharness.mozilla.testing.testbase import TestingMixin, testing_config_options
 from mozharness.mozilla.testing.codecoverage import (
     CodeCoverageMixin,
     code_coverage_config_options
 )
 
@@ -80,18 +73,16 @@ class AndroidEmulatorTest(TestingMixin, 
         {"action": "store",
          "dest": "log_tbpl_level",
          "default": "info",
          "help": "Set log level (debug|info|warning|error|critical|fatal)",
          }
     ]] + copy.deepcopy(testing_config_options) + \
         copy.deepcopy(code_coverage_config_options)
 
-    app_name = None
-
     def __init__(self, require_config_file=False):
         super(AndroidEmulatorTest, self).__init__(
             config_options=self.config_options,
             all_actions=['clobber',
                          'setup-avds',
                          'start-emulator',
                          'download-and-extract',
                          'create-virtualenv',
@@ -168,134 +159,16 @@ class AndroidEmulatorTest(TestingMixin, 
     def _query_tests_dir(self, test_suite):
         dirs = self.query_abs_dirs()
         try:
             test_dir = self.config["suite_definitions"][test_suite]["testsdir"]
         except Exception:
             test_dir = test_suite
         return os.path.join(dirs['abs_test_install_dir'], test_dir)
 
-    def _launch_emulator(self):
-        env = self.query_env()
-
-        # Write a default ddms.cfg to avoid unwanted prompts
-        avd_home_dir = self.abs_dirs['abs_avds_dir']
-        DDMS_FILE = os.path.join(avd_home_dir, "ddms.cfg")
-        with open(DDMS_FILE, 'w') as f:
-            f.write("pingOptIn=false\npingId=0\n")
-        self.info("wrote dummy %s" % DDMS_FILE)
-
-        # Delete emulator auth file, so it doesn't prompt
-        AUTH_FILE = os.path.join(os.path.expanduser('~'), '.emulator_console_auth_token')
-        if os.path.exists(AUTH_FILE):
-            try:
-                os.remove(AUTH_FILE)
-                self.info("deleted %s" % AUTH_FILE)
-            except Exception:
-                self.warning("failed to remove %s" % AUTH_FILE)
-
-        avd_path = os.path.join(avd_home_dir, 'avd')
-        if os.path.exists(avd_path):
-            env['ANDROID_AVD_HOME'] = avd_path
-            self.info("Found avds at %s" % avd_path)
-        else:
-            self.warning("AVDs missing? Not found at %s" % avd_path)
-
-        if "deprecated_sdk_path" in self.config:
-            sdk_path = os.path.abspath(os.path.join(avd_home_dir, '..'))
-        else:
-            sdk_path = os.path.join(self.abs_dirs['abs_work_dir'], 'android-sdk-linux')
-        if os.path.exists(sdk_path):
-            env['ANDROID_SDK_HOME'] = sdk_path
-            self.info("Found sdk at %s" % sdk_path)
-        else:
-            self.warning("Android sdk missing? Not found at %s" % sdk_path)
-
-        # extra diagnostics for kvm acceleration
-        emu = self.config.get('emulator_process_name')
-        if os.path.exists('/dev/kvm') and emu and 'x86' in emu:
-            try:
-                self.run_command(['ls', '-l', '/dev/kvm'])
-                self.run_command(['kvm-ok'])
-                self.run_command(["emulator", "-accel-check"], env=env)
-            except Exception as e:
-                self.warning("Extra kvm diagnostics failed: %s" % str(e))
-
-        command = ["emulator", "-avd", self.config["emulator_avd_name"]]
-        if "emulator_extra_args" in self.config:
-            command += self.config["emulator_extra_args"].split()
-
-        dir = self.query_abs_dirs()['abs_blob_upload_dir']
-        tmp_file = tempfile.NamedTemporaryFile(mode='w', prefix='emulator-',
-                                               suffix='.log', dir=dir, delete=False)
-        self.info("Launching the emulator with: %s" % ' '.join(command))
-        self.info("Writing log to %s" % tmp_file.name)
-        proc = subprocess.Popen(command, stdout=tmp_file, stderr=tmp_file, env=env, bufsize=0)
-        return {
-            "process": proc,
-        }
-
-    def _retry(self, max_attempts, interval, func, description, max_time=0):
-        '''
-        Execute func until it returns True, up to max_attempts times, waiting for
-        interval seconds between each attempt. description is logged on each attempt.
-        If max_time is specified, no further attempts will be made once max_time
-        seconds have elapsed; this provides some protection for the case where
-        the run-time for func is long or highly variable.
-        '''
-        status = False
-        attempts = 0
-        if max_time > 0:
-            end_time = datetime.datetime.now() + datetime.timedelta(seconds=max_time)
-        else:
-            end_time = None
-        while attempts < max_attempts and not status:
-            if (end_time is not None) and (datetime.datetime.now() > end_time):
-                self.info("Maximum retry run-time of %d seconds exceeded; "
-                          "remaining attempts abandoned" % max_time)
-                break
-            if attempts != 0:
-                self.info("Sleeping %d seconds" % interval)
-                time.sleep(interval)
-            attempts += 1
-            self.info(">> %s: Attempt #%d of %d" % (description, attempts, max_attempts))
-            status = func()
-        return status
-
-    def _verify_emulator(self):
-        boot_ok = self._retry(30, 10, self.is_boot_completed, "Verify Android boot completed",
-                              max_time=330)
-        if not boot_ok:
-            self.warning('Unable to verify Android boot completion')
-            return False
-        return True
-
-    def _verify_emulator_and_restart_on_fail(self):
-        emulator_ok = self._verify_emulator()
-        if not emulator_ok:
-            self.screenshot("emulator-startup-screenshot-")
-            self._kill_processes(self.config["emulator_process_name"])
-            subprocess.check_call(['ps', '-ef'])
-            # remove emulator tmp files
-            for dir in glob.glob("/tmp/android-*"):
-                self.rmtree(dir)
-            time.sleep(5)
-            self.emulator_proc = self._launch_emulator()
-        return emulator_ok
-
-    def _kill_processes(self, process_name):
-        p = subprocess.Popen(['ps', '-A'], stdout=subprocess.PIPE, bufsize=0)
-        out, err = p.communicate()
-        self.info("Killing every process called %s" % process_name)
-        for line in out.splitlines():
-            if process_name in line:
-                pid = int(line.split(None, 1)[0])
-                self.info("Killing pid %d." % pid)
-                os.kill(pid, signal.SIGKILL)
-
     def _build_command(self):
         c = self.config
         dirs = self.query_abs_dirs()
 
         if self.test_suite not in self.config["suite_definitions"]:
             self.fatal("Key '%s' not defined in the config!" % self.test_suite)
 
         cmd = [
@@ -375,31 +248,16 @@ class AndroidEmulatorTest(TestingMixin, 
                 try_tests))
 
         if self.java_code_coverage_enabled:
             cmd.extend(['--enable-coverage',
                         '--coverage-output-dir', self.java_coverage_output_dir])
 
         return cmd
 
-    def _install_emulator(self):
-        dirs = self.query_abs_dirs()
-        if self.config.get('emulator_url'):
-            self.download_unpack(self.config['emulator_url'], dirs['abs_work_dir'])
-        elif self.config.get('emulator_manifest'):
-            manifest_path = self.create_tooltool_manifest(self.config['emulator_manifest'])
-            dirs = self.query_abs_dirs()
-            cache = self.config.get("tooltool_cache", None)
-            if self.tooltool_fetch(manifest_path,
-                                   output_dir=dirs['abs_work_dir'],
-                                   cache=cache):
-                self.fatal("Unable to download emulator via tooltool!")
-        else:
-            self.warning("Cannot get emulator: configure emulator_url or emulator_manifest")
-
     def _query_suites(self):
         if self.test_suite:
             return [(self.test_suite, self.test_suite)]
         # per-test mode: determine test suites to run
 
         # For each test category, provide a list of supported sub-suites and a mapping
         # between the per_test_base suite name and the android suite name.
         all = [('mochitest', {'plain': 'mochitest',
@@ -446,83 +304,16 @@ class AndroidEmulatorTest(TestingMixin, 
                                         'websocketprocessbridge_requirements.txt')
         elif ('marionette', 'marionette') in suites:
             requirements = os.path.join(dirs['abs_test_install_dir'],
                                         'config', 'marionette_requirements.txt')
         if requirements:
             self.register_virtualenv_module(requirements=[requirements],
                                             two_pass=True)
 
-    def setup_avds(self):
-        '''
-        If tooltool cache mechanism is enabled, the cached version is used by
-        the fetch command. If the manifest includes an "unpack" field, tooltool
-        will unpack all compressed archives mentioned in the manifest.
-        '''
-        c = self.config
-        dirs = self.query_abs_dirs()
-        self.mkdir_p(dirs['abs_work_dir'])
-        self.mkdir_p(dirs['abs_blob_upload_dir'])
-
-        # Always start with a clean AVD: AVD includes Android images
-        # which can be stateful.
-        self.rmtree(dirs['abs_avds_dir'])
-        self.mkdir_p(dirs['abs_avds_dir'])
-        if 'avd_url' in c:
-            # Intended for experimental setups to evaluate an avd prior to
-            # tooltool deployment.
-            url = c['avd_url']
-            self.download_unpack(url, dirs['abs_avds_dir'])
-        else:
-            url = self._get_repo_url(c["tooltool_manifest_path"])
-            self._tooltool_fetch(url, dirs['abs_avds_dir'])
-
-        avd_home_dir = self.abs_dirs['abs_avds_dir']
-        if avd_home_dir != "/home/cltbld/.android":
-            # Modify the downloaded avds to point to the right directory.
-            cmd = [
-                'bash', '-c',
-                'sed -i "s|/home/cltbld/.android|%s|" %s/test-*.ini' %
-                (avd_home_dir, os.path.join(avd_home_dir, 'avd'))
-            ]
-            proc = ProcessHandler(cmd)
-            proc.run()
-            proc.wait()
-
-    def start_emulator(self):
-        '''
-        Starts the emulator
-        '''
-        if 'emulator_url' in self.config or 'emulator_manifest' in self.config:
-            self._install_emulator()
-
-        if not os.path.isfile(self.adb_path):
-            self.fatal("The adb binary '%s' is not a valid file!" % self.adb_path)
-
-        if not self.config.get("developer_mode"):
-            self._kill_processes("xpcshell")
-
-        self.emulator_proc = self._launch_emulator()
-
-    def verify_device(self):
-        '''
-        Check to see if the emulator can be contacted via adb.
-        If any communication attempt fails, kill the emulator, re-launch, and re-check.
-        '''
-        max_restarts = 5
-        emulator_ok = self._retry(max_restarts, 10, self._verify_emulator_and_restart_on_fail,
-                                  "Check emulator")
-        if not emulator_ok:
-            self.fatal('INFRA-ERROR: Unable to start emulator after %d attempts' % max_restarts,
-                       EXIT_STATUS_DICT[TBPL_RETRY])
-        self.dump_perf_info()
-        self.logcat_start()
-        # Get a post-boot device process list for diagnostics
-        self.info(self.shell_output('ps'))
-
     def download_and_extract(self):
         """
         Download and extract fennec APK, tests.zip, host utils, and robocop (if required).
         """
         super(AndroidEmulatorTest, self).download_and_extract(
             suite_categories=self._query_suite_categories())
         dirs = self.query_abs_dirs()
         if self.test_suite and self.test_suite.startswith('robocop'):
@@ -614,20 +405,12 @@ class AndroidEmulatorTest(TestingMixin, 
                 if len(per_test_args) > 0:
                     self.record_status(tbpl_status, level=log_level)
                     self.log_per_test_status(per_test_args[-1], tbpl_status, log_level)
                 else:
                     self.record_status(tbpl_status, level=log_level)
                     self.log("The %s suite: %s ran with return status: %s" %
                              (suite_category, suite, tbpl_status), level=log_level)
 
-    @PostScriptAction('run-tests')
-    def stop_device(self, action, success=None):
-        '''
-        Make sure that the emulator has been stopped
-        '''
-        self.logcat_stop()
-        self._kill_processes(self.config["emulator_process_name"])
-
 
 if __name__ == '__main__':
     test = AndroidEmulatorTest()
     test.run_and_exit()
--- a/testing/mozharness/scripts/android_hardware_unittest.py
+++ b/testing/mozharness/scripts/android_hardware_unittest.py
@@ -11,17 +11,17 @@ import os
 import re
 import sys
 import subprocess
 
 # load modules from parent dir
 sys.path.insert(1, os.path.dirname(sys.path[0]))
 
 from mozharness.base.log import FATAL
-from mozharness.base.script import BaseScript, PreScriptAction, PostScriptAction
+from mozharness.base.script import BaseScript, PreScriptAction
 from mozharness.mozilla.mozbase import MozbaseMixin
 from mozharness.mozilla.testing.android import AndroidMixin
 from mozharness.mozilla.testing.testbase import TestingMixin, testing_config_options
 from mozharness.mozilla.testing.codecoverage import CodeCoverageMixin
 
 
 class AndroidHardwareTest(TestingMixin, BaseScript, MozbaseMixin,
                           CodeCoverageMixin, AndroidMixin):
@@ -63,18 +63,16 @@ class AndroidHardwareTest(TestingMixin, 
         ["--log-tbpl-level"],
         {"action": "store",
          "dest": "log_tbpl_level",
          "default": "info",
          "help": "Set log level (debug|info|warning|error|critical|fatal)",
          }
     ]] + copy.deepcopy(testing_config_options)
 
-    app_name = None
-
     def __init__(self, require_config_file=False):
         super(AndroidHardwareTest, self).__init__(
             config_options=self.config_options,
             all_actions=['clobber',
                          'download-and-extract',
                          'create-virtualenv',
                          'verify-device',
                          'install',
@@ -94,19 +92,16 @@ class AndroidHardwareTest(TestingMixin, 
         c = self.config
         abs_dirs = self.query_abs_dirs()
         self.installer_url = c.get('installer_url')
         self.installer_path = c.get('installer_path')
         self.test_url = c.get('test_url')
         self.test_packages_url = c.get('test_packages_url')
         self.test_manifest = c.get('test_manifest')
         self.robocop_path = os.path.join(abs_dirs['abs_work_dir'], "robocop.apk")
-        self.device_name = os.environ['DEVICE_NAME']
-        self.device_serial = os.environ['DEVICE_SERIAL']
-        self.device_ip = os.environ['DEVICE_IP']
         self.test_suite = c.get('test_suite')
         self.this_chunk = c.get('this_chunk')
         self.total_chunks = c.get('total_chunks')
         if self.test_suite and self.test_suite not in self.config["suite_definitions"]:
             # accept old-style test suite name like "mochitest-3"
             m = re.match("(.*)-(\d*)", self.test_suite)
             if m:
                 self.test_suite = m.group(1)
@@ -285,26 +280,16 @@ class AndroidHardwareTest(TestingMixin, 
                                         'websocketprocessbridge_requirements.txt')
         elif ('marionette', 'marionette') in suites:
             requirements = os.path.join(dirs['abs_test_install_dir'],
                                         'config', 'marionette_requirements.txt')
         if requirements:
             self.register_virtualenv_module(requirements=[requirements],
                                             two_pass=True)
 
-    def verify_device(self):
-        '''
-        Check to see if the device can be contacted via adb.
-        '''
-        self.mkdir_p(self.query_abs_dirs()['abs_blob_upload_dir'])
-        self.dump_perf_info()
-        self.logcat_start()
-        # Get a post-boot device process list for diagnostics
-        self.info(self.shell_output('ps'))
-
     def download_and_extract(self):
         """
         Download and extract fennec APK, tests.zip, host utils, and robocop (if required).
         """
         super(AndroidHardwareTest, self).download_and_extract(
             suite_categories=self._query_suite_categories())
         dirs = self.query_abs_dirs()
         if self.test_suite and self.test_suite.startswith('robocop'):
@@ -395,19 +380,12 @@ class AndroidHardwareTest(TestingMixin, 
                 if len(per_test_args) > 0:
                     self.record_status(tbpl_status, level=log_level)
                     self.log_per_test_status(per_test_args[-1], tbpl_status, log_level)
                 else:
                     self.record_status(tbpl_status, level=log_level)
                     self.log("The %s suite: %s ran with return status: %s" %
                              (suite_category, suite, tbpl_status), level=log_level)
 
-    @PostScriptAction('run-tests')
-    def stop_device(self, action, success=None):
-        '''
-        Cleanup after test run.
-        '''
-        self.logcat_stop()
-
 
 if __name__ == '__main__':
     test = AndroidHardwareTest()
     test.run_and_exit()
--- a/testing/mozharness/scripts/web_platform_tests.py
+++ b/testing/mozharness/scripts/web_platform_tests.py
@@ -13,28 +13,29 @@ from datetime import datetime, timedelta
 # load modules from parent dir
 sys.path.insert(1, os.path.dirname(sys.path[0]))
 
 import mozinfo
 
 from mozharness.base.errors import BaseErrorList
 from mozharness.base.script import PreScriptAction
 from mozharness.base.vcs.vcsbase import MercurialScript
+from mozharness.mozilla.testing.android import AndroidMixin
 from mozharness.mozilla.testing.testbase import TestingMixin, testing_config_options
 from mozharness.mozilla.testing.codecoverage import (
     CodeCoverageMixin,
     code_coverage_config_options
 )
 from mozharness.mozilla.testing.errors import HarnessErrorList
 
 from mozharness.mozilla.structuredlog import StructuredOutputParser
 from mozharness.base.log import INFO
 
 
-class WebPlatformTest(TestingMixin, MercurialScript, CodeCoverageMixin):
+class WebPlatformTest(TestingMixin, MercurialScript, CodeCoverageMixin, AndroidMixin):
     config_options = [
         [['--test-type'], {
             "action": "extend",
             "dest": "test_type",
             "help": "Specify the test types to run."}
          ],
         [['--e10s'], {
             "action": "store_true",
@@ -99,33 +100,39 @@ class WebPlatformTest(TestingMixin, Merc
     ] + copy.deepcopy(testing_config_options) + \
         copy.deepcopy(code_coverage_config_options)
 
     def __init__(self, require_config_file=True):
         super(WebPlatformTest, self).__init__(
             config_options=self.config_options,
             all_actions=[
                 'clobber',
+                'setup-avds',
+                'start-emulator',
                 'download-and-extract',
                 'create-virtualenv',
                 'pull',
+                'verify-device',
                 'install',
                 'run-tests',
             ],
             require_config_file=require_config_file,
             config={'require_test_zip': True})
 
         # Surely this should be in the superclass
         c = self.config
         self.installer_url = c.get('installer_url')
         self.test_url = c.get('test_url')
         self.test_packages_url = c.get('test_packages_url')
         self.installer_path = c.get('installer_path')
         self.binary_path = c.get('binary_path')
         self.abs_app_dir = None
+        self.xre_path = None
+        if self.is_emulator:
+            self.device_serial = 'emulator-5554'
 
     def query_abs_app_dir(self):
         """We can't set this in advance, because OSX install directories
         change depending on branding and opt/debug.
         """
         if self.abs_app_dir:
             return self.abs_app_dir
         if not self.binary_path:
@@ -139,16 +146,20 @@ class WebPlatformTest(TestingMixin, Merc
         abs_dirs = super(WebPlatformTest, self).query_abs_dirs()
 
         dirs = {}
         dirs['abs_app_install_dir'] = os.path.join(abs_dirs['abs_work_dir'], 'application')
         dirs['abs_test_install_dir'] = os.path.join(abs_dirs['abs_work_dir'], 'tests')
         dirs['abs_test_bin_dir'] = os.path.join(dirs['abs_test_install_dir'], 'bin')
         dirs["abs_wpttest_dir"] = os.path.join(dirs['abs_test_install_dir'], "web-platform")
         dirs['abs_blob_upload_dir'] = os.path.join(abs_dirs['abs_work_dir'], 'blobber_upload_dir')
+        if self.is_android:
+            dirs['abs_xre_dir'] = os.path.join(abs_dirs['abs_work_dir'], 'hostutils')
+        if self.is_emulator:
+            dirs['abs_avds_dir'] = self.config.get('avds_dir')
 
         abs_dirs.update(dirs)
         self.abs_dirs = abs_dirs
 
         return self.abs_dirs
 
     @PreScriptAction('create-virtualenv')
     def _pre_create_virtualenv(self, action):
@@ -182,17 +193,18 @@ class WebPlatformTest(TestingMixin, Merc
 
         dirs = self.query_abs_dirs()
         abs_app_dir = self.query_abs_app_dir()
         str_format_values = {
             'binary_path': self.binary_path,
             'test_path': dirs["abs_wpttest_dir"],
             'test_install_path': dirs["abs_test_install_dir"],
             'abs_app_dir': abs_app_dir,
-            'abs_work_dir': dirs["abs_work_dir"]
+            'abs_work_dir': dirs["abs_work_dir"],
+            'xre_path': self.xre_path,
         }
 
         cmd = [self.query_python_path('python'), '-u']
         cmd.append(os.path.join(dirs["abs_wpttest_dir"], run_file_name))
 
         # Make sure that the logging directory exists
         if self.mkdir_p(dirs["abs_blob_upload_dir"]) == -1:
             self.fatal("Could not create blobber upload directory")
@@ -209,16 +221,20 @@ class WebPlatformTest(TestingMixin, Merc
                                                        "wpt_errorsummary.log"),
                 "--binary=%s" % self.binary_path,
                 "--symbols-path=%s" % self.query_symbols_url(),
                 "--stackwalk-binary=%s" % self.query_minidump_stackwalk(),
                 "--stackfix-dir=%s" % os.path.join(dirs["abs_test_install_dir"], "bin"),
                 "--run-by-dir=%i" % (3 if not mozinfo.info["asan"] else 0),
                 "--no-pause-after-test"]
 
+        if self.is_android:
+            cmd += ["--device-serial=%s" % self.device_serial]
+            cmd += ["--package-name=%s" % self.query_package_name()]
+
         if not sys.platform.startswith("linux"):
             cmd += ["--exclude=css"]
 
         for test_type in test_types:
             cmd.append("--test-type=%s" % test_type)
 
         if c['extra_prefs']:
             cmd.extend(['--setpref={}'.format(p) for p in c['extra_prefs']])
@@ -277,18 +293,29 @@ class WebPlatformTest(TestingMixin, Merc
                           "config/*",
                           "mozbase/*",
                           "marionette/*",
                           "tools/*",
                           "web-platform/*",
                           "mozpack/*",
                           "mozbuild/*"],
             suite_categories=["web-platform"])
+        if self.is_android:
+            dirs = self.query_abs_dirs()
+            self.xre_path = self.download_hostutils(dirs['abs_xre_dir'])
+
+    def install(self):
+        if self.is_android:
+            self.install_apk(self.installer_path)
+        else:
+            super(WebPlatformTest, self).install()
 
     def _install_fonts(self):
+        if self.is_android:
+            return
         # Ensure the Ahem font is available
         dirs = self.query_abs_dirs()
 
         if not sys.platform.startswith("darwin"):
             font_path = os.path.join(os.path.dirname(self.binary_path), "fonts")
         else:
             font_path = os.path.join(os.path.dirname(self.binary_path), os.pardir,
                                      "Resources", "res", "fonts")
@@ -322,16 +349,19 @@ class WebPlatformTest(TestingMixin, Merc
             env['MOZ_HEADLESS_WIDTH'] = self.config['headless_width']
             env['MOZ_HEADLESS_HEIGHT'] = self.config['headless_height']
 
         if self.config['single_stylo_traversal']:
             env['STYLO_THREADS'] = '1'
         else:
             env['STYLO_THREADS'] = '4'
 
+        if self.is_android:
+            env['ADB_PATH'] = self.adb_path
+
         env = self.query_env(partial_env=env, log_level=INFO)
 
         start_time = datetime.now()
         max_per_test_time = timedelta(minutes=60)
         max_per_test_tests = 10
         if self.per_test_coverage:
             max_per_test_tests = 30
         executed_tests = 0
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/fennec.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/fennec.py
@@ -215,26 +215,16 @@ class FennecBrowser(FirefoxBrowser):
         self.logger.debug("Starting Fennec")
         # connect to a running emulator
         self.runner.device.connect()
 
         write_hosts_file(self.config, self.runner.device.device)
 
         self.runner.start(debug_args=debug_args, interactive=self.debug_info and self.debug_info.interactive)
 
-        # gecko_log comes from logcat when running with device/emulator
-        logcat_args = {
-            "filterspec": "Gecko",
-            "serial": self.runner.device.app_ctx.device_serial
-        }
-        # TODO setting logcat_args["logfile"] yields an almost empty file
-        # even without filterspec
-        logcat_args["stream"] = sys.stdout
-        self.runner.device.start_logcat(**logcat_args)
-
         self.runner.device.device.forward(
             local="tcp:{}".format(self.marionette_port),
             remote="tcp:{}".format(self.marionette_port))
 
         self.logger.debug("Fennec Started")
 
     def stop(self, force=False):
         if self.runner is not None:
--- a/widget/windows/InProcessWinCompositorWidget.cpp
+++ b/widget/windows/InProcessWinCompositorWidget.cpp
@@ -32,16 +32,41 @@ InProcessWinCompositorWidget::InProcessW
                                                            const layers::CompositorOptions& aOptions,
                                                            nsWindow* aWindow)
  : WinCompositorWidget(aInitData, aOptions),
    mWindow(aWindow)
 {
   MOZ_ASSERT(mWindow);
 }
 
+void
+InProcessWinCompositorWidget::OnDestroyWindow()
+{
+  EnterPresentLock();
+  WinCompositorWidget::OnDestroyWindow();
+  LeavePresentLock();
+}
+
+void
+InProcessWinCompositorWidget::UpdateTransparency(nsTransparencyMode aMode)
+{
+  EnterPresentLock();
+  WinCompositorWidget::UpdateTransparency(aMode);
+  LeavePresentLock();
+}
+
+void
+InProcessWinCompositorWidget::ClearTransparentWindow()
+{
+  EnterPresentLock();
+  WinCompositorWidget::ClearTransparentWindow();
+  LeavePresentLock();
+}
+
+
 nsIWidget*
 InProcessWinCompositorWidget::RealWidget()
 {
   return mWindow;
 }
 
 void
 InProcessWinCompositorWidget::ObserveVsync(VsyncObserver* aObserver)
--- a/widget/windows/InProcessWinCompositorWidget.h
+++ b/widget/windows/InProcessWinCompositorWidget.h
@@ -19,16 +19,20 @@ namespace widget {
 // with the compositor.
 class InProcessWinCompositorWidget final : public WinCompositorWidget
 {
 public:
   InProcessWinCompositorWidget(const WinCompositorWidgetInitData& aInitData,
                                const layers::CompositorOptions& aOptions,
                                nsWindow* aWindow);
 
+  void OnDestroyWindow() override;
+  void UpdateTransparency(nsTransparencyMode aMode) override;
+  void ClearTransparentWindow() override;
+
   void ObserveVsync(VsyncObserver* aObserver) override;
   nsIWidget* RealWidget() override;
 
 private:
   nsWindow* mWindow;
 };
 
 } // namespace widget
--- a/xpcom/base/ErrorList.py
+++ b/xpcom/base/ErrorList.py
@@ -970,16 +970,18 @@ with modules["DOM_INDEXEDDB"]:
     errors["NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR"] = FAILURE(7)
     errors["NS_ERROR_DOM_INDEXEDDB_ABORT_ERR"] = FAILURE(8)
     errors["NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR"] = FAILURE(9)
     errors["NS_ERROR_DOM_INDEXEDDB_TIMEOUT_ERR"] = FAILURE(10)
     errors["NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR"] = FAILURE(11)
     errors["NS_ERROR_DOM_INDEXEDDB_VERSION_ERR"] = FAILURE(12)
     errors["NS_ERROR_DOM_INDEXEDDB_RECOVERABLE_ERR"] = FAILURE(1001)
     errors["NS_ERROR_DOM_INDEXEDDB_KEY_ERR"] = FAILURE(1002)
+    errors["NS_ERROR_DOM_INDEXEDDB_RENAME_OBJECT_STORE_ERR"] = FAILURE(1003)
+    errors["NS_ERROR_DOM_INDEXEDDB_RENAME_INDEX_ERR"] = FAILURE(1004)
 
 
 # =======================================================================
 # 34: NS_ERROR_MODULE_DOM_FILEHANDLE
 # =======================================================================
 with modules["DOM_FILEHANDLE"]:
     errors["NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR"] = FAILURE(1)
     errors["NS_ERROR_DOM_FILEHANDLE_NOT_ALLOWED_ERR"] = FAILURE(2)