Merge mozilla-central to inbound. a=merge CLOSED TREE
authorBogdan Tara <btara@mozilla.com>
Thu, 19 Apr 2018 01:47:13 +0300
changeset 414371 c8b6fba2ae947808a8965b2e1254487dbe1b7d48
parent 414320 1c46524d2fbf42dfb53008b0e684d9f154200f2a (current diff)
parent 414370 a0c804993efc599a95e97bea39fa1528fd0195d8 (diff)
child 414372 98b307b376401db95e4b3db071adec6ee16130f5
push id102317
push userbtara@mozilla.com
push dateWed, 18 Apr 2018 22:47:42 +0000
treeherdermozilla-inbound@c8b6fba2ae94 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone61.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to inbound. a=merge CLOSED TREE
browser/locales/searchplugins/yandex-uk.xml
devtools/client/aboutdebugging/modules/worker.js
layout/style/Makefile.in
layout/style/PythonCSSProps.h
toolkit/themes/shared/icons/input-clear.svg
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -2740,18 +2740,24 @@ window._gBrowser = {
       }
 
       newTab = true;
     }
     aTab._endRemoveArgs = [closeWindow, newTab];
 
     // swapBrowsersAndCloseOther will take care of closing the window without animation.
     if (closeWindow && aAdoptedByTab) {
-      // Remove the tab's filter to avoid leaking.
+      // Remove the tab's filter and progress listener to avoid leaking.
       if (aTab.linkedPanel) {
+        const filter = this._tabFilters.get(aTab);
+        browser.webProgress.removeProgressListener(filter);
+        const listener = this._tabListeners.get(aTab);
+        filter.removeProgressListener(listener);
+        listener.destroy();
+        this._tabListeners.delete(aTab);
         this._tabFilters.delete(aTab);
       }
       return true;
     }
 
     if (!aTab._fullyOpen) {
       // If the opening tab animation hasn't finished before we start closing the
       // tab, decrement the animation count since _handleNewTab will not get called.
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -325,20 +325,24 @@
 @RESPATH@/components/SlowScriptDebug.manifest
 @RESPATH@/components/SlowScriptDebug.js
 
 #ifdef MOZ_WEBRTC
 @RESPATH@/components/PeerConnection.js
 @RESPATH@/components/PeerConnection.manifest
 #endif
 
+; Remote control protocol
+#ifdef ENABLE_MARIONETTE
 @RESPATH@/chrome/marionette@JAREXT@
 @RESPATH@/chrome/marionette.manifest
 @RESPATH@/components/marionette.manifest
 @RESPATH@/components/marionette.js
+@RESPATH@/defaults/pref/marionette.js
+#endif
 
 @RESPATH@/components/nsAsyncShutdown.manifest
 @RESPATH@/components/nsAsyncShutdown.js
 
 @RESPATH@/components/BuiltinProviders.manifest
 @RESPATH@/components/PresentationControlService.js
 @RESPATH@/components/PresentationDataChannelSessionTransport.js
 @RESPATH@/components/PresentationDataChannelSessionTransport.manifest
@@ -451,20 +455,16 @@
 @RESPATH@/browser/defaults/blocklists
 @RESPATH@/browser/defaults/pinning
 
 ; Warning: changing the path to channel-prefs.js can cause bugs (Bug 756325)
 ; Technically this is an app pref file, but we are keeping it in the original
 ; gre location for now.
 @RESPATH@/defaults/pref/channel-prefs.js
 
-; Remote control protocol prefs
-; defined in ../../testing/marionette/prefs/marionette.js
-@RESPATH@/defaults/pref/marionette.js
-
 ; Services (gre) prefs
 @RESPATH@/defaults/pref/services-sync.js
 
 ; [Layout Engine Resources]
 ; Style Sheets, Graphics and other Resources used by the layout engine.
 @RESPATH@/res/EditorOverride.css
 @RESPATH@/res/contenteditable.css
 @RESPATH@/res/designmode.css
--- a/browser/locales/all-locales
+++ b/browser/locales/all-locales
@@ -8,16 +8,17 @@ az
 be
 bg
 bn-BD
 bn-IN
 br
 bs
 ca
 cak
+crh
 cs
 cy
 da
 de
 dsb
 el
 en-GB
 en-ZA
--- a/browser/locales/l10n.toml
+++ b/browser/locales/l10n.toml
@@ -15,16 +15,17 @@ locales = [
     "be",
     "bg",
     "bn-BD",
     "bn-IN",
     "br",
     "bs",
     "ca",
     "cak",
+    "crh",
     "cs",
     "cy",
     "da",
     "de",
     "dsb",
     "el",
     "en-GB",
     "en-ZA",
--- a/browser/locales/search/list.json
+++ b/browser/locales/search/list.json
@@ -168,16 +168,23 @@
     },
     "cak": {
       "default": {
         "visibleDefaultEngines": [
           "google", "bing", "amazondotcom", "ddg", "wikipedia-es"
         ]
       }
     },
+    "crh": {
+      "default": {
+        "visibleDefaultEngines": [
+          "google", "ddg", "twitter", "wikipedia-crh"
+        ]
+      }
+    },
     "cs": {
       "default": {
         "visibleDefaultEngines": [
           "google", "seznam-cz", "ddg", "heureka-cz", "mapy-cz", "wikipedia-cz"
         ]
       }
     },
     "cy": {
@@ -749,17 +756,17 @@
         "visibleDefaultEngines": [
           "yandex-tr", "google", "ddg", "twitter", "wikipedia-tr"
         ]
       }
     },
     "uk": {
       "default": {
         "visibleDefaultEngines": [
-          "google", "yandex-uk", "meta-ua", "ddg", "wikipedia-uk", "metamarket"
+          "google", "bing", "meta-ua", "ddg", "wikipedia-uk", "metamarket"
         ]
       }
     },
     "ur": {
       "default": {
         "visibleDefaultEngines": [
           "google", "bing", "amazon-in", "ddg", "twitter", "wikipedia-ur"
         ]
new file mode 100644
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-crh.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Vikipediya (crh)</ShortName>
+<Description>Vikipediya, Azat Entsiklopediya</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">resource://search-plugins/images/wikipedia.ico</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://crh.wikipedia.org/w/api.php">
+  <Param name="action" value="opensearch"/>
+  <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://crh.wikipedia.org/wiki/Mahsus:Search"
+     resultdomain="wikipedia.org" rel="searchform">
+  <Param name="search" value="{searchTerms}"/>
+  <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
deleted file mode 100644
--- a/browser/locales/searchplugins/yandex-uk.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
-<ShortName>Яндекс</ShortName>
-<Description>Пошук Яндексом.</Description>
-<InputEncoding>UTF-8</InputEncoding>
-<Image width="16" height="16">resource://search-plugins/images/yandex-ru.ico</Image>
-<Url type="application/x-suggestions+json" method="GET" template="https://suggest.yandex.ua/suggest-ff.cgi">
-  <Param name="part" value="{searchTerms}"/>
-</Url>
-<Url type="text/html" method="GET" template="https://www.yandex.ua/search" resultdomain="yandex.ua">
-  <MozParam name="clid" condition="purpose" purpose="searchbar"   value="2186618"/>
-  <MozParam name="clid" condition="purpose" purpose="keyword"     value="2186621"/>
-  <MozParam name="clid" condition="purpose" purpose="contextmenu" value="2186623"/>
-  <MozParam name="clid" condition="purpose" purpose="homepage"    value="2186617"/>
-  <MozParam name="clid" condition="purpose" purpose="newtab"      value="2186620"/>
-  <Param    name="text" value="{searchTerms}"/>
-</Url>
-<SearchForm>https://www.yandex.ua/</SearchForm>
-</SearchPlugin>
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -2081,17 +2081,21 @@ Toolbox.prototype = {
                                 getUnicodeUrl(this.target.url));
     }
     this.postMessage({
       name: "set-host-title",
       title
     });
   },
 
-  // Returns an instance of the preference actor
+  /**
+   * Returns an instance of the preference actor. This is a lazily initialized root
+   * actor that persists preferences to the debuggee, instead of just to the DevTools
+   * client. See the definition of the preference actor for more information.
+   */
   get preferenceFront() {
     if (this._preferenceFront) {
       return Promise.resolve(this._preferenceFront);
     }
     return this.isOpen.then(() => {
       return this.target.root.then(rootForm => {
         let front = getPreferenceFront(this.target.client, rootForm);
         this._preferenceFront = front;
--- a/devtools/server/actors/preference.js
+++ b/devtools/server/actors/preference.js
@@ -11,16 +11,26 @@ const {preferenceSpec} = require("devtoo
 
 exports.register = function(handle) {
   handle.addGlobalActor(PreferenceActor, "preferenceActor");
 };
 
 exports.unregister = function(handle) {
 };
 
+/**
+ * Normally the preferences are set using Services.prefs, but this actor allows
+ * a debugger client to set preferences on the debuggee. This is particularly useful
+ * when remote debugging, and the preferences should persist to the remote target
+ * and not to the client. If used for a local target, it effectively behaves the same
+ * as using Services.prefs.
+ *
+ * This actor is used as a Root actor, targeting the entire browser, not an individual
+ * tab.
+ */
 var PreferenceActor = protocol.ActorClassWithSpec(preferenceSpec, {
 
   typeName: "preference",
 
   getBoolPref: function(name) {
     return Services.prefs.getBoolPref(name);
   },
 
--- a/dom/media/gmp-plugin-openh264/gmp-fake-openh264.cpp
+++ b/dom/media/gmp-plugin-openh264/gmp-fake-openh264.cpp
@@ -80,18 +80,16 @@ static int g_log_level = GL_CRIT;
         if ((l >= 0) && (l <= 3)) {                        \
         log_string = kLogStrings[l];                       \
         }                                                  \
         std::cerr << log_string << ": " << x << std::endl; \
         }                                                  \
     } while(0)
 
 
-GMPPlatformAPI* g_platform_api = nullptr;
-
 class FakeVideoEncoder;
 class FakeVideoDecoder;
 
 struct EncodedFrame {
   uint32_t length_;
   uint8_t h264_compat_;
   uint32_t magic_;
   uint32_t width_;
@@ -121,31 +119,16 @@ template <typename T> class SelfDestruct
     return t;
   }
 #endif
 
  private:
   T* t_;
 };
 
-class FakeEncoderTask : public GMPTask {
- public:
-  FakeEncoderTask(FakeVideoEncoder* encoder,
-                  GMPVideoi420Frame* frame,
-                  GMPVideoFrameType type)
-      : encoder_(encoder), frame_(frame), type_(type) {}
-
-  void Run() override;
-  void Destroy() override { delete this; }
-
-  FakeVideoEncoder* encoder_;
-  GMPVideoi420Frame* frame_;
-  GMPVideoFrameType type_;
-};
-
 class FakeVideoEncoder : public GMPVideoEncoder {
  public:
   explicit FakeVideoEncoder (GMPVideoHost* hostAPI) :
     host_ (hostAPI),
     callback_ (nullptr),
     frame_size_(BIG_FRAME),
     frames_encoded_(0) {}
 
@@ -251,24 +234,18 @@ class FakeVideoEncoder : public GMPVideo
                const GMPVideoFrameType* aFrameTypes,
                uint32_t aFrameTypesLength) override {
     GMPLOG (GL_DEBUG,
             __FUNCTION__
             << " size="
             << inputImage->Width() << "x" << inputImage->Height());
 
     assert (aFrameTypesLength != 0);
+    GMPVideoFrameType frame_type = aFrameTypes[0];
 
-    g_platform_api->runonmainthread(new FakeEncoderTask(this,
-                                                        inputImage,
-                                                        aFrameTypes[0]));
-  }
-
-  void Encode_m (GMPVideoi420Frame* inputImage,
-                 GMPVideoFrameType frame_type) {
     SelfDestruct<GMPVideoi420Frame> ifd (inputImage);
 
     if (frame_type  == kGMPKeyFrame) {
       if (!inputImage)
         return;
     }
     if (!inputImage) {
       GMPLOG (GL_ERROR, "no input image");
@@ -310,36 +287,16 @@ class FakeVideoEncoder : public GMPVideo
   }
 
   GMPVideoHost* host_;
   GMPVideoEncoderCallback* callback_;
   uint32_t frame_size_;
   uint32_t frames_encoded_;
 };
 
-void FakeEncoderTask::Run() {
-  encoder_->Encode_m(frame_, type_);
-  frame_ = nullptr; // Encode_m() destroys the frame
-}
-
-class FakeDecoderTask : public GMPTask {
- public:
-  FakeDecoderTask(FakeVideoDecoder* decoder,
-                  GMPVideoEncodedFrame* frame,
-                  int64_t time)
-      : decoder_(decoder), frame_(frame), time_(time) {}
-
-  void Run() override;
-  void Destroy() override { delete this; }
-
-  FakeVideoDecoder* decoder_;
-  GMPVideoEncodedFrame* frame_;
-  int64_t time_;
-};
-
 class FakeVideoDecoder : public GMPVideoDecoder {
  public:
   explicit FakeVideoDecoder (GMPVideoHost* hostAPI) :
     host_ (hostAPI),
     callback_ (nullptr) {}
 
   ~FakeVideoDecoder() override = default;
 
@@ -360,32 +317,17 @@ class FakeVideoDecoder : public GMPVideo
   void Decode (GMPVideoEncodedFrame* inputFrame,
                bool missingFrames,
                const uint8_t* aCodecSpecificInfo,
                uint32_t aCodecSpecificInfoLength,
                int64_t renderTimeMs = -1) override {
     GMPLOG (GL_DEBUG, __FUNCTION__
             << "Decoding frame size=" << inputFrame->Size()
             << " timestamp=" << inputFrame->TimeStamp());
-    g_platform_api->runonmainthread(new FakeDecoderTask(this, inputFrame, renderTimeMs));
-  }
 
-  void Reset() override {
-  }
-
-  void Drain() override {
-  }
-
-  void DecodingComplete() override {
-    delete this;
-  }
-
-  // Return the decoded data back to the parent.
-  void Decode_m (GMPVideoEncodedFrame* inputFrame,
-                 int64_t renderTimeMs) {
     // Attach a self-destructor so that the input frame is destroyed on return.
     SelfDestruct<GMPVideoEncodedFrame> ifd (inputFrame);
 
     EncodedFrame *eframe;
     eframe = reinterpret_cast<EncodedFrame*>(inputFrame->Buffer());
     GMPLOG(GL_DEBUG,"magic="  << eframe->magic_ << " h264_compat="  << (int) eframe->h264_compat_
            << " width=" << eframe->width_ << " height=" << eframe->height_
            << " timestamp=" << inputFrame->TimeStamp()
@@ -447,30 +389,34 @@ class FakeVideoDecoder : public GMPVideo
 
     GMPLOG (GL_DEBUG, "Allocated size = "
             << frame->AllocatedSize (kGMPYPlane));
     frame->SetTimestamp (inputFrame->TimeStamp());
     frame->SetDuration (inputFrame->Duration());
     callback_->Decoded (frame);
   }
 
+  void Reset() override {
+  }
+
+  void Drain() override {
+  }
+
+  void DecodingComplete() override {
+    delete this;
+  }
+
   GMPVideoHost* host_;
   GMPVideoDecoderCallback* callback_;
 };
 
-void FakeDecoderTask::Run() {
-  decoder_->Decode_m(frame_, time_);
-  frame_ = nullptr; // Decode_m() destroys the frame
-}
-
 extern "C" {
 
   PUBLIC_FUNC GMPErr
   GMPInit (GMPPlatformAPI* aPlatformAPI) {
-    g_platform_api = aPlatformAPI;
     return GMPNoErr;
   }
 
   PUBLIC_FUNC GMPErr
   GMPGetAPI (const char* aApiName, void* aHostAPI, void** aPluginApi) {
     if (!strcmp (aApiName, GMP_API_VIDEO_DECODER)) {
       *aPluginApi = new FakeVideoDecoder (static_cast<GMPVideoHost*> (aHostAPI));
       return GMPNoErr;
@@ -479,12 +425,11 @@ extern "C" {
       *aPluginApi = new FakeVideoEncoder (static_cast<GMPVideoHost*> (aHostAPI));
       return GMPNoErr;
     }
     return GMPGenericErr;
   }
 
   PUBLIC_FUNC void
   GMPShutdown (void) {
-    g_platform_api = nullptr;
   }
 
 } // extern "C"
--- a/dom/media/platforms/PDMFactory.cpp
+++ b/dom/media/platforms/PDMFactory.cpp
@@ -17,16 +17,19 @@
 #include "FFmpegRuntimeLinker.h"
 #endif
 #ifdef MOZ_APPLEMEDIA
 #include "AppleDecoderModule.h"
 #endif
 #ifdef MOZ_WIDGET_ANDROID
 #include "AndroidDecoderModule.h"
 #endif
+#ifdef MOZ_OMX
+#include "OmxDecoderModule.h"
+#endif
 #include "GMPDecoderModule.h"
 
 #include "mozilla/CDMProxy.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/SharedThreadPool.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/StaticPrefs.h"
 #include "mozilla/SyncRunnable.h"
@@ -58,16 +61,19 @@ public:
   PDMFactoryImpl()
   {
 #ifdef XP_WIN
     WMFDecoderModule::Init();
 #endif
 #ifdef MOZ_APPLEMEDIA
     AppleDecoderModule::Init();
 #endif
+#ifdef MOZ_OMX
+    OmxDecoderModule::Init();
+#endif
 #ifdef MOZ_FFVPX
     FFVPXRuntimeLinker::Init();
 #endif
 #ifdef MOZ_FFMPEG
     FFmpegRuntimeLinker::Init();
 #endif
   }
 };
@@ -348,16 +354,22 @@ PDMFactory::CreatePDMs()
     RefPtr<PlatformDecoderModule> remote = new dom::RemoteDecoderModule(m);
     StartupPDM(remote);
     mWMFFailedToLoad = !StartupPDM(m);
   } else {
     mWMFFailedToLoad =
       StaticPrefs::MediaDecoderDoctorWmfDisabledIsFailure();
   }
 #endif
+#ifdef MOZ_OMX
+  if (StaticPrefs::MediaOmxEnabled()) {
+    m = OmxDecoderModule::Create();
+    StartupPDM(m);
+  }
+#endif
 #ifdef MOZ_FFVPX
   if (StaticPrefs::MediaFfvpxEnabled()) {
     m = FFVPXRuntimeLinker::CreateDecoderModule();
     StartupPDM(m);
   }
 #endif
 #ifdef MOZ_FFMPEG
   if (StaticPrefs::MediaFfmpegEnabled()) {
--- a/dom/media/platforms/moz.build
+++ b/dom/media/platforms/moz.build
@@ -65,16 +65,24 @@ if CONFIG['MOZ_FFMPEG']:
 if CONFIG['MOZ_AV1']:
     EXPORTS += [
         'agnostic/AOMDecoder.h',
     ]
     UNIFIED_SOURCES += [
         'agnostic/AOMDecoder.cpp',
     ]
 
+if CONFIG['MOZ_OMX']:
+    EXPORTS += [
+        'omx/OmxCoreLibLinker.h',
+    ]
+    UNIFIED_SOURCES += [
+        'omx/OmxCoreLibLinker.cpp',
+    ]
+
 if CONFIG['MOZ_APPLEMEDIA']:
   EXPORTS += [
       'apple/AppleDecoderModule.h',
   ]
   UNIFIED_SOURCES += [
       'apple/AppleATDecoder.cpp',
       'apple/AppleCMLinker.cpp',
       'apple/AppleDecoderModule.cpp',
new file mode 100644
--- /dev/null
+++ b/dom/media/platforms/omx/OmxCoreLibLinker.cpp
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "OmxCoreLibLinker.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Preferences.h"
+#include "MainThreadUtils.h"
+#include "prlink.h"
+#include "PlatformDecoderModule.h"
+
+#ifdef LOG
+#undef LOG
+#endif
+
+#define LOG(arg, ...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, ("OmxCoreLibLinker::%s: " arg, __func__, ##__VA_ARGS__))
+
+namespace mozilla
+{
+
+OmxCoreLibLinker::LinkStatus OmxCoreLibLinker::sLinkStatus =
+  LinkStatus_INIT;
+
+const char* OmxCoreLibLinker::sLibNames[] = {
+  "libopenmaxil.so",        // Raspberry Pi
+  "libomxr_core.so",        // Renesas R-Car, RZ/G
+  "libomxil-bellagio.so.0", // Bellagio: An OSS implementation of OpenMAX IL
+};
+
+PRLibrary* OmxCoreLibLinker::sLinkedLib = nullptr;
+const char* OmxCoreLibLinker::sLibName = nullptr;
+
+#define OMX_FUNC(func) void (*func)();
+#include "OmxFunctionList.h"
+#undef OMX_FUNC
+
+bool
+OmxCoreLibLinker::TryLinkingLibrary(const char *libName)
+{
+  PRLibSpec lspec;
+  lspec.type = PR_LibSpec_Pathname;
+  lspec.value.pathname = libName;
+  sLinkedLib = PR_LoadLibraryWithFlags(lspec, PR_LD_NOW | PR_LD_LOCAL);
+  if (sLinkedLib) {
+    if (Bind(libName)) {
+      sLibName = libName;
+      LOG("Succeeded to load %s", libName);
+      return true;
+    } else {
+      LOG("Failed to link %s", libName);
+    }
+    Unlink();
+  }
+  return false;
+}
+
+/* static */ bool
+OmxCoreLibLinker::Link()
+{
+  LOG("");
+
+  if (sLinkStatus) {
+    return sLinkStatus == LinkStatus_SUCCEEDED;
+  }
+
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsAutoCString libPath;
+  nsresult rv = Preferences::GetCString("media.omx.core-lib-path", libPath);
+  if (NS_SUCCEEDED(rv) && !libPath.IsEmpty()) {
+    if (TryLinkingLibrary(libPath.Data())) {
+      sLinkStatus = LinkStatus_SUCCEEDED;
+      return true;
+    }
+  }
+
+  // try known paths
+  for (size_t i = 0; i < ArrayLength(sLibNames); i++) {
+    if (TryLinkingLibrary(sLibNames[i])) {
+      sLinkStatus = LinkStatus_SUCCEEDED;
+      return true;
+    }
+  }
+  sLinkStatus = LinkStatus_FAILED;
+  return false;
+}
+
+/* static */ bool
+OmxCoreLibLinker::Bind(const char* aLibName)
+{
+#define OMX_FUNC(func)                                                  \
+  {                                                                     \
+    if (!(func = (typeof(func))PR_FindSymbol(sLinkedLib, #func))) {     \
+      LOG("Couldn't load function " #func " from %s.", aLibName);       \
+      return false;                                                     \
+    }                                                                   \
+  }
+#include "OmxFunctionList.h"
+#undef OMX_FUNC
+  return true;
+}
+
+/* static */ void
+OmxCoreLibLinker::Unlink()
+{
+  LOG("");
+
+  if (sLinkedLib) {
+    PR_UnloadLibrary(sLinkedLib);
+    sLinkedLib = nullptr;
+    sLibName = nullptr;
+    sLinkStatus = LinkStatus_INIT;
+  }
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/platforms/omx/OmxCoreLibLinker.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __OmxCoreLibLinker_h__
+#define __OmxCoreLibLinker_h__
+
+struct PRLibrary;
+
+namespace mozilla
+{
+
+class OmxCoreLibLinker
+{
+public:
+  static bool Link();
+  static void Unlink();
+
+private:
+  static PRLibrary* sLinkedLib;
+  static const char* sLibName;
+  static const char* sLibNames[];
+
+  static bool TryLinkingLibrary(const char *libName);
+  static bool Bind(const char* aLibName);
+
+  static enum LinkStatus
+  {
+    LinkStatus_INIT = 0,
+    LinkStatus_FAILED,
+    LinkStatus_SUCCEEDED
+  } sLinkStatus;
+};
+
+}
+
+#endif // __OmxCoreLibLinker_h__
--- a/dom/media/platforms/omx/OmxDataDecoder.cpp
+++ b/dom/media/platforms/omx/OmxDataDecoder.cpp
@@ -95,18 +95,20 @@ protected:
 
   AudioCompactor mAudioCompactor;
 
   // video output
   RefPtr<layers::ImageContainer> mImageContainer;
 };
 
 OmxDataDecoder::OmxDataDecoder(const TrackInfo& aTrackInfo,
+                               TaskQueue* aTaskQueue,
                                layers::ImageContainer* aImageContainer)
   : mOmxTaskQueue(CreateMediaDecodeTaskQueue("OmxDataDecoder::mOmxTaskQueue"))
+  , mTaskQueue(aTaskQueue)
   , mImageContainer(aImageContainer)
   , mWatchManager(this, mOmxTaskQueue)
   , mOmxState(OMX_STATETYPE::OMX_StateInvalid, "OmxDataDecoder::mOmxState")
   , mTrackInfo(aTrackInfo.Clone())
   , mFlushing(false)
   , mShuttingDown(false)
   , mCheckingInputExhausted(false)
   , mPortSettingsChanged(-1, "OmxDataDecoder::mPortSettingsChanged")
@@ -266,35 +268,39 @@ OmxDataDecoder::DoAsyncShutdown()
 
              return p;
            },
            [self] (const OmxCommandFailureHolder& aError) {
              self->mOmxLayer->Shutdown();
              return OmxCommandPromise::CreateAndReject(aError, __func__);
            })
     ->Then(mOmxTaskQueue, __func__,
-           [self] () {
+           [self] () -> RefPtr<ShutdownPromise> {
              LOGL("DoAsyncShutdown: OMX_StateLoaded, it is safe to shutdown omx");
              self->mOmxLayer->Shutdown();
              self->mWatchManager.Shutdown();
              self->mOmxLayer = nullptr;
              self->mMediaDataHelper = nullptr;
-
              self->mShuttingDown = false;
+             return ShutdownPromise::CreateAndResolve(true, __func__);
+           },
+           [self] () -> RefPtr<ShutdownPromise> {
+             self->mOmxLayer->Shutdown();
+             self->mWatchManager.Shutdown();
+             self->mOmxLayer = nullptr;
+             self->mMediaDataHelper = nullptr;
+             return ShutdownPromise::CreateAndReject(false, __func__);
+           })
+    ->Then(mTaskQueue, __func__,
+           [self] () {
              self->mOmxTaskQueue->BeginShutdown();
              self->mOmxTaskQueue->AwaitShutdownAndIdle();
              self->mShutdownPromise.Resolve(true, __func__);
            },
            [self] () {
-             self->mOmxLayer->Shutdown();
-             self->mWatchManager.Shutdown();
-             self->mOmxLayer = nullptr;
-             self->mMediaDataHelper = nullptr;
-
-             self->mShuttingDown = false;
              self->mOmxTaskQueue->BeginShutdown();
              self->mOmxTaskQueue->AwaitShutdownAndIdle();
              self->mShutdownPromise.Resolve(true, __func__);
            });
   return mShutdownPromise.Ensure(__func__);
 }
 
 void
--- a/dom/media/platforms/omx/OmxDataDecoder.h
+++ b/dom/media/platforms/omx/OmxDataDecoder.h
@@ -62,16 +62,17 @@ class OmxDataDecoder
   : public MediaDataDecoder
   , public DecoderDoctorLifeLogger<OmxDataDecoder>
 {
 protected:
   virtual ~OmxDataDecoder();
 
 public:
   OmxDataDecoder(const TrackInfo& aTrackInfo,
+                 TaskQueue* aTaskQueue,
                  layers::ImageContainer* aImageContainer);
 
   RefPtr<InitPromise> Init() override;
   RefPtr<DecodePromise> Decode(MediaRawData* aSample) override;
   RefPtr<DecodePromise> Drain() override;
   RefPtr<FlushPromise> Flush() override;
   RefPtr<ShutdownPromise> Shutdown() override;
 
@@ -155,16 +156,18 @@ protected:
 
   // aType could be OMX_DirMax for all types.
   RefPtr<OmxPromiseLayer::OmxBufferPromise::AllPromiseType>
   CollectBufferPromises(OMX_DIRTYPE aType);
 
   // The Omx TaskQueue.
   RefPtr<TaskQueue> mOmxTaskQueue;
 
+  RefPtr<TaskQueue> mTaskQueue;
+
   RefPtr<layers::ImageContainer> mImageContainer;
 
   WatchManager<OmxDataDecoder> mWatchManager;
 
   // It is accessed in omx TaskQueue.
   Watchable<OMX_STATETYPE> mOmxState;
 
   RefPtr<OmxPromiseLayer> mOmxLayer;
--- a/dom/media/platforms/omx/OmxDecoderModule.cpp
+++ b/dom/media/platforms/omx/OmxDecoderModule.cpp
@@ -4,30 +4,56 @@
  * 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 "OmxDecoderModule.h"
 
 #include "OmxDataDecoder.h"
 #include "OmxPlatformLayer.h"
 
+#ifdef MOZ_OMX
+#include "PureOmxPlatformLayer.h"
+#endif
+
 namespace mozilla {
 
+/* static */ bool
+OmxDecoderModule::Init()
+{
+#ifdef MOZ_OMX
+  return PureOmxPlatformLayer::Init();
+#endif
+  return false;
+}
+
+OmxDecoderModule*
+OmxDecoderModule::Create()
+{
+#ifdef MOZ_OMX
+  if (Init()) {
+    return new OmxDecoderModule();
+  }
+#endif
+  return nullptr;
+}
+
 already_AddRefed<MediaDataDecoder>
 OmxDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams)
 {
   RefPtr<OmxDataDecoder> decoder = new OmxDataDecoder(aParams.mConfig,
+                                                      aParams.mTaskQueue,
                                                       aParams.mImageContainer);
   return decoder.forget();
 }
 
 already_AddRefed<MediaDataDecoder>
 OmxDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams)
 {
   RefPtr<OmxDataDecoder> decoder = new OmxDataDecoder(aParams.mConfig,
+                                                      aParams.mTaskQueue,
                                                       nullptr);
   return decoder.forget();
 }
 
 bool
 OmxDecoderModule::SupportsMimeType(const nsACString& aMimeType,
                                    DecoderDoctorDiagnostics* aDiagnostics) const
 {
--- a/dom/media/platforms/omx/OmxDecoderModule.h
+++ b/dom/media/platforms/omx/OmxDecoderModule.h
@@ -9,16 +9,20 @@
 
 #include "PlatformDecoderModule.h"
 
 namespace mozilla {
 
 class OmxDecoderModule : public PlatformDecoderModule
 {
 public:
+  // Called on main thread.
+  static bool Init();
+  static OmxDecoderModule* Create();
+
   already_AddRefed<MediaDataDecoder>
   CreateVideoDecoder(const CreateDecoderParams& aParams) override;
 
   already_AddRefed<MediaDataDecoder>
   CreateAudioDecoder(const CreateDecoderParams& aParams) override;
 
   bool SupportsMimeType(const nsACString& aMimeType,
                         DecoderDoctorDiagnostics* aDiagnostics) const override;
new file mode 100644
--- /dev/null
+++ b/dom/media/platforms/omx/OmxFunctionList.h
@@ -0,0 +1,13 @@
+/* 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/. */
+
+OMX_FUNC(OMX_Init)
+OMX_FUNC(OMX_Deinit)
+OMX_FUNC(OMX_GetHandle)
+OMX_FUNC(OMX_FreeHandle)
+OMX_FUNC(OMX_ComponentNameEnum)
+OMX_FUNC(OMX_GetComponentsOfRole)
+OMX_FUNC(OMX_GetRolesOfComponent)
+OMX_FUNC(OMX_SetupTunnel)
+OMX_FUNC(OMX_GetContentPipe)
--- a/dom/media/platforms/omx/OmxPlatformLayer.cpp
+++ b/dom/media/platforms/omx/OmxPlatformLayer.cpp
@@ -3,16 +3,20 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "OmxPlatformLayer.h"
 
 #include "OMX_VideoExt.h" // For VP8.
 
+#ifdef MOZ_OMX
+#include "PureOmxPlatformLayer.h"
+#endif
+
 #include "VPXDecoder.h"
 
 #ifdef LOG
 #undef LOG
 #endif
 
 #define LOG(arg, ...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, ("OmxPlatformLayer -- %s: " arg, __func__, ##__VA_ARGS__))
 
@@ -277,25 +281,46 @@ OmxPlatformLayer::CompressionFormat()
   } else if (VPXDecoder::IsVP8(mInfo->mMimeType)) {
     return static_cast<OMX_VIDEO_CODINGTYPE>(OMX_VIDEO_CodingVP8);
   } else {
     MOZ_ASSERT_UNREACHABLE("Unsupported compression format");
     return OMX_VIDEO_CodingUnused;
   }
 }
 
-// For platforms without OMX IL support.
+// Implementations for different platforms will be defined in their own files.
+#if defined(MOZ_OMX)
+
+bool
+OmxPlatformLayer::SupportsMimeType(const nsACString& aMimeType)
+{
+  return PureOmxPlatformLayer::SupportsMimeType(aMimeType);
+}
+
+OmxPlatformLayer*
+OmxPlatformLayer::Create(OmxDataDecoder* aDataDecoder,
+                         OmxPromiseLayer* aPromiseLayer,
+                         TaskQueue* aTaskQueue,
+                         layers::ImageContainer* aImageContainer)
+{
+  return new PureOmxPlatformLayer(aDataDecoder, aPromiseLayer,
+                                  aTaskQueue, aImageContainer);
+}
+
+#else // For platforms without OMX IL support.
+
 bool
 OmxPlatformLayer::SupportsMimeType(const nsACString& aMimeType)
 {
   return false;
 }
 
 OmxPlatformLayer*
 OmxPlatformLayer::Create(OmxDataDecoder* aDataDecoder,
                         OmxPromiseLayer* aPromiseLayer,
                         TaskQueue* aTaskQueue,
                         layers::ImageContainer* aImageContainer)
 {
   return nullptr;
 }
 
+#endif
 }
new file mode 100644
--- /dev/null
+++ b/dom/media/platforms/omx/PureOmxPlatformLayer.cpp
@@ -0,0 +1,452 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "OmxDataDecoder.h"
+#include "OmxPromiseLayer.h"
+#include "PureOmxPlatformLayer.h"
+#include "OmxCoreLibLinker.h"
+
+#ifdef LOG
+#undef LOG
+#endif
+
+#define LOG(arg, ...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, ("PureOmxPlatformLayer(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
+#define LOG_BUF(arg, ...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, ("PureOmxBufferData(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
+
+namespace mozilla {
+
+#define OMX_FUNC(func) extern typeof(func)* func;
+#include "OmxFunctionList.h"
+#undef OMX_FUNC
+
+PureOmxBufferData::PureOmxBufferData(const PureOmxPlatformLayer& aPlatformLayer,
+                                     const OMX_PARAM_PORTDEFINITIONTYPE& aPortDef)
+  : BufferData(nullptr)
+  , mPlatformLayer(aPlatformLayer)
+  , mPortDef(aPortDef)
+{
+  LOG_BUF("");
+
+  if (ShouldUseEGLImage()) {
+    // TODO
+    LOG_BUF("OMX_UseEGLImage() seems available but using it isn't implemented yet.");
+  }
+
+  OMX_ERRORTYPE err;
+  err = OMX_AllocateBuffer(mPlatformLayer.GetComponent(),
+                           &mBuffer,
+                           mPortDef.nPortIndex,
+                           this,
+                           mPortDef.nBufferSize);
+  if (err != OMX_ErrorNone) {
+    LOG_BUF("Failed to allocate the buffer!: 0x%08x", err);
+  }
+}
+
+PureOmxBufferData::~PureOmxBufferData()
+{
+  LOG_BUF("");
+  ReleaseBuffer();
+}
+
+void PureOmxBufferData::ReleaseBuffer()
+{
+  LOG_BUF("");
+
+  if (mBuffer) {
+    OMX_ERRORTYPE err;
+    err = OMX_FreeBuffer(mPlatformLayer.GetComponent(),
+                         mPortDef.nPortIndex,
+                         mBuffer);
+    if (err != OMX_ErrorNone) {
+      LOG_BUF("Failed to free the buffer!: 0x%08x", err);
+    }
+    mBuffer = nullptr;
+  }
+}
+
+bool PureOmxBufferData::ShouldUseEGLImage()
+{
+  OMX_ERRORTYPE err;
+  err = OMX_UseEGLImage(mPlatformLayer.GetComponent(),
+                        nullptr,
+                        mPortDef.nPortIndex,
+                        nullptr,
+                        nullptr);
+  return (err != OMX_ErrorNotImplemented);
+}
+
+/* static */ bool
+PureOmxPlatformLayer::Init(void)
+{
+  if (!OmxCoreLibLinker::Link()) {
+    return false;
+  }
+
+  OMX_ERRORTYPE err = OMX_Init();
+  if (err != OMX_ErrorNone) {
+    MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug,
+            ("PureOmxPlatformLayer::%s: Failed to initialize OMXCore: 0x%08x",
+             __func__, err));
+    return false;
+  }
+
+  return true;
+}
+
+/* static */ OMX_CALLBACKTYPE PureOmxPlatformLayer::sCallbacks =
+  { EventHandler, EmptyBufferDone, FillBufferDone };
+
+PureOmxPlatformLayer::PureOmxPlatformLayer(OmxDataDecoder* aDataDecoder,
+                                           OmxPromiseLayer* aPromiseLayer,
+                                           TaskQueue* aTaskQueue,
+                                           layers::ImageContainer* aImageContainer)
+  : mComponent(nullptr)
+  , mDataDecoder(aDataDecoder)
+  , mPromiseLayer(aPromiseLayer)
+  , mTaskQueue(aTaskQueue)
+  , mImageContainer(aImageContainer)
+{
+  LOG("");
+}
+
+PureOmxPlatformLayer::~PureOmxPlatformLayer()
+{
+  LOG("");
+  if (mComponent) {
+    OMX_FreeHandle(mComponent);
+  }
+}
+
+OMX_ERRORTYPE
+PureOmxPlatformLayer::InitOmxToStateLoaded(const TrackInfo* aInfo)
+{
+  LOG("");
+
+  if (!aInfo) {
+    return OMX_ErrorUndefined;
+  }
+  mInfo = aInfo;
+
+  return CreateComponent();
+}
+
+OMX_ERRORTYPE
+PureOmxPlatformLayer::EmptyThisBuffer(BufferData* aData)
+{
+  LOG("");
+  return OMX_EmptyThisBuffer(mComponent, aData->mBuffer);
+}
+
+OMX_ERRORTYPE
+PureOmxPlatformLayer::FillThisBuffer(BufferData* aData)
+{
+  LOG("");
+  return OMX_FillThisBuffer(mComponent, aData->mBuffer);
+}
+
+OMX_ERRORTYPE
+PureOmxPlatformLayer::SendCommand(OMX_COMMANDTYPE aCmd,
+                                  OMX_U32 aParam1,
+                                  OMX_PTR aCmdData)
+{
+  LOG("aCmd: 0x%08x", aCmd);
+  if (!mComponent) {
+    return OMX_ErrorUndefined;
+  }
+  return OMX_SendCommand(mComponent, aCmd, aParam1, aCmdData);
+}
+
+nsresult
+PureOmxPlatformLayer::FindPortDefinition(OMX_DIRTYPE aType,
+                                         OMX_PARAM_PORTDEFINITIONTYPE& portDef)
+{
+  nsTArray<uint32_t> portIndex;
+  GetPortIndices(portIndex);
+  for (auto idx : portIndex) {
+    InitOmxParameter(&portDef);
+    portDef.nPortIndex = idx;
+
+    OMX_ERRORTYPE err;
+    err = GetParameter(OMX_IndexParamPortDefinition,
+                       &portDef,
+                       sizeof(OMX_PARAM_PORTDEFINITIONTYPE));
+    if (err != OMX_ErrorNone) {
+      return NS_ERROR_FAILURE;
+    } else if (portDef.eDir == aType) {
+      LOG("Found OMX_IndexParamPortDefinition: port: %d, type: %d",
+          portDef.nPortIndex, portDef.eDir);
+      return NS_OK;
+    }
+  }
+  return NS_ERROR_FAILURE;
+}
+
+nsresult
+PureOmxPlatformLayer::AllocateOmxBuffer(OMX_DIRTYPE aType,
+                                        BUFFERLIST* aBufferList)
+{
+  LOG("aType: %d", aType);
+
+  OMX_PARAM_PORTDEFINITIONTYPE portDef;
+  nsresult result = FindPortDefinition(aType, portDef);
+  if (result != NS_OK) {
+    return result;
+  }
+
+  LOG("nBufferCountActual: %d, nBufferSize: %d",
+      portDef.nBufferCountActual, portDef.nBufferSize);
+
+  for (OMX_U32 i = 0; i < portDef.nBufferCountActual; ++i) {
+    RefPtr<PureOmxBufferData> buffer = new PureOmxBufferData(*this, portDef);
+    aBufferList->AppendElement(buffer);
+  }
+
+  return NS_OK;
+}
+
+nsresult
+PureOmxPlatformLayer::ReleaseOmxBuffer(OMX_DIRTYPE aType,
+                                       BUFFERLIST* aBufferList)
+{
+  LOG("aType: 0x%08x", aType);
+
+  uint32_t len = aBufferList->Length();
+  for (uint32_t i = 0; i < len; i++) {
+    PureOmxBufferData* buffer =
+      static_cast<PureOmxBufferData*>(aBufferList->ElementAt(i).get());
+
+    // All raw OpenMAX buffers have to be released here to flush
+    // OMX_CommandStateSet for switching the state to OMX_StateLoaded.
+    // See OmxDataDecoder::DoAsyncShutdown() for more detail.
+    buffer->ReleaseBuffer();
+  }
+  aBufferList->Clear();
+
+  return NS_OK;
+}
+
+OMX_ERRORTYPE
+PureOmxPlatformLayer::GetState(OMX_STATETYPE* aType)
+{
+  LOG("");
+
+  if (mComponent) {
+    return OMX_GetState(mComponent, aType);
+  }
+
+  return OMX_ErrorUndefined;
+}
+
+OMX_ERRORTYPE
+PureOmxPlatformLayer::GetParameter(OMX_INDEXTYPE aParamIndex,
+                                   OMX_PTR aComponentParameterStructure,
+                                   OMX_U32 aComponentParameterSize)
+{
+  LOG("aParamIndex: 0x%08x", aParamIndex);
+
+  if (!mComponent) {
+    return OMX_ErrorUndefined;
+  }
+
+  return OMX_GetParameter(mComponent,
+                          aParamIndex,
+                          aComponentParameterStructure);
+}
+
+OMX_ERRORTYPE
+PureOmxPlatformLayer::SetParameter(OMX_INDEXTYPE aParamIndex,
+                                   OMX_PTR aComponentParameterStructure,
+                                   OMX_U32 aComponentParameterSize)
+{
+  LOG("aParamIndex: 0x%08x", aParamIndex);
+
+  if (!mComponent) {
+    return OMX_ErrorUndefined;
+  }
+
+  return OMX_SetParameter(mComponent,
+                          aParamIndex,
+                          aComponentParameterStructure);
+}
+
+nsresult
+PureOmxPlatformLayer::Shutdown()
+{
+  LOG("");
+  return NS_OK;
+}
+
+/* static */ OMX_ERRORTYPE
+PureOmxPlatformLayer::EventHandler(OMX_HANDLETYPE hComponent,
+                                   OMX_PTR pAppData,
+                                   OMX_EVENTTYPE eEventType,
+                                   OMX_U32 nData1,
+                                   OMX_U32 nData2,
+                                   OMX_PTR pEventData)
+{
+  PureOmxPlatformLayer* self = static_cast<PureOmxPlatformLayer*>(pAppData);
+  nsCOMPtr<nsIRunnable> r =
+    NS_NewRunnableFunction(
+        "mozilla::PureOmxPlatformLayer::EventHandler",
+        [self, eEventType, nData1, nData2, pEventData] () {
+          self->EventHandler(eEventType, nData1, nData2, pEventData);
+        });
+  nsresult rv = self->mTaskQueue->Dispatch(r.forget());
+  return NS_SUCCEEDED(rv) ? OMX_ErrorNone : OMX_ErrorUndefined;
+}
+
+/* static */ OMX_ERRORTYPE
+PureOmxPlatformLayer::EmptyBufferDone(OMX_HANDLETYPE hComponent,
+                                      OMX_IN OMX_PTR pAppData,
+                                      OMX_IN OMX_BUFFERHEADERTYPE* pBuffer)
+{
+  PureOmxPlatformLayer* self = static_cast<PureOmxPlatformLayer*>(pAppData);
+  nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+      "mozilla::PureOmxPlatformLayer::EmptyBufferDone",
+      [self, pBuffer] () {
+        self->EmptyBufferDone(pBuffer);
+      });
+  nsresult rv = self->mTaskQueue->Dispatch(r.forget());
+  return NS_SUCCEEDED(rv) ? OMX_ErrorNone : OMX_ErrorUndefined;
+}
+
+/* static */ OMX_ERRORTYPE
+PureOmxPlatformLayer::FillBufferDone(OMX_OUT OMX_HANDLETYPE hComponent,
+                                     OMX_OUT OMX_PTR pAppData,
+                                     OMX_OUT OMX_BUFFERHEADERTYPE* pBuffer)
+{
+  PureOmxPlatformLayer* self = static_cast<PureOmxPlatformLayer*>(pAppData);
+  nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+      "mozilla::PureOmxPlatformLayer::FillBufferDone",
+      [self, pBuffer] () {
+        self->FillBufferDone(pBuffer);
+      });
+  nsresult rv = self->mTaskQueue->Dispatch(r.forget());
+  return NS_SUCCEEDED(rv) ? OMX_ErrorNone : OMX_ErrorUndefined;
+}
+
+OMX_ERRORTYPE
+PureOmxPlatformLayer::EventHandler(OMX_EVENTTYPE eEventType,
+                                   OMX_U32 nData1,
+                                   OMX_U32 nData2,
+                                   OMX_PTR pEventData)
+{
+  bool handled = mPromiseLayer->Event(eEventType, nData1, nData2);
+  LOG("eEventType: 0x%08x, handled: %d", eEventType, handled);
+  return OMX_ErrorNone;
+}
+
+OMX_ERRORTYPE
+PureOmxPlatformLayer::EmptyBufferDone(OMX_IN OMX_BUFFERHEADERTYPE* pBuffer)
+{
+  PureOmxBufferData* buffer = static_cast<PureOmxBufferData*>(pBuffer->pAppPrivate);
+  OMX_DIRTYPE portDirection = buffer->GetPortDirection();
+  LOG("PortDirection: %d", portDirection);
+  mPromiseLayer->EmptyFillBufferDone(portDirection, buffer);
+  return OMX_ErrorNone;
+}
+
+OMX_ERRORTYPE
+PureOmxPlatformLayer::FillBufferDone(OMX_OUT OMX_BUFFERHEADERTYPE* pBuffer)
+{
+  PureOmxBufferData* buffer = static_cast<PureOmxBufferData*>(pBuffer->pAppPrivate);
+  OMX_DIRTYPE portDirection = buffer->GetPortDirection();
+  LOG("PortDirection: %d", portDirection);
+  mPromiseLayer->EmptyFillBufferDone(portDirection, buffer);
+  return OMX_ErrorNone;
+}
+
+bool
+PureOmxPlatformLayer::SupportsMimeType(const nsACString& aMimeType)
+{
+  return FindStandardComponent(aMimeType, nullptr);
+}
+
+static bool
+GetStandardComponentRole(const nsACString& aMimeType,
+                         nsACString& aRole)
+{
+  if (aMimeType.EqualsLiteral("video/avc") ||
+      aMimeType.EqualsLiteral("video/mp4") ||
+      aMimeType.EqualsLiteral("video/mp4v-es")) {
+    aRole.Assign("video_decoder.avc");
+    return true;
+  } else if (aMimeType.EqualsLiteral("audio/mp4a-latm") ||
+             aMimeType.EqualsLiteral("audio/mp4") ||
+             aMimeType.EqualsLiteral("audio/aac")) {
+    aRole.Assign("audio_decoder.aac");
+    return true;
+  }
+  return false;
+}
+
+/* static */ bool
+PureOmxPlatformLayer::FindStandardComponent(const nsACString& aMimeType,
+                                            nsACString* aComponentName)
+{
+  nsAutoCString role;
+  if (!GetStandardComponentRole(aMimeType, role))
+    return false;
+
+  OMX_U32 nComponents = 0;
+  OMX_ERRORTYPE err;
+  err = OMX_GetComponentsOfRole(const_cast<OMX_STRING>(role.Data()),
+                                &nComponents, nullptr);
+  if (err != OMX_ErrorNone || nComponents <= 0) {
+    return false;
+  }
+  if (!aComponentName) {
+    return true;
+  }
+
+  // TODO:
+  // Only the first component will be used.
+  // We should detect the most preferred component.
+  OMX_U8* componentNames[1];
+  UniquePtr<OMX_U8[]> componentName;
+  componentName = MakeUniqueFallible<OMX_U8[]>(OMX_MAX_STRINGNAME_SIZE);
+  componentNames[0] = componentName.get();
+  nComponents = 1;
+  err = OMX_GetComponentsOfRole(const_cast<OMX_STRING>(role.Data()),
+                                &nComponents, componentNames);
+  if (err == OMX_ErrorNone) {
+    MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug,
+            ("PureOmxPlatformLayer::%s: A component has been found for %s: %s",
+             __func__, aMimeType.Data(), componentNames[0]));
+    aComponentName->Assign(reinterpret_cast<char*>(componentNames[0]));
+  }
+
+  return err == OMX_ErrorNone;
+}
+
+OMX_ERRORTYPE
+PureOmxPlatformLayer::CreateComponent(const nsACString* aComponentName)
+{
+  nsAutoCString componentName;
+  if (aComponentName) {
+    componentName = *aComponentName;
+  } else if (!FindStandardComponent(mInfo->mMimeType, &componentName)) {
+    return OMX_ErrorComponentNotFound;
+  }
+
+  OMX_ERRORTYPE err;
+  err = OMX_GetHandle(&mComponent,
+                      const_cast<OMX_STRING>(componentName.Data()),
+                      this,
+                      &sCallbacks);
+
+  const char* mime = mInfo->mMimeType.Data();
+  if (err == OMX_ErrorNone) {
+    LOG("Succeeded to create the component for %s", mime);
+  } else {
+    LOG("Failed to create the component for %s: 0x%08x", mime, err);
+  }
+
+  return err;
+}
+
+}
new file mode 100644
--- /dev/null
+++ b/dom/media/platforms/omx/PureOmxPlatformLayer.h
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#if !defined(PureOmxPlatformLayer_h_)
+#define PureOmxPlatformLayer_h_
+
+#include "OmxPlatformLayer.h"
+
+namespace mozilla {
+
+class PureOmxPlatformLayer;
+
+class PureOmxBufferData : public OmxPromiseLayer::BufferData
+{
+protected:
+  virtual ~PureOmxBufferData();
+
+public:
+  PureOmxBufferData(const PureOmxPlatformLayer& aPlatformLayer,
+                    const OMX_PARAM_PORTDEFINITIONTYPE& aPortDef);
+
+  void ReleaseBuffer();
+  OMX_DIRTYPE GetPortDirection() const { return mPortDef.eDir; };
+
+protected:
+  bool ShouldUseEGLImage();
+
+  const PureOmxPlatformLayer& mPlatformLayer;
+  const OMX_PARAM_PORTDEFINITIONTYPE mPortDef;
+};
+
+class PureOmxPlatformLayer : public OmxPlatformLayer
+{
+public:
+  static bool Init(void);
+
+  static bool SupportsMimeType(const nsACString& aMimeType);
+
+  PureOmxPlatformLayer(OmxDataDecoder* aDataDecoder,
+                       OmxPromiseLayer* aPromiseLayer,
+                       TaskQueue* aTaskQueue,
+                       layers::ImageContainer* aImageContainer);
+
+  virtual ~PureOmxPlatformLayer();
+
+  OMX_ERRORTYPE InitOmxToStateLoaded(const TrackInfo* aInfo) override;
+
+  OMX_ERRORTYPE EmptyThisBuffer(BufferData* aData) override;
+
+  OMX_ERRORTYPE FillThisBuffer(BufferData* aData) override;
+
+  OMX_ERRORTYPE SendCommand(OMX_COMMANDTYPE aCmd,
+                            OMX_U32 aParam1,
+                            OMX_PTR aCmdData) override;
+
+  nsresult AllocateOmxBuffer(OMX_DIRTYPE aType, BUFFERLIST* aBufferList) override;
+
+  nsresult ReleaseOmxBuffer(OMX_DIRTYPE aType, BUFFERLIST* aBufferList) override;
+
+  OMX_ERRORTYPE GetState(OMX_STATETYPE* aType) override;
+
+  OMX_ERRORTYPE GetParameter(OMX_INDEXTYPE aParamIndex,
+                             OMX_PTR aComponentParameterStructure,
+                             OMX_U32 aComponentParameterSize) override;
+
+  OMX_ERRORTYPE SetParameter(OMX_INDEXTYPE aParamIndex,
+                             OMX_PTR aComponentParameterStructure,
+                             OMX_U32 aComponentParameterSize) override;
+
+  nsresult Shutdown() override;
+
+  OMX_HANDLETYPE GetComponent() const { return mComponent; };
+
+  static OMX_ERRORTYPE EventHandler(OMX_HANDLETYPE hComponent,
+                                    OMX_PTR pAppData,
+                                    OMX_EVENTTYPE eEventType,
+                                    OMX_U32 nData1,
+                                    OMX_U32 nData2,
+                                    OMX_PTR pEventData);
+  static OMX_ERRORTYPE EmptyBufferDone(OMX_HANDLETYPE hComponent,
+                                       OMX_IN OMX_PTR pAppData,
+                                       OMX_IN OMX_BUFFERHEADERTYPE* pBuffer);
+  static OMX_ERRORTYPE FillBufferDone(OMX_OUT OMX_HANDLETYPE hComponent,
+                                      OMX_OUT OMX_PTR pAppData,
+                                      OMX_OUT OMX_BUFFERHEADERTYPE* pBuffer);
+
+protected:
+  static bool FindStandardComponent(const nsACString& aMimeType,
+                                    nsACString* aComponentName);
+
+  OMX_ERRORTYPE CreateComponent(const nsACString* aComponentName = nullptr);
+  nsresult FindPortDefinition(OMX_DIRTYPE aType,
+                              OMX_PARAM_PORTDEFINITIONTYPE& portDef);
+
+  OMX_ERRORTYPE EventHandler(OMX_EVENTTYPE eEventType,
+                             OMX_U32 nData1,
+                             OMX_U32 nData2,
+                             OMX_PTR pEventData);
+  OMX_ERRORTYPE EmptyBufferDone(OMX_IN OMX_BUFFERHEADERTYPE* pBuffer);
+  OMX_ERRORTYPE FillBufferDone(OMX_OUT OMX_BUFFERHEADERTYPE* pBuffer);
+
+protected:
+  static OMX_CALLBACKTYPE sCallbacks;
+
+  OMX_HANDLETYPE mComponent;
+  RefPtr<OmxDataDecoder> mDataDecoder;
+  RefPtr<OmxPromiseLayer> mPromiseLayer;
+  RefPtr<TaskQueue> mTaskQueue;
+  RefPtr<layers::ImageContainer> mImageContainer;
+};
+
+}
+
+#endif // PureOmxPlatformLayer_h_
--- a/dom/media/platforms/omx/moz.build
+++ b/dom/media/platforms/omx/moz.build
@@ -16,16 +16,21 @@ UNIFIED_SOURCES += [
 ]
 
 LOCAL_INCLUDES += [
     '/media/openmax_il/il112',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
+if CONFIG['MOZ_OMX']:
+    UNIFIED_SOURCES += [
+        'PureOmxPlatformLayer.cpp',
+    ]
+
 FINAL_LIBRARY = 'xul'
 
 if CONFIG['CC_TYPE'] in ('clang', 'gcc'):
     CXXFLAGS += ['-Wno-error=shadow']
 
 if CONFIG['CC_TYPE'] in ('msvc', 'clang-cl'):
     # Avoid warnings from third-party code that we can not modify.
     if CONFIG['CC_TYPE'] == 'clang-cl':
--- a/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp
@@ -37,16 +37,17 @@ MediaEngineRemoteVideoSource::MediaEngin
     bool aScary)
   : mCaptureIndex(aIndex)
   , mMediaSource(aMediaSource)
   , mCapEngine(aCapEngine)
   , mScary(aScary)
   , mMutex("MediaEngineRemoteVideoSource::mMutex")
   , mRescalingBufferPool(/* zero_initialize */ false,
                          /* max_number_of_buffers */ 1)
+  , mSettingsUpdatedByFrame(MakeAndAddRef<media::Refcountable<AtomicBool>>())
   , mSettings(MakeAndAddRef<media::Refcountable<MediaTrackSettings>>())
 {
   MOZ_ASSERT(aMediaSource != MediaSourceEnum::Other);
   mSettings->mWidth.Construct(0);
   mSettings->mHeight.Construct(0);
   mSettings->mFrameRate.Construct(0);
   Init();
 }
@@ -295,44 +296,51 @@ MediaEngineRemoteVideoSource::Start(cons
   MOZ_ASSERT(mStream);
   MOZ_ASSERT(IsTrackIDExplicit(mTrackID));
 
   {
     MutexAutoLock lock(mMutex);
     mState = kStarted;
   }
 
+  mSettingsUpdatedByFrame->mValue = false;
+
   if (camera::GetChildAndCall(&camera::CamerasChild::StartCapture,
                               mCapEngine, mCaptureIndex, mCapability, this)) {
     LOG(("StartCapture failed"));
     MutexAutoLock lock(mMutex);
     mState = kStopped;
     return NS_ERROR_FAILURE;
   }
 
   NS_DispatchToMainThread(NS_NewRunnableFunction(
       "MediaEngineRemoteVideoSource::SetLastCapability",
-      [settings = mSettings, source = mMediaSource, cap = mCapability]() mutable {
+      [settings = mSettings,
+       updated = mSettingsUpdatedByFrame,
+       source = mMediaSource,
+       cap = mCapability]() mutable {
     switch (source) {
       case dom::MediaSourceEnum::Screen:
       case dom::MediaSourceEnum::Window:
       case dom::MediaSourceEnum::Application:
         // Undo the hack where ideal and max constraints are crammed together
         // in mCapability for consumption by low-level code. We don't actually
         // know the real resolution yet, so report min(ideal, max) for now.
         // TODO: This can be removed in bug 1453269.
         cap.width = std::min(cap.width >> 16, cap.width & 0xffff);
         cap.height = std::min(cap.height >> 16, cap.height & 0xffff);
         break;
       default:
         break;
     }
 
-    settings->mWidth.Value() = cap.width;
-    settings->mHeight.Value() = cap.height;
+    if (!updated->mValue) {
+      settings->mWidth.Value() = cap.width;
+      settings->mHeight.Value() = cap.height;
+    }
     settings->mFrameRate.Value() = cap.maxFPS;
   }));
 
   return NS_OK;
 }
 
 nsresult
 MediaEngineRemoteVideoSource::Stop(const RefPtr<const AllocationHandle>& aHandle)
@@ -616,35 +624,36 @@ MediaEngineRemoteVideoSource::DeliverFra
   static uint32_t frame_num = 0;
   LOGFRAME(("frame %d (%dx%d)->(%dx%d); rotation %d, timeStamp %u, "
             "ntpTimeMs %" PRIu64 ", renderTimeMs %" PRIu64,
             frame_num++, aProps.width(), aProps.height(), dst_width, dst_height,
             aProps.rotation(), aProps.timeStamp(), aProps.ntpTimeMs(),
             aProps.renderTimeMs()));
 #endif
 
-  bool sizeChanged = false;
+  if (mImageSize.width != dst_width || mImageSize.height != dst_height) {
+    NS_DispatchToMainThread(NS_NewRunnableFunction(
+        "MediaEngineRemoteVideoSource::FrameSizeChange",
+        [settings = mSettings,
+         updated = mSettingsUpdatedByFrame,
+         dst_width,
+         dst_height]() mutable {
+      settings->mWidth.Value() = dst_width;
+      settings->mHeight.Value() = dst_height;
+      updated->mValue = true;
+    }));
+  }
+
   {
     MutexAutoLock lock(mMutex);
     // implicitly releases last image
-    sizeChanged = (!mImage && image) ||
-                  (mImage && image && mImage->GetSize() != image->GetSize());
     mImage = image.forget();
     mImageSize = mImage->GetSize();
   }
 
-  if (sizeChanged) {
-    NS_DispatchToMainThread(NS_NewRunnableFunction(
-        "MediaEngineRemoteVideoSource::FrameSizeChange",
-        [settings = mSettings, dst_width, dst_height]() mutable {
-      settings->mWidth.Value() = dst_width;
-      settings->mHeight.Value() = dst_height;
-    }));
-  }
-
   // We'll push the frame into the MSG on the next Pull. This will avoid
   // swamping the MSG with frames should it be taking longer than normal to run
   // an iteration.
 
   return 0;
 }
 
 uint32_t
--- a/dom/media/webrtc/MediaEngineRemoteVideoSource.h
+++ b/dom/media/webrtc/MediaEngineRemoteVideoSource.h
@@ -219,19 +219,28 @@ private:
   RefPtr<layers::Image> mImage;
 
   // A buffer pool used to manage the temporary buffer used when rescaling
   // incoming images. Cameras IPC thread only.
   webrtc::I420BufferPool mRescalingBufferPool;
 
   // The intrinsic size of the latest captured image, so we can feed black
   // images of the same size while stopped.
-  // Set under mMutex on the owning thread. Accessed under one of the two.
+  // Set under mMutex on the Cameras IPC thread. Accessed under one of the two.
   gfx::IntSize mImageSize = gfx::IntSize(0, 0);
 
+  struct AtomicBool {
+    Atomic<bool> mValue;
+  };
+
+  // True when resolution settings have been updated from a real frame's
+  // resolution. Threadsafe.
+  // TODO: This can be removed in bug 1453269.
+  const RefPtr<media::Refcountable<AtomicBool>> mSettingsUpdatedByFrame;
+
   // The current settings of this source.
   // Note that these may be different from the settings of the underlying device
   // since we scale frames to avoid fingerprinting.
   // Members are main thread only.
   const RefPtr<media::Refcountable<dom::MediaTrackSettings>> mSettings;
 
   // The capability currently chosen by constraints of the user of this source.
   // Set under mMutex on the owning thread. Accessed under one of the two.
--- a/intl/l10n/l10n.js
+++ b/intl/l10n/l10n.js
@@ -1,45 +1,36 @@
 {
   const { DOMLocalization } =
     ChromeUtils.import("resource://gre/modules/DOMLocalization.jsm", {});
 
   /**
    * Polyfill for document.ready polyfill.
    * See: https://github.com/whatwg/html/issues/127 for details.
    *
-   * XXX: The callback is a temporary workaround for bug 1193394. Once Promises in Gecko
-   *      start beeing a microtask and stop pushing translation post-layout, we can
-   *      remove it and start using the returned Promise again.
-   *
-   * @param {Function} callback - function to be called when the document is ready.
    * @returns {Promise}
    */
-  function documentReady(callback) {
+  function documentReady() {
     if (document.contentType === "application/vnd.mozilla.xul+xml") {
       // XUL
       return new Promise(
         resolve => document.addEventListener(
-          "MozBeforeInitialXULLayout", () => {
-            resolve(callback());
-          }, { once: true }
+          "MozBeforeInitialXULLayout", resolve, { once: true }
         )
       );
     }
 
     // HTML
     const rs = document.readyState;
     if (rs === "interactive" || rs === "completed") {
-      return Promise.resolve(callback);
+      return Promise.resolve();
     }
     return new Promise(
       resolve => document.addEventListener(
-        "readystatechange", () => {
-          resolve(callback());
-        }, { once: true }
+        "readystatechange", resolve, { once: true }
       )
     );
   }
 
   /**
    * Scans the `elem` for links with localization resources.
    *
    * @param {Element} elem
@@ -53,14 +44,14 @@
 
   const resourceIds = getResourceLinks(document.head || document);
 
   document.l10n = new DOMLocalization(window, resourceIds);
 
   // trigger first context to be fetched eagerly
   document.l10n.ctxs.touchNext();
 
-  document.l10n.ready = documentReady(() => {
+  document.l10n.ready = documentReady().then(() => {
     document.l10n.registerObservers();
     document.l10n.connectRoot(document.documentElement);
     return document.l10n.translateRoots();
   });
 }
--- a/layout/generic/nsFlexContainerFrame.cpp
+++ b/layout/generic/nsFlexContainerFrame.cpp
@@ -30,16 +30,17 @@ using namespace mozilla::layout;
 // Convenience typedefs for helper classes that we forward-declare in .h file
 // (so that nsFlexContainerFrame methods can use them as parameters):
 typedef nsFlexContainerFrame::FlexItem FlexItem;
 typedef nsFlexContainerFrame::FlexLine FlexLine;
 typedef nsFlexContainerFrame::FlexboxAxisTracker FlexboxAxisTracker;
 typedef nsFlexContainerFrame::StrutInfo StrutInfo;
 typedef nsFlexContainerFrame::CachedMeasuringReflowResult
           CachedMeasuringReflowResult;
+typedef nsLayoutUtils::IntrinsicISizeType IntrinsicISizeType;
 
 static mozilla::LazyLogModule gFlexContainerLog("nsFlexContainerFrame");
 
 // XXXdholbert Some of this helper-stuff should be separated out into a general
 // "main/cross-axis utils" header, shared by grid & flexbox?
 // (Particularly when grid gets support for align-*/justify-* properties.)
 
 // Helper enums
@@ -1738,16 +1739,19 @@ nsFlexContainerFrame::MeasureAscentAndBS
 
   aItem.Frame()->SetProperty(CachedFlexMeasuringReflow(), result);
   return *result;
 }
 
 /* virtual */ void
 nsFlexContainerFrame::MarkIntrinsicISizesDirty()
 {
+  mCachedMinISize = NS_INTRINSIC_WIDTH_UNKNOWN;
+  mCachedPrefISize = NS_INTRINSIC_WIDTH_UNKNOWN;
+
   for (nsIFrame* childFrame : mFrames) {
     childFrame->DeleteProperty(CachedFlexMeasuringReflow());
   }
   nsContainerFrame::MarkIntrinsicISizesDirty();
 }
 
 nscoord
 nsFlexContainerFrame::
@@ -5068,83 +5072,72 @@ nsFlexContainerFrame::ReflowPlaceholders
 
     // Mark the placeholder frame to indicate that it's not actually at the
     // element's static position, because we need to apply CSS Alignment after
     // we determine the OOF's size:
     placeholder->AddStateBits(PLACEHOLDER_STATICPOS_NEEDS_CSSALIGN);
   }
 }
 
-/* virtual */ nscoord
-nsFlexContainerFrame::GetMinISize(gfxContext* aRenderingContext)
+nscoord
+nsFlexContainerFrame::IntrinsicISize(gfxContext* aRenderingContext,
+                                     IntrinsicISizeType aType)
 {
-  nscoord minISize = 0;
-  DISPLAY_MIN_WIDTH(this, minISize);
-
+  nscoord containerISize = 0;
   RenumberList();
 
   const nsStylePosition* stylePos = StylePosition();
   const FlexboxAxisTracker axisTracker(this, GetWritingMode());
 
   const bool useMozBoxCollapseBehavior =
     ShouldUseMozBoxCollapseBehavior(StyleDisplay());
 
   for (nsIFrame* childFrame : mFrames) {
     // If we're using legacy "visibility:collapse" behavior, then we don't
     // care about the sizes of any collapsed children.
     if (!useMozBoxCollapseBehavior ||
         (NS_STYLE_VISIBILITY_COLLAPSE !=
          childFrame->StyleVisibility()->mVisible)) {
-      nscoord childMinISize =
+      nscoord childISize =
         nsLayoutUtils::IntrinsicForContainer(aRenderingContext, childFrame,
-                                             nsLayoutUtils::MIN_ISIZE);
-      // For a horizontal single-line flex container, the intrinsic min
-      // isize is the sum of its items' min isizes.
-      // For a column-oriented flex container, or for a multi-line row-
-      // oriented flex container, the intrinsic min isize is the max of
-      // its items' min isizes.
+                                             aType);
+      // * For a row-oriented single-line flex container, the intrinsic
+      // {min/pref}-isize is the sum of its items' {min/pref}-isizes.
+      // * For a column-oriented flex container, the intrinsic min isize
+      // is the max of its items' min isizes.
+      // * For a row-oriented multi-line flex container, the intrinsic
+      // pref isize is former (sum), and its min isize is the latter (max).
+      bool isSingleLine = (NS_STYLE_FLEX_WRAP_NOWRAP == stylePos->mFlexWrap);
       if (axisTracker.IsRowOriented() &&
-          NS_STYLE_FLEX_WRAP_NOWRAP == stylePos->mFlexWrap) {
-        minISize += childMinISize;
-      } else {
-        minISize = std::max(minISize, childMinISize);
+          (isSingleLine || aType == nsLayoutUtils::PREF_ISIZE)) {
+        containerISize += childISize;
+      } else { // (col-oriented, or MIN_ISIZE for multi-line row flex container)
+        containerISize = std::max(containerISize, childISize);
       }
     }
   }
-  return minISize;
+
+  return containerISize;
+}
+
+/* virtual */ nscoord
+nsFlexContainerFrame::GetMinISize(gfxContext* aRenderingContext)
+{
+  DISPLAY_MIN_WIDTH(this, mCachedMinISize);
+  if (mCachedMinISize == NS_INTRINSIC_WIDTH_UNKNOWN) {
+    mCachedMinISize = IntrinsicISize(aRenderingContext,
+                                     nsLayoutUtils::MIN_ISIZE);
+  }
+
+  return mCachedMinISize;
 }
 
 /* virtual */ nscoord
 nsFlexContainerFrame::GetPrefISize(gfxContext* aRenderingContext)
 {
-  nscoord prefISize = 0;
-  DISPLAY_PREF_WIDTH(this, prefISize);
-
-  RenumberList();
-
-  // XXXdholbert Optimization: We could cache our intrinsic widths like
-  // nsBlockFrame does (and return it early from this function if it's set).
-  // Whenever anything happens that might change it, set it to
-  // NS_INTRINSIC_WIDTH_UNKNOWN (like nsBlockFrame::MarkIntrinsicISizesDirty
-  // does)
-  const FlexboxAxisTracker axisTracker(this, GetWritingMode());
-
-  const bool useMozBoxCollapseBehavior =
-    ShouldUseMozBoxCollapseBehavior(StyleDisplay());
-
-  for (nsIFrame* childFrame : mFrames) {
-    // If we're using legacy "visibility:collapse" behavior, then we don't
-    // care about the sizes of any collapsed children.
-    if (!useMozBoxCollapseBehavior ||
-        (NS_STYLE_VISIBILITY_COLLAPSE !=
-         childFrame->StyleVisibility()->mVisible)) {
-      nscoord childPrefISize =
-        nsLayoutUtils::IntrinsicForContainer(aRenderingContext, childFrame,
-                                             nsLayoutUtils::PREF_ISIZE);
-      if (axisTracker.IsRowOriented()) {
-        prefISize += childPrefISize;
-      } else {
-        prefISize = std::max(prefISize, childPrefISize);
-      }
-    }
-  }
-  return prefISize;
+  DISPLAY_PREF_WIDTH(this, mCachedPrefISize);
+  if (mCachedPrefISize == NS_INTRINSIC_WIDTH_UNKNOWN) {
+    mCachedPrefISize = IntrinsicISize(aRenderingContext,
+                                      nsLayoutUtils::PREF_ISIZE);
+  }
+
+  return mCachedPrefISize;
 }
--- a/layout/generic/nsFlexContainerFrame.h
+++ b/layout/generic/nsFlexContainerFrame.h
@@ -224,16 +224,18 @@ public:
    */
   static bool IsUsedFlexBasisContent(const nsStyleCoord* aFlexBasis,
                                      const nsStyleCoord* aMainSize);
 
 protected:
   // Protected constructor & destructor
   explicit nsFlexContainerFrame(ComputedStyle* aStyle)
     : nsContainerFrame(aStyle, kClassID)
+    , mCachedMinISize(NS_INTRINSIC_WIDTH_UNKNOWN)
+    , mCachedPrefISize(NS_INTRINSIC_WIDTH_UNKNOWN)
     , mBaselineFromLastReflow(NS_INTRINSIC_WIDTH_UNKNOWN)
     , mLastBaselineFromLastReflow(NS_INTRINSIC_WIDTH_UNKNOWN)
   {}
 
   virtual ~nsFlexContainerFrame();
 
   /*
    * This method does the bulk of the flex layout, implementing the algorithm
@@ -431,14 +433,26 @@ protected:
    *                           reflow methods to interpret positions correctly).
    */
   void ReflowPlaceholders(nsPresContext* aPresContext,
                           const ReflowInput& aReflowInput,
                           nsTArray<nsIFrame*>& aPlaceholders,
                           const mozilla::LogicalPoint& aContentBoxOrigin,
                           const nsSize& aContainerSize);
 
+  /**
+   * Helper for GetMinISize / GetPrefISize.
+   */
+  nscoord IntrinsicISize(gfxContext* aRenderingContext,
+                         nsLayoutUtils::IntrinsicISizeType aType);
+
+  /**
+   * Cached values to optimize GetMinISize/GetPrefISize.
+   */
+  nscoord mCachedMinISize;
+  nscoord mCachedPrefISize;
+
   nscoord mBaselineFromLastReflow;
   // Note: the last baseline is a distance from our border-box end edge.
   nscoord mLastBaselineFromLastReflow;
 };
 
 #endif /* nsFlexContainerFrame_h___ */
--- a/mobile/android/app/geckoview-prefs.js
+++ b/mobile/android/app/geckoview-prefs.js
@@ -4,9 +4,12 @@
 
 #filter substitution
 
 #include mobile.js
 
 pref("privacy.trackingprotection.pbmode.enabled", false);
 pref("dom.ipc.processCount", 1);
 pref("dom.ipc.keepProcessesAlive.web", 1);
-pref("dom.ipc.processPrelaunch.enabled", false);
\ No newline at end of file
+pref("dom.ipc.processPrelaunch.enabled", false);
+
+// Tell Telemetry that we're in GeckoView mode.
+pref("toolkit.telemetry.isGeckoViewMode", true);
--- a/mobile/android/components/geckoview/GeckoViewStartup.js
+++ b/mobile/android/components/geckoview/GeckoViewStartup.js
@@ -1,15 +1,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/. */
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetters(this, {
+  GeckoViewTelemetryController: "resource://gre/modules/GeckoViewTelemetryController.jsm",
   GeckoViewUtils: "resource://gre/modules/GeckoViewUtils.jsm",
   Services: "resource://gre/modules/Services.jsm",
 });
 
 function GeckoViewStartup() {
 }
 
 GeckoViewStartup.prototype = {
@@ -64,15 +65,20 @@ GeckoViewStartup.prototype = {
           module: "resource://gre/modules/ContentPrefServiceParent.jsm",
           init: cpsp => cpsp.alwaysInit(),
           ppmm: [
             "ContentPrefs:FunctionCall",
             "ContentPrefs:AddObserverForName",
             "ContentPrefs:RemoveObserverForName",
           ],
         });
+
+        // This initializes Telemetry for GeckoView only in the parent process.
+        // The Telemetry initialization for the content process is performed in
+        // ContentProcessSingleton.js for consistency with Desktop Telemetry.
+        GeckoViewTelemetryController.setup();
         break;
       }
     }
   },
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([GeckoViewStartup]);
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/BaseSessionTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/BaseSessionTest.kt
@@ -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/. */
 
 package org.mozilla.geckoview.test
 
 import android.os.Parcel
 import android.support.test.InstrumentationRegistry
 import org.mozilla.geckoview.GeckoSession
+import org.mozilla.geckoview.GeckoSessionSettings
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule
 
 import org.hamcrest.Matcher
 import org.hamcrest.Matchers
 import org.junit.Rule
 import org.junit.rules.ErrorCollector
 
 import kotlin.reflect.KClass
@@ -41,20 +42,23 @@ open class BaseSessionTest(noErrorCollec
     init {
         if (!noErrorCollector) {
             sessionRule.errorCollector = errors
         }
     }
 
     fun <T> forEachCall(vararg values: T): T = sessionRule.forEachCall(*values)
 
-    fun GeckoSession.getTestBytes(path: String) =
+    fun getTestBytes(path: String) =
             InstrumentationRegistry.getTargetContext().resources.assets
                     .open(path.removePrefix("/assets/")).readBytes()
 
+    val GeckoSession.isRemote
+        get() = this.settings.getBoolean(GeckoSessionSettings.USE_MULTIPROCESS)
+
     fun GeckoSession.loadTestPath(path: String) =
             this.loadUri(GeckoSessionTestRule.APK_URI_PREFIX + path.removePrefix("/"))
 
     inline fun GeckoSession.toParcel(lambda: (Parcel) -> Unit) {
         val parcel = Parcel.obtain()
         try {
             this.writeToParcel(parcel, 0)
 
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt
@@ -121,17 +121,17 @@ class NavigationDelegateTest : BaseSessi
             @AssertCalled(count = 1)
             override fun onPageStop(session: GeckoSession, success: Boolean) {
                 assertThat("Page should load successfully", success, equalTo(true))
             }
         })
     }
 
     @Test fun loadData_html() {
-        var bytes = sessionRule.session.getTestBytes(HELLO_HTML_PATH)
+        val bytes = getTestBytes(HELLO_HTML_PATH)
         assertThat("test html should have data", bytes.size, greaterThan(0))
 
         sessionRule.session.loadData(bytes, "text/html");
         sessionRule.waitForPageStop();
 
         sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate, Callbacks.ProgressDelegate, Callbacks.ContentDelegate {
             @AssertCalled(count = 1)
             override fun onTitleChange(session: GeckoSession, title: String) {
@@ -146,17 +146,17 @@ class NavigationDelegateTest : BaseSessi
             @AssertCalled(count = 1)
             override fun onPageStop(session: GeckoSession, success: Boolean) {
                 assertThat("Page should load successfully", success, equalTo(true))
             }
         })
     }
 
     fun loadDataHelper(assetPath: String, mimeType: String? = null, baseUri: String? = null) {
-        var bytes = sessionRule.session.getTestBytes(assetPath)
+        val bytes = getTestBytes(assetPath)
         assertThat("test data should have bytes", bytes.size, greaterThan(0))
 
         sessionRule.session.loadData(bytes, mimeType, baseUri);
         sessionRule.waitForPageStop();
 
         sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate, Callbacks.ProgressDelegate {
             @AssertCalled(count = 1)
             override fun onLocationChange(session: GeckoSession, url: String) {
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java
@@ -398,19 +398,20 @@ public class GeckoSessionTestRule extend
             }
             return "";
         }
 
         public boolean isAutomation() {
             return !getEnvVar("MOZ_IN_AUTOMATION").isEmpty();
         }
 
-        public boolean isE10s() {
-            return mMainSession.getSettings().getBoolean(
-                    GeckoSessionSettings.USE_MULTIPROCESS);
+        public boolean isMultiprocess() {
+            return Boolean.valueOf(InstrumentationRegistry.getArguments()
+                                                          .getString("use_multiprocess",
+                                                                     "true"));
         }
 
         public boolean isDebugging() {
             return Debug.isDebuggerConnected();
         }
     }
 
     protected class CallbackDelegates {
@@ -531,16 +532,17 @@ public class GeckoSessionTestRule extend
     protected Point mDisplaySize;
     protected SurfaceTexture mDisplayTexture;
     protected Surface mDisplaySurface;
     protected GeckoDisplay mDisplay;
     protected boolean mClosedSession;
 
     public GeckoSessionTestRule() {
         mDefaultSettings = new GeckoSessionSettings();
+        mDefaultSettings.setBoolean(GeckoSessionSettings.USE_MULTIPROCESS, env.isMultiprocess());
     }
 
     /**
      * Set an ErrorCollector for assertion errors, or null to not use one.
      *
      * @param ec ErrorCollector or null.
      */
     public void setErrorCollector(final @Nullable ErrorCollector ec) {
@@ -724,18 +726,18 @@ public class GeckoSessionTestRule extend
 
         final Class<?>[] classes = CALLBACK_CLASSES.toArray(new Class<?>[CALLBACK_CLASSES.size()]);
         mCallbackProxy = Proxy.newProxyInstance(GeckoSession.class.getClassLoader(),
                                                 classes, recorder);
 
         if (sRuntime == null) {
             final GeckoRuntimeSettings.Builder runtimeSettingsBuilder =
                 new GeckoRuntimeSettings.Builder();
-            runtimeSettingsBuilder.arguments(new String[] { "-purgecaches" });
-
+            runtimeSettingsBuilder.arguments(new String[] { "-purgecaches" })
+                                  .extras(InstrumentationRegistry.getArguments());
             sRuntime = GeckoRuntime.create(
                 InstrumentationRegistry.getTargetContext(),
                 runtimeSettingsBuilder.build());
         }
 
         mMainSession = new GeckoSession(settings);
         prepareSession(mMainSession);
 
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -365,21 +365,23 @@
 @BINPATH@/components/PresentationDevicePrompt.js
 @BINPATH@/components/PresentationRequestUIGlue.js
 @BINPATH@/components/PromptService.js
 @BINPATH@/components/SessionStore.js
 @BINPATH@/components/Snippets.js
 @BINPATH@/components/XPIDialogService.js
 #endif
 
+; Remote control protocol
 #ifdef ENABLE_MARIONETTE
 @BINPATH@/chrome/marionette@JAREXT@
 @BINPATH@/chrome/marionette.manifest
 @BINPATH@/components/marionette.manifest
 @BINPATH@/components/marionette.js
+@BINPATH@/defaults/pref/marionette.js
 #endif
 
 #ifdef PKG_LOCALE_MANIFEST
 #include @PKG_LOCALE_MANIFEST@
 #endif
 
 ; NOTE: This must match the config checks in
 ; /toolkit/components/backgroundhangmonitor/moz.build.
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -513,16 +513,24 @@ PREF("media.navigator.hardware.vp8_decod
 // decoding.
 VARCACHE_PREF(
   "media.navigator.mediadatadecoder_enabled",
    MediaNavigatorMediadatadecoderEnabled,
   bool, false
 )
 #endif // MOZ_WEBRTC
 
+#ifdef MOZ_OMX
+VARCACHE_PREF(
+  "media.omx.enabled",
+   MediaOmxEnabled,
+  bool, false
+)
+#endif
+
 #ifdef MOZ_FFMPEG
 
 # if defined(XP_MACOSX)
 #  define PREF_VALUE false
 # else
 #  define PREF_VALUE true
 # endif
 VARCACHE_PREF(
--- a/netwerk/dns/TRRService.cpp
+++ b/netwerk/dns/TRRService.cpp
@@ -281,41 +281,41 @@ TRRService::Observe(nsISupports *aSubjec
 
   } else if (!strcmp(aTopic, kOpenCaptivePortalLoginEvent)) {
     // We are in a captive portal
     LOG(("TRRservice in captive portal\n"));
     mCaptiveIsPassed = false;
   } else if (!strcmp(aTopic, NS_CAPTIVE_PORTAL_CONNECTIVITY)) {
     nsAutoCString data = NS_ConvertUTF16toUTF8(aData);
     LOG(("TRRservice captive portal was %s\n", data.get()));
-    if (data.Equals("clear")) {
-      if (!mTRRBLStorage) {
-        mTRRBLStorage = DataStorage::Get(DataStorageClass::TRRBlacklist);
-        if (mTRRBLStorage) {
-          bool storageWillPersist = true;
-          if (NS_FAILED(mTRRBLStorage->Init(storageWillPersist))) {
-            mTRRBLStorage = nullptr;
+    if (!mTRRBLStorage) {
+      mTRRBLStorage = DataStorage::Get(DataStorageClass::TRRBlacklist);
+      if (mTRRBLStorage) {
+        bool storageWillPersist = true;
+        if (NS_FAILED(mTRRBLStorage->Init(storageWillPersist))) {
+          mTRRBLStorage = nullptr;
+        }
+        if (mClearTRRBLStorage) {
+          if (mTRRBLStorage) {
+            mTRRBLStorage->Clear();
           }
-          if (mClearTRRBLStorage) {
-            if (mTRRBLStorage) {
-              mTRRBLStorage->Clear();
-            }
-            mClearTRRBLStorage = false;
-          }
+          mClearTRRBLStorage = false;
         }
       }
-      if (mConfirmationState != CONFIRM_OK) {
-        mConfirmationState = CONFIRM_TRYING;
-        MaybeConfirm();
-      } else {
-        LOG(("TRRservice CP clear when already up!\n"));
-      }
-      mCaptiveIsPassed = true;
     }
 
+    if (mConfirmationState != CONFIRM_OK) {
+      mConfirmationState = CONFIRM_TRYING;
+      MaybeConfirm();
+    } else {
+      LOG(("TRRservice CP clear when already up!\n"));
+    }
+
+    mCaptiveIsPassed = true;
+
   } else if (!strcmp(aTopic, kClearPrivateData) ||
              !strcmp(aTopic, kPurge)) {
     // flush the TRR blacklist, both in-memory and on-disk
     if (mTRRBLStorage) {
       mTRRBLStorage->Clear();
     }
   }
   return NS_OK;
--- a/testing/marionette/prefs/marionette.js
+++ b/testing/marionette/prefs/marionette.js
@@ -1,18 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* global pref */
 
-// Marionette is the remote protocol that lets OOP programs communicate
-// with, instrument, and control Gecko.
+// Marionette is the remote protocol that lets OOP programs
+// communicate with, instrument, and control Gecko.
 
-// Controls whether the Marionette component is enabled.
+// Starts and stops the Marionette server.
 pref("marionette.enabled", false);
 
 // Delay server startup until a modal dialogue has been clicked to
 // allow time for user to set breakpoints in Browser Toolbox.
 pref("marionette.debugging.clicktostart", false);
 
 // Marionette logging verbosity.  Allowed values are "fatal", "error",
 // "warn", "info", "config", "debug", and "trace".
--- a/toolkit/components/places/nsNavHistoryResult.cpp
+++ b/toolkit/components/places/nsNavHistoryResult.cpp
@@ -128,17 +128,20 @@ getUpdateRequirements(const RefPtr<nsNav
   if (aOptions->ResultType() ==
         nsINavHistoryQueryOptions::RESULTS_AS_LEFT_PANE_QUERY)
       return QUERYUPDATE_NONE;
 
   // Whenever there is a maximum number of results,
   // and we are not a bookmark query we must requery. This
   // is because we can't generally know if any given addition/change causes
   // the item to be in the top N items in the database.
-  if (aOptions->MaxResults() > 0)
+  uint16_t sortingMode = aOptions->SortingMode();
+  if (aOptions->MaxResults() > 0 &&
+      sortingMode != nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING &&
+      sortingMode != nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING)
     return QUERYUPDATE_COMPLEX;
 
   if (domainBasedItems)
     return QUERYUPDATE_HOST;
   if (!nonTimeBasedItems)
     return QUERYUPDATE_TIME;
 
   return QUERYUPDATE_SIMPLE;
@@ -2431,16 +2434,20 @@ static nsresult setHistoryDetailsCallbac
  */
 nsresult
 nsNavHistoryQueryResultNode::OnVisit(nsIURI* aURI, int64_t aVisitId,
                                      PRTime aTime, uint32_t aTransitionType,
                                      bool aHidden, uint32_t* aAdded)
 {
   if (aHidden && !mOptions->IncludeHidden())
     return NS_OK;
+  // Skip the notification if the query is filtered by specific transition types
+  // and this visit has a different one.
+  if (mTransitions.Length() > 0 && !mTransitions.Contains(aTransitionType))
+    return NS_OK;
 
   nsNavHistoryResult* result = GetResult();
   NS_ENSURE_STATE(result);
   if (result->mBatchInProgress &&
       ++mBatchChanges > MAX_BATCH_CHANGES_BEFORE_REFRESH) {
     nsresult rv = Refresh();
     NS_ENSURE_SUCCESS(rv, rv);
     return NS_OK;
@@ -2491,35 +2498,49 @@ nsNavHistoryQueryResultNode::OnVisit(nsI
           return NS_OK; // after our time range
       }
       // Now we know that our visit satisfies the time range, fall through to
       // the QUERYUPDATE_SIMPLE case below.
       MOZ_FALLTHROUGH;
     }
 
     case QUERYUPDATE_SIMPLE: {
-      // If the query is filtered by some transitions, skip the
-      // update if aTransitionType doesn't match any of them.
-      if (mTransitions.Length() > 0 && !mTransitions.Contains(aTransitionType))
-        return NS_OK;
-
       // The history service can tell us whether the new item should appear
       // in the result.  We first have to construct a node for it to check.
       RefPtr<nsNavHistoryResultNode> addition;
       nsresult rv = history->VisitIdToResultNode(aVisitId, mOptions,
                                                  getter_AddRefs(addition));
       NS_ENSURE_SUCCESS(rv, rv);
       if (!addition) {
         // Certain result types manage the nodes by themselves.
         return NS_OK;
       }
       addition->mTransitionType = aTransitionType;
       if (!evaluateQueryForNode(mQuery, mOptions, addition))
         return NS_OK; // don't need to include in our query
 
+      // Optimization for a common case: if the query has maxResults and is
+      // sorted by date, get the current boundaries and check if the added visit
+      // would fit.
+      // Later, we may have to remove the last child to respect maxResults.
+      if (mOptions->MaxResults() &&
+          static_cast<uint32_t>(mChildren.Count()) >= mOptions->MaxResults()) {
+        uint16_t sortType = GetSortType();
+        if (sortType == nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING &&
+            aTime > std::max(mChildren[0]->mTime,
+                             mChildren[mChildren.Count() -1]->mTime)) {
+          return NS_OK;
+        }
+        if (sortType == nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING &&
+            aTime < std::min(mChildren[0]->mTime,
+                             mChildren[mChildren.Count() -1]->mTime)) {
+          return NS_OK;
+        }
+      }
+
       if (mOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_VISIT) {
         // If this is a visit type query, just insert the new visit.  We never
         // update visits, only add or remove them.
         rv = InsertSortedChild(addition);
         NS_ENSURE_SUCCESS(rv, rv);
       } else {
         uint16_t sortType = GetSortType();
         bool updateSorting =
@@ -2534,16 +2555,22 @@ nsNavHistoryQueryResultNode::OnVisit(nsI
                         setHistoryDetailsCallback,
                         const_cast<void*>(static_cast<void*>(addition.get())))) {
           // Couldn't find a node to update.
           rv = InsertSortedChild(addition);
           NS_ENSURE_SUCCESS(rv, rv);
         }
       }
 
+      // Trim the children if necessary.
+      if (mOptions->MaxResults() &&
+          static_cast<uint32_t>(mChildren.Count()) > mOptions->MaxResults()) {
+        mChildren.RemoveObjectAt(mChildren.Count() - 1);
+      }
+
       if (aAdded)
         ++(*aAdded);
 
       break;
     }
 
     case QUERYUPDATE_COMPLEX:
     case QUERYUPDATE_COMPLEX_WITH_BOOKMARKS:
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/queries/test_downloadHistory_liveUpdate.js
@@ -0,0 +1,112 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This test ensures that download history (filtered by transition) queries
+// don't invalidate (and requery) too often.
+
+function accumulateNotifications(result) {
+  let notifications = [];
+  let resultObserver = new Proxy(NavHistoryResultObserver, {
+    get(target, name) {
+      if (name == "check") {
+        result.removeObserver(resultObserver, false);
+        return expectedNotifications =>
+          Assert.deepEqual(notifications, expectedNotifications);
+      }
+      // ignore a few uninteresting notifications.
+      if (["QueryInterface", "containerStateChanged"].includes(name))
+        return () => {};
+      return () => {
+        notifications.push(name);
+      };
+    }
+  });
+  result.addObserver(resultObserver, false);
+  return resultObserver;
+}
+
+add_task(async function test_downloadhistory_query_notifications() {
+  const MAX_RESULTS = 5;
+  let query = PlacesUtils.history.getNewQuery();
+  query.setTransitions([PlacesUtils.history.TRANSITIONS.DOWNLOAD], 1);
+  let options = PlacesUtils.history.getNewQueryOptions();
+  options.sortingMode = Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING;
+  options.maxResults = MAX_RESULTS;
+  let result = PlacesUtils.history.executeQuery(query, options);
+  let notifications = accumulateNotifications(result);
+  let root = PlacesUtils.asContainer(result.root);
+  root.containerOpen = true;
+  // Add more maxResults downloads in order.
+  let transitions = Object.values(PlacesUtils.history.TRANSITIONS);
+  for (let transition of transitions) {
+    let uri = Services.io.newURI("http://fx-search.com/" + transition);
+    await PlacesTestUtils.addVisits({ uri, transition, title: "test " + transition });
+    // For each visit also set apart:
+    //  - a bookmark
+    //  - an annotation
+    //  - an icon
+    await PlacesUtils.bookmarks.insert({
+      url: uri,
+      parentGuid: PlacesUtils.bookmarks.unfiledGuid
+    });
+    PlacesUtils.annotations.setPageAnnotation(uri, "test/anno", "testValue", 0,
+                                              PlacesUtils.annotations.EXPIRE_WITH_HISTORY);
+    await PlacesTestUtils.addFavicons(new Map([[uri.spec, SMALLPNG_DATA_URI.spec]]));
+  }
+  // Remove all the visits one by one.
+  for (let transition of transitions) {
+    let uri = Services.io.newURI("http://fx-search.com/" + transition);
+    await PlacesUtils.history.remove(uri);
+  }
+  root.containerOpen = false;
+  // We pretty much don't want to see invalidateContainer here, because that
+  // means we requeried.
+  // We also don't want to see changes caused by filtered-out transition types.
+  notifications.check(["nodeHistoryDetailsChanged",
+                       "nodeInserted",
+                       "nodeTitleChanged",
+                       "nodeIconChanged",
+                       "nodeRemoved"]);
+});
+
+add_task(async function test_downloadhistory_query_filtering() {
+  const MAX_RESULTS = 3;
+  let query = PlacesUtils.history.getNewQuery();
+  query.setTransitions([PlacesUtils.history.TRANSITIONS.DOWNLOAD], 1);
+  let options = PlacesUtils.history.getNewQueryOptions();
+  options.sortingMode = Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING;
+  options.maxResults = MAX_RESULTS;
+  let result = PlacesUtils.history.executeQuery(query, options);
+  let root = PlacesUtils.asContainer(result.root);
+  root.containerOpen = true;
+  Assert.equal(root.childCount, 0, "No visits found");
+  // Add more than maxResults downloads.
+  let uris = [];
+  // Define a monotonic visit date to ensure results order stability.
+  let visitDate = Date.now() * 1000;
+  for (let i = 0; i < MAX_RESULTS + 1; ++i, visitDate += 1000) {
+    let uri = `http://fx-search.com/download/${i}`;
+    await PlacesTestUtils.addVisits({
+      uri,
+      transition: PlacesUtils.history.TRANSITIONS.DOWNLOAD,
+      visitDate
+    });
+    uris.push(uri);
+  }
+  // Add an older download visit out of the maxResults timeframe.
+  await PlacesTestUtils.addVisits({
+    uri: `http://fx-search.com/download/unordered`,
+    transition: PlacesUtils.history.TRANSITIONS.DOWNLOAD,
+    visitDate: new Date(Date.now() - 7200000)
+  });
+
+  Assert.equal(root.childCount, MAX_RESULTS, "Result should be limited");
+  // Invert the uris array because we are sorted by date descending.
+  uris.reverse();
+  for (let i = 0; i < root.childCount; ++i) {
+    let node = root.getChild(i);
+    Assert.equal(node.uri, uris[i], "Found the expected uri");
+  }
+
+  root.containerOpen = false;
+});
--- a/toolkit/components/places/tests/queries/xpcshell.ini
+++ b/toolkit/components/places/tests/queries/xpcshell.ini
@@ -3,16 +3,17 @@ head = head_queries.js
 skip-if = toolkit == 'android'
 
 [test_415716.js]
 [test_abstime-annotation-domain.js]
 [test_abstime-annotation-uri.js]
 [test_async.js]
 [test_bookmarks.js]
 [test_containersQueries_sorting.js]
+[test_downloadHistory_liveUpdate.js]
 [test_excludeQueries.js]
 [test_history_queries_tags_liveUpdate.js]
 [test_history_queries_titles_liveUpdate.js]
 [test_onlyBookmarked.js]
 [test_options_inherit.js]
 [test_query_uri_liveupdate.js]
 [test_queryMultipleFolder.js]
 [test_querySerialization.js]
--- a/toolkit/components/processsingleton/ContentProcessSingleton.js
+++ b/toolkit/components/processsingleton/ContentProcessSingleton.js
@@ -2,29 +2,38 @@
  * 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";
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
-ChromeUtils.defineModuleGetter(this, "TelemetryController",
-                               "resource://gre/modules/TelemetryController.jsm");
+XPCOMUtils.defineLazyModuleGetters(this, {
+  GeckoViewTelemetryController: "resource://gre/modules/GeckoViewTelemetryController.jsm",
+  TelemetryController: "resource://gre/modules/TelemetryController.jsm",
+});
 
 function ContentProcessSingleton() {}
 ContentProcessSingleton.prototype = {
   classID: Components.ID("{ca2a8470-45c7-11e4-916c-0800200c9a66}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                          Ci.nsISupportsWeakReference]),
 
   observe(subject, topic, data) {
     switch (topic) {
     case "app-startup": {
       Services.obs.addObserver(this, "xpcom-shutdown");
+      // Initialize Telemetry in the content process: use a different
+      // controller depending on the platform.
+      if (Services.prefs.getBoolPref("toolkit.telemetry.isGeckoViewMode", false)) {
+        GeckoViewTelemetryController.setup();
+        return;
+      }
+      // Initialize Firefox Desktop Telemetry.
       TelemetryController.observe(null, topic, null);
       break;
     }
     case "xpcom-shutdown":
       Services.obs.removeObserver(this, "xpcom-shutdown");
       break;
     }
   },
--- a/toolkit/components/telemetry/Telemetry.cpp
+++ b/toolkit/components/telemetry/Telemetry.cpp
@@ -1810,17 +1810,25 @@ TelemetryImpl::SnapshotEvents(uint32_t a
   return TelemetryEvent::CreateSnapshots(aDataset, aClear, aCx, optional_argc, aResult);
 }
 
 NS_IMETHODIMP
 TelemetryImpl::RegisterEvents(const nsACString& aCategory,
                               JS::Handle<JS::Value> aEventData,
                               JSContext* cx)
 {
-  return TelemetryEvent::RegisterEvents(aCategory, aEventData, cx);
+  return TelemetryEvent::RegisterEvents(aCategory, aEventData, false, cx);
+}
+
+NS_IMETHODIMP
+TelemetryImpl::RegisterBuiltinEvents(const nsACString& aCategory,
+                              JS::Handle<JS::Value> aEventData,
+                              JSContext* cx)
+{
+  return TelemetryEvent::RegisterEvents(aCategory, aEventData, true, cx);
 }
 
 NS_IMETHODIMP
 TelemetryImpl::ClearEvents()
 {
   TelemetryEvent::ClearEvents();
   return NS_OK;
 }
--- a/toolkit/components/telemetry/TelemetryController.jsm
+++ b/toolkit/components/telemetry/TelemetryController.jsm
@@ -570,32 +570,17 @@ var Impl = {
       Telemetry.canRecordExtended = false;
       return false;
     }
 
     // Configure base Telemetry recording.
     // Unified Telemetry makes it opt-out. If extended Telemetry is enabled, base recording
     // is always on as well.
     if (IS_UNIFIED_TELEMETRY) {
-      // Enable extended Telemetry on pre-release channels and disable it
-      // on Release/ESR.
-      let prereleaseChannels = ["nightly", "aurora", "beta"];
-      if (!AppConstants.MOZILLA_OFFICIAL) {
-        // Turn extended telemetry for local developer builds.
-        prereleaseChannels.push("default");
-      }
-      const isPrereleaseChannel =
-        prereleaseChannels.includes(AppConstants.MOZ_UPDATE_CHANNEL);
-      const isReleaseCandidateOnBeta =
-        AppConstants.MOZ_UPDATE_CHANNEL === "release" &&
-        Services.prefs.getCharPref("app.update.channel", null) === "beta";
-      Telemetry.canRecordBase = true;
-      Telemetry.canRecordExtended = isPrereleaseChannel ||
-        isReleaseCandidateOnBeta ||
-        Services.prefs.getBoolPref(TelemetryUtils.Preferences.OverridePreRelease, false);
+      TelemetryUtils.setTelemetryRecordingFlags();
     } else {
       // We're not on unified Telemetry, stick to the old behaviour for
       // supporting Fennec.
       Telemetry.canRecordBase = Telemetry.canRecordExtended = Utils.isTelemetryEnabled;
     }
 
     this._log.config("enableTelemetryRecording - canRecordBase:" + Telemetry.canRecordBase +
                      ", canRecordExtended: " + Telemetry.canRecordExtended);
@@ -1010,29 +995,39 @@ var Impl = {
   async registerJsProbes() {
     // We don't support this outside of developer builds.
     if (AppConstants.MOZILLA_OFFICIAL && !this._testMode) {
       return;
     }
 
     this._log.trace("registerJsProbes - registering builtin JS probes");
 
+    await this.registerScalarProbes();
+    await this.registerEventProbes();
+  },
+
+  _loadProbeDefinitions(filename) {
+    let probeFile = Services.dirsvc.get("GreD", Ci.nsIFile);
+    probeFile.append(filename);
+    if (!probeFile.exists()) {
+      this._log.trace(`loadProbeDefinitions - no builtin JS probe file ${filename}`);
+      return null;
+    }
+
+    return OS.File.read(probeFile.path, { encoding: "utf-8" });
+  },
+
+  async registerScalarProbes() {
+    this._log.trace("registerScalarProbes - registering scalar builtin JS probes");
+
     // Load the scalar probes JSON file.
     const scalarProbeFilename = "ScalarArtifactDefinitions.json";
-    let scalarProbeFile = Services.dirsvc.get("GreD", Ci.nsIFile);
-    scalarProbeFile.append(scalarProbeFilename);
-    if (!scalarProbeFile.exists()) {
-      this._log.trace("registerJsProbes - no scalar builtin JS probes");
-      return;
-    }
-
-    // Load the file off the disk.
     let scalarJSProbes = {};
     try {
-      let fileContent = await OS.File.read(scalarProbeFile.path, { encoding: "utf-8" });
+      let fileContent = await this._loadProbeDefinitions(scalarProbeFilename);
       scalarJSProbes = JSON.parse(fileContent, (property, value) => {
         // Fixup the "kind" property: it's a string, and we need the constant
         // coming from nsITelemetry.
         if (property !== "kind" || typeof value != "string") {
           return value;
         }
 
         let newValue;
@@ -1045,18 +1040,38 @@ var Impl = {
             break;
           case "nsITelemetry::SCALAR_TYPE_STRING":
             newValue = Telemetry.SCALAR_TYPE_STRING;
             break;
         }
         return newValue;
       });
     } catch (ex) {
-      this._log.error(`registerJsProbes - there was an error loading {$scalarProbeFilename}`,
+      this._log.error(`registerScalarProbes - there was an error loading ${scalarProbeFilename}`,
                       ex);
     }
 
     // Register the builtin probes.
     for (let category in scalarJSProbes) {
       Telemetry.registerBuiltinScalars(category, scalarJSProbes[category]);
     }
   },
+
+  async registerEventProbes() {
+    this._log.trace("registerEventProbes - registering builtin JS Event probes");
+
+    // Load the event probes JSON file.
+    const eventProbeFilename = "EventArtifactDefinitions.json";
+    let eventJSProbes = {};
+    try {
+      let fileContent = await this._loadProbeDefinitions(eventProbeFilename);
+      eventJSProbes = JSON.parse(fileContent);
+    } catch (ex) {
+      this._log.error(`registerEventProbes - there was an error loading ${eventProbeFilename}`,
+                      ex);
+    }
+
+    // Register the builtin probes.
+    for (let category in eventJSProbes) {
+      Telemetry.registerBuiltinEvents(category, eventJSProbes[category]);
+    }
+  },
 };
--- a/toolkit/components/telemetry/TelemetryEvent.cpp
+++ b/toolkit/components/telemetry/TelemetryEvent.cpp
@@ -123,32 +123,34 @@ typedef nsClassHashtable<nsCStringHashKe
 struct EventKey {
   uint32_t id;
   bool dynamic;
 };
 
 struct DynamicEventInfo {
   DynamicEventInfo(const nsACString& category, const nsACString& method,
                    const nsACString& object, nsTArray<nsCString>& extra_keys,
-                   bool recordOnRelease)
+                   bool recordOnRelease, bool builtin)
     : category(category)
     , method(method)
     , object(object)
     , extra_keys(extra_keys)
     , recordOnRelease(recordOnRelease)
+    , builtin(builtin)
   {}
 
   DynamicEventInfo(const DynamicEventInfo&) = default;
   DynamicEventInfo& operator=(const DynamicEventInfo&) = delete;
 
   const nsCString category;
   const nsCString method;
   const nsCString object;
   const nsTArray<nsCString> extra_keys;
   const bool recordOnRelease;
+  const bool builtin;
 
   size_t
   SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
   {
     size_t n = 0;
 
     n += category.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
     n += method.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
@@ -311,18 +313,24 @@ nsClassHashtable<nsCStringHashKey, Event
 // The CategoryName -> CategoryID cache map.
 StringUintMap gCategoryNameIDMap;
 
 // This tracks the IDs of the categories for which recording is enabled.
 nsTHashtable<nsCStringHashKey> gEnabledCategories;
 
 // The main event storage. Events are inserted here, keyed by process id and
 // in recording order.
+typedef nsUint32HashKey ProcessIDHashKey;
 typedef nsTArray<EventRecord> EventRecordArray;
-nsClassHashtable<nsUint32HashKey, EventRecordArray> gEventRecords;
+typedef nsClassHashtable<ProcessIDHashKey, EventRecordArray> EventRecordsMapType;
+
+EventRecordsMapType gEventRecords;
+// Provide separate storage for "dynamic builtin" events needed to
+// support "build faster" in local developer builds.
+EventRecordsMapType gBuiltinEventRecords;
 
 // The details on dynamic events that are recorded from addons are registered here.
 StaticAutoPtr<nsTArray<DynamicEventInfo>> gDynamicEventInfo;
 
 } // namespace
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
@@ -387,22 +395,27 @@ CanRecordEvent(const StaticMutexAutoLock
 bool
 IsExpired(const EventKey& key)
 {
   return key.id == kExpiredEventId;
 }
 
 EventRecordArray*
 GetEventRecordsForProcess(const StaticMutexAutoLock& lock, ProcessID processType,
-                          const EventKey& eventKey)
+                          const EventKey& eventKey, bool isDynamicBuiltin)
 {
+  // Put dynamic-builtin events (used to support "build faster") in a
+  // separate storage.
+  EventRecordsMapType& processStorage =
+    isDynamicBuiltin ? gBuiltinEventRecords : gEventRecords;
+
   EventRecordArray* eventRecords = nullptr;
-  if (!gEventRecords.Get(uint32_t(processType), &eventRecords)) {
+  if (!processStorage.Get(uint32_t(processType), &eventRecords)) {
     eventRecords = new EventRecordArray();
-    gEventRecords.Put(uint32_t(processType), eventRecords);
+    processStorage.Put(uint32_t(processType), eventRecords);
   }
   return eventRecords;
 }
 
 EventKey*
 GetEventKey(const StaticMutexAutoLock& lock, const nsACString& category,
            const nsACString& method, const nsACString& object)
 {
@@ -446,35 +459,40 @@ RecordEvent(const StaticMutexAutoLock& l
             const Maybe<nsCString>& value, const ExtraArray& extra)
 {
   // Look up the event id.
   EventKey* eventKey = GetEventKey(lock, category, method, object);
   if (!eventKey) {
     return RecordEventResult::UnknownEvent;
   }
 
-  if (eventKey->dynamic) {
-    processType = ProcessID::Dynamic;
-  }
-
-  EventRecordArray* eventRecords = GetEventRecordsForProcess(lock, processType, *eventKey);
-
-  // Apply hard limit on event count in storage.
-  if (eventRecords->Length() >= kMaxEventRecords) {
-    return RecordEventResult::StorageLimitReached;
-  }
-
   // If the event is expired or not enabled for this process, we silently drop this call.
   // We don't want recording for expired probes to be an error so code doesn't
   // have to be removed at a specific time or version.
   // Even logging warnings would become very noisy.
   if (IsExpired(*eventKey)) {
     return RecordEventResult::ExpiredEvent;
   }
 
+  // Fixup the process id only for non-builtin (e.g. supporting build faster)
+  // dynamic events.
+  if (eventKey->dynamic &&
+      !(*gDynamicEventInfo)[eventKey->id].builtin) {
+    processType = ProcessID::Dynamic;
+  }
+
+  bool isDynamicBuiltin = eventKey->dynamic && (*gDynamicEventInfo)[eventKey->id].builtin;
+  EventRecordArray* eventRecords =
+    GetEventRecordsForProcess(lock, processType, *eventKey, isDynamicBuiltin);
+
+  // Apply hard limit on event count in storage.
+  if (eventRecords->Length() >= kMaxEventRecords) {
+    return RecordEventResult::StorageLimitReached;
+  }
+
   // Check whether the extra keys passed are valid.
   if (!CheckExtraKeysValid(*eventKey, extra)) {
     return RecordEventResult::InvalidExtraKey;
   }
 
   // Check whether we can record this event.
   if (!CanRecordEvent(lock, *eventKey, processType)) {
     return RecordEventResult::Ok;
@@ -727,16 +745,17 @@ TelemetryEvent::DeInitializeGlobalState(
 
   gCanRecordBase = false;
   gCanRecordExtended = false;
 
   gEventNameIDMap.Clear();
   gCategoryNameIDMap.Clear();
   gEnabledCategories.Clear();
   gEventRecords.Clear();
+  gBuiltinEventRecords.Clear();
 
   gDynamicEventInfo = nullptr;
 
   gInitDone = false;
 }
 
 void
 TelemetryEvent::SetCanRecordBase(bool b)
@@ -951,22 +970,23 @@ GetArrayPropertyValues(JSContext* cx, JS
   }
 
   return true;
 }
 
 nsresult
 TelemetryEvent::RegisterEvents(const nsACString& aCategory,
                                JS::Handle<JS::Value> aEventData,
+                               bool aBuiltin,
                                JSContext* cx)
 {
   MOZ_ASSERT(XRE_IsParentProcess(),
              "Events can only be registered in the parent process");
 
-  if (!IsValidIdentifierString(aCategory, 30, true, false)) {
+  if (!IsValidIdentifierString(aCategory, 30, true, true)) {
     JS_ReportErrorASCII(cx, "Category parameter should match the identifier pattern.");
     return NS_ERROR_INVALID_ARG;
   }
 
   if (!aEventData.isObject()) {
     JS_ReportErrorASCII(cx, "Event data parameter should be an object");
     return NS_ERROR_INVALID_ARG;
   }
@@ -1040,17 +1060,17 @@ TelemetryEvent::RegisterEvents(const nsA
         return NS_ERROR_FAILURE;
       }
 
       recordOnRelease = temp.toBoolean();
     }
 
     // Validate methods.
     for (auto& method : methods) {
-      if (!IsValidIdentifierString(method, kMaxMethodNameByteLength, false, false)) {
+      if (!IsValidIdentifierString(method, kMaxMethodNameByteLength, false, true)) {
         JS_ReportErrorASCII(cx, "Method names should match the identifier pattern.");
         return NS_ERROR_INVALID_ARG;
       }
     }
 
     // Validate objects.
     for (auto& object : objects) {
       if (!IsValidIdentifierString(object, kMaxObjectNameByteLength, false, true)) {
@@ -1060,29 +1080,29 @@ TelemetryEvent::RegisterEvents(const nsA
     }
 
     // Validate extra keys.
     if (extra_keys.Length() > kMaxExtraKeyCount) {
       JS_ReportErrorASCII(cx, "No more than 10 extra keys can be registered.");
       return NS_ERROR_INVALID_ARG;
     }
     for (auto& key : extra_keys) {
-      if (!IsValidIdentifierString(key, kMaxExtraKeyNameByteLength, false, false)) {
+      if (!IsValidIdentifierString(key, kMaxExtraKeyNameByteLength, false, true)) {
         JS_ReportErrorASCII(cx, "Extra key names should match the identifier pattern.");
         return NS_ERROR_INVALID_ARG;
       }
     }
 
     // Append event infos to be registered.
     for (auto& method : methods) {
       for (auto& object : objects) {
         // We defer the actual registration here in case any other event description is invalid.
         // In that case we don't need to roll back any partial registration.
         DynamicEventInfo info{aCategory, method, object,
-                              extra_keys, recordOnRelease};
+                              extra_keys, recordOnRelease, aBuiltin};
         newEventInfos.AppendElement(info);
         newEventExpired.AppendElement(expired);
       }
     }
   }
 
   {
     StaticMutexAutoLock locker(gTelemetryEventsMutex);
@@ -1110,36 +1130,48 @@ TelemetryEvent::CreateSnapshots(uint32_t
   nsTArray<mozilla::Pair<const char*, EventRecordArray>> processEvents;
   {
     StaticMutexAutoLock locker(gTelemetryEventsMutex);
 
     if (!gInitDone) {
       return NS_ERROR_FAILURE;
     }
 
-    for (auto iter = gEventRecords.Iter(); !iter.Done(); iter.Next()) {
-      const EventRecordArray* eventStorage = static_cast<EventRecordArray*>(iter.Data());
-      EventRecordArray events;
+    // The snapshotting function is the same for both static and dynamic builtin events.
+    // We can use the same function and store the events in the same output storage.
+    auto snapshotter = [aDataset, &locker, &processEvents]
+                       (EventRecordsMapType& aProcessStorage)
+    {
+
+      for (auto iter = aProcessStorage.Iter(); !iter.Done(); iter.Next()) {
+        const EventRecordArray* eventStorage = static_cast<EventRecordArray*>(iter.Data());
+        EventRecordArray events;
 
-      const uint32_t len = eventStorage->Length();
-      for (uint32_t i = 0; i < len; ++i) {
-        const EventRecord& record = (*eventStorage)[i];
-        if (IsInDataset(GetDataset(locker, record.GetEventKey()), aDataset)) {
-          events.AppendElement(record);
+        const uint32_t len = eventStorage->Length();
+        for (uint32_t i = 0; i < len; ++i) {
+          const EventRecord& record = (*eventStorage)[i];
+          if (IsInDataset(GetDataset(locker, record.GetEventKey()), aDataset)) {
+            events.AppendElement(record);
+          }
+        }
+
+        if (events.Length()) {
+          const char* processName = GetNameForProcessID(ProcessID(iter.Key()));
+          processEvents.AppendElement(mozilla::MakePair(processName, Move(events)));
         }
       }
+    };
 
-      if (events.Length()) {
-        const char* processName = GetNameForProcessID(ProcessID(iter.Key()));
-        processEvents.AppendElement(mozilla::MakePair(processName, Move(events)));
-      }
-    }
+    // Take a snapshot of the plain and dynamic builtin events.
+    snapshotter(gEventRecords);
+    snapshotter(gBuiltinEventRecords);
 
     if (aClear) {
       gEventRecords.Clear();
+      gBuiltinEventRecords.Clear();
     }
   }
 
   // (2) Serialize the events to a JS object.
   JS::RootedObject rootObj(cx, JS_NewPlainObject(cx));
   if (!rootObj) {
     return NS_ERROR_FAILURE;
   }
@@ -1169,16 +1201,17 @@ TelemetryEvent::ClearEvents()
 {
   StaticMutexAutoLock lock(gTelemetryEventsMutex);
 
   if (!gInitDone) {
     return;
   }
 
   gEventRecords.Clear();
+  gBuiltinEventRecords.Clear();
 }
 
 void
 TelemetryEvent::SetEventRecordingEnabled(const nsACString& category, bool enabled)
 {
   StaticMutexAutoLock locker(gTelemetryEventsMutex);
 
   uint32_t categoryId;
@@ -1196,27 +1229,33 @@ TelemetryEvent::SetEventRecordingEnabled
 }
 
 size_t
 TelemetryEvent::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
 {
   StaticMutexAutoLock locker(gTelemetryEventsMutex);
   size_t n = 0;
 
-
-  n += gEventRecords.ShallowSizeOfExcludingThis(aMallocSizeOf);
-  for (auto iter = gEventRecords.Iter(); !iter.Done(); iter.Next()) {
-    EventRecordArray* eventRecords = static_cast<EventRecordArray*>(iter.Data());
-    n += eventRecords->ShallowSizeOfIncludingThis(aMallocSizeOf);
+  auto getSizeOfRecords = [aMallocSizeOf](auto &storageMap)
+  {
+    size_t partial = storageMap.ShallowSizeOfExcludingThis(aMallocSizeOf);
+    for (auto iter = storageMap.Iter(); !iter.Done(); iter.Next()) {
+      EventRecordArray* eventRecords = static_cast<EventRecordArray*>(iter.Data());
+      partial += eventRecords->ShallowSizeOfIncludingThis(aMallocSizeOf);
 
-    const uint32_t len = eventRecords->Length();
-    for (uint32_t i = 0; i < len; ++i) {
-      n += (*eventRecords)[i].SizeOfExcludingThis(aMallocSizeOf);
+      const uint32_t len = eventRecords->Length();
+      for (uint32_t i = 0; i < len; ++i) {
+        partial += (*eventRecords)[i].SizeOfExcludingThis(aMallocSizeOf);
+      }
     }
-  }
+    return partial;
+  };
+
+  n += getSizeOfRecords(gEventRecords);
+  n += getSizeOfRecords(gBuiltinEventRecords);
 
   n += gEventNameIDMap.ShallowSizeOfExcludingThis(aMallocSizeOf);
   for (auto iter = gEventNameIDMap.ConstIter(); !iter.Done(); iter.Next()) {
     n += iter.Key().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
   }
 
   n += gCategoryNameIDMap.ShallowSizeOfExcludingThis(aMallocSizeOf);
   for (auto iter = gCategoryNameIDMap.ConstIter(); !iter.Done(); iter.Next()) {
--- a/toolkit/components/telemetry/TelemetryEvent.h
+++ b/toolkit/components/telemetry/TelemetryEvent.h
@@ -31,17 +31,17 @@ void SetCanRecordExtended(bool b);
 // JS API Endpoints.
 nsresult RecordEvent(const nsACString& aCategory, const nsACString& aMethod,
                      const nsACString& aObject, JS::HandleValue aValue,
                      JS::HandleValue aExtra, JSContext* aCx,
                      uint8_t optional_argc);
 
 void SetEventRecordingEnabled(const nsACString& aCategory, bool aEnabled);
 nsresult RegisterEvents(const nsACString& aCategory, JS::Handle<JS::Value> aEventData,
-                        JSContext* cx);
+                        bool aBuiltin, JSContext* cx);
 
 nsresult CreateSnapshots(uint32_t aDataset, bool aClear, JSContext* aCx,
                          uint8_t optional_argc, JS::MutableHandleValue aResult);
 
 // Record events from child processes.
 nsresult RecordChildEvents(mozilla::Telemetry::ProcessID aProcessType,
                            const nsTArray<mozilla::Telemetry::ChildEventData>& aEvents);
 
--- a/toolkit/components/telemetry/TelemetryUtils.jsm
+++ b/toolkit/components/telemetry/TelemetryUtils.jsm
@@ -4,16 +4,18 @@
 
 "use strict";
 
 var EXPORTED_SYMBOLS = [
   "TelemetryUtils"
 ];
 
 ChromeUtils.import("resource://gre/modules/Services.jsm", this);
+ChromeUtils.defineModuleGetter(this, "AppConstants",
+                               "resource://gre/modules/AppConstants.jsm");
 
 const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
 
 const PREF_TELEMETRY_ENABLED = "toolkit.telemetry.enabled";
 
 const IS_CONTENT_PROCESS = (function() {
   // We cannot use Services.appinfo here because in telemetry xpcshell tests,
   // appinfo is initially unavailable, and becomes available only later on.
@@ -196,10 +198,32 @@ var TelemetryUtils = {
    * or (non-monotonic) Date value if this fails back.
    */
   monotonicNow() {
     try {
       return Services.telemetry.msSinceProcessStart();
     } catch (ex) {
       return Date.now();
     }
-  }
+  },
+
+  /**
+   * Set the Telemetry core recording flag for Unified Telemetry.
+   */
+  setTelemetryRecordingFlags() {
+    // Enable extended Telemetry on pre-release channels and disable it
+    // on Release/ESR.
+    let prereleaseChannels = ["nightly", "aurora", "beta"];
+    if (!AppConstants.MOZILLA_OFFICIAL) {
+      // Turn extended telemetry for local developer builds.
+      prereleaseChannels.push("default");
+    }
+    const isPrereleaseChannel =
+      prereleaseChannels.includes(AppConstants.MOZ_UPDATE_CHANNEL);
+    const isReleaseCandidateOnBeta =
+      AppConstants.MOZ_UPDATE_CHANNEL === "release" &&
+      Services.prefs.getCharPref("app.update.channel", null) === "beta";
+    Services.telemetry.canRecordBase = true;
+    Services.telemetry.canRecordExtended = isPrereleaseChannel ||
+      isReleaseCandidateOnBeta ||
+      Services.prefs.getBoolPref(this.Preferences.OverridePreRelease, false);
+  },
 };
--- a/toolkit/components/telemetry/docs/collection/events.rst
+++ b/toolkit/components/telemetry/docs/collection/events.rst
@@ -1,16 +1,17 @@
 .. _eventtelemetry:
 
 ======
 Events
 ======
 
 Across the different Firefox initiatives, there is a common need for a mechanism for recording, storing, sending & analysing application usage in an event-oriented format.
 *Event Telemetry* specifies a common events data format, which allows for broader, shared usage of data processing tools.
+Adding events is supported in artifact builds and build faster workflows.
 
 For events recorded into Firefox Telemetry we also provide an API that opaquely handles storage and submission to our servers.
 
 .. important::
 
     Every new data collection in Firefox needs a `data collection review <https://wiki.mozilla.org/Firefox/Data_Collection#Requesting_Approval>`_ from a data collection peer. Just set the feedback? flag for one of the data peers. We try to reply within a business day.
 
 Serialization format
@@ -38,17 +39,17 @@ Each event is of the form:
 
 Where the individual fields are:
 
 - ``timestamp``: ``Number``, positive integer. This is the time in ms when the event was recorded, relative to the main process start time.
 - ``category``: ``String``, identifier. The category is a group name for events and helps to avoid name conflicts.
 - ``method``: ``String``, identifier. This describes the type of event that occurred, e.g. ``click``, ``keydown`` or ``focus``.
 - ``object``: ``String``, identifier. This is the object the event occurred on, e.g. ``reload_button`` or ``urlbar``.
 - ``value``: ``String``, optional, may be ``null``. This is a user defined value, providing context for the event.
-- ``extra``: ``Object``, optional, may be ``null``. This is an object of the form ``{"key": "value", ...}``, both keys and values need to be strings. This is used for events where additional richer context is needed.
+- ``extra``: ``Object``, optional, may be ``null``. This is an object of the form ``{"key": "value", ...}``, both keys and values need to be strings, keys are identifiers. This is used for events where additional richer context is needed.
 
 .. _eventlimits:
 
 Limits
 ------
 
 Each ``String`` marked as an identifier is restricted to the following regex pattern: ``^[:alpha:][:alnum:_.]*[:alnum:]$``.
 
@@ -228,8 +229,9 @@ Version History
 - Firefox 52: Initial event support (`bug 1302663 <https://bugzilla.mozilla.org/show_bug.cgi?id=1302663>`_).
 - Firefox 53: Event recording disabled by default (`bug 1329139 <https://bugzilla.mozilla.org/show_bug.cgi?id=1329139>`_).
 - Firefox 54: Added child process events (`bug 1313326 <https://bugzilla.mozilla.org/show_bug.cgi?id=1313326>`_).
 - Firefox 56: Added support for recording new probes from add-ons (`bug 1302681 <bug https://bugzilla.mozilla.org/show_bug.cgi?id=1302681>`_).
 - Firefox 58:
 
    - Ignore re-registering existing events for a category instead of failing (`bug 1408975 <https://bugzilla.mozilla.org/show_bug.cgi?id=1408975>`_).
    - Removed support for the ``expiry_date`` property, as it was unused (`bug 1414638 <https://bugzilla.mozilla.org/show_bug.cgi?id=1414638>`_).
+- Firefox 61: Enabled support for adding events in artifact builds and build-faster workflows (`bug 1448945 <https://bugzilla.mozilla.org/show_bug.cgi?id=1448945>`_).
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/geckoview/GeckoViewTelemetryController.jsm
@@ -0,0 +1,32 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+ChromeUtils.import("resource://gre/modules/GeckoViewUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+  Services: "resource://gre/modules/Services.jsm",
+  TelemetryUtils: "resource://gre/modules/TelemetryUtils.jsm",
+});
+
+GeckoViewUtils.initLogging("GeckoView.TelemetryController", this);
+
+var EXPORTED_SYMBOLS = ["GeckoViewTelemetryController"];
+
+let GeckoViewTelemetryController = {
+  /**
+   * Setup the Telemetry recording flags. This must be called
+   * in all the processes that need to collect Telemetry.
+   */
+  setup() {
+    debug `setup`;
+
+    TelemetryUtils.setTelemetryRecordingFlags();
+
+    debug `setup - canRecordPrereleaseData ${Services.telemetry.canRecordPrereleaseData
+          }, canRecordReleaseData ${Services.telemetry.canRecordReleaseData}`;
+  },
+};
--- a/toolkit/components/telemetry/gen_event_data.py
+++ b/toolkit/components/telemetry/gen_event_data.py
@@ -1,18 +1,20 @@
 # 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/.
 
 # Write out event information for C++. The events are defined
 # in a file provided as a command-line argument.
 
 from __future__ import print_function
+from collections import OrderedDict
 from shared_telemetry_utils import StringTable, static_assert, ParserError
 
+import json
 import parse_events
 import sys
 import itertools
 
 # The banner/text at the top of the generated file.
 banner = """/* This file is auto-generated, only for internal use in TelemetryEvent.h,
    see gen_event_data.py. */
 """
@@ -104,16 +106,52 @@ def write_event_table(events, output, st
                    string_table.stringIndex(object_name)),
                   file=output)
 
     print("};", file=output)
     static_assert(output, "sizeof(%s) <= UINT32_MAX" % table_name,
                   "index overflow")
 
 
+def generate_JSON_definitions(output, *filenames):
+    """ Write the event definitions to a JSON file.
+
+    :param output: the file to write the content to.
+    :param filenames: a list of filenames provided by the build system.
+           We only support a single file.
+    """
+    # Load the event data.
+    if len(filenames) > 1:
+        raise Exception('We don\'t support loading from more than one file.')
+    try:
+        events = parse_events.load_events(filenames[0], True)
+    except ParserError as ex:
+        print("\nError processing events:\n" + str(ex) + "\n")
+        sys.exit(1)
+
+    event_definitions = OrderedDict()
+    for event in events:
+        category = event.category
+
+        if category not in event_definitions:
+            event_definitions[category] = OrderedDict()
+
+        event_definitions[category][event.name] = OrderedDict({
+            'methods': event.methods,
+            'objects': event.objects,
+            'extra_keys': event.extra_keys,
+            'record_on_release': True if event.dataset == 'opt-out' else False,
+            # We don't expire dynamic-builtin scalars: they're only meant for
+            # use in local developer builds anyway. They will expire when rebuilding.
+            'expired': False,
+        })
+
+    json.dump(event_definitions, output)
+
+
 def main(output, *filenames):
     # Load the event data.
     if len(filenames) > 1:
         raise Exception('We don\'t support loading from more than one file.')
     try:
         events = parse_events.load_events(filenames[0], True)
     except ParserError as ex:
         print("\nError processing events:\n" + str(ex) + "\n")
--- a/toolkit/components/telemetry/moz.build
+++ b/toolkit/components/telemetry/moz.build
@@ -104,16 +104,17 @@ TESTING_JS_MODULES += [
   'tests/unit/TelemetryArchiveTesting.jsm',
 ]
 
 PYTHON_UNITTEST_MANIFESTS += [
     'tests/python/python.ini',
 ]
 
 GENERATED_FILES = [
+    'EventArtifactDefinitions.json',
     'ScalarArtifactDefinitions.json',
     'TelemetryEventData.h',
     'TelemetryEventEnums.h',
     'TelemetryHistogramData.inc',
     'TelemetryHistogramEnums.h',
     'TelemetryProcessData.h',
     'TelemetryProcessEnums.h',
     'TelemetryScalarData.h',
@@ -165,23 +166,41 @@ event_files = [
 event_data = GENERATED_FILES['TelemetryEventData.h']
 event_data.script = 'gen_event_data.py'
 event_data.inputs = event_files
 
 event_enums = GENERATED_FILES['TelemetryEventEnums.h']
 event_enums.script = 'gen_event_enum.py'
 event_enums.inputs = event_files
 
+# Generate the JSON event definitions. They will only be
+# used in artifact or "build faster" builds.
+event_json_data = GENERATED_FILES['EventArtifactDefinitions.json']
+event_json_data.script = 'gen_event_data.py:generate_JSON_definitions'
+event_json_data.inputs = event_files
+
+# Move the events JSON file to the directory where the Firefox binary is.
+FINAL_TARGET_FILES += ['!EventArtifactDefinitions.json']
+
 # Generate data from Processes.yaml
 processes_files = [
     'Processes.yaml',
 ]
 
 processes_enum = GENERATED_FILES['TelemetryProcessEnums.h']
 processes_enum.script = 'gen_process_enum.py'
 processes_enum.inputs = processes_files
 
 processes_data = GENERATED_FILES['TelemetryProcessData.h']
 processes_data.script = 'gen_process_data.py'
 processes_data.inputs = processes_files
 
+# Add support for GeckoView: please note that building GeckoView
+# implies having an Android build. The packaging step decides
+# which files to include. As a consequence, we can simply only
+# include the GeckoView files on all Android builds.
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
+    EXTRA_JS_MODULES += [
+        'geckoview/GeckoViewTelemetryController.jsm',
+    ]
+
 with Files('**'):
     BUG_COMPONENT = ('Toolkit', 'Telemetry')
--- a/toolkit/components/telemetry/nsITelemetry.idl
+++ b/toolkit/components/telemetry/nsITelemetry.idl
@@ -511,16 +511,27 @@ interface nsITelemetry : nsISupports
    *                                            Defaults to false.
    * @param aEventData.<name>.expired Optional, whether this event entry is expired. This allows
    *                                  recording it without error, but it will be discarded. Defaults to false.
    */
   [implicit_jscontext]
   void registerEvents(in ACString aCategory, in jsval aEventData);
 
   /**
+   * Parent process only. Register dynamic builtin events. The parameters
+   * have the same meaning as the usual |registerEvents| function.
+   *
+   * This function is only meant to be used to support the "artifact build"/
+   * "build faster" developers by allowing to add new events without rebuilding
+   * the C++ components including the headers files.
+  */
+  [implicit_jscontext]
+  void registerBuiltinEvents(in ACString aCategory, in jsval aEventData);
+
+  /**
    * Parent process only. Register new scalars to record them from addons. This
    * allows registering multiple scalars for a category. They will be valid only for
    * the current Firefox session.
    * Note that scalars shipping in Firefox should be registered in Scalars.yaml.
    *
    * @param aCategoryName The unique category the scalars are registered in.
    * @param aScalarData An object that contains registration data for multiple scalars in the form:
    *   {
@@ -542,17 +553,17 @@ interface nsITelemetry : nsISupports
   [implicit_jscontext]
   void registerScalars(in ACString aCategoryName, in jsval aScalarData);
 
   /**
    * Parent process only. Register dynamic builtin scalars. The parameters
    * have the same meaning as the usual |registerScalars| function.
    *
    * This function is only meant to be used to support the "artifact build"/
-   * "built faster" developers by allowing to add new scalars without rebuilding
+   * "build faster" developers by allowing to add new scalars without rebuilding
    * the C++ components including the headers files.
    */
   [implicit_jscontext]
   void registerBuiltinScalars(in ACString aCategoryName, in jsval aScalarData);
 
   /**
    * Resets all the stored events. This is intended to be only used in tests.
    */
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryAndroidEnvironment.js
@@ -0,0 +1,62 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+*/
+/* Android-only TelemetryEnvironment xpcshell test that ensures that the device data is stored in the Environment.
+*/
+
+ChromeUtils.import("resource://gre/modules/TelemetryEnvironment.jsm", this);
+
+/**
+ * Check that a value is a string and not empty.
+ *
+ * @param aValue The variable to check.
+ * @return True if |aValue| has type "string" and is not empty, False otherwise.
+ */
+function checkString(aValue) {
+  return (typeof aValue == "string") && (aValue != "");
+}
+
+/**
+ * If value is non-null, check if it's a valid string.
+ *
+ * @param aValue The variable to check.
+ * @return True if it's null or a valid string, false if it's non-null and an invalid
+ *         string.
+ */
+function checkNullOrString(aValue) {
+  if (aValue) {
+    return checkString(aValue);
+  } else if (aValue === null) {
+    return true;
+  }
+
+  return false;
+}
+
+/**
+ * If value is non-null, check if it's a boolean.
+ *
+ * @param aValue The variable to check.
+ * @return True if it's null or a valid boolean, false if it's non-null and an invalid
+ *         boolean.
+ */
+function checkNullOrBool(aValue) {
+  return aValue === null || (typeof aValue == "boolean");
+}
+
+function checkSystemSection(data) {
+Assert.ok("system" in data, "There must be a system section in Environment.");
+// Device data is only available on Android.
+  if (gIsAndroid) {
+    let deviceData = data.system.device;
+    Assert.ok(checkNullOrString(deviceData.model));
+    Assert.ok(checkNullOrString(deviceData.manufacturer));
+    Assert.ok(checkNullOrString(deviceData.hardware));
+    Assert.ok(checkNullOrBool(deviceData.isTablet));
+  }
+}
+
+add_task(async function test_systemEnvironment() {
+  let environmentData = TelemetryEnvironment.currentEnvironment;
+  checkSystemSection(environmentData);
+});
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js
@@ -555,25 +555,16 @@ function checkSystemSection(data) {
                 f + " must be a number if non null.");
     }
   }
 
   let cpuData = data.system.cpu;
   Assert.ok(Number.isFinite(cpuData.count), "CPU count must be a number.");
   Assert.ok(Array.isArray(cpuData.extensions), "CPU extensions must be available.");
 
-  // Device data is only available on Android.
-  if (gIsAndroid) {
-    let deviceData = data.system.device;
-    Assert.ok(checkNullOrString(deviceData.model));
-    Assert.ok(checkNullOrString(deviceData.manufacturer));
-    Assert.ok(checkNullOrString(deviceData.hardware));
-    Assert.ok(checkNullOrBool(deviceData.isTablet));
-  }
-
   let osData = data.system.os;
   Assert.ok(checkNullOrString(osData.name));
   Assert.ok(checkNullOrString(osData.version));
   Assert.ok(checkNullOrString(osData.locale));
 
   // Service pack is only available on Windows.
   if (gIsWindows) {
     Assert.ok(Number.isFinite(osData.servicePackMajor),
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryEvents_buildFaster.js
@@ -0,0 +1,152 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/
+*/
+
+ChromeUtils.import("resource://services-common/utils.js");
+
+/**
+ * Return the path to the definitions file for the events.
+ */
+function getDefinitionsPath() {
+  // Write the event definition to the spec file in the binary directory.
+  let definitionFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+  definitionFile = Services.dirsvc.get("GreD", Ci.nsIFile);
+  definitionFile.append("EventArtifactDefinitions.json");
+  return definitionFile.path;
+}
+
+add_task(async function test_setup() {
+  do_get_profile();
+});
+
+add_task({
+    // The test needs to write a file, and that fails in tests on Android.
+    // We don't really need the Android coverage, so skip on Android.
+    skip_if: () => AppConstants.platform == "android"
+  }, async function test_invalidJSON() {
+  const INVALID_JSON = "{ invalid,JSON { {1}";
+  const FILE_PATH = getDefinitionsPath();
+
+  // Write a corrupted JSON file.
+  await OS.File.writeAtomic(FILE_PATH, INVALID_JSON, { encoding: "utf-8", noOverwrite: false });
+
+  // Simulate Firefox startup. This should not throw!
+  await TelemetryController.testSetup();
+  await TelemetryController.testPromiseJsProbeRegistration();
+
+  // Cleanup.
+  await TelemetryController.testShutdown();
+  await OS.File.remove(FILE_PATH);
+});
+
+add_task({
+    // The test needs to write a file, and that fails in tests on Android.
+    // We don't really need the Android coverage, so skip on Android.
+    skip_if: () => AppConstants.platform == "android"
+  }, async function test_dynamicBuiltin() {
+  const DYNAMIC_EVENT_SPEC =  {
+    "telemetry.test.builtin": {
+      "test": {
+        "objects": [
+          "object1",
+          "object2"
+        ],
+        "expired": false,
+        "methods": [
+          "test1",
+          "test2"
+        ],
+        "extra_keys": [
+          "key2",
+          "key1"
+        ],
+        "record_on_release": false
+      }
+    }
+  };
+
+  Telemetry.clearEvents();
+
+  // Let's write to the definition file to also cover the file
+  // loading part.
+  const FILE_PATH = getDefinitionsPath();
+  await CommonUtils.writeJSON(DYNAMIC_EVENT_SPEC, FILE_PATH);
+
+  // Start TelemetryController to trigger loading the specs.
+  await TelemetryController.testReset();
+  await TelemetryController.testPromiseJsProbeRegistration();
+
+  // Record the events
+  const TEST_EVENT_NAME = "telemetry.test.builtin";
+  Telemetry.recordEvent(TEST_EVENT_NAME, "test1", "object1");
+  Telemetry.recordEvent(TEST_EVENT_NAME, "test2", "object1", null,
+                        {"key1": "foo", "key2": "bar"});
+  Telemetry.recordEvent(TEST_EVENT_NAME, "test2", "object2", null,
+                        {"key2": "bar"});
+
+  // Check the values we tried to store.
+  const snapshot =
+    Telemetry.snapshotEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  Assert.ok(("parent" in snapshot), "Should have parent events in the snapshot.");
+
+  let expected = [
+    [TEST_EVENT_NAME, "test1", "object1"],
+    [TEST_EVENT_NAME, "test2", "object1", null, {key1: "foo", key2: "bar"}],
+    [TEST_EVENT_NAME, "test2", "object2", null, {key2: "bar"}],
+  ];
+  let events = snapshot.parent;
+  Assert.equal(events.length, expected.length, "Should have recorded the right amount of events.");
+  for (let i = 0; i < expected.length; ++i) {
+    Assert.deepEqual(events[i].slice(1), expected[i],
+                     "Should have recorded the expected event data.");
+  }
+
+  // Clean up.
+  await TelemetryController.testShutdown();
+  await OS.File.remove(FILE_PATH);
+});
+
+add_task(async function test_dynamicBuiltinEvents() {
+  Telemetry.clearEvents();
+  Telemetry.canRecordExtended = true;
+
+  const TEST_EVENT_NAME = "telemetry.test.dynamicbuiltin";
+
+  // Register some dynamic builtin test events.
+  Telemetry.registerBuiltinEvents(TEST_EVENT_NAME, {
+    // Event with only required fields.
+    "test1": {
+      methods: ["test1"],
+      objects: ["object1"],
+    },
+    // Event with extra_keys.
+    "test2": {
+      methods: ["test2", "test2b"],
+      objects: ["object1", "object2"],
+      extra_keys: ["key1", "key2"],
+    },
+  });
+
+  // Record some events.
+  Telemetry.recordEvent(TEST_EVENT_NAME, "test1", "object1");
+  Telemetry.recordEvent(TEST_EVENT_NAME, "test2", "object1", null,
+                        {"key1": "foo", "key2": "bar"});
+  Telemetry.recordEvent(TEST_EVENT_NAME, "test2b", "object2", null,
+                        {"key2": "bar"});
+  // Now check that the snapshot contains the expected data.
+  let snapshot =
+    Telemetry.snapshotEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  Assert.ok(("parent" in snapshot), "Should have parent events in the snapshot.");
+
+  let expected = [
+    [TEST_EVENT_NAME, "test1", "object1"],
+    [TEST_EVENT_NAME, "test2", "object1", null, {key1: "foo", key2: "bar"}],
+    [TEST_EVENT_NAME, "test2b", "object2", null, {key2: "bar"}],
+  ];
+  let events = snapshot.parent;
+  Assert.equal(events.length, expected.length, "Should have recorded the right amount of events.");
+  for (let i = 0; i < expected.length; ++i) {
+    Assert.deepEqual(events[i].slice(1), expected[i],
+                     "Should have recorded the expected event data.");
+  }
+});
--- a/toolkit/components/telemetry/tests/unit/xpcshell.ini
+++ b/toolkit/components/telemetry/tests/unit/xpcshell.ini
@@ -63,14 +63,16 @@ skip-if = os == "android" # Disabled due
 skip-if = os == "android" # Disabled due to crashes (see bug 1367762)
 tags = addons
 [test_TelemetryScalars.js]
 [test_TelemetryScalars_buildFaster.js]
 [test_TelemetryTimestamps.js]
 skip-if = toolkit == 'android'
 [test_TelemetryCaptureStack.js]
 [test_TelemetryEvents.js]
+[test_TelemetryEvents_buildFaster.js]
 [test_ChildEvents.js]
 skip-if = os == "android" # Disabled due to crashes (see bug 1331366)
 [test_TelemetryModules.js]
 [test_PingSender.js]
 skip-if = (os == "android") || (os == "linux" && bits == 32)
 [test_TelemetryGC.js]
+[test_TelemetryAndroidEnvironment.js]
--- a/toolkit/moz.configure
+++ b/toolkit/moz.configure
@@ -414,16 +414,30 @@ def fmp4(value, target, wmf, applemedia)
         enabled = wmf or applemedia or target.os == 'Android'
     if enabled:
         return True
 
 set_config('MOZ_FMP4', fmp4)
 set_define('MOZ_FMP4', fmp4)
 add_old_configure_assignment('MOZ_FMP4', fmp4)
 
+# OpenMAX IL Decoding Support
+# ==============================================================
+option('--enable-openmax',
+       help='Enable OpenMAX IL for video/audio decoding')
+
+@depends('--enable-openmax')
+def openmax(value):
+    enabled = bool(value)
+    if enabled:
+        return True
+
+set_config('MOZ_OMX', openmax)
+set_define('MOZ_OMX', openmax)
+
 # EME Support
 # ==============================================================
 # Widevine is enabled by default in desktop browser builds.
 @depends(build_project, '--help')
 def eme_default(build_project, help):
     if build_project == 'browser':
         return 'widevine'