Merge mozilla-central to inbound. a=merge CLOSED TREE
authorshindli <shindli@mozilla.com>
Tue, 07 May 2019 18:18:43 +0300
changeset 534764 c620d5893b60882304ee79f614965beb3d667376
parent 534763 cac07df678c379c2fed3e3a647e82214ef25f46e (current diff)
parent 534761 f1082516d62f3b8ff6ed1ec44c1e2328bd2a3977 (diff)
child 534765 bc84c39a78f231be563403e2d370cc32beac2f63
push id2082
push userffxbld-merge
push dateMon, 01 Jul 2019 08:34:18 +0000
treeherdermozilla-release@2fb19d0466d2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone68.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
dom/webidl/MediaDebugInfo.webidl
testing/web-platform/meta/selection/Document-open.html.ini
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -4149,16 +4149,23 @@ const BrowserSearch = {
         [name], 1);
     } else {
       placeholder = gURLBar.getAttribute("defaultPlaceholder");
     }
     gURLBar.setAttribute("placeholder", placeholder);
   },
 
   addEngine(browser, engine, uri) {
+    if (!this._searchInitComplete) {
+      // We haven't finished initialising search yet. This means we can't
+      // call getEngineByName here. Since this is only on start-up and unlikely
+      // to happen in the normal case, we'll just return early rather than
+      // trying to handle it asynchronously.
+      return;
+    }
     // Check to see whether we've already added an engine with this title
     if (browser.engines) {
       if (browser.engines.some(e => e.title == engine.title))
         return;
     }
 
     var hidden = false;
     // If this engine (identified by title) is already in the list, add it
--- a/browser/base/content/test/static/browser_all_files_referenced.js
+++ b/browser/base/content/test/static/browser_all_files_referenced.js
@@ -170,16 +170,18 @@ var whitelist = [
   {file: "chrome://devtools/skin/images/aboutdebugging-firefox-release.svg",
    isFromDevTools: true},
   {file: "chrome://devtools/skin/images/next.svg", isFromDevTools: true},
   // Bug 1526672
   {file: "resource://app/localization/en-US/browser/touchbar/touchbar.ftl",
    platforms: ["linux", "win"]},
   // Referenced by the webcompat system addon for localization
   {file: "resource://gre/localization/en-US/toolkit/about/aboutCompat.ftl"},
+  // Bug 1547016 activity-stream conditionally/dynamically references
+  {file: "resource://app/localization/en-US/browser/branding/brandings.ftl"},
 ];
 
 if (!AppConstants.MOZ_NEW_NOTIFICATION_STORE) {
   // kvstore.jsm wraps the API in nsIKeyValue.idl in a more ergonomic API
   // It landed in bug 1490496, and we expect to start using it shortly.
   whitelist.push({file: "resource://gre/modules/kvstore.jsm"});
 }
 
--- a/browser/components/search/extensions/list.json
+++ b/browser/components/search/extensions/list.json
@@ -51,21 +51,16 @@
     }
   },
   "locales": {
     "en-US": {
       "default": {
         "visibleDefaultEngines": [
           "google-b-d", "amazondotcom", "bing", "ddg", "ebay", "twitter", "wikipedia"
         ]
-      },
-      "experimental-hidden": {
-        "visibleDefaultEngines": [
-          "amazon-ca", "amazon-au", "google-2018", "yandex-en", "google", "google-b-1-e", "google-b-e"
-        ]
       }
     },
     "ach": {
       "default": {
         "visibleDefaultEngines": [
           "google-b-d", "bing", "amazondotcom", "ddg", "twitter", "wikipedia"
         ]
       }
@@ -244,21 +239,16 @@
         ]
       }
     },
     "en-GB": {
       "default": {
         "visibleDefaultEngines": [
           "google-b-d", "bing", "amazon-en-GB", "chambers-en-GB", "ddg", "ebay-uk", "twitter", "wikipedia"
         ]
-      },
-      "experimental-hidden": {
-        "visibleDefaultEngines": [
-          "yandex-en"
-        ]
       }
     },
     "en-ZA": {
       "default": {
         "visibleDefaultEngines": [
           "google-b-d", "bing", "amazondotcom", "ddg", "twitter", "wikipedia"
         ]
       }
@@ -291,21 +281,16 @@
         ]
       }
     },
     "es-MX": {
       "default": {
         "visibleDefaultEngines": [
           "google-b-d", "bing", "ddg", "mercadolibre-mx", "wikipedia-es"
         ]
-      },
-      "experimental-hidden": {
-        "visibleDefaultEngines": [
-          "amazon-mx"
-        ]
       }
     },
     "et": {
       "default": {
         "visibleDefaultEngines": [
           "google-b-d", "neti-ee", "ddg", "osta-ee", "wikipedia-et", "eki-ee"
         ]
       }
@@ -345,21 +330,16 @@
         ]
       }
     },
     "fy-NL": {
       "default": {
         "visibleDefaultEngines": [
           "google-b-d", "bing", "bolcom-fy-NL", "ddg", "ebay-nl", "marktplaats-fy-NL", "wikipedia-fy-NL"
         ]
-      },
-      "experimental-hidden": {
-        "visibleDefaultEngines": [
-          "amazon-nl"
-        ]
       }
     },
     "ga-IE": {
       "default": {
         "visibleDefaultEngines": [
           "google-b-d", "amazon-en-GB", "ddg", "ebay-ie", "tearma", "twitter", "wikipedia-ga-IE"
         ]
       }
@@ -623,21 +603,16 @@
         ]
       }
     },
     "nl": {
       "default": {
         "visibleDefaultEngines": [
           "google-b-d", "bing", "bolcom-nl", "ddg", "ebay-nl", "marktplaats-nl", "wikipedia-nl"
         ]
-      },
-      "experimental-hidden": {
-        "visibleDefaultEngines": [
-          "amazon-nl"
-        ]
       }
     },
     "nn-NO": {
       "default": {
         "visibleDefaultEngines": [
           "google-b-d", "bing", "amazon-en-GB", "ddg", "gulesider-NO", "bok-NO", "qxl-NO", "wikipedia-NN"
         ]
       }
@@ -670,21 +645,16 @@
         ]
       }
     },
     "pt-BR": {
       "default": {
         "visibleDefaultEngines": [
           "google-b-d", "bing", "ddg", "mercadolivre", "twitter", "wikipedia-pt"
         ]
-      },
-      "experimental-hidden": {
-        "visibleDefaultEngines": [
-          "amazon-br"
-        ]
       }
     },
     "pt-PT": {
       "default": {
         "visibleDefaultEngines": [
           "google-b-d", "amazon-en-GB", "ddg", "priberam", "wikipedia-pt"
         ]
       }
--- a/browser/components/search/test/browser/browser_tooManyEnginesOffered.js
+++ b/browser/components/search/test/browser/browser_tooManyEnginesOffered.js
@@ -4,16 +4,18 @@
 // popup shows a submenu that lists them instead of showing them in the popup
 // itself.
 
 const searchPopup = document.getElementById("PopupSearchAutoComplete");
 const oneOffsContainer = searchPopup.searchOneOffsContainer;
 
 add_task(async function test_setup() {
   await gCUITestUtils.addSearchBar();
+
+  await Services.search.init();
   registerCleanupFunction(() => {
     gCUITestUtils.removeSearchBar();
   });
 });
 
 add_task(async function test() {
   let searchbar = BrowserSearch.searchBar;
 
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/browser/branding/brandings.ftl
@@ -0,0 +1,15 @@
+# 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/.
+
+## The following feature names must be treated as a brand, and kept in English.
+## They cannot be:
+## - Declined to adapt to grammatical case.
+## - Transliterated.
+## - Translated.
+
+-facebook-container-brand-name = Facebook Container
+-lockwise-brand-name = Firefox Lockwise
+-monitor-brand-name = Firefox Monitor
+-pocket-brand-name = Pocket
+-send-brand-name = Firefox Send
--- a/browser/locales/en-US/browser/newtab/onboarding.ftl
+++ b/browser/locales/en-US/browser/newtab/onboarding.ftl
@@ -1,19 +1,50 @@
 # 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/.
 
 ## UI strings for the simplified onboarding modal
 
+onboarding-button-label-learn-more = Learn More
 onboarding-button-label-try-now = Try It Now
 onboarding-button-label-get-started = Get Started
+
 onboarding-welcome-header = Welcome to { -brand-short-name }
+onboarding-welcome-body = You’ve got the browser.<br/>Meet the rest of { -brand-product-name }.
+onboarding-welcome-learn-more = Learn more about the benefits.
+
+onboarding-join-form-header = Join { -brand-product-name }
+onboarding-join-form-body = Enter your email address to get started.
+onboarding-join-form-email =
+    .placeholder = Enter email
+onboarding-join-form-email-error = Valid email required
+onboarding-join-form-legal = By proceeding, you agree to the <a data-l10n-name="terms">Terms of Service</a> and <a data-l10n-name="privacy">Privacy Notice</a>.
+onboarding-join-form-continue = Continue
+
 onboarding-start-browsing-button-label = Start Browsing
 
+## These are individual benefit messages shown with an image, title and
+## description.
+
+onboarding-benefit-products-title = Useful Products
+onboarding-benefit-products-text = Get things done with a family of tools that respects your privacy across your devices.
+
+onboarding-benefit-knowledge-title = Practical Knowledge
+onboarding-benefit-knowledge-text = Learn everything you need to know to stay smarter and safer online.
+
+onboarding-benefit-privacy-title = True Privacy
+# "Personal Data Promise" should be treated as a brand and should be kept in
+# English. It refers to a concept shown elsewhere to the user: "The Firefox
+# Personal Data Promise is the way we honor your data in everything we make and
+# do. We take less data. We keep it safe. And we make sure that we are
+# transparent about how we use it."
+onboarding-benefit-privacy-text = Everything we do honors our Personal Data Promise: Take less. Keep it safe. No secrets.
+
+
 ## These strings belong to the individual onboarding messages.
 
 ## Each message has a title and a description of what the browser feature is.
 ## Each message also has an associated button for the user to try the feature.
 ## The string for the button is found above, in the UI strings section
 onboarding-private-browsing-title = Private Browsing
 onboarding-private-browsing-text = Browse by yourself. Private Browsing with Content Blocking blocks online trackers that follow you around the web.
 
@@ -25,16 +56,68 @@ onboarding-addons-text = Add even more f
 
 onboarding-ghostery-title = Ghostery
 onboarding-ghostery-text = Browse faster, smarter, or safer with extensions like Ghostery, which lets you block annoying ads.
 
 # Note: "Sync" in this case is a generic verb, as in "to synchronize"
 onboarding-fxa-title = Sync
 onboarding-fxa-text = Sign up for a { -fxaccount-brand-name } and sync your bookmarks, passwords, and open tabs everywhere you use { -brand-short-name }.
 
+onboarding-tracking-protection-title = Control How You’re Tracked
+onboarding-tracking-protection-text = Don’t like when ads follow you around? { -brand-short-name } helps you control how advertisers track your activity online.
+# "Update" is a verb, as in "Update the existing settings", not "Options about
+# updates".
+onboarding-tracking-protection-button = { PLATFORM() ->
+  [windows] Update Options
+ *[other] Update Preferences
+}
+
+onboarding-data-sync-title = Take Your Settings with You
+# "Sync" is short for synchronize.
+onboarding-data-sync-text = Sync your bookmarks and passwords everywhere you use { -brand-product-name }.
+onboarding-data-sync-button = Turn on { -sync-brand-short-name }
+
+onboarding-firefox-monitor-title = Stay Alert to Data Breaches
+onboarding-firefox-monitor-text = { -monitor-brand-name } monitors if your email has appeared in a data breach and alerts you if it appears in a new breach.
+onboarding-firefox-monitor-button = Sign up for Alerts
+
+onboarding-browse-privately-title = Browse Privately
+onboarding-browse-privately-text = Private Browsing clears your search and browsing history to keep it secret from anyone who uses your computer.
+onboarding-browse-privately-button = Open a Private Window
+
+onboarding-firefox-send-title = Keep Your Shared Files Private
+onboarding-firefox-send-text = { -send-brand-name } protects the files you share with end-to-end encryption and a link that automatically expires.
+onboarding-firefox-send-button = Try { -send-brand-name }
+
+onboarding-mobile-phone-title = Get { -brand-product-name } on Your Phone
+onboarding-mobile-phone-text = Download { -brand-product-name } for iOS or Android and sync your data across devices.
+# "Mobile" is short for mobile/cellular phone, "Browser" is short for web
+# browser.
+onboarding-mobile-phone-button = Download Mobile Browser
+
+onboarding-send-tabs-title = Instantly Send Yourself Tabs
+# "Send Tabs" refers to "Send Tab to Device" feature that appears when opening a
+# tab's context menu.
+onboarding-send-tabs-text = Send Tabs instantly shares pages between your devices without having to copy, paste, or leave the browser.
+onboarding-send-tabs-button = Start Using Send Tabs
+
+onboarding-pocket-anywhere-title = Read and Listen Anywhere
+# "downtime" refers to the user's free/spare time.
+onboarding-pocket-anywhere-text = { -pocket-brand-name } saves your favorite stories so you can read, listen, and watch during your downtime, even if you’re offline.
+onboarding-pocket-anywhere-button = Try { -pocket-brand-name }
+
+onboarding-lockwise-passwords-title = Take Your Passwords Everywhere
+onboarding-lockwise-passwords-text = { -lockwise-brand-name } saves your passwords in a secure place so you can easily log in to your accounts.
+onboarding-lockwise-passwords-button = Get { -lockwise-brand-name }
+
+onboarding-facebook-container-title = Set Boundaries with Facebook
+onboarding-facebook-container-text = { -facebook-container-brand-name } keeps your Facebook identity separate from everything else, making it harder to track you across the web.
+onboarding-facebook-container-button = Add the Extension
+
+
 ## Message strings belonging to the Return to AMO flow
 return-to-amo-sub-header = Great, you’ve got { -brand-short-name }
 
 # <icon></icon> will be replaced with the icon belonging to the extension
 #
 # Variables:
 #   $addon-name (String) - Name of the add-on
 return-to-amo-addon-header = Now let’s get you <icon></icon><b>{ $addon-name }.</b>
--- a/devtools/client/aboutdebugging-new/src/constants.js
+++ b/devtools/client/aboutdebugging-new/src/constants.js
@@ -104,16 +104,18 @@ const PAGE_TYPES = {
 
 const PREFERENCES = {
   // Preference that drives the display of the "Tabs" category on This Firefox.
   LOCAL_TAB_DEBUGGING_ENABLED: "devtools.aboutdebugging.local-tab-debugging",
   // Preference that drives the display of the "Processes" debug target category.
   PROCESS_DEBUGGING_ENABLED: "devtools.aboutdebugging.process-debugging",
   // Preference that drives the display of hidden & system addons in about:debugging.
   SHOW_HIDDEN_ADDONS: "devtools.aboutdebugging.showHiddenAddons",
+  // Preference to store the last path used for loading a temporary extension.
+  TEMPORARY_EXTENSION_PATH: "devtools.aboutdebugging.tmpExtDirPath",
   // Preference that disables installing extensions when set to false.
   XPINSTALL_ENABLED: "xpinstall.enabled",
 };
 
 const RUNTIME_PREFERENCE = {
   CHROME_DEBUG_ENABLED: "devtools.chrome.enabled",
   CONNECTION_PROMPT: "devtools.debugger.prompt-connection",
   PERMANENT_PRIVATE_BROWSING: "browser.privatebrowsing.autostart",
--- a/devtools/client/aboutdebugging-new/src/modules/extensions-helper.js
+++ b/devtools/client/aboutdebugging-new/src/modules/extensions-helper.js
@@ -1,20 +1,23 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { Cc, Ci } = require("chrome");
+const Services = require("Services");
 loader.lazyImporter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
+loader.lazyRequireGetter(this, "FileUtils", "resource://gre/modules/FileUtils.jsm", true);
 
 const {Toolbox} = require("devtools/client/framework/toolbox");
+const {gDevTools} = require("devtools/client/framework/devtools");
 
-const {gDevTools} = require("devtools/client/framework/devtools");
+const { PREFERENCES } = require("../constants");
 
 let addonToolbox = null;
 
 /**
  * Start debugging an addon.
  *
  * @param {String} id
  *        The addon id to debug.
@@ -80,24 +83,38 @@ exports.getExtensionUuid = function(exte
  *         The help message that should be displayed to the user in the filepicker.
  * @return {Promise} returns a promise that resolves a File object corresponding to the
  *         file selected by the user.
  */
 exports.openTemporaryExtension = function(win, message) {
   return new Promise(resolve => {
     const fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
     fp.init(win, message, Ci.nsIFilePicker.modeOpen);
+
+    // Try to set the last directory used as "displayDirectory".
+    try {
+      const lastDirPath =
+        Services.prefs.getCharPref(PREFERENCES.TEMPORARY_EXTENSION_PATH, "");
+      const lastDir = new FileUtils.File(lastDirPath);
+      fp.displayDirectory = lastDir;
+    } catch (e) {
+      // Empty or invalid value, nothing to handle.
+    }
+
     fp.open(res => {
       if (res == Ci.nsIFilePicker.returnCancel || !fp.file) {
         return;
       }
       let file = fp.file;
       // AddonManager.installTemporaryAddon accepts either
       // addon directory or final xpi file.
       if (!file.isDirectory() &&
           !file.leafName.endsWith(".xpi") && !file.leafName.endsWith(".zip")) {
         file = file.parent;
       }
 
+      // We are about to resolve, store the path to the file for the next call.
+      Services.prefs.setCharPref(PREFERENCES.TEMPORARY_EXTENSION_PATH, file.path);
+
       resolve(file);
     });
   });
 };
--- a/devtools/client/aboutdebugging-new/test/browser/browser.ini
+++ b/devtools/client/aboutdebugging-new/test/browser/browser.ini
@@ -38,16 +38,17 @@ tags = webextensions
 [browser_aboutdebugging_addons_debug_setting_usb.js]
 [browser_aboutdebugging_addons_manifest_url.js]
 skip-if = (os == 'linux' && bits == 32) # ADB start() fails on linux 32, see Bug 1499638
 [browser_aboutdebugging_addons_remote_runtime.js]
 [browser_aboutdebugging_addons_temporary_addon_buttons.js]
 skip-if = (os == 'win') # On windows the AddonManager locks the XPI file loaded as a temporary extension and we can not test the reload of the extension.
 [browser_aboutdebugging_addons_temporary_id_message.js]
 [browser_aboutdebugging_addons_temporary_install_error.js]
+[browser_aboutdebugging_addons_temporary_install_path.js]
 [browser_aboutdebugging_addons_temporary_reload_error.js]
 skip-if = (os == 'win') # On windows the AddonManager locks the XPI file loaded as a temporary extension and we can not test the reload of the extension.
 [browser_aboutdebugging_addons_warnings.js]
 [browser_aboutdebugging_connect_networklocations.js]
 [browser_aboutdebugging_connect_toggle_usb_devices.js]
 skip-if = (os == 'linux' && bits == 32) # ADB start() fails on linux 32, see Bug 1499638
 [browser_aboutdebugging_connection_prompt_setting.js]
 [browser_aboutdebugging_debug-target-pane_collapsibilities_interaction.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_addons_temporary_install_path.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from helper-addons.js */
+Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "helper-addons.js", this);
+
+/**
+ * Test the the path used to install a temporary addon is saved in a preference and reused
+ * next time the feature is used.
+ */
+
+const EXTENSION_PATH = "resources/test-temporary-extension/manifest.json";
+const EXTENSION_NAME = "test-temporary-extension";
+const LAST_DIR_PREF = "devtools.aboutdebugging.tmpExtDirPath";
+
+// Check that the preference is updated when trying to install a temporary extension.
+add_task(async function testPreferenceUpdatedAfterInstallingExtension() {
+  registerCleanupFunction(() => {
+    Services.prefs.clearUserPref(LAST_DIR_PREF);
+  });
+  const { document, tab, window } = await openAboutDebugging();
+  await selectThisFirefoxPage(document, window.AboutDebugging.store);
+
+  await installTemporaryExtension(EXTENSION_PATH, EXTENSION_NAME, document);
+
+  info("Check whether the selected dir sets into the pref");
+  const lastDirPath = Services.prefs.getCharPref(LAST_DIR_PREF, "");
+  const expectedPath = getTestFilePath("resources/test-temporary-extension");
+  is(lastDirPath, expectedPath, "The selected dir should set into the pref");
+
+  await waitUntil(() => findDebugTargetByText(EXTENSION_NAME, document));
+  await removeTemporaryExtension(EXTENSION_NAME, document);
+  await removeTab(tab);
+});
+
+// Check that the preference is updated when trying to install a temporary extension.
+add_task(async function testPreferenceRetrievedWhenInstallingExtension() {
+  const selectedDir = getTestFilePath("resources/packaged-extension");
+
+  await pushPref(LAST_DIR_PREF, selectedDir);
+
+  const { document, tab, window } = await openAboutDebugging();
+  await selectThisFirefoxPage(document, window.AboutDebugging.store);
+
+  const MockFilePicker = SpecialPowers.MockFilePicker;
+  MockFilePicker.init(window);
+  const onFilePickerShown = new Promise(resolve => {
+    MockFilePicker.showCallback = fp => {
+      resolve(fp);
+    };
+  });
+  document.querySelector(".qa-temporary-extension-install-button").click();
+
+  info("Check whether the shown dir is same as the pref");
+  const fp = await onFilePickerShown;
+  is(fp.displayDirectory.path, selectedDir, "Shown directory sets as same as the pref");
+
+  await removeTab(tab);
+});
--- a/devtools/client/responsive.html/index.css
+++ b/devtools/client/responsive.html/index.css
@@ -647,27 +647,27 @@ input:-moz-focusring {
 
 #device-form label {
   display: flex;
   flex-direction: column;
   margin: 5px;
 }
 
 #device-form label > .viewport-dimension {
-  color: var(--theme-text-color-inactive);
+  color: var(--theme-body-color);
   display: flex;
   align-items: center;
 }
 
 #device-form input {
   background: transparent;
   border: 1px solid;
   border-radius: 2px;
   text-align: center;
-  color: var(--theme-text-color-inactive);
+  color: var(--theme-body-color);
   transition: all 0.25s ease;
 }
 
 #device-form #device-form-name input,
 #device-form #device-form-user-agent input {
   text-align: left;
   padding-left: 12px;
   padding-right: 12px;
--- a/dom/events/IMEStateManager.cpp
+++ b/dom/events/IMEStateManager.cpp
@@ -196,17 +196,17 @@ void IMEStateManager::OnFocusMovedBetwee
     }
   }
 
   if (aBlur) {
     MOZ_LOG(sISMLog, LogLevel::Debug,
             ("  OnFocusMovedBetweenBrowsers(), notifying previous "
              "focused child process of parent process or another child process "
              "getting focus"));
-    Unused << aBlur->SendStopIMEStateManagement();
+    aBlur->StopIMEStateManagement();
   }
 
   if (sActiveIMEContentObserver) {
     DestroyIMEContentObserver();
   }
 
   if (sFocusedIMEWidget) {
     // sFocusedIMEBrowserParent can be null, if IME focus hasn't been
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -1538,40 +1538,59 @@ void HTMLMediaElement::ContentRemoved(ns
 }
 
 already_AddRefed<MediaSource> HTMLMediaElement::GetMozMediaSourceObject()
     const {
   RefPtr<MediaSource> source = mMediaSource;
   return source.forget();
 }
 
+void HTMLMediaElement::GetMozDebugReaderData(nsAString& aString) {
+  if (mDecoder && !mSrcStream) {
+    nsAutoCString result;
+    mDecoder->GetMozDebugReaderData(result);
+    CopyUTF8toUTF16(result, aString);
+  }
+}
+
 already_AddRefed<Promise> HTMLMediaElement::MozRequestDebugInfo(
     ErrorResult& aRv) {
   RefPtr<Promise> promise = CreateDOMPromise(aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
-  auto result =
-      MakeRefPtr<media::Refcountable<dom::HTMLMediaElementDebugInfo>>();
-  if (mMediaKeys) {
-    GetEMEInfo(result->mEMEInfo);
-  }
+
+  nsAutoString result;
+  GetMozDebugReaderData(result);
+
   if (mVideoFrameContainer) {
-    result->mCompositorDroppedFrames =
-        mVideoFrameContainer->GetDroppedImageCount();
-  }
+    result.AppendPrintf(
+        "Compositor dropped frame(including when element's invisible): %u\n",
+        mVideoFrameContainer->GetDroppedImageCount());
+  }
+
+  if (mMediaKeys) {
+    nsString EMEInfo;
+    GetEMEInfo(EMEInfo);
+    result.AppendLiteral("EME Info: ");
+    result.Append(EMEInfo);
+    result.AppendLiteral("\n");
+  }
+
   if (mDecoder) {
-    mDecoder->RequestDebugInfo(result->mDecoder)
-        ->Then(
-            mAbstractMainThread, __func__,
-            [promise, result]() { promise->MaybeResolve(result.get()); },
-            [promise, result]() { promise->MaybeResolve(result.get()); });
+    mDecoder->RequestDebugInfo()->Then(
+        mAbstractMainThread, __func__,
+        [promise, result](const nsACString& aString) {
+          promise->MaybeResolve(result + NS_ConvertUTF8toUTF16(aString));
+        },
+        [promise, result]() { promise->MaybeResolve(result); });
   } else {
-    promise->MaybeResolve(result.get());
-  }
+    promise->MaybeResolve(result);
+  }
+
   return promise.forget();
 }
 
 /* static */
 void HTMLMediaElement::MozEnableDebugLog(const GlobalObject&) {
   DecoderDoctorLogger::EnableLogging();
 }
 
@@ -1587,16 +1606,32 @@ already_AddRefed<Promise> HTMLMediaEleme
       [promise](const nsACString& aString) {
         promise->MaybeResolve(NS_ConvertUTF8toUTF16(aString));
       },
       [promise](nsresult rv) { promise->MaybeReject(rv); });
 
   return promise.forget();
 }
 
+already_AddRefed<Promise> HTMLMediaElement::MozDumpDebugInfo() {
+  ErrorResult rv;
+  RefPtr<Promise> promise = CreateDOMPromise(rv);
+  if (NS_WARN_IF(rv.Failed())) {
+    return nullptr;
+  }
+  if (mDecoder) {
+    mDecoder->DumpDebugInfo()->Then(mAbstractMainThread, __func__,
+                                    promise.get(),
+                                    &Promise::MaybeResolveWithUndefined);
+  } else {
+    promise->MaybeResolveWithUndefined();
+  }
+  return promise.forget();
+}
+
 void HTMLMediaElement::SetVisible(bool aVisible) {
   mForcedHidden = !aVisible;
   if (mDecoder) {
     mDecoder->SetForcedHidden(!aVisible);
   }
 }
 
 bool HTMLMediaElement::IsVideoDecodingSuspended() const {
@@ -7115,22 +7150,31 @@ void HTMLMediaElement::AsyncRejectPendin
   }
 
   nsCOMPtr<nsIRunnable> event = new nsResolveOrRejectPendingPlayPromisesRunner(
       this, TakePendingPlayPromises(), aError);
 
   mMainThreadEventTarget->Dispatch(event.forget());
 }
 
-void HTMLMediaElement::GetEMEInfo(dom::EMEDebugInfo& aInfo) {
+void HTMLMediaElement::GetEMEInfo(nsString& aEMEInfo) {
   if (!mMediaKeys) {
     return;
   }
-  mMediaKeys->GetKeySystem(aInfo.mKeySystem);
-  mMediaKeys->GetSessionsInfo(aInfo.mSessionsInfo);
+
+  nsString keySystem;
+  mMediaKeys->GetKeySystem(keySystem);
+
+  nsString sessionsInfo;
+  mMediaKeys->GetSessionsInfo(sessionsInfo);
+
+  aEMEInfo.AppendLiteral("Key System=");
+  aEMEInfo.Append(keySystem);
+  aEMEInfo.AppendLiteral(" SessionsInfo=");
+  aEMEInfo.Append(sessionsInfo);
 }
 
 void HTMLMediaElement::NotifyDecoderActivityChanges() const {
   if (mDecoder) {
     mDecoder->NotifyOwnerActivityChanged(!IsHidden(), mVisibilityState,
                                          IsInComposedDoc());
   }
 }
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -14,17 +14,16 @@
 #include "MediaPromiseDefs.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIObserver.h"
 #include "mozilla/CORSMode.h"
 #include "DecoderTraits.h"
 #include "nsIAudioChannelAgent.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/TextTrackManager.h"
-#include "mozilla/dom/MediaDebugInfoBinding.h"
 #include "mozilla/WeakPtr.h"
 #include "mozilla/dom/MediaKeys.h"
 #include "mozilla/StateWatching.h"
 #include "nsGkAtoms.h"
 #include "PrincipalChangeObserver.h"
 #include "nsStubMutationObserver.h"
 #include "MediaSegment.h"  // for PrincipalHandle, GraphTime
 
@@ -288,17 +287,17 @@ class HTMLMediaElement : public nsGeneri
   // to the image container, we return the last video principal we had. Should
   // the image container be empty with no live video tracks, we return nullptr.
   already_AddRefed<nsIPrincipal> GetCurrentVideoPrincipal();
 
   // called to notify that the principal of the decoder's media resource has
   // changed.
   void NotifyDecoderPrincipalChanged() final;
 
-  void GetEMEInfo(dom::EMEDebugInfo& aInfo);
+  void GetEMEInfo(nsString& aEMEInfo);
 
   class StreamCaptureTrackSource;
 
   // Update the visual size of the media. Called from the decoder on the
   // main thread when/if the size changes.
   virtual void UpdateMediaSize(const nsIntSize& aSize);
   // Like UpdateMediaSize, but only updates the size if no size has yet
   // been set.
@@ -534,28 +533,33 @@ class HTMLMediaElement : public nsGeneri
 
   // Returns whether a call to Play() would be rejected with NotAllowedError.
   // This assumes "worst case" for unknowns. So if prompting for permission is
   // enabled and no permission is stored, this behaves as if the user would
   // opt to block.
   bool AllowedToPlay() const;
 
   already_AddRefed<MediaSource> GetMozMediaSourceObject() const;
+  // Returns a string describing the state of the media player internal
+  // data. Used for debugging purposes.
+  void GetMozDebugReaderData(nsAString& aString);
 
   // Returns a promise which will be resolved after collecting debugging
   // data from decoder/reader/MDSM. Used for debugging purposes.
   already_AddRefed<Promise> MozRequestDebugInfo(ErrorResult& aRv);
 
   // Enables DecoderDoctorLogger logging. Used for debugging purposes.
   static void MozEnableDebugLog(const GlobalObject&);
 
   // Returns a promise which will be resolved after collecting debugging
   // log associated with this element. Used for debugging purposes.
   already_AddRefed<Promise> MozRequestDebugLog(ErrorResult& aRv);
 
+  already_AddRefed<Promise> MozDumpDebugInfo();
+
   // For use by mochitests. Enabling pref "media.test.video-suspend"
   void SetVisible(bool aVisible);
 
   // For use by mochitests. Enabling pref "media.test.video-suspend"
   bool HasSuspendTaint() const;
 
   // For use by mochitests.
   bool IsVideoDecodingSuspended() const;
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -9495,17 +9495,17 @@ void DatabaseConnection::FinishWriteTran
   }
 
   mInReadTransaction = true;
 }
 
 nsresult DatabaseConnection::StartSavepoint() {
   AssertIsOnConnectionThread();
   MOZ_ASSERT(mStorageConnection);
-  MOZ_ASSERT(mUpdateRefcountFunction);
+  MOZ_DIAGNOSTIC_ASSERT(mUpdateRefcountFunction);
   MOZ_ASSERT(mInWriteTransaction);
 
   AUTO_PROFILER_LABEL("DatabaseConnection::StartSavepoint", DOM);
 
   CachedStatement stmt;
   nsresult rv = GetCachedStatement(NS_LITERAL_CSTRING(SAVEPOINT_CLAUSE), &stmt);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
@@ -10057,22 +10057,24 @@ nsresult DatabaseConnection::AutoSavepoi
              aTransaction->GetMode() == IDBTransaction::READ_WRITE_FLUSH ||
              aTransaction->GetMode() == IDBTransaction::CLEANUP ||
              aTransaction->GetMode() == IDBTransaction::VERSION_CHANGE);
 
   DatabaseConnection* connection = aTransaction->GetDatabase()->GetConnection();
   MOZ_ASSERT(connection);
   connection->AssertIsOnConnectionThread();
 
+#ifndef NIGHTLY_BUILD
   // This is just a quick fix for preventing accessing the nullptr. The cause is
   // probably because the connection was unexpectedly closed.
   if (!connection->GetUpdateRefcountFunction()) {
     NS_WARNING("The connection was closed for some reasons!");
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
+#endif
 
   MOZ_ASSERT(!mConnection);
   MOZ_ASSERT(!mDEBUGTransaction);
 
   nsresult rv = connection->StartSavepoint();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
--- a/dom/ipc/BrowserParent.cpp
+++ b/dom/ipc/BrowserParent.cpp
@@ -2171,16 +2171,23 @@ LayoutDeviceIntPoint BrowserParent::GetC
   if (widget == docWidget) {
     return widget->GetClientOffset();
   }
 
   return (docWidget->GetClientOffset() +
           nsLayoutUtils::WidgetToWidgetOffset(widget, docWidget));
 }
 
+void BrowserParent::StopIMEStateManagement() {
+  if (mIsDestroyed) {
+    return;
+  }
+  Unused << SendStopIMEStateManagement();
+}
+
 mozilla::ipc::IPCResult BrowserParent::RecvReplyKeyEvent(
     const WidgetKeyboardEvent& aEvent) {
   NS_ENSURE_TRUE(mFrameElement, IPC_OK());
 
   WidgetKeyboardEvent localEvent(aEvent);
   localEvent.MarkAsHandledInRemoteProcess();
 
   // Here we convert the WidgetEvent that we received to an Event
--- a/dom/ipc/BrowserParent.h
+++ b/dom/ipc/BrowserParent.h
@@ -568,16 +568,18 @@ class BrowserParent final : public PBrow
   LayoutDeviceIntPoint GetChildProcessOffset();
 
   // Returns the offset from the on-screen origin of our top-level window's
   // widget (including window decorations) to the origin of our frameloader's
   // nearest widget. This offset is used to translate coordinates from the
   // PuppetWidget's origin to absolute screen coordinates in the child.
   LayoutDeviceIntPoint GetClientOffset();
 
+  void StopIMEStateManagement();
+
   /**
    * Native widget remoting protocol for use with windowed plugins with e10s.
    */
   PPluginWidgetParent* AllocPPluginWidgetParent();
 
   bool DeallocPPluginWidgetParent(PPluginWidgetParent* aActor);
 
   PPaymentRequestParent* AllocPPaymentRequestParent();
--- a/dom/media/BaseMediaResource.h
+++ b/dom/media/BaseMediaResource.h
@@ -7,17 +7,16 @@
 #define BaseMediaResource_h
 
 #include "MediaResource.h"
 #include "MediaResourceCallback.h"
 #include "MediaCache.h"
 #include "nsIChannel.h"
 #include "nsIURI.h"
 #include "nsIStreamListener.h"
-#include "mozilla/dom/MediaDebugInfoBinding.h"
 
 class nsIPrincipal;
 
 namespace mozilla {
 
 DDLoggedTypeDeclNameAndBase(BaseMediaResource, MediaResource);
 
 class BaseMediaResource : public MediaResource,
@@ -104,17 +103,17 @@ class BaseMediaResource : public MediaRe
     // - mCallback
     return 0;
   }
 
   virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   }
 
-  virtual void GetDebugInfo(dom::MediaResourceDebugInfo& aInfo) {}
+  virtual nsCString GetDebugInfo() { return nsCString(); }
 
  protected:
   BaseMediaResource(MediaResourceCallback* aCallback, nsIChannel* aChannel,
                     nsIURI* aURI)
       : mCallback(aCallback),
         mChannel(aChannel),
         mURI(aURI),
         mLoadInBackground(false) {}
--- a/dom/media/ChannelMediaDecoder.cpp
+++ b/dom/media/ChannelMediaDecoder.cpp
@@ -1,16 +1,15 @@
 /* -*- 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 "ChannelMediaDecoder.h"
-#include "ChannelMediaResource.h"
 #include "DecoderTraits.h"
 #include "MediaDecoderStateMachine.h"
 #include "MediaFormatReader.h"
 #include "BaseMediaResource.h"
 #include "MediaShutdownManager.h"
 #include "mozilla/StaticPrefs.h"
 #include "VideoUtils.h"
 
@@ -531,19 +530,20 @@ void ChannelMediaDecoder::MetadataLoaded
     UniquePtr<MediaInfo> aInfo, UniquePtr<MetadataTags> aTags,
     MediaDecoderEventVisibility aEventVisibility) {
   MediaDecoder::MetadataLoaded(std::move(aInfo), std::move(aTags),
                                aEventVisibility);
   // Set mode to PLAYBACK after reading metadata.
   mResource->SetReadMode(MediaCacheStream::MODE_PLAYBACK);
 }
 
-void ChannelMediaDecoder::GetDebugInfo(dom::MediaDecoderDebugInfo& aInfo) {
-  MediaDecoder::GetDebugInfo(aInfo);
+nsCString ChannelMediaDecoder::GetDebugInfo() {
+  nsCString str = MediaDecoder::GetDebugInfo();
   if (mResource) {
-    mResource->GetDebugInfo(aInfo.mResource);
+    AppendStringIfNotEmpty(str, mResource->GetDebugInfo());
   }
+  return str;
 }
 
 }  // namespace mozilla
 
 // avoid redefined macro in unified build
 #undef LOG
--- a/dom/media/ChannelMediaDecoder.h
+++ b/dom/media/ChannelMediaDecoder.h
@@ -65,17 +65,17 @@ class ChannelMediaDecoder
                       MediaDecoderEventVisibility aEventVisibility) override;
   void NotifyPrincipalChanged() override;
 
   RefPtr<ResourceCallback> mResourceCallback;
   RefPtr<BaseMediaResource> mResource;
 
   explicit ChannelMediaDecoder(MediaDecoderInit& aInit);
 
-  void GetDebugInfo(dom::MediaDecoderDebugInfo& aInfo);
+  nsCString GetDebugInfo() override;
 
  public:
   // Create a decoder for the given aType. Returns null if we were unable
   // to create the decoder, for example because the requested MIME type in
   // the init struct was unsupported.
   static already_AddRefed<ChannelMediaDecoder> Create(
       MediaDecoderInit& aInit, DecoderDoctorDiagnostics* aDiagnostics);
 
--- a/dom/media/ChannelMediaResource.cpp
+++ b/dom/media/ChannelMediaResource.cpp
@@ -906,18 +906,19 @@ void ChannelMediaResource::Pin() { mCach
 void ChannelMediaResource::Unpin() { mCacheStream.Unpin(); }
 
 double ChannelMediaResource::GetDownloadRate(bool* aIsReliable) {
   return mCacheStream.GetDownloadRate(aIsReliable);
 }
 
 int64_t ChannelMediaResource::GetLength() { return mCacheStream.GetLength(); }
 
-void ChannelMediaResource::GetDebugInfo(dom::MediaResourceDebugInfo& aInfo) {
-  mCacheStream.GetDebugInfo(aInfo.mCacheStream);
+nsCString ChannelMediaResource::GetDebugInfo() {
+  return NS_LITERAL_CSTRING("ChannelMediaResource: ") +
+         mCacheStream.GetDebugInfo();
 }
 
 // ChannelSuspendAgent
 
 bool ChannelSuspendAgent::Suspend() {
   MOZ_ASSERT(NS_IsMainThread());
   SuspendInternal();
   if (++mSuspendCount == 1) {
--- a/dom/media/ChannelMediaResource.h
+++ b/dom/media/ChannelMediaResource.h
@@ -150,17 +150,17 @@ class ChannelMediaResource
 
     return size;
   }
 
   size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   }
 
-  void GetDebugInfo(dom::MediaResourceDebugInfo& aInfo) override;
+  nsCString GetDebugInfo() override;
 
   class Listener final : public nsIStreamListener,
                          public nsIInterfaceRequestor,
                          public nsIChannelEventSink,
                          public nsIThreadRetargetableStreamListener {
     ~Listener() {}
 
    public:
--- a/dom/media/MediaCache.cpp
+++ b/dom/media/MediaCache.cpp
@@ -480,18 +480,17 @@ class MediaCache {
   static bool sThreadInit;
 
  private:
   // MediaCache thread only. True if we're on a cellular network connection.
   static bool sOnCellular;
 
   // Used by MediaCacheStream::GetDebugInfo() only for debugging.
   // Don't add new callers to this function.
-  friend void MediaCacheStream::GetDebugInfo(
-      dom::MediaCacheStreamDebugInfo& aInfo);
+  friend nsCString MediaCacheStream::GetDebugInfo();
   mozilla::Monitor& GetMonitorOnTheMainThread() {
     MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
     return mMonitor;
   }
 };
 
 // Initialized to nullptr by non-local static initialization.
 /* static */
@@ -2771,22 +2770,21 @@ nsresult MediaCacheStream::GetCachedRang
 }
 
 double MediaCacheStream::GetDownloadRate(bool* aIsReliable) {
   MOZ_ASSERT(!NS_IsMainThread());
   AutoLock lock(mMediaCache->Monitor());
   return mDownloadStatistics.GetRate(aIsReliable);
 }
 
-void MediaCacheStream::GetDebugInfo(dom::MediaCacheStreamDebugInfo& aInfo) {
+nsCString MediaCacheStream::GetDebugInfo() {
   AutoLock lock(mMediaCache->GetMonitorOnTheMainThread());
-  aInfo.mStreamLength = mStreamLength;
-  aInfo.mChannelOffset = mChannelOffset;
-  aInfo.mCacheSuspended = mCacheSuspended;
-  aInfo.mChannelEnded = mChannelEnded;
-  aInfo.mLoadID = mLoadID;
+  return nsPrintfCString("mStreamLength=%" PRId64 " mChannelOffset=%" PRId64
+                         " mCacheSuspended=%d mChannelEnded=%d mLoadID=%u",
+                         mStreamLength, mChannelOffset, mCacheSuspended,
+                         mChannelEnded, mLoadID);
 }
 
 }  // namespace mozilla
 
 // avoid redefined macro in unified build
 #undef LOG
 #undef LOGI
--- a/dom/media/MediaCache.h
+++ b/dom/media/MediaCache.h
@@ -12,17 +12,16 @@
 #include "mozilla/Result.h"
 #include "mozilla/UniquePtr.h"
 #include "nsCOMPtr.h"
 #include "nsHashKeys.h"
 #include "nsTArray.h"
 #include "nsTHashtable.h"
 
 #include "MediaChannelStatistics.h"
-#include "mozilla/dom/MediaDebugInfoBinding.h"
 
 class nsIEventTarget;
 class nsIPrincipal;
 
 namespace mozilla {
 // defined in MediaResource.h
 class ChannelMediaResource;
 typedef media::IntervalSet<int64_t> MediaByteRangeSet;
@@ -355,17 +354,17 @@ class MediaCacheStream : public DecoderD
   // 'Read' for argument and return details.
   nsresult ReadAt(int64_t aOffset, char* aBuffer, uint32_t aCount,
                   uint32_t* aBytes);
 
   void ThrottleReadahead(bool bThrottle);
 
   size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
 
-  void GetDebugInfo(dom::MediaCacheStreamDebugInfo& aInfo);
+  nsCString GetDebugInfo();
 
  private:
   friend class MediaCache;
 
   /**
    * A doubly-linked list of blocks. Add/Remove/Get methods are all
    * constant time. We declare this here so that a stream can contain a
    * BlockList of its read-ahead blocks. Blocks are referred to by index
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -1319,43 +1319,77 @@ MediaDecoderOwner::NextFrameStatus Media
   auto currentPosition = CurrentPosition();
   media::TimeInterval interval(
       currentPosition, currentPosition + DEFAULT_NEXT_FRAME_AVAILABLE_BUFFERED);
   return GetBuffered().Contains(interval)
              ? MediaDecoderOwner::NEXT_FRAME_AVAILABLE
              : MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE;
 }
 
-void MediaDecoder::GetDebugInfo(dom::MediaDecoderDebugInfo& aInfo) {
-  aInfo.mInstance = NS_ConvertUTF8toUTF16(nsPrintfCString("%p", this));
-  aInfo.mChannels = mInfo ? mInfo->mAudio.mChannels : 0;
-  aInfo.mRate = mInfo ? mInfo->mAudio.mRate : 0;
-  aInfo.mHasAudio = mInfo ? mInfo->HasAudio() : false;
-  aInfo.mHasVideo = mInfo ? mInfo->HasVideo() : false;
-  aInfo.mPlayState = NS_ConvertUTF8toUTF16(PlayStateStr());
-  aInfo.mContainerType =
-      NS_ConvertUTF8toUTF16(ContainerType().Type().AsString());
-  mReader->GetDebugInfo(aInfo.mReader);
+nsCString MediaDecoder::GetDebugInfo() {
+  return nsPrintfCString(
+      "MediaDecoder=%p: channels=%u rate=%u hasAudio=%d hasVideo=%d "
+      "mPlayState=%s",
+      this, mInfo ? mInfo->mAudio.mChannels : 0,
+      mInfo ? mInfo->mAudio.mRate : 0, mInfo ? mInfo->HasAudio() : 0,
+      mInfo ? mInfo->HasVideo() : 0, PlayStateStr());
 }
 
-RefPtr<MediaDecoder::DebugInfoPromise> MediaDecoder::RequestDebugInfo(
-    MediaDecoderDebugInfo& aInfo) {
+RefPtr<GenericPromise> MediaDecoder::DumpDebugInfo() {
   MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
-  GetDebugInfo(aInfo);
+  nsCString str = GetDebugInfo();
+
+  nsAutoCString readerStr;
+  GetMozDebugReaderData(readerStr);
+  if (!readerStr.IsEmpty()) {
+    str += "\nreader data:\n";
+    str += readerStr;
+  }
 
   if (!GetStateMachine()) {
-    return DebugInfoPromise::CreateAndResolve(true, __func__);
+    DUMP("%s", str.get());
+    return GenericPromise::CreateAndResolve(true, __func__);
   }
 
-  return GetStateMachine()
-      ->RequestDebugInfo(aInfo.mStateMachine)
-      ->Then(
-          SystemGroup::AbstractMainThreadFor(TaskCategory::Other), __func__,
-          []() { return DebugInfoPromise::CreateAndResolve(true, __func__); },
-          []() { return DebugInfoPromise::CreateAndResolve(false, __func__); });
+  return GetStateMachine()->RequestDebugInfo()->Then(
+      SystemGroup::AbstractMainThreadFor(TaskCategory::Other), __func__,
+      [str](const nsACString& aString) {
+        DUMP("%s", str.get());
+        DUMP("%s", aString.Data());
+        return GenericPromise::CreateAndResolve(true, __func__);
+      },
+      [str]() {
+        DUMP("%s", str.get());
+        return GenericPromise::CreateAndResolve(true, __func__);
+      });
+}
+
+RefPtr<MediaDecoder::DebugInfoPromise> MediaDecoder::RequestDebugInfo() {
+  MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
+
+  auto str = GetDebugInfo();
+  if (!GetStateMachine()) {
+    return DebugInfoPromise::CreateAndResolve(str, __func__);
+  }
+
+  return GetStateMachine()->RequestDebugInfo()->Then(
+      SystemGroup::AbstractMainThreadFor(TaskCategory::Other), __func__,
+      [str](const nsACString& aString) {
+        nsCString result = str + nsCString("\n") + aString;
+        return DebugInfoPromise::CreateAndResolve(result, __func__);
+      },
+      [str]() { return DebugInfoPromise::CreateAndResolve(str, __func__); });
+}
+
+void MediaDecoder::GetMozDebugReaderData(nsACString& aString) {
+  aString += nsPrintfCString("Container Type: %s\n",
+                             ContainerType().Type().AsString().get());
+  if (mReader) {
+    mReader->GetMozDebugReaderData(aString);
+  }
 }
 
 void MediaDecoder::NotifyAudibleStateChanged() {
   MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
   GetOwner()->SetAudibleState(mIsAudioDataAudible);
 }
 
 MediaMemoryTracker::MediaMemoryTracker() {}
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -21,17 +21,16 @@
 #  include "TimeUnits.h"
 #  include "TrackID.h"
 #  include "mozilla/Atomics.h"
 #  include "mozilla/CDMProxy.h"
 #  include "mozilla/MozPromise.h"
 #  include "mozilla/ReentrantMonitor.h"
 #  include "mozilla/StateMirroring.h"
 #  include "mozilla/StateWatching.h"
-#  include "mozilla/dom/MediaDebugInfoBinding.h"
 #  include "nsAutoPtr.h"
 #  include "nsCOMPtr.h"
 #  include "nsIObserver.h"
 #  include "nsISupports.h"
 #  include "nsITimer.h"
 
 class nsIPrincipal;
 
@@ -385,20 +384,24 @@ class MediaDecoder : public DecoderDocto
   }
 
   MediaDecoderOwner::NextFrameStatus NextFrameStatus() const {
     return mNextFrameStatus;
   }
 
   virtual MediaDecoderOwner::NextFrameStatus NextFrameBufferedStatus();
 
-  using DebugInfoPromise = MozPromise<bool, bool, true>;
-  RefPtr<DebugInfoPromise> RequestDebugInfo(dom::MediaDecoderDebugInfo& aInfo);
+  // Returns a string describing the state of the media player internal
+  // data. Used for debugging purposes.
+  virtual void GetMozDebugReaderData(nsACString& aString);
 
-  void GetDebugInfo(dom::MediaDecoderDebugInfo& aInfo);
+  RefPtr<GenericPromise> DumpDebugInfo();
+
+  using DebugInfoPromise = MozPromise<nsCString, bool, true>;
+  RefPtr<DebugInfoPromise> RequestDebugInfo();
 
  protected:
   virtual ~MediaDecoder();
 
   // Called when the first audio and/or video from the media file has been
   // loaded by the state machine. Call on the main thread only.
   virtual void FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo,
                                 MediaDecoderEventVisibility aEventVisibility);
@@ -462,16 +465,18 @@ class MediaDecoder : public DecoderDocto
   RefPtr<MediaFormatReader> mReader;
 
   // Amount of buffered data ahead of current time required to consider that
   // the next frame is available.
   // An arbitrary value of 250ms is used.
   static constexpr auto DEFAULT_NEXT_FRAME_AVAILABLE_BUFFERED =
       media::TimeUnit::FromMicroseconds(250000);
 
+  virtual nsCString GetDebugInfo();
+
  private:
   // Called when the owner's activity changed.
   void NotifyCompositor();
 
   void OnPlaybackErrorEvent(const MediaResult& aError);
 
   void OnDecoderDoctorEvent(DecoderDoctorEvent aEvent);
 
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -209,18 +209,17 @@ class MediaDecoderStateMachine::StateObj
   virtual RefPtr<ShutdownPromise> HandleShutdown();
 
   virtual void HandleVideoSuspendTimeout() = 0;
 
   virtual void HandleResumeVideoDecoding(const TimeUnit& aTarget);
 
   virtual void HandlePlayStateChanged(MediaDecoder::PlayState aPlayState) {}
 
-  virtual void GetDebugInfo(
-      MediaDecoderStateMachineDecodingStateDebugInfo& aInfo) {}
+  virtual nsCString GetDebugInfo() { return nsCString(); }
 
   virtual void HandleLoopingChanged() {}
 
  private:
   template <class S, typename R, typename... As>
   auto ReturnTypeHelper(R (S::*)(As...)) -> R;
 
   void Crash(const char* aReason, const char* aSite) {
@@ -632,19 +631,18 @@ class MediaDecoderStateMachine::Decoding
 
     if (aPlayState == MediaDecoder::PLAY_STATE_PAUSED) {
       StartDormantTimer();
     } else {
       mDormantTimer.Reset();
     }
   }
 
-  void GetDebugInfo(
-      MediaDecoderStateMachineDecodingStateDebugInfo& aInfo) override {
-    aInfo.mIsPrerolling = mIsPrerolling;
+  nsCString GetDebugInfo() override {
+    return nsPrintfCString("mIsPrerolling=%d", mIsPrerolling);
   }
 
   void HandleLoopingChanged() override { SetDecodingState(); }
 
  protected:
   virtual void EnsureAudioDecodeTaskQueued();
 
  private:
@@ -3695,47 +3693,52 @@ void MediaDecoderStateMachine::SetAudioC
 
 uint32_t MediaDecoderStateMachine::GetAmpleVideoFrames() const {
   MOZ_ASSERT(OnTaskQueue());
   return mReader->VideoIsHardwareAccelerated()
              ? std::max<uint32_t>(sVideoQueueHWAccelSize, MIN_VIDEO_QUEUE_SIZE)
              : std::max<uint32_t>(sVideoQueueDefaultSize, MIN_VIDEO_QUEUE_SIZE);
 }
 
-void MediaDecoderStateMachine::GetDebugInfo(
-    dom::MediaDecoderStateMachineDebugInfo& aInfo) {
+nsCString MediaDecoderStateMachine::GetDebugInfo() {
   MOZ_ASSERT(OnTaskQueue());
-  aInfo.mDuration =
+  int64_t duration =
       mDuration.Ref() ? mDuration.Ref().ref().ToMicroseconds() : -1;
-  aInfo.mMediaTime = GetMediaTime().ToMicroseconds();
-  aInfo.mClock = mMediaSink->IsStarted() ? GetClock().ToMicroseconds() : -1;
-  aInfo.mPlayState = (int32_t)mPlayState.Ref();
-  aInfo.mSentFirstFrameLoadedEvent = mSentFirstFrameLoadedEvent;
-  aInfo.mIsPlaying = IsPlaying();
-  aInfo.mAudioRequestStatus = NS_ConvertUTF8toUTF16(AudioRequestStatus());
-  aInfo.mVideoRequestStatus = NS_ConvertUTF8toUTF16(VideoRequestStatus());
-  aInfo.mDecodedAudioEndTime = mDecodedAudioEndTime.ToMicroseconds();
-  aInfo.mDecodedVideoEndTime = mDecodedVideoEndTime.ToMicroseconds();
-  aInfo.mAudioCompleted = mAudioCompleted;
-  aInfo.mVideoCompleted = mVideoCompleted;
-  mStateObj->GetDebugInfo(aInfo.mStateObj);
-  mMediaSink->GetDebugInfo(aInfo.mMediaSink);
+  auto str = nsPrintfCString(
+      "MDSM: duration=%" PRId64 " GetMediaTime=%" PRId64
+      " GetClock="
+      "%" PRId64
+      " mMediaSink=%p state=%s mPlayState=%d "
+      "mSentFirstFrameLoadedEvent=%d IsPlaying=%d mAudioStatus=%s "
+      "mVideoStatus=%s mDecodedAudioEndTime=%" PRId64
+      " mDecodedVideoEndTime=%" PRId64
+      " mAudioCompleted=%d "
+      "mVideoCompleted=%d %s",
+      duration, GetMediaTime().ToMicroseconds(),
+      mMediaSink->IsStarted() ? GetClock().ToMicroseconds() : -1,
+      mMediaSink.get(), ToStateStr(), mPlayState.Ref(),
+      mSentFirstFrameLoadedEvent, IsPlaying(), AudioRequestStatus(),
+      VideoRequestStatus(), mDecodedAudioEndTime.ToMicroseconds(),
+      mDecodedVideoEndTime.ToMicroseconds(), mAudioCompleted, mVideoCompleted,
+      mStateObj->GetDebugInfo().get());
+
+  AppendStringIfNotEmpty(str, mMediaSink->GetDebugInfo());
+
+  return std::move(str);
 }
 
-RefPtr<StateMachineDebugInfoPromise> MediaDecoderStateMachine::RequestDebugInfo(
-    dom::MediaDecoderStateMachineDebugInfo& aInfo) {
-  using PromiseType = StateMachineDebugInfoPromise;
+RefPtr<MediaDecoder::DebugInfoPromise>
+MediaDecoderStateMachine::RequestDebugInfo() {
+  using PromiseType = MediaDecoder::DebugInfoPromise;
   RefPtr<PromiseType::Private> p = new PromiseType::Private(__func__);
   RefPtr<MediaDecoderStateMachine> self = this;
   nsresult rv = OwnerThread()->Dispatch(
-      NS_NewRunnableFunction("MediaDecoderStateMachine::RequestDebugInfo",
-                             [self, p, &aInfo]() {
-                               self->GetDebugInfo(aInfo);
-                               p->Resolve(true, __func__);
-                             }),
+      NS_NewRunnableFunction(
+          "MediaDecoderStateMachine::RequestDebugInfo",
+          [self, p]() { p->Resolve(self->GetDebugInfo(), __func__); }),
       AbstractThread::TailDispatch);
   MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
   Unused << rv;
   return p.forget();
 }
 
 void MediaDecoderStateMachine::SetOutputStreamPrincipal(
     const nsCOMPtr<nsIPrincipal>& aPrincipal) {
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -93,17 +93,16 @@ hardware (via AudioStream).
 #  include "MediaStatistics.h"
 #  include "MediaTimer.h"
 #  include "SeekJob.h"
 #  include "mozilla/Attributes.h"
 #  include "mozilla/ReentrantMonitor.h"
 #  include "mozilla/StateMirroring.h"
 #  include "nsAutoPtr.h"
 #  include "nsThreadUtils.h"
-#  include "mozilla/dom/MediaDebugInfoBinding.h"
 
 namespace mozilla {
 
 class AbstractThread;
 class AudioSegment;
 class DecodedStream;
 class DOMMediaStream;
 class OutputStreamManager;
@@ -149,18 +148,16 @@ DDLoggedTypeDeclName(MediaDecoderStateMa
   state machine thread, and controls the audio "push" thread.
 
   All internal state is synchronised via the decoder monitor. State changes
   are propagated by scheduling the state machine to run another cycle on the
   shared state machine thread.
 
   See MediaDecoder.h for more details.
 */
-using StateMachineDebugInfoPromise = MozPromise<bool, bool, true>;
-
 class MediaDecoderStateMachine
     : public DecoderDoctorLifeLogger<MediaDecoderStateMachine> {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDecoderStateMachine)
 
   using TrackSet = MediaFormatReader::TrackSet;
 
  public:
   typedef MediaDecoderOwner::NextFrameStatus NextFrameStatus;
@@ -180,18 +177,17 @@ class MediaDecoderStateMachine
     DECODER_STATE_BUFFERING,
     DECODER_STATE_COMPLETED,
     DECODER_STATE_SHUTDOWN
   };
 
   // Returns the state machine task queue.
   TaskQueue* OwnerThread() const { return mTaskQueue; }
 
-  RefPtr<StateMachineDebugInfoPromise> RequestDebugInfo(
-      dom::MediaDecoderStateMachineDebugInfo& aInfo);
+  RefPtr<MediaDecoder::DebugInfoPromise> RequestDebugInfo();
 
   void SetOutputStreamPrincipal(const nsCOMPtr<nsIPrincipal>& aPrincipal);
   void SetOutputStreamCORSMode(CORSMode aCORSMode);
   // If an OutputStreamManager does not exist, one will be created.
   void EnsureOutputStreamManager(MediaStreamGraph* aGraph);
   // If an OutputStreamManager exists, tracks matching aLoadedInfo will be
   // created unless they already exist in the manager.
   void EnsureOutputStreamManagerHasTracks(const MediaInfo& aLoadedInfo);
@@ -309,17 +305,17 @@ class MediaDecoderStateMachine
   class VideoOnlySeekingState;
   class BufferingState;
   class CompletedState;
   class ShutdownState;
 
   static const char* ToStateStr(State aState);
   const char* ToStateStr();
 
-  void GetDebugInfo(dom::MediaDecoderStateMachineDebugInfo& aInfo);
+  nsCString GetDebugInfo();
 
   // Functions used by assertions to ensure we're calling things
   // on the appropriate threads.
   bool OnTaskQueue() const;
 
   // Initialization that needs to happen on the task queue. This is the first
   // task that gets run on the task queue, and is dispatched from the MDSM
   // constructor immediately after the task queue is created.
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -2948,17 +2948,17 @@ void MediaFormatReader::UpdateBuffered()
   }
 }
 
 layers::ImageContainer* MediaFormatReader::GetImageContainer() {
   return mVideoFrameContainer ? mVideoFrameContainer->GetImageContainer()
                               : nullptr;
 }
 
-void MediaFormatReader::GetDebugInfo(dom::MediaFormatReaderDebugInfo& aInfo) {
+void MediaFormatReader::GetMozDebugReaderData(nsACString& aString) {
   nsCString result;
   nsAutoCString audioDecoderName("unavailable");
   nsAutoCString videoDecoderName = audioDecoderName;
   nsAutoCString audioType("none");
   nsAutoCString videoType("none");
 
   AudioInfo audioInfo;
   {
@@ -2977,86 +2977,80 @@ void MediaFormatReader::GetDebugInfo(dom
     if (HasVideo()) {
       videoInfo = *mVideo.GetWorkingInfo()->GetAsVideoInfo();
       videoDecoderName = mVideo.mDecoder ? mVideo.mDecoder->GetDescriptionName()
                                          : mVideo.mDescription;
       videoType = videoInfo.mMimeType;
     }
   }
 
-  aInfo.mAudioDecoderName = NS_ConvertUTF8toUTF16(audioDecoderName);
-  aInfo.mAudioType = NS_ConvertUTF8toUTF16(audioType);
-  aInfo.mAudioChannels = audioInfo.mChannels;
-  aInfo.mAudioRate = audioInfo.mRate / 1000.0f;
-  aInfo.mAudioFramesDecoded = mAudio.mNumSamplesOutputTotal;
-
+  result += nsPrintfCString("Audio Decoder(%s, %u channels @ %0.1fkHz): %s\n",
+                            audioType.get(), audioInfo.mChannels,
+                            audioInfo.mRate / 1000.0f, audioDecoderName.get());
+  result += nsPrintfCString("Audio Frames Decoded: %" PRIu64 "\n",
+                            mAudio.mNumSamplesOutputTotal);
   if (HasAudio()) {
-    aInfo.mAudioState.mNeedInput = NeedInput(mAudio);
-    aInfo.mAudioState.mHasPromise = mAudio.HasPromise();
-    aInfo.mAudioState.mWaitingPromise = !mAudio.mWaitingPromise.IsEmpty();
-    aInfo.mAudioState.mHasDemuxRequest = mAudio.mDemuxRequest.Exists();
-    aInfo.mAudioState.mDemuxQueueSize =
-        uint32_t(mAudio.mQueuedSamples.Length());
-    aInfo.mAudioState.mHasDecoder = mAudio.mDecodeRequest.Exists();
-    aInfo.mAudioState.mTimeTreshold =
+    result += nsPrintfCString(
+        "Audio State: ni=%d no=%d wp=%d demuxr=%d demuxq=%u decoder=%d tt=%.1f "
+        "tths=%d in=%" PRIu64 " out=%" PRIu64
+        " qs=%u pending=%u wfd=%d eos=%d ds=%d wfk=%d sid=%u\n",
+        NeedInput(mAudio), mAudio.HasPromise(),
+        !mAudio.mWaitingPromise.IsEmpty(), mAudio.mDemuxRequest.Exists(),
+        uint32_t(mAudio.mQueuedSamples.Length()),
+        mAudio.mDecodeRequest.Exists(),
         mAudio.mTimeThreshold ? mAudio.mTimeThreshold.ref().Time().ToSeconds()
-                              : -1.0;
-    aInfo.mAudioState.mTimeTresholdHasSeeked =
-        mAudio.mTimeThreshold ? mAudio.mTimeThreshold.ref().mHasSeeked : false;
-    aInfo.mAudioState.mNumSamplesInput = mAudio.mNumSamplesInput;
-    aInfo.mAudioState.mNumSamplesOutput = mAudio.mNumSamplesOutput;
-    aInfo.mAudioState.mQueueSize = size_t(mAudio.mSizeOfQueue);
-    aInfo.mAudioState.mPending = mAudio.mOutput.Length();
-    aInfo.mAudioState.mWaitingForData = mAudio.mWaitingForData;
-    aInfo.mAudioState.mDemuxEOS = mAudio.mDemuxEOS;
-    aInfo.mAudioState.mDrainState = int32_t(mAudio.mDrainState);
-    aInfo.mAudioState.mWaitingForKey = mAudio.mWaitingForKey;
-    aInfo.mAudioState.mLastStreamSourceID = mAudio.mLastStreamSourceID;
+                              : -1.0,
+        mAudio.mTimeThreshold ? mAudio.mTimeThreshold.ref().mHasSeeked : -1,
+        mAudio.mNumSamplesInput, mAudio.mNumSamplesOutput,
+        unsigned(size_t(mAudio.mSizeOfQueue)),
+        unsigned(mAudio.mOutput.Length()), mAudio.mWaitingForData,
+        mAudio.mDemuxEOS, int32_t(mAudio.mDrainState), mAudio.mWaitingForKey,
+        mAudio.mLastStreamSourceID);
   }
 
-  aInfo.mVideoDecoderName = NS_ConvertUTF8toUTF16(videoDecoderName);
-  aInfo.mVideoType = NS_ConvertUTF8toUTF16(videoType);
-  aInfo.mVideoWidth =
-      videoInfo.mDisplay.width < 0 ? 0 : videoInfo.mDisplay.width;
-  aInfo.mVideoHeight =
-      videoInfo.mDisplay.height < 0 ? 0 : videoInfo.mDisplay.height;
-  aInfo.mVideoRate = mVideo.mMeanRate.Mean();
-  aInfo.mVideoHardwareAccelerated = VideoIsHardwareAccelerated();
-  aInfo.mVideoNumSamplesOutputTotal = mVideo.mNumSamplesOutputTotal;
-  aInfo.mVideoNumSamplesSkippedTotal = mVideo.mNumSamplesSkippedTotal;
-
+  result += nsPrintfCString(
+      "Video Decoder(%s, %dx%d @ %0.2f): %s\n", videoType.get(),
+      videoInfo.mDisplay.width < 0 ? 0 : videoInfo.mDisplay.width,
+      videoInfo.mDisplay.height < 0 ? 0 : videoInfo.mDisplay.height,
+      mVideo.mMeanRate.Mean(), videoDecoderName.get());
+
+  result +=
+      nsPrintfCString("Hardware Video Decoding: %s\n",
+                      VideoIsHardwareAccelerated() ? "enabled" : "disabled");
+  result += nsPrintfCString(
+      "Video Frames Decoded: %" PRIu64 " (skipped=%" PRIu64 ")\n",
+      mVideo.mNumSamplesOutputTotal, mVideo.mNumSamplesSkippedTotal);
   if (HasVideo()) {
-    aInfo.mVideoState.mNeedInput = NeedInput(mVideo);
-    aInfo.mVideoState.mHasPromise = mVideo.HasPromise();
-    aInfo.mVideoState.mWaitingPromise = !mVideo.mWaitingPromise.IsEmpty();
-    aInfo.mVideoState.mHasDemuxRequest = mVideo.mDemuxRequest.Exists();
-    aInfo.mVideoState.mDemuxQueueSize =
-        uint32_t(mVideo.mQueuedSamples.Length());
-    aInfo.mVideoState.mHasDecoder = mVideo.mDecodeRequest.Exists();
-    aInfo.mVideoState.mTimeTreshold =
+    result += nsPrintfCString(
+        "Video State: ni=%d no=%d wp=%d demuxr=%d demuxq=%u decoder=%d tt=%.1f "
+        "tths=%d in=%" PRIu64 " out=%" PRIu64
+        " qs=%u pending:%u wfd=%d eos=%d ds=%d wfk=%d sid=%u\n",
+        NeedInput(mVideo), mVideo.HasPromise(),
+        !mVideo.mWaitingPromise.IsEmpty(), mVideo.mDemuxRequest.Exists(),
+        uint32_t(mVideo.mQueuedSamples.Length()),
+        mVideo.mDecodeRequest.Exists(),
         mVideo.mTimeThreshold ? mVideo.mTimeThreshold.ref().Time().ToSeconds()
-                              : -1.0;
-    aInfo.mVideoState.mTimeTresholdHasSeeked =
-        mVideo.mTimeThreshold ? mVideo.mTimeThreshold.ref().mHasSeeked : false;
-    aInfo.mVideoState.mNumSamplesInput = mVideo.mNumSamplesInput;
-    aInfo.mVideoState.mNumSamplesOutput = mVideo.mNumSamplesOutput;
-    aInfo.mVideoState.mQueueSize = size_t(mVideo.mSizeOfQueue);
-    aInfo.mVideoState.mPending = mVideo.mOutput.Length();
-    aInfo.mVideoState.mWaitingForData = mVideo.mWaitingForData;
-    aInfo.mVideoState.mDemuxEOS = mVideo.mDemuxEOS;
-    aInfo.mVideoState.mDrainState = int32_t(mVideo.mDrainState);
-    aInfo.mVideoState.mWaitingForKey = mVideo.mWaitingForKey;
-    aInfo.mVideoState.mLastStreamSourceID = mVideo.mLastStreamSourceID;
+                              : -1.0,
+        mVideo.mTimeThreshold ? mVideo.mTimeThreshold.ref().mHasSeeked : -1,
+        mVideo.mNumSamplesInput, mVideo.mNumSamplesOutput,
+        unsigned(size_t(mVideo.mSizeOfQueue)),
+        unsigned(mVideo.mOutput.Length()), mVideo.mWaitingForData,
+        mVideo.mDemuxEOS, int32_t(mVideo.mDrainState), mVideo.mWaitingForKey,
+        mVideo.mLastStreamSourceID);
   }
 
-  // Looking at dropped frames
+  // Looking at dropped frames in details.
   FrameStatisticsData stats = mFrameStats->GetFrameStatisticsData();
-  aInfo.mFrameStats.mDroppedDecodedFrames = stats.mDroppedDecodedFrames;
-  aInfo.mFrameStats.mDroppedSinkFrames = stats.mDroppedSinkFrames;
-  aInfo.mFrameStats.mDroppedCompositorFrames = stats.mDroppedCompositorFrames;
+  result +=
+      nsPrintfCString("Dropped Frames: reader=%" PRIu64 " sink=%" PRIu64
+                      " compositor=%" PRIu64 "\n",
+                      stats.mDroppedDecodedFrames, stats.mDroppedSinkFrames,
+                      stats.mDroppedCompositorFrames);
+
+  aString += result;
 }
 
 void MediaFormatReader::SetVideoNullDecode(bool aIsNullDecode) {
   MOZ_ASSERT(OnTaskQueue());
   return SetNullDecode(TrackType::kVideoTrack, aIsNullDecode);
 }
 
 void MediaFormatReader::UpdateCompositor(
--- a/dom/media/MediaFormatReader.h
+++ b/dom/media/MediaFormatReader.h
@@ -17,17 +17,16 @@
 #  include "FrameStatistics.h"
 #  include "MediaEventSource.h"
 #  include "MediaDataDemuxer.h"
 #  include "MediaMetadataManager.h"
 #  include "MediaPromiseDefs.h"
 #  include "nsAutoPtr.h"
 #  include "PDMFactory.h"
 #  include "SeekTarget.h"
-#  include "mozilla/dom/MediaDebugInfoBinding.h"
 
 namespace mozilla {
 
 class CDMProxy;
 class GMPCrashHelper;
 class MediaResource;
 class VideoFrameContainer;
 
@@ -174,19 +173,19 @@ class MediaFormatReader final
   // The MediaDecoderStateMachine uses various heuristics that assume that
   // raw media data is arriving sequentially from a network channel. This
   // makes sense in the <video src="foo"> case, but not for more advanced use
   // cases like MSE.
   bool UseBufferingHeuristics() const { return mTrackDemuxersMayBlock; }
 
   RefPtr<SetCDMPromise> SetCDMProxy(CDMProxy* aProxy);
 
-  // Returns a MediaDebugInfo structure
+  // Returns a string describing the state of the decoder data.
   // Used for debugging purposes.
-  void GetDebugInfo(dom::MediaFormatReaderDebugInfo& aInfo);
+  void GetMozDebugReaderData(nsACString& aString);
 
   // Switch the video decoder to NullDecoderModule. It might takes effective
   // since a few samples later depends on how much demuxed samples are already
   // queued in the original video decoder.
   void SetVideoNullDecode(bool aIsNullDecode);
 
   void UpdateCompositor(already_AddRefed<layers::KnowsCompositor>);
 
--- a/dom/media/mediasink/AudioSink.cpp
+++ b/dom/media/mediasink/AudioSink.cpp
@@ -493,20 +493,19 @@ uint32_t AudioSink::DrainConverter(uint3
       CreateAudioFromBuffer(std::move(convertedData), lastPacket);
   if (!data) {
     return 0;
   }
   mProcessedQueue.Push(data);
   return data->Frames();
 }
 
-void AudioSink::GetDebugInfo(dom::MediaSinkDebugInfo& aInfo) {
+nsCString AudioSink::GetDebugInfo() {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
-  aInfo.mAudioSink.mStartTime = mStartTime.ToMicroseconds();
-  aInfo.mAudioSink.mLastGoodPosition = mLastGoodPosition.ToMicroseconds();
-  aInfo.mAudioSink.mIsPlaying = mPlaying;
-  aInfo.mAudioSink.mOutputRate = mOutputRate;
-  aInfo.mAudioSink.mWritten = mWritten;
-  aInfo.mAudioSink.mHasErrored = bool(mErrored);
-  aInfo.mAudioSink.mPlaybackComplete = bool(mPlaybackComplete);
+  return nsPrintfCString(
+      "AudioSink: StartTime=%" PRId64 " LastGoodPosition=%" PRId64
+      " Playing=%d  OutputRate=%u Written=%" PRId64
+      " Errored=%d PlaybackComplete=%d",
+      mStartTime.ToMicroseconds(), mLastGoodPosition.ToMicroseconds(), mPlaying,
+      mOutputRate, mWritten, bool(mErrored), bool(mPlaybackComplete));
 }
 
 }  // namespace mozilla
--- a/dom/media/mediasink/AudioSink.h
+++ b/dom/media/mediasink/AudioSink.h
@@ -52,17 +52,17 @@ class AudioSink : private AudioStream::D
 
   void SetVolume(double aVolume);
   void SetPlaybackRate(double aPlaybackRate);
   void SetPreservesPitch(bool aPreservesPitch);
   void SetPlaying(bool aPlaying);
 
   MediaEventSource<bool>& AudibleEvent() { return mAudibleEvent; }
 
-  void GetDebugInfo(dom::MediaSinkDebugInfo& aInfo);
+  nsCString GetDebugInfo();
 
  private:
   // Allocate and initialize mAudioStream. Returns NS_OK on success.
   nsresult InitializeAudioStream(const PlaybackParams& aParams);
 
   // Interface of AudioStream::DataSource.
   // Called on the callback thread of cubeb.
   UniquePtr<AudioStream::Chunk> PopFrames(uint32_t aFrames) override;
--- a/dom/media/mediasink/AudioSinkWrapper.cpp
+++ b/dom/media/mediasink/AudioSinkWrapper.cpp
@@ -217,16 +217,20 @@ void AudioSinkWrapper::OnAudioEnded() {
   mAudioSinkEndedPromise.Complete();
   mPlayDuration = GetPosition();
   if (!mPlayStartTime.IsNull()) {
     mPlayStartTime = TimeStamp::Now();
   }
   mAudioEnded = true;
 }
 
-void AudioSinkWrapper::GetDebugInfo(dom::MediaSinkDebugInfo& aInfo) {
+nsCString AudioSinkWrapper::GetDebugInfo() {
   AssertOwnerThread();
-  aInfo.mAudioSink.mIsPlaying = IsPlaying();
-  aInfo.mAudioSink.mIsStarted = IsStarted();
-  aInfo.mAudioSink.mAudioEnded = mAudioEnded;
+  auto str = nsPrintfCString(
+      "AudioSinkWrapper: IsStarted=%d IsPlaying=%d AudioEnded=%d", IsStarted(),
+      IsPlaying(), mAudioEnded);
+  if (mAudioSink) {
+    AppendStringIfNotEmpty(str, mAudioSink->GetDebugInfo());
+  }
+  return std::move(str);
 }
 
 }  // namespace mozilla
--- a/dom/media/mediasink/AudioSinkWrapper.h
+++ b/dom/media/mediasink/AudioSinkWrapper.h
@@ -72,17 +72,17 @@ class AudioSinkWrapper : public MediaSin
   nsresult Start(const media::TimeUnit& aStartTime,
                  const MediaInfo& aInfo) override;
   void Stop() override;
   bool IsStarted() const override;
   bool IsPlaying() const override;
 
   void Shutdown() override;
 
-  void GetDebugInfo(dom::MediaSinkDebugInfo& aInfo) override;
+  nsCString GetDebugInfo() override;
 
  private:
   virtual ~AudioSinkWrapper();
 
   void AssertOwnerThread() const {
     MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
   }
 
--- a/dom/media/mediasink/DecodedStream.cpp
+++ b/dom/media/mediasink/DecodedStream.cpp
@@ -236,27 +236,27 @@ void DecodedStreamTrackListener::NotifyE
 /*
  * All MediaStream-related data is protected by the decoder's monitor.
  * We have at most one DecodedStreamDaata per MediaDecoder. Its stream
  * is used as the input for each ProcessedMediaStream created by calls to
  * captureStream(UntilEnded). Seeking creates a new source stream, as does
  * replaying after the input as ended. In the latter case, the new source is
  * not connected to streams created by captureStreamUntilEnded.
  */
-class DecodedStreamData final {
+class DecodedStreamData {
  public:
   DecodedStreamData(
       OutputStreamManager* aOutputStreamManager, PlaybackInfoInit&& aInit,
       MozPromiseHolder<DecodedStream::EndedPromise>&& aAudioEndedPromise,
       MozPromiseHolder<DecodedStream::EndedPromise>&& aVideoEndedPromise,
       AbstractThread* aMainThread);
   ~DecodedStreamData();
   MediaEventSource<int64_t>& OnOutput();
   void Forget();
-  void GetDebugInfo(dom::DecodedStreamDataDebugInfo& aInfo);
+  nsCString GetDebugInfo();
 
   void WriteVideoToSegment(layers::Image* aImage, const TimeUnit& aStart,
                            const TimeUnit& aEnd,
                            const gfx::IntSize& aIntrinsicSize,
                            const TimeStamp& aTimeStamp, VideoSegment* aOutput,
                            const PrincipalHandle& aPrincipalHandle);
 
   /* The following group of fields are protected by the decoder's monitor
@@ -332,29 +332,30 @@ DecodedStreamData::DecodedStreamData(
 DecodedStreamData::~DecodedStreamData() { MOZ_ASSERT(NS_IsMainThread()); }
 
 MediaEventSource<int64_t>& DecodedStreamData::OnOutput() {
   return mListener->OnOutput();
 }
 
 void DecodedStreamData::Forget() { mListener->Forget(); }
 
-void DecodedStreamData::GetDebugInfo(dom::DecodedStreamDataDebugInfo& aInfo) {
-  aInfo.mInstance = NS_ConvertUTF8toUTF16(nsPrintfCString("%p", this));
-  aInfo.mAudioFramesWritten = mAudioFramesWritten;
-  aInfo.mStreamAudioWritten = mStreamAudioWritten;
-  aInfo.mNextAudioTime = mNextAudioTime.ToMicroseconds();
-  aInfo.mLastVideoStartTime =
+nsCString DecodedStreamData::GetDebugInfo() {
+  return nsPrintfCString(
+      "DecodedStreamData=%p mAudioFramesWritten=%" PRId64
+      " mStreamAudioWritten=%" PRId64 " mStreamVideoWritten=%" PRId64
+      " mNextAudioTime=%" PRId64 " mLastVideoStartTime=%" PRId64
+      " mLastVideoEndTime=%" PRId64
+      " mHaveSentFinishAudio=%d mHaveSentFinishVideo=%d",
+      this, mAudioFramesWritten, mStreamAudioWritten, mStreamVideoWritten,
+      mNextAudioTime.ToMicroseconds(),
       mLastVideoStartTime.valueOr(TimeUnit::FromMicroseconds(-1))
-          .ToMicroseconds();
-  aInfo.mLastVideoEndTime =
+          .ToMicroseconds(),
       mLastVideoEndTime.valueOr(TimeUnit::FromMicroseconds(-1))
-          .ToMicroseconds();
-  aInfo.mHaveSentFinishAudio = mHaveSentFinishAudio;
-  aInfo.mHaveSentFinishVideo = mHaveSentFinishVideo;
+          .ToMicroseconds(),
+      mHaveSentFinishAudio, mHaveSentFinishVideo);
 }
 
 DecodedStream::DecodedStream(AbstractThread* aOwnerThread,
                              AbstractThread* aMainThread,
                              MediaQueue<AudioData>& aAudioQueue,
                              MediaQueue<VideoData>& aVideoQueue,
                              OutputStreamManager* aOutputStreamManager,
                              const bool& aSameOrigin)
@@ -948,20 +949,23 @@ void DecodedStream::DisconnectListener()
   AssertOwnerThread();
 
   mAudioPushListener.Disconnect();
   mVideoPushListener.Disconnect();
   mAudioFinishListener.Disconnect();
   mVideoFinishListener.Disconnect();
 }
 
-void DecodedStream::GetDebugInfo(dom::MediaSinkDebugInfo& aInfo) {
+nsCString DecodedStream::GetDebugInfo() {
   AssertOwnerThread();
   int64_t startTime = mStartTime.isSome() ? mStartTime->ToMicroseconds() : -1;
-  aInfo.mDecodedStream.mInstance =
-      NS_ConvertUTF8toUTF16(nsPrintfCString("%p", this));
-  aInfo.mDecodedStream.mStartTime = startTime;
-  aInfo.mDecodedStream.mLastOutputTime = mLastOutputTime.ToMicroseconds();
-  aInfo.mDecodedStream.mPlaying = mPlaying.Ref();
-  mData->GetDebugInfo(aInfo.mDecodedStream.mData);
+  auto str =
+      nsPrintfCString("DecodedStream=%p mStartTime=%" PRId64
+                      " mLastOutputTime=%" PRId64 " mPlaying=%d mData=%p",
+                      this, startTime, mLastOutputTime.ToMicroseconds(),
+                      mPlaying.Ref(), mData.get());
+  if (mData) {
+    AppendStringIfNotEmpty(str, mData->GetDebugInfo());
+  }
+  return std::move(str);
 }
 
 }  // namespace mozilla
--- a/dom/media/mediasink/DecodedStream.h
+++ b/dom/media/mediasink/DecodedStream.h
@@ -61,17 +61,18 @@ class DecodedStream : public MediaSink {
   void SetPlaying(bool aPlaying) override;
 
   nsresult Start(const media::TimeUnit& aStartTime,
                  const MediaInfo& aInfo) override;
   void Stop() override;
   bool IsStarted() const override;
   bool IsPlaying() const override;
   void Shutdown() override;
-  void GetDebugInfo(dom::MediaSinkDebugInfo& aInfo) override;
+
+  nsCString GetDebugInfo() override;
 
  protected:
   virtual ~DecodedStream();
 
  private:
   void DestroyData(UniquePtr<DecodedStreamData>&& aData);
   void SendAudio(double aVolume, bool aIsSameOrigin,
                  const PrincipalHandle& aPrincipalHandle);
--- a/dom/media/mediasink/MediaSink.h
+++ b/dom/media/mediasink/MediaSink.h
@@ -7,17 +7,16 @@
 #ifndef MediaSink_h_
 #define MediaSink_h_
 
 #include "AudioDeviceInfo.h"
 #include "MediaInfo.h"
 #include "mozilla/MozPromise.h"
 #include "mozilla/RefPtr.h"
 #include "nsISupportsImpl.h"
-#include "mozilla/dom/MediaDebugInfoBinding.h"
 
 namespace mozilla {
 
 class TimeStamp;
 class VideoFrameContainer;
 
 /**
  * A consumer of audio/video data which plays audio and video tracks and
@@ -125,17 +124,19 @@ class MediaSink {
   // Called on the state machine thread to shut down the sink. All resources
   // allocated by this sink should be released.
   // Must be called after playback stopped.
   virtual void Shutdown() {}
 
   virtual void SetSecondaryVideoContainer(VideoFrameContainer* aSecondary) {}
   virtual void ClearSecondaryVideoContainer() {}
 
-  virtual void GetDebugInfo(dom::MediaSinkDebugInfo& aInfo) {}
+  // Return a string containing debugging information.
+  // Can be called in any phase.
+  virtual nsCString GetDebugInfo() { return nsCString(); }
 
  protected:
   virtual ~MediaSink() = default;
 };
 
 }  // namespace mozilla
 
 #endif  // MediaSink_h_
--- a/dom/media/mediasink/VideoSink.cpp
+++ b/dom/media/mediasink/VideoSink.cpp
@@ -641,27 +641,28 @@ void VideoSink::SetSecondaryVideoContain
   }
 }
 
 void VideoSink::ClearSecondaryVideoContainer() {
   AssertOwnerThread();
   mSecondaryContainer = nullptr;
 }
 
-void VideoSink::GetDebugInfo(dom::MediaSinkDebugInfo& aInfo) {
+nsCString VideoSink::GetDebugInfo() {
   AssertOwnerThread();
-  aInfo.mVideoSink.mIsStarted = IsStarted();
-  aInfo.mVideoSink.mIsPlaying = IsPlaying();
-  aInfo.mVideoSink.mFinished = VideoQueue().IsFinished();
-  aInfo.mVideoSink.mSize = VideoQueue().GetSize();
-  aInfo.mVideoSink.mVideoFrameEndTime = mVideoFrameEndTime.ToMicroseconds();
-  aInfo.mVideoSink.mHasVideo = mHasVideo;
-  aInfo.mVideoSink.mVideoSinkEndRequestExists = mVideoSinkEndRequest.Exists();
-  aInfo.mVideoSink.mEndPromiseHolderIsEmpty = mEndPromiseHolder.IsEmpty();
-  mAudioSink->GetDebugInfo(aInfo);
+  auto str = nsPrintfCString(
+      "VideoSink: IsStarted=%d IsPlaying=%d VideoQueue(finished=%d "
+      "size=%zu) mVideoFrameEndTime=%" PRId64
+      " mHasVideo=%d "
+      "mVideoSinkEndRequest.Exists()=%d mEndPromiseHolder.IsEmpty()=%d",
+      IsStarted(), IsPlaying(), VideoQueue().IsFinished(),
+      VideoQueue().GetSize(), mVideoFrameEndTime.ToMicroseconds(), mHasVideo,
+      mVideoSinkEndRequest.Exists(), mEndPromiseHolder.IsEmpty());
+  AppendStringIfNotEmpty(str, mAudioSink->GetDebugInfo());
+  return std::move(str);
 }
 
 bool VideoSink::InitializeBlankImage() {
   mBlankImage = mContainer->GetImageContainer()->CreatePlanarYCbCrImage();
   if (mBlankImage == nullptr) {
     return false;
   }
   SetImageToGreenPixel(mBlankImage->AsPlanarYCbCrImage());
--- a/dom/media/mediasink/VideoSink.h
+++ b/dom/media/mediasink/VideoSink.h
@@ -62,17 +62,17 @@ class VideoSink : public MediaSink {
 
   bool IsPlaying() const override;
 
   void Shutdown() override;
 
   void SetSecondaryVideoContainer(VideoFrameContainer* aSecondary) override;
   void ClearSecondaryVideoContainer() override;
 
-  void GetDebugInfo(dom::MediaSinkDebugInfo& aInfo) override;
+  nsCString GetDebugInfo() override;
 
  private:
   virtual ~VideoSink();
 
   // VideoQueue listener related.
   void OnVideoQueuePushed(RefPtr<VideoData>&& aSample);
   void OnVideoQueueFinished();
   void ConnectListener();
--- a/dom/media/mediasource/MediaSource.cpp
+++ b/dom/media/mediasource/MediaSource.cpp
@@ -610,32 +610,20 @@ void MediaSource::DurationChange(double 
   //    4.1 Update new duration to equal highest end time.
   aNewDuration = std::max(aNewDuration, highestEndTime);
 
   // 5. Update the media duration to new duration and run the HTMLMediaElement
   // duration change algorithm.
   mDecoder->SetMediaSourceDuration(aNewDuration);
 }
 
-already_AddRefed<Promise> MediaSource::MozDebugReaderData(ErrorResult& aRv) {
-  // Creating a JS promise
-  nsPIDOMWindowInner* win = GetOwner();
-  if (!win) {
-    aRv.Throw(NS_ERROR_UNEXPECTED);
-    return nullptr;
-  }
-  RefPtr<Promise> domPromise = Promise::Create(win->AsGlobal(), aRv);
-  if (NS_WARN_IF(aRv.Failed())) {
-    return nullptr;
-  }
-  MOZ_ASSERT(domPromise);
-  MediaSourceDecoderDebugInfo info;
-  mDecoder->GetDebugInfo(info);
-  domPromise->MaybeResolve(info);
-  return domPromise.forget();
+void MediaSource::GetMozDebugReaderData(nsAString& aString) {
+  nsAutoCString result;
+  mDecoder->GetMozDebugReaderData(result);
+  aString = NS_ConvertUTF8toUTF16(result);
 }
 
 nsPIDOMWindowInner* MediaSource::GetParentObject() const { return GetOwner(); }
 
 JSObject* MediaSource::WrapObject(JSContext* aCx,
                                   JS::Handle<JSObject*> aGivenProto) {
   return MediaSource_Binding::Wrap(aCx, this, aGivenProto);
 }
--- a/dom/media/mediasource/MediaSource.h
+++ b/dom/media/mediasource/MediaSource.h
@@ -109,19 +109,19 @@ class MediaSource final : public DOMEven
   // Set mReadyState to aState and fire the required events at the MediaSource.
   void SetReadyState(MediaSourceReadyState aState);
 
   // Used by SourceBuffer to call CreateSubDecoder.
   MediaSourceDecoder* GetDecoder() { return mDecoder; }
 
   nsIPrincipal* GetPrincipal() { return mPrincipal; }
 
-  // Returns a structure describing the state of the MediaSource internal
+  // Returns a string describing the state of the MediaSource internal
   // buffered data. Used for debugging purposes.
-  already_AddRefed<Promise> MozDebugReaderData(ErrorResult& aRv);
+  void GetMozDebugReaderData(nsAString& aString);
 
   bool HasLiveSeekableRange() const { return mLiveSeekableRange.isSome(); }
   media::TimeInterval LiveSeekableRange() const {
     return mLiveSeekableRange.value();
   }
 
   AbstractThread* AbstractMainThread() const { return mAbstractMainThread; }
 
--- a/dom/media/mediasource/MediaSourceDecoder.cpp
+++ b/dom/media/mediasource/MediaSourceDecoder.cpp
@@ -230,18 +230,22 @@ void MediaSourceDecoder::SetMediaSourceD
       checkedDuration = INT64_MAX - 1;
     }
     SetExplicitDuration(aDuration);
   } else {
     SetExplicitDuration(PositiveInfinity<double>());
   }
 }
 
-void MediaSourceDecoder::GetDebugInfo(dom::MediaSourceDecoderDebugInfo& aInfo) {
-  mReader->GetDebugInfo(aInfo.mReader);
+void MediaSourceDecoder::GetMozDebugReaderData(nsACString& aString) {
+  aString += NS_LITERAL_CSTRING("Container Type: MediaSource\n");
+  if (mReader && mDemuxer) {
+    mReader->GetMozDebugReaderData(aString);
+    mDemuxer->GetMozDebugReaderData(aString);
+  }
 }
 
 double MediaSourceDecoder::GetDuration() {
   MOZ_ASSERT(NS_IsMainThread());
   AbstractThread::AutoEnter context(AbstractMainThread());
   return ExplicitDuration();
 }
 
--- a/dom/media/mediasource/MediaSourceDecoder.h
+++ b/dom/media/mediasource/MediaSourceDecoder.h
@@ -2,17 +2,16 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef MOZILLA_MEDIASOURCEDECODER_H_
 #define MOZILLA_MEDIASOURCEDECODER_H_
 
-#include "mozilla/dom/MediaDebugInfoBinding.h"
 #include "MediaDecoder.h"
 #include "mozilla/RefPtr.h"
 
 namespace mozilla {
 
 class MediaDecoderStateMachine;
 class MediaSourceDemuxer;
 
@@ -47,19 +46,19 @@ class MediaSourceDecoder : public MediaD
   void SetMediaSourceDuration(double aDuration);
 
   MediaSourceDemuxer* GetDemuxer() { return mDemuxer; }
 
   already_AddRefed<nsIPrincipal> GetCurrentPrincipal() override;
 
   bool IsTransportSeekable() override { return true; }
 
-  // Returns a structure describing the state of the MediaSource internal
+  // Returns a string describing the state of the MediaSource internal
   // buffered data. Used for debugging purposes.
-  void GetDebugInfo(dom::MediaSourceDecoderDebugInfo& aInfo);
+  void GetMozDebugReaderData(nsACString& aString) override;
 
   void AddSizeOfResources(ResourceSizes* aSizes) override;
 
   MediaDecoderOwner::NextFrameStatus NextFrameBufferedStatus() override;
 
   bool IsMSE() const override { return true; }
 
   void NotifyInitDataArrived();
--- a/dom/media/mediasource/MediaSourceDemuxer.cpp
+++ b/dom/media/mediasource/MediaSourceDemuxer.cpp
@@ -225,20 +225,59 @@ RefPtr<TrackBuffersManager> MediaSourceD
       return nullptr;
   }
 }
 
 MediaSourceDemuxer::~MediaSourceDemuxer() {
   mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
 }
 
-void MediaSourceDemuxer::GetDebugInfo(dom::MediaSourceDemuxerDebugInfo& aInfo) {
+void MediaSourceDemuxer::GetMozDebugReaderData(nsACString& aString) {
   MonitorAutoLock mon(mMonitor);
-  mAudioTrack->GetDebugInfo(aInfo.mAudioTrack);
-  mVideoTrack->GetDebugInfo(aInfo.mVideoTrack);
+  nsAutoCString result;
+  result += nsPrintfCString("Dumping Data for Demuxer: %p\n", this);
+  if (mAudioTrack) {
+    result += nsPrintfCString(
+        "\tDumping Audio Track Buffer(%s): mLastAudioTime=%f\n"
+        "\t\tAudio Track Buffer Details: NumSamples=%zu"
+        " Size=%u Evictable=%u "
+        "NextGetSampleIndex=%u NextInsertionIndex=%d\n",
+        mAudioTrack->mType.Type().AsString().get(),
+        mAudioTrack->mAudioTracks.mNextSampleTime.ToSeconds(),
+        mAudioTrack->mAudioTracks.mBuffers[0].Length(),
+        mAudioTrack->mAudioTracks.mSizeBuffer,
+        mAudioTrack->Evictable(TrackInfo::kAudioTrack),
+        mAudioTrack->mAudioTracks.mNextGetSampleIndex.valueOr(-1),
+        mAudioTrack->mAudioTracks.mNextInsertionIndex.valueOr(-1));
+
+    result += nsPrintfCString(
+        "\t\tAudio Track Buffered: ranges=%s\n",
+        DumpTimeRanges(mAudioTrack->SafeBuffered(TrackInfo::kAudioTrack))
+            .get());
+  }
+  if (mVideoTrack) {
+    result += nsPrintfCString(
+        "\tDumping Video Track Buffer(%s): mLastVideoTime=%f\n"
+        "\t\tVideo Track Buffer Details: NumSamples=%zu"
+        " Size=%u Evictable=%u "
+        "NextGetSampleIndex=%u NextInsertionIndex=%d\n",
+        mVideoTrack->mType.Type().AsString().get(),
+        mVideoTrack->mVideoTracks.mNextSampleTime.ToSeconds(),
+        mVideoTrack->mVideoTracks.mBuffers[0].Length(),
+        mVideoTrack->mVideoTracks.mSizeBuffer,
+        mVideoTrack->Evictable(TrackInfo::kVideoTrack),
+        mVideoTrack->mVideoTracks.mNextGetSampleIndex.valueOr(-1),
+        mVideoTrack->mVideoTracks.mNextInsertionIndex.valueOr(-1));
+
+    result += nsPrintfCString(
+        "\t\tVideo Track Buffered: ranges=%s\n",
+        DumpTimeRanges(mVideoTrack->SafeBuffered(TrackInfo::kVideoTrack))
+            .get());
+  }
+  aString += result;
 }
 
 MediaSourceTrackDemuxer::MediaSourceTrackDemuxer(MediaSourceDemuxer* aParent,
                                                  TrackInfo::TrackType aType,
                                                  TrackBuffersManager* aManager)
     : mParent(aParent),
       mType(aType),
       mMonitor("MediaSourceTrackDemuxer"),
--- a/dom/media/mediasource/MediaSourceDemuxer.h
+++ b/dom/media/mediasource/MediaSourceDemuxer.h
@@ -10,17 +10,16 @@
 #  include "MediaDataDemuxer.h"
 #  include "MediaResource.h"
 #  include "MediaSource.h"
 #  include "TrackBuffersManager.h"
 #  include "mozilla/Atomics.h"
 #  include "mozilla/Maybe.h"
 #  include "mozilla/Monitor.h"
 #  include "mozilla/TaskQueue.h"
-#  include "mozilla/dom/MediaDebugInfoBinding.h"
 
 namespace mozilla {
 
 class AbstractThread;
 class MediaResult;
 class MediaSourceTrackDemuxer;
 
 DDLoggedTypeDeclNameAndBase(MediaSourceDemuxer, MediaDataDemuxer);
@@ -45,19 +44,19 @@ class MediaSourceDemuxer : public MediaD
   bool ShouldComputeStartTime() const override { return false; }
 
   /* interface for TrackBuffersManager */
   void AttachSourceBuffer(RefPtr<TrackBuffersManager>& aSourceBuffer);
   void DetachSourceBuffer(RefPtr<TrackBuffersManager>& aSourceBuffer);
   TaskQueue* GetTaskQueue() { return mTaskQueue; }
   void NotifyInitDataArrived();
 
-  // Returns a structure describing the state of the MediaSource internal
+  // Returns a string describing the state of the MediaSource internal
   // buffered data. Used for debugging purposes.
-  void GetDebugInfo(dom::MediaSourceDemuxerDebugInfo& aInfo);
+  void GetMozDebugReaderData(nsACString& aString);
 
   void AddSizeOfResources(MediaSourceDecoder::ResourceSizes* aSizes);
 
   // Gap allowed between frames.
   // Due to inaccuracies in determining buffer end
   // frames (Bug 1065207). This value is based on videos seen in the wild.
   static constexpr media::TimeUnit EOS_FUZZ =
       media::TimeUnit::FromMicroseconds(500000);
--- a/dom/media/mediasource/TrackBuffersManager.cpp
+++ b/dom/media/mediasource/TrackBuffersManager.cpp
@@ -2761,61 +2761,16 @@ void TrackBuffersManager::TrackData::Add
     MediaSourceDecoder::ResourceSizes* aSizes) const {
   for (const TrackBuffer& buffer : mBuffers) {
     for (const MediaRawData* data : buffer) {
       aSizes->mByteSize += data->SizeOfIncludingThis(aSizes->mMallocSizeOf);
     }
   }
 }
 
-void TrackBuffersManager::GetDebugInfo(
-    dom::TrackBuffersManagerDebugInfo& aInfo) {
-  aInfo.mType = NS_ConvertUTF8toUTF16(mType.Type().AsString());
-
-  if (HasAudio()) {
-    aInfo.mNextSampleTime = mAudioTracks.mNextSampleTime.ToSeconds();
-    aInfo.mNumSamples = mAudioTracks.mBuffers[0].Length();
-    aInfo.mBufferSize = mAudioTracks.mSizeBuffer;
-    aInfo.mEvictable = Evictable(TrackInfo::kAudioTrack);
-    aInfo.mNextGetSampleIndex = mAudioTracks.mNextGetSampleIndex.valueOr(-1);
-    aInfo.mNextInsertionIndex = mAudioTracks.mNextInsertionIndex.valueOr(-1);
-    media::TimeIntervals ranges = SafeBuffered(TrackInfo::kAudioTrack);
-    dom::Sequence<dom::BufferRange> items;
-    for (uint32_t i = 0; i < ranges.Length(); ++i) {
-      // dom::Sequence is a FallibleTArray
-      dom::BufferRange* range = items.AppendElement(fallible);
-      if (!range) {
-        break;
-      }
-      range->mStart = ranges.Start(i).ToSeconds();
-      range->mEnd = ranges.End(i).ToSeconds();
-    }
-    aInfo.mRanges = items;
-  } else if (HasVideo()) {
-    aInfo.mNextSampleTime = mVideoTracks.mNextSampleTime.ToSeconds();
-    aInfo.mNumSamples = mVideoTracks.mBuffers[0].Length();
-    aInfo.mBufferSize = mVideoTracks.mSizeBuffer;
-    aInfo.mEvictable = Evictable(TrackInfo::kVideoTrack);
-    aInfo.mNextGetSampleIndex = mVideoTracks.mNextGetSampleIndex.valueOr(-1);
-    aInfo.mNextInsertionIndex = mVideoTracks.mNextInsertionIndex.valueOr(-1);
-    media::TimeIntervals ranges = SafeBuffered(TrackInfo::kVideoTrack);
-    dom::Sequence<dom::BufferRange> items;
-    for (uint32_t i = 0; i < ranges.Length(); ++i) {
-      // dom::Sequence is a FallibleTArray
-      dom::BufferRange* range = items.AppendElement(fallible);
-      if (!range) {
-        break;
-      }
-      range->mStart = ranges.Start(i).ToSeconds();
-      range->mEnd = ranges.End(i).ToSeconds();
-    }
-    aInfo.mRanges = items;
-  }
-}
-
 void TrackBuffersManager::AddSizeOfResources(
     MediaSourceDecoder::ResourceSizes* aSizes) const {
   MOZ_ASSERT(OnTaskQueue());
   mVideoTracks.AddSizeOfResources(aSizes);
   mAudioTracks.AddSizeOfResources(aSizes);
 }
 
 }  // namespace mozilla
--- a/dom/media/mediasource/TrackBuffersManager.h
+++ b/dom/media/mediasource/TrackBuffersManager.h
@@ -7,17 +7,16 @@
 #ifndef MOZILLA_TRACKBUFFERSMANAGER_H_
 #define MOZILLA_TRACKBUFFERSMANAGER_H_
 
 #include "mozilla/Atomics.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/NotNull.h"
 #include "mozilla/TaskQueue.h"
-#include "mozilla/dom/MediaDebugInfoBinding.h"
 
 #include "MediaContainerType.h"
 #include "MediaData.h"
 #include "MediaDataDemuxer.h"
 #include "MediaResult.h"
 #include "MediaSourceDecoder.h"
 #include "SourceBufferTask.h"
 #include "TimeUnits.h"
@@ -57,17 +56,17 @@ class SourceBufferTaskQueue {
   }
 
  private:
   nsTArray<RefPtr<SourceBufferTask>> mQueue;
 };
 
 DDLoggedTypeDeclName(TrackBuffersManager);
 
-class TrackBuffersManager final
+class TrackBuffersManager
     : public DecoderDoctorLifeLogger<TrackBuffersManager> {
  public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TrackBuffersManager);
 
   enum class EvictDataResult : int8_t {
     NO_DATA_EVICTED,
     CANT_EVICT,
     BUFFER_FULL,
@@ -161,22 +160,23 @@ class TrackBuffersManager final
   // timecode or is empty.
   nsresult SetNextGetSampleIndexIfNeeded(TrackInfo::TrackType aTrack,
                                          const media::TimeUnit& aFuzz);
 
   media::TimeUnit GetNextRandomAccessPoint(TrackInfo::TrackType aTrack,
                                            const media::TimeUnit& aFuzz);
 
   void AddSizeOfResources(MediaSourceDecoder::ResourceSizes* aSizes) const;
-  void GetDebugInfo(dom::TrackBuffersManagerDebugInfo& aInfo);
 
  private:
   typedef MozPromise<bool, MediaResult, /* IsExclusive = */ true>
       CodedFrameProcessingPromise;
 
+  // for MediaSourceDemuxer::GetMozDebugReaderData
+  friend class MediaSourceDemuxer;
   ~TrackBuffersManager();
   // All following functions run on the taskqueue.
   RefPtr<AppendPromise> DoAppendData(already_AddRefed<MediaByteBuffer> aData,
                                      const SourceBufferAttributes& aAttributes);
   void ScheduleSegmentParserLoop();
   void SegmentParserLoop();
   void InitializationSegmentReceived();
   void ShutdownDemuxers();
--- a/dom/media/test/marionette/yttest/support.py
+++ b/dom/media/test/marionette/yttest/support.py
@@ -89,20 +89,21 @@ class VideoStreamTestCase(MarionetteTest
             with using_page(video_id, self.marionette, **options) as page:
                 yield page
 
     def assertVideoQuality(self, res):
         self.assertTrue(res is not None, "We did not get back the results")
         debug_info = res["mozRequestDebugInfo"]
 
         # looking at mNumSamplesOutputTotal vs mNumSamplesSkippedTotal
-        reader_info = debug_info['decoder']['reader']
-        self.assertLess(reader_info["videoNumSamplesSkippedTotal"],
-                        reader_info["videoNumSamplesOutputTotal"] * 0.04)
+        decoded, skipped = debug_info["Video Frames Decoded"].split(" ", 1)
+        decoded = int(decoded)
+        skipped = int(skipped.split("=")[-1][:-1])
+        self.assertLess(skipped, decoded * 0.04)
 
         # extracting in/out from the debugInfo
-        video_state = reader_info["videoState"]
-        video_in = video_state["numSamplesInput"]
-        video_out = video_state["numSamplesOutput"]
+        video_state = debug_info["Video State"]
+        video_in = int(video_state["in"])
+        video_out = int(video_state["out"])
         # what's the ratio ? we want 99%+
         if video_out != video_in:
             in_out_ratio = float(video_out) / float(video_in) * 100
             self.assertGreater(in_out_ratio, 99.0)
--- a/dom/media/test/marionette/yttest/ytpage.py
+++ b/dom/media/test/marionette/yttest/ytpage.py
@@ -3,16 +3,17 @@
 # You can obtain one at http://mozilla.org/MPL/2.0/.
 """
 Drives the browser during the playback test.
 """
 import contextlib
 import os
 import time
 import json
+import re
 
 from marionette_driver.by import By
 
 
 here = os.path.dirname(__file__)
 js = os.path.join(here, "until_end_test.js")
 with open(js) as f:
     UNTIL_END_TEST = f.read()
@@ -80,25 +81,57 @@ class YoutubePage:
         options.update(self.options)
         if "duration" in options:
             script = DURATION_TEST % options
         else:
             script = UNTIL_END_TEST % options
         res = self.execute_async_script(script)
         if res is None:
             return res
+        res = self._parse_res(res)
         self._dump_res(res)
         return res
 
     def execute_async_script(self, script, context=None):
         if context is None:
             context = self.marionette.CONTEXT_CONTENT
         with self.marionette.using_context(context):
             return self.marionette.execute_async_script(script, sandbox="system")
 
+    def _parse_res(self, res):
+        debug_info = {}
+        # The parsing won't be necessary once we have bug 1542674
+        for key, value in res["mozRequestDebugInfo"].items():
+            key, value = key.strip(), value.strip()
+            if key.startswith(SPLIT_FIELD):
+                value_dict = {}
+                for field in re.findall(r"\S+\(.+\)\s|\S+", value):
+                    field = field.strip()
+                    if field == "":
+                        continue
+                    if field.startswith("VideoQueue"):
+                        k = "VideoQueue"
+                        v = field[len("VideoQueue(") : -2]  # noqa: E203
+                        fields = {}
+                        v = v.split(" ")
+                        for h in v:
+                            f, vv = h.split("=")
+                            fields[f] = vv
+                        v = fields
+                    else:
+                        if "=" in field:
+                            k, v = field.split("=", 1)
+                        else:
+                            k, v = field.split(":", 1)
+                    value_dict[k] = v
+                value = value_dict
+            debug_info[key] = value
+        res["mozRequestDebugInfo"] = debug_info
+        return res
+
     def _dump_res(self, res):
         raw = json.dumps(res, indent=2, sort_keys=True)
         print(raw)
         if "upload_dir" in self.options:
             fn = "%s-videoPlaybackQuality.json" % self.video_id
             fn = os.path.join(self.options["upload_dir"], fn)
             # dumping on disk
             with open(fn, "w") as f:
--- a/dom/webidl/HTMLMediaElement.webidl
+++ b/dom/webidl/HTMLMediaElement.webidl
@@ -97,26 +97,31 @@ interface HTMLMediaElement : HTMLElement
                          optional DOMString label = "",
                          optional DOMString language = "");
 };
 
 // Mozilla extensions:
 partial interface HTMLMediaElement {
   [Func="HasDebuggerOrTabsPrivilege"]
   readonly attribute MediaSource? mozMediaSourceObject;
-
+  [Func="HasDebuggerOrTabsPrivilege"]
+  readonly attribute DOMString mozDebugReaderData;
   [Func="HasDebuggerOrTabsPrivilege", NewObject]
-  Promise<HTMLMediaElementDebugInfo> mozRequestDebugInfo();
+  Promise<DOMString> mozRequestDebugInfo();
 
   [Func="HasDebuggerOrTabsPrivilege", NewObject]
   static void mozEnableDebugLog();
   [Func="HasDebuggerOrTabsPrivilege", NewObject]
   Promise<DOMString> mozRequestDebugLog();
 
+  [Pref="media.test.dumpDebugInfo"]
+  Promise<void> mozDumpDebugInfo();
+
   attribute MediaStream? srcObject;
+
   attribute boolean mozPreservesPitch;
 
   // NB: for internal use with the video controls:
   [Func="IsChromeOrXBLOrUAWidget"] attribute boolean mozAllowCasting;
   [Func="IsChromeOrXBLOrUAWidget"] attribute boolean mozIsCasting;
 
   // Mozilla extension: stream capture
   [Throws]
deleted file mode 100644
--- a/dom/webidl/MediaDebugInfo.webidl
+++ /dev/null
@@ -1,225 +0,0 @@
-/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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/.
- */
-
-/*
- * This module defines dictonaries that are filled with debug information
- * through GetDebugInfo() calls in the media component. To get the information
- * filled and returned, we have two methods that return promises, one in
- * HTMLMediaElement and one in MediaSource.
- *
- * If you need to add some extra info, there's one dictionary per class,
- * following the pattern <ClassName>DebugInfo, where you can add some fields
- * and fill them in the corresponding GetDebugInfo() call.
- *
- * Below is the structures returned.
- *
- * Used by HTMLMediaElement.GetMozRequestDebugInfo(), see HTMLMediaElement.webidl:
- *
- * HTMLMediaElementDebugInfo
- *   EMEDebugInfo
- *   MediaDecoderDebugInfo
- *     MediaFormatReaderDebugInfo
- *       MediaStateDebugInfo
- *       MediaStateDebugInfo
- *       MediaFrameStats
- *     MediaDecoderStateMachineDebugInfo
- *       MediaDecoderStateMachineDecodingStateDebugInfo
- *       MediaSinkDebugInfo
- *         VideoSinkDebugInfo
- *         AudioSinkDebugInfo
- *         DecodedStreamDebugInfo
- *           DecodedStreamDataDebugInfo
- *     MediaResourceDebugInfo
- *       MediaCacheStreamDebugInfo
- *
- * Used by MediaSource.GetMozDebugReaderData(), see MediaSource.webidl:
- *
- * MediaSourceDecoderDebugInfo
- *  MediaFormatReaderDebugInfo
- *    MediaStateDebugInfo
- *    MediaStateDebugInfo
- *    MediaFrameStats
- *  MediaSourceDemuxerDebugInfo
- *    TrackBuffersManagerDebugInfo
- *    TrackBuffersManagerDebugInfo
- */
-dictionary MediaCacheStreamDebugInfo {
-  long long streamLength = 0;
-  long long channelOffset = 0;
-  boolean cacheSuspended = false;
-  boolean channelEnded = false;
-  long loadID = 0;
-};
-
-dictionary MediaResourceDebugInfo {
-  required MediaCacheStreamDebugInfo cacheStream;
-};
-
-dictionary MediaDecoderDebugInfo {
-  DOMString instance = "";
-  unsigned long channels = 0;
-  unsigned long rate = 0;
-  boolean hasAudio = false;
-  boolean hasVideo = false;
-  DOMString PlayState = "";
-  DOMString containerType = "";
-  required MediaFormatReaderDebugInfo reader;
-  required MediaDecoderStateMachineDebugInfo stateMachine;
-  required MediaResourceDebugInfo resource;
-};
-
-dictionary AudioSinkDebugInfo {
-  long long startTime = 0;
-  long long lastGoodPosition = 0;
-  boolean isPlaying = false;
-  boolean isStarted = false;
-  boolean audioEnded = false;
-  long outputRate = 0;
-  long long written = 0;
-  boolean hasErrored = false;
-  boolean playbackComplete = false;
-};
-
-dictionary VideoSinkDebugInfo {
-  boolean isStarted = false;
-  boolean isPlaying = false;
-  boolean finished = false;
-  long size = 0;
-  long long videoFrameEndTime = 0;
-  boolean hasVideo = false;
-  boolean videoSinkEndRequestExists = false;
-  boolean endPromiseHolderIsEmpty = false;
-};
-
-dictionary DecodedStreamDataDebugInfo {
-  DOMString instance = "";
-  long long audioFramesWritten = 0;
-  long long streamAudioWritten = 0;
-  long long streamVideoWritten = 0;
-  long long nextAudioTime = 0;
-  long long lastVideoStartTime = 0;
-  long long lastVideoEndTime = 0;
-  boolean haveSentFinishAudio = false;
-  boolean haveSentFinishVideo = false;
-};
-
-dictionary DecodedStreamDebugInfo {
-  DOMString instance = "";
-  long long startTime = 0;
-  long long lastOutputTime = 0;
-  long playing = 0;
-  required DecodedStreamDataDebugInfo data;
-};
-
-dictionary MediaSinkDebugInfo {
-  required AudioSinkDebugInfo audioSink;
-  required VideoSinkDebugInfo videoSink;
-  required DecodedStreamDebugInfo decodedStream;
-};
-
-dictionary MediaDecoderStateMachineDecodingStateDebugInfo {
-  boolean isPrerolling = false;
-};
-
-dictionary MediaDecoderStateMachineDebugInfo {
-  long long duration = 0;
-  long long mediaTime = 0;
-  long long clock = 0;
-  DOMString state = "";
-  long playState = 0;
-  boolean sentFirstFrameLoadedEvent = false;
-  boolean isPlaying = false;
-  DOMString audioRequestStatus = "";
-  DOMString videoRequestStatus = "";
-  long long decodedAudioEndTime = 0;
-  long long decodedVideoEndTime = 0;
-  boolean audioCompleted = false;
-  boolean videoCompleted = false;
-  required MediaDecoderStateMachineDecodingStateDebugInfo stateObj;
-  required MediaSinkDebugInfo mediaSink;
-};
-
-dictionary MediaStateDebugInfo {
-  boolean needInput = false;
-  boolean hasPromise = false;
-  boolean waitingPromise = false;
-  boolean hasDemuxRequest = false;
-  long demuxQueueSize = 0;
-  boolean hasDecoder = false;
-  double timeTreshold = 0.0;
-  boolean timeTresholdHasSeeked = false;
-  long long numSamplesInput = 0;
-  long long numSamplesOutput = 0;
-  long queueSize = 0;
-  long pending = 0;
-  long waitingForData = 0;
-  long demuxEOS = 0;
-  long drainState = 0;
-  long waitingForKey = 0;
-  long lastStreamSourceID = 0;
-};
-
-dictionary MediaFrameStats {
-  long long droppedDecodedFrames = 0;
-  long long droppedSinkFrames = 0;
-  long long droppedCompositorFrames = 0;
-};
-
-dictionary MediaFormatReaderDebugInfo {
-  DOMString videoType = "";
-  DOMString videoDecoderName = "";
-  long videoWidth = 0;
-  long videoHeight = 0;
-  double videoRate = 0.0;
-  DOMString audioType = "";
-  DOMString audioDecoderName = "";
-  boolean videoHardwareAccelerated = false;
-  long long videoNumSamplesOutputTotal = 0;
-  long long videoNumSamplesSkippedTotal = 0;
-  long audioChannels = 0;
-  double audioRate = 0.0;
-  long long audioFramesDecoded = 0;
-  required MediaStateDebugInfo audioState;
-  required MediaStateDebugInfo videoState;
-  required MediaFrameStats frameStats;
-};
-
-dictionary BufferRange {
-  double start = 0;
-  double end = 0;
-};
-
-dictionary TrackBuffersManagerDebugInfo {
-  DOMString type = "";
-  double nextSampleTime = 0.0;
-  long numSamples = 0;
-  long bufferSize = 0;
-  long evictable = 0;
-  long nextGetSampleIndex = 0;
-  long nextInsertionIndex = 0;
-  sequence<BufferRange> ranges = [];
-};
-
-dictionary MediaSourceDemuxerDebugInfo {
-  required TrackBuffersManagerDebugInfo audioTrack;
-  required TrackBuffersManagerDebugInfo videoTrack;
-};
-
-dictionary MediaSourceDecoderDebugInfo {
-  required MediaFormatReaderDebugInfo reader;
-  required MediaSourceDemuxerDebugInfo demuxer;
-};
-
-dictionary EMEDebugInfo {
-  DOMString keySystem = "";
-  DOMString sessionsInfo = "";
-};
-
-dictionary HTMLMediaElementDebugInfo {
-  long compositorDroppedFrames = 0;
-  required EMEDebugInfo EMEInfo;
-  required MediaDecoderDebugInfo decoder;
-};
--- a/dom/webidl/MediaSource.webidl
+++ b/dom/webidl/MediaSource.webidl
@@ -37,11 +37,11 @@ interface MediaSource : EventTarget {
   void removeSourceBuffer(SourceBuffer sourceBuffer);
   [Throws]
   void endOfStream(optional MediaSourceEndOfStreamError error);
   [Throws]
   void setLiveSeekableRange(double start, double end);
   [Throws]
   void clearLiveSeekableRange();
   static boolean isTypeSupported(DOMString type);
-  [Throws, ChromeOnly]
-  Promise<MediaSourceDecoderDebugInfo> mozDebugReaderData();
+  [ChromeOnly]
+  readonly attribute DOMString mozDebugReaderData;
 };
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -635,17 +635,16 @@ WEBIDL_FILES = [
     'KeyframeEffect.webidl',
     'KeyIdsInitData.webidl',
     'L10nUtils.webidl',
     'LegacyQueryInterface.webidl',
     'LinkStyle.webidl',
     'LoadURIOptions.webidl',
     'Location.webidl',
     'MediaCapabilities.webidl',
-    'MediaDebugInfo.webidl',
     'MediaDeviceInfo.webidl',
     'MediaDevices.webidl',
     'MediaElementAudioSourceNode.webidl',
     'MediaEncryptedEvent.webidl',
     'MediaError.webidl',
     'MediaKeyError.webidl',
     'MediaKeyMessageEvent.webidl',
     'MediaKeys.webidl',
--- a/editor/libeditor/EditorDOMPoint.h
+++ b/editor/libeditor/EditorDOMPoint.h
@@ -110,20 +110,21 @@ class EditorDOMPointBase final {
   }
 
   EditorDOMPointBase(nsINode* aContainer, nsIContent* aPointedNode,
                      int32_t aOffset)
       : mParent(aContainer),
         mChild(aPointedNode),
         mOffset(mozilla::Some(aOffset)),
         mIsChildInitialized(true) {
-    MOZ_RELEASE_ASSERT(
+    MOZ_DIAGNOSTIC_ASSERT(
         aContainer, "This constructor shouldn't be used when pointing nowhere");
     MOZ_ASSERT(mOffset.value() <= mParent->Length());
-    MOZ_ASSERT(mChild || mParent->Length() == mOffset.value());
+    MOZ_ASSERT(mChild || mParent->Length() == mOffset.value() ||
+               !mParent->IsContainerNode());
     MOZ_ASSERT(!mChild || mParent == mChild->GetParentNode());
     MOZ_ASSERT(mParent->GetChildAt_Deprecated(mOffset.value()) == mChild);
   }
 
   template <typename PT, typename CT>
   explicit EditorDOMPointBase(const RangeBoundaryBase<PT, CT>& aOther)
       : mParent(aOther.mParent),
         mChild(aOther.mRef ? aOther.mRef->GetNextSibling()
--- a/editor/libeditor/HTMLEditRules.cpp
+++ b/editor/libeditor/HTMLEditRules.cpp
@@ -6661,146 +6661,114 @@ nsresult HTMLEditRules::NormalizeSelecti
     return NS_OK;
   }
 
   RefPtr<nsRange> range = SelectionRefPtr()->GetRangeAt(0);
   if (NS_WARN_IF(!range)) {
     return NS_ERROR_FAILURE;
   }
 
-  nsCOMPtr<nsINode> startNode = range->GetStartContainer();
-  if (NS_WARN_IF(!startNode)) {
+  EditorDOMPoint startPoint(range->StartRef());
+  if (NS_WARN_IF(!startPoint.IsSet())) {
     return NS_ERROR_FAILURE;
   }
-  nsCOMPtr<nsINode> endNode = range->GetEndContainer();
-  if (NS_WARN_IF(!endNode)) {
+  EditorDOMPoint endPoint(range->EndRef());
+  if (NS_WARN_IF(!endPoint.IsSet())) {
     return NS_ERROR_FAILURE;
   }
-  nsIContent* startChild = range->GetChildAtStartOffset();
-  nsIContent* endChild = range->GetChildAtEndOffset();
-  uint32_t startOffset = range->StartOffset();
-  uint32_t endOffset = range->EndOffset();
 
   // adjusted values default to original values
-  nsCOMPtr<nsINode> newStartNode = startNode;
-  uint32_t newStartOffset = startOffset;
-  nsCOMPtr<nsINode> newEndNode = endNode;
-  uint32_t newEndOffset = endOffset;
+  EditorDOMPoint newStartPoint(startPoint);
+  EditorDOMPoint newEndPoint(endPoint);
 
   // some locals we need for whitespace code
   WSType wsType;
 
   // let the whitespace code do the heavy lifting
-  WSRunObject wsEndObj(&HTMLEditorRef(), endNode,
-                       static_cast<int32_t>(endOffset));
+  WSRunObject wsEndObj(&HTMLEditorRef(), endPoint);
   // Is there any intervening visible whitespace?  If so we can't push
   // selection past that, it would visibly change meaning of users selection.
-  wsEndObj.PriorVisibleNode(EditorRawDOMPoint(endNode, endOffset), &wsType);
+  wsEndObj.PriorVisibleNode(endPoint, &wsType);
   if (wsType != WSType::text && wsType != WSType::normalWS) {
     // eThisBlock and eOtherBlock conveniently distinguish cases
     // of going "down" into a block and "up" out of a block.
     if (wsEndObj.mStartReason == WSType::otherBlock) {
       // endpoint is just after the close of a block.
       nsINode* child =
           HTMLEditorRef().GetRightmostChild(wsEndObj.mStartReasonNode, true);
       if (child) {
-        int32_t offset = -1;
-        newEndNode = EditorBase::GetNodeLocation(child, &offset);
-        // offset *after* child
-        newEndOffset = static_cast<uint32_t>(offset + 1);
+        newEndPoint.SetAfter(child);
       }
       // else block is empty - we can leave selection alone here, i think.
     } else if (wsEndObj.mStartReason == WSType::thisBlock) {
       // endpoint is just after start of this block
-      EditorRawDOMPoint atEnd(endNode, endChild, endOffset);
-      nsINode* child = HTMLEditorRef().GetPreviousEditableHTMLNode(atEnd);
+      nsINode* child = HTMLEditorRef().GetPreviousEditableHTMLNode(endPoint);
       if (child) {
-        int32_t offset = -1;
-        newEndNode = EditorBase::GetNodeLocation(child, &offset);
-        // offset *after* child
-        newEndOffset = static_cast<uint32_t>(offset + 1);
+        newEndPoint.SetAfter(child);
       }
       // else block is empty - we can leave selection alone here, i think.
     } else if (wsEndObj.mStartReason == WSType::br) {
       // endpoint is just after break.  lets adjust it to before it.
-      int32_t offset = -1;
-      newEndNode =
-          EditorBase::GetNodeLocation(wsEndObj.mStartReasonNode, &offset);
-      newEndOffset = static_cast<uint32_t>(offset);
-      ;
+      newEndPoint.Set(wsEndObj.mStartReasonNode);
     }
   }
 
   // similar dealio for start of range
-  WSRunObject wsStartObj(&HTMLEditorRef(), startNode,
-                         static_cast<int32_t>(startOffset));
+  WSRunObject wsStartObj(&HTMLEditorRef(), startPoint);
   // Is there any intervening visible whitespace?  If so we can't push
   // selection past that, it would visibly change meaning of users selection.
-  wsStartObj.NextVisibleNode(EditorRawDOMPoint(startNode, startOffset),
-                             &wsType);
+  wsStartObj.NextVisibleNode(startPoint, &wsType);
   if (wsType != WSType::text && wsType != WSType::normalWS) {
     // eThisBlock and eOtherBlock conveniently distinguish cases
     // of going "down" into a block and "up" out of a block.
     if (wsStartObj.mEndReason == WSType::otherBlock) {
       // startpoint is just before the start of a block.
       nsINode* child =
           HTMLEditorRef().GetLeftmostChild(wsStartObj.mEndReasonNode, true);
       if (child) {
-        int32_t offset = -1;
-        newStartNode = EditorBase::GetNodeLocation(child, &offset);
-        newStartOffset = static_cast<uint32_t>(offset);
+        newStartPoint.Set(child);
       }
       // else block is empty - we can leave selection alone here, i think.
     } else if (wsStartObj.mEndReason == WSType::thisBlock) {
       // startpoint is just before end of this block
-      nsINode* child = HTMLEditorRef().GetNextEditableHTMLNode(
-          EditorRawDOMPoint(startNode, startChild, startOffset));
+      nsINode* child = HTMLEditorRef().GetNextEditableHTMLNode(startPoint);
       if (child) {
-        int32_t offset = -1;
-        newStartNode = EditorBase::GetNodeLocation(child, &offset);
-        newStartOffset = static_cast<uint32_t>(offset);
+        newStartPoint.Set(child);
       }
       // else block is empty - we can leave selection alone here, i think.
     } else if (wsStartObj.mEndReason == WSType::br) {
       // startpoint is just before a break.  lets adjust it to after it.
-      int32_t offset = -1;
-      newStartNode =
-          EditorBase::GetNodeLocation(wsStartObj.mEndReasonNode, &offset);
-      // offset *after* break
-      newStartOffset = static_cast<uint32_t>(offset + 1);
+      newStartPoint.SetAfter(wsStartObj.mEndReasonNode);
     }
   }
 
   // There is a demented possiblity we have to check for.  We might have a very
   // strange selection that is not collapsed and yet does not contain any
   // editable content, and satisfies some of the above conditions that cause
   // tweaking.  In this case we don't want to tweak the selection into a block
   // it was never in, etc.  There are a variety of strategies one might use to
   // try to detect these cases, but I think the most straightforward is to see
   // if the adjusted locations "cross" the old values: i.e., new end before old
   // start, or new start after old end.  If so then just leave things alone.
 
   int16_t comp;
-  comp = nsContentUtils::ComparePoints(startNode, startOffset, newEndNode,
-                                       newEndOffset);
+  comp = nsContentUtils::ComparePoints(startPoint, newEndPoint);
   if (comp == 1) {
     return NS_OK;  // New end before old start.
   }
-  comp = nsContentUtils::ComparePoints(newStartNode, newStartOffset, endNode,
-                                       endOffset);
+  comp = nsContentUtils::ComparePoints(newStartPoint, endPoint);
   if (comp == 1) {
     return NS_OK;  // New start after old end.
   }
 
   // Otherwise set selection to new values.  Note that end point may be prior
   // to start point.  So, we cannot use Selection::SetStartAndEndInLimit() here.
   ErrorResult error;
   MOZ_KnownLive(SelectionRefPtr())
-      ->SetBaseAndExtentInLimiter(*newStartNode, newStartOffset, *newEndNode,
-                                  newEndOffset, error);
+      ->SetBaseAndExtentInLimiter(newStartPoint, newEndPoint, error);
   if (NS_WARN_IF(!CanHandleEditAction())) {
     error.SuppressException();
     return NS_ERROR_EDITOR_DESTROYED;
   }
   NS_WARNING_ASSERTION(!error.Failed(), "Failed to set selection");
   return error.StealNSResult();
 }
 
--- a/editor/libeditor/TextEditRules.cpp
+++ b/editor/libeditor/TextEditRules.cpp
@@ -917,34 +917,60 @@ nsresult TextEditRules::WillSetText(bool
   }
 
   nsresult rv = WillInsert(aCancel);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   RefPtr<Element> rootElement = TextEditorRef().GetRoot();
-  uint32_t count = rootElement->GetChildCount();
+  nsIContent* firstChild = rootElement->GetFirstChild();
 
-  // handles only when there is only one node and it's a text node, or empty.
-
-  if (count > 1) {
-    return NS_OK;
+  // We can use this fast path only when:
+  //  - we need to insert a text node.
+  //  - we need to replace content of existing text node.
+  // Additionally, for avoiding odd result, we should check whether we're in
+  // usual condition.
+  if (IsSingleLineEditor()) {
+    // If we're a single line text editor, i.e., <input>, there is only bogus-
+    // <br> element.  Otherwise, there should be only one text node.  But note
+    // that even if there is a bogus node, it's already been removed by
+    // WillInsert().  So, at here, there should be only one text node or no
+    // children.
+    if (firstChild &&
+        (!EditorBase::IsTextNode(firstChild) || firstChild->GetNextSibling())) {
+      return NS_OK;
+    }
+  } else {
+    // If we're a multiline text editor, i.e., <textarea>, there is a moz-<br>
+    // element followed by scrollbar/resizer elements.  Otherwise, a text node
+    // is followed by them.
+    if (!firstChild) {
+      return NS_OK;
+    }
+    if (EditorBase::IsTextNode(firstChild)) {
+      if (!firstChild->GetNextSibling() ||
+          !TextEditUtils::IsMozBR(firstChild->GetNextSibling())) {
+        return NS_OK;
+      }
+    } else if (!TextEditUtils::IsMozBR(firstChild)) {
+      return NS_OK;
+    }
   }
 
   nsAutoString tString(*aString);
 
   if (IsPasswordEditor()) {
     mPasswordText.Assign(tString);
     FillBufWithPWChars(&tString, tString.Length());
   } else if (IsSingleLineEditor()) {
     HandleNewLines(tString);
   }
 
-  if (!count) {
+  if (!firstChild || !EditorBase::IsTextNode(firstChild)) {
     if (tString.IsEmpty()) {
       *aHandled = true;
       return NS_OK;
     }
     RefPtr<Document> doc = TextEditorRef().GetDocument();
     if (NS_WARN_IF(!doc)) {
       return NS_OK;
     }
@@ -963,31 +989,39 @@ nsresult TextEditRules::WillSetText(bool
     }
     *aHandled = true;
 
     ASSERT_PASSWORD_LENGTHS_EQUAL();
 
     return NS_OK;
   }
 
-  nsINode* curNode = rootElement->GetFirstChild();
-  if (NS_WARN_IF(!EditorBase::IsTextNode(curNode))) {
+  // Even if empty text, we don't remove text node and set empty text
+  // for performance
+  RefPtr<Text> textNode = firstChild->GetAsText();
+  if (MOZ_UNLIKELY(NS_WARN_IF(!textNode))) {
     return NS_OK;
   }
-
-  // Even if empty text, we don't remove text node and set empty text
-  // for performance
-  rv = TextEditorRef().SetTextImpl(tString, *curNode->GetAsText());
+  rv = TextEditorRef().SetTextImpl(tString, *textNode);
   if (NS_WARN_IF(!CanHandleEditAction())) {
     return NS_ERROR_EDITOR_DESTROYED;
   }
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  // If we replaced non-empty value with empty string, we need to delete the
+  // text node.
+  if (tString.IsEmpty()) {
+    DebugOnly<nsresult> rvIgnored = DidDeleteSelection();
+    MOZ_ASSERT(rvIgnored != NS_ERROR_EDITOR_DESTROYED);
+    NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+                         "DidDeleteSelection() failed");
+  }
+
   *aHandled = true;
 
   ASSERT_PASSWORD_LENGTHS_EQUAL();
 
   return NS_OK;
 }
 
 nsresult TextEditRules::WillSetTextProperty(bool* aCancel, bool* aHandled) {
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/crashtests/1547898.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+    <script>
+      function jsfuzzer () {
+        window.find('1')
+        const selection = window.getSelection()
+        selection.extend(document.getElementById('list_item'))
+        document.getElementById('list_item').contentEditable = 'true'
+        document.execCommand('indent', false)
+      }
+    </script>
+</head>
+<body onload=jsfuzzer()>
+<li id="list_item">16</li>
+</body>
+</html>
--- a/editor/libeditor/crashtests/crashtests.list
+++ b/editor/libeditor/crashtests/crashtests.list
@@ -103,8 +103,9 @@ needs-focus load 1424450.html
 load 1425091.html
 load 1441619.html
 load 1443664.html
 skip-if(Android) needs-focus load 1444630.html
 load 1446451.html
 asserts(0-2) load 1464251.html # assertion is that mutation event listener modifies content
 pref(layout.accessiblecaret.enabled,true) load 1470926.html
 load 1525481.html
+load 1547898.html
--- a/js/public/PropertySpec.h
+++ b/js/public/PropertySpec.h
@@ -200,23 +200,16 @@ struct JSPropertySpec {
 
   static constexpr JSPropertySpec sentinel() {
     return JSPropertySpec(nullptr, 0,
                           AccessorsOrValue::fromAccessors(
                               JSPropertySpec::Accessor::noAccessor(),
                               JSPropertySpec::Accessor::noAccessor()));
   }
 
-  // Because reinterpret_cast cannot be used in constexpr, expose the
-  // static method to convert JS::SymbolCode to `name` parameter for
-  // {nativeAccessors, selfHostedAccessors,int32Value,stringValue}.
-  static const char* symbolToName(JS::SymbolCode symbol) {
-    return reinterpret_cast<const char*>(uint32_t(symbol) + 1);
-  }
-
   bool isAccessor() const { return !(flags & JSPROP_INTERNAL_USE_BIT); }
   JS_PUBLIC_API bool getValue(JSContext* cx,
                               JS::MutableHandle<JS::Value> value) const;
 
   bool isSelfHosted() const {
     MOZ_ASSERT(isAccessor());
 
 #ifdef DEBUG
@@ -251,46 +244,60 @@ struct JSPropertySpec {
   }
 
   void checkAccessorsAreSelfHosted() const {
     MOZ_ASSERT(!u.accessors.getter.selfHosted.unused);
     MOZ_ASSERT(!u.accessors.setter.selfHosted.unused);
   }
 };
 
+// JSPropertySpec::{nativeAccessors, selfHostedAccessors,int32Value,
+// stringValue} methods require symbol names to be casted to `const char*`,
+// and the cast is `reinterpret_cast`.
+//
+// Provide a macro for the cast because of the following reasons:
+//
+//   * `reinterpret_cast` cannot be used in constexpr
+//   * using non-constexpr static method in parameter disables constexpr of
+//     above methods
+//   * top-level `reinterpret_cast` doesn't disable constexpr of above methods
+//
+#define SYMBOL_TO_PROPERTY_NAME(symbol) \
+  reinterpret_cast<const char*>(uint32_t(symbol) + 1)
+
 #define JS_CHECK_ACCESSOR_FLAGS(flags)                                         \
   (static_cast<std::enable_if<((flags) & ~(JSPROP_ENUMERATE |                  \
                                            JSPROP_PERMANENT)) == 0>::type>(0), \
    (flags))
 
 #define JS_PSG(name, getter, flags)                                     \
   JSPropertySpec::nativeAccessors(name, JS_CHECK_ACCESSOR_FLAGS(flags), \
                                   getter, nullptr)
 #define JS_PSGS(name, getter, setter, flags)                            \
   JSPropertySpec::nativeAccessors(name, JS_CHECK_ACCESSOR_FLAGS(flags), \
                                   getter, nullptr, setter, nullptr)
-#define JS_SYM_GET(symbol, getter, flags)                     \
-  JSPropertySpec::nativeAccessors(                            \
-      JSPropertySpec::symbolToName(::JS::SymbolCode::symbol), \
+#define JS_SYM_GET(symbol, getter, flags)                \
+  JSPropertySpec::nativeAccessors(                       \
+      SYMBOL_TO_PROPERTY_NAME(::JS::SymbolCode::symbol), \
       JS_CHECK_ACCESSOR_FLAGS(flags), getter, nullptr)
 #define JS_SELF_HOSTED_GET(name, getterName, flags)                         \
   JSPropertySpec::selfHostedAccessors(name, JS_CHECK_ACCESSOR_FLAGS(flags), \
                                       getterName)
 #define JS_SELF_HOSTED_GETSET(name, getterName, setterName, flags)          \
   JSPropertySpec::selfHostedAccessors(name, JS_CHECK_ACCESSOR_FLAGS(flags), \
                                       getterName, setterName)
-#define JS_SELF_HOSTED_SYM_GET(symbol, getterName, flags)     \
-  JSPropertySpec::selfHostedAccessors(                        \
-      JSPropertySpec::symbolToName(::JS::SymbolCode::symbol), \
+#define JS_SELF_HOSTED_SYM_GET(symbol, getterName, flags) \
+  JSPropertySpec::selfHostedAccessors(                    \
+      SYMBOL_TO_PROPERTY_NAME(::JS::SymbolCode::symbol),  \
       JS_CHECK_ACCESSOR_FLAGS(flags), getterName)
 #define JS_STRING_PS(name, string, flags) \
   JSPropertySpec::stringValue(name, flags, string)
 #define JS_STRING_SYM_PS(symbol, string, flags) \
   JSPropertySpec::stringValue(                  \
-      JSPropertySpec::symbolToName(::JS::SymbolCode::symbol), flags, string)
+      SYMBOL_TO_PROPERTY_NAME(::JS::SymbolCode::symbol), flags, string)
 #define JS_INT32_PS(name, value, flags) \
   JSPropertySpec::int32Value(name, flags, value)
 #define JS_PS_END JSPropertySpec::sentinel()
 
 /**
  * To define a native function, set call to a JSNativeWrapper. To define a
  * self-hosted function, set selfHostedName to the name of a function
  * compiled during JSRuntime::initSelfHosting.
--- a/js/src/jit-test/tests/wasm/control-flow.js
+++ b/js/src/jit-test/tests/wasm/control-flow.js
@@ -6,37 +6,40 @@ const RuntimeError = WebAssembly.Runtime
 // Condition is an int32
 wasmFailValidateText('(module (func (local f32) (if (local.get 0) (i32.const 1))))', mismatchError("f32", "i32"));
 wasmFailValidateText('(module (func (local f32) (if (local.get 0) (i32.const 1) (i32.const 0))))', mismatchError("f32", "i32"));
 wasmFailValidateText('(module (func (local f64) (if (local.get 0) (i32.const 1) (i32.const 0))))', mismatchError("f64", "i32"));
 wasmEvalText('(module (func (local i32) (if (local.get 0) (nop))) (export "" 0))');
 wasmEvalText('(module (func (local i32) (if (local.get 0) (nop) (nop))) (export "" 0))');
 
 // Expression values types are consistent
+// Also test that we support (result t) for `if`
 wasmFailValidateText('(module (func (result i32) (local f32) (if f32 (i32.const 42) (local.get 0) (i32.const 0))))', mismatchError("i32", "f32"));
 wasmFailValidateText('(module (func (result i32) (local f64) (if i32 (i32.const 42) (i32.const 0) (local.get 0))))', mismatchError("f64", "i32"));
+wasmFailValidateText('(module (func (result i64) (if (result i64) (i32.const 0) (i32.const 1) (i32.const 2))))', mismatchError("i32", "i64"));
 assertEq(wasmEvalText('(module (func (result i32) (if i32 (i32.const 42) (i32.const 1) (i32.const 2))) (export "" 0))').exports[""](), 1);
-assertEq(wasmEvalText('(module (func (result i32) (if i32 (i32.const 0) (i32.const 1) (i32.const 2))) (export "" 0))').exports[""](), 2);
+assertEq(wasmEvalText('(module (func (result i32) (if (result i32) (i32.const 0) (i32.const 1) (i32.const 2))) (export "" 0))').exports[""](), 2);
 
 // Even if we don't yield, sub expressions types still have to match.
 wasmFailValidateText('(module (func (param f32) (if i32 (i32.const 42) (i32.const 1) (local.get 0))) (export "" 0))', mismatchError('f32', 'i32'));
 wasmFailValidateText('(module (func (if i32 (i32.const 42) (i32.const 1) (i32.const 0))) (export "" 0))', /unused values not explicitly dropped by end of block/);
 wasmFullPass('(module (func (drop (if i32 (i32.const 42) (i32.const 1) (i32.const 0)))) (export "run" 0))', undefined);
 wasmFullPass('(module (func (param f32) (if (i32.const 42) (drop (i32.const 1)) (drop (local.get 0)))) (export "run" 0))', undefined, {}, 13.37);
 
 // Sub-expression values are returned
+// Also test that we support (result t) for `block`
 wasmFullPass(`(module
     (func
         (result i32)
         (if i32
             (i32.const 42)
             (block i32
                 (
                     if i32
-                    (block i32
+                    (block (result i32)
                         (drop (i32.const 3))
                         (drop (i32.const 5))
                         (i32.const 0)
                     )
                     (i32.const 1)
                     (i32.const 2)
                 )
             )
--- a/js/src/proxy/Proxy.cpp
+++ b/js/src/proxy/Proxy.cpp
@@ -767,16 +767,22 @@ const ObjectOps js::ProxyObjectOps = {
 const Class js::ProxyClass =
     PROXY_CLASS_DEF("Proxy", JSCLASS_HAS_CACHED_PROTO(JSProto_Proxy) |
                                  JSCLASS_HAS_RESERVED_SLOTS(2));
 
 JS_FRIEND_API JSObject* js::NewProxyObject(JSContext* cx,
                                            const BaseProxyHandler* handler,
                                            HandleValue priv, JSObject* proto_,
                                            const ProxyOptions& options) {
+  AssertHeapIsIdle();
+  CHECK_THREAD(cx);
+  if (proto_ != TaggedProto::LazyProto) {
+    cx->check(proto_); // |priv| might be cross-compartment.
+  }
+
   if (options.lazyProto()) {
     MOZ_ASSERT(!proto_);
     proto_ = TaggedProto::LazyProto;
   }
 
   return ProxyObject::New(cx, handler, priv, TaggedProto(proto_), options);
 }
 
--- a/js/src/vm/ProxyObject.cpp
+++ b/js/src/vm/ProxyObject.cpp
@@ -183,20 +183,24 @@ void ProxyObject::nuke() {
       return cx->alreadyReportedOOM();
     }
 
     shape = EmptyShape::getInitialShape(cx, clasp, proto, /* nfixed = */ 0);
     if (!shape) {
       return cx->alreadyReportedOOM();
     }
 
-    MOZ_ASSERT(group->realm() == realm);
     realm->newProxyCache.add(group, shape);
   }
 
+  MOZ_ASSERT(group->realm() == realm);
+  MOZ_ASSERT(shape->zone() == cx->zone());
+  MOZ_ASSERT(!IsAboutToBeFinalizedUnbarriered(group.address()));
+  MOZ_ASSERT(!IsAboutToBeFinalizedUnbarriered(shape.address()));
+
   gc::InitialHeap heap = GetInitialHeap(newKind, group);
   debugCheckNewObject(group, shape, allocKind, heap);
 
   JSObject* obj =
       js::AllocateObject(cx, allocKind, /* nDynamicSlots = */ 0, heap, clasp);
   if (!obj) {
     return cx->alreadyReportedOOM();
   }
--- a/js/src/wasm/WasmTextToBinary.cpp
+++ b/js/src/wasm/WasmTextToBinary.cpp
@@ -2443,18 +2443,34 @@ static bool ParseValType(WasmParseContex
 
   return true;
 }
 
 static bool ParseBlockSignature(WasmParseContext& c, AstExprType* type) {
   WasmToken token;
   AstValType vt;
 
-  if (!MaybeParseValType(c, &vt)) {
-    return false;
+  if (c.ts.getIf(WasmToken::OpenParen, &token)) {
+    if (c.ts.getIf(WasmToken::Result)) {
+      if (!ParseValType(c, &vt)) {
+        return false;
+      }
+      if (!c.ts.match(WasmToken::CloseParen, c.error)) {
+        return false;
+      }
+    } else {
+      c.ts.unget(token);
+      if (!MaybeParseValType(c, &vt)) {
+        return false;
+      }
+    }
+  } else {
+    if (!MaybeParseValType(c, &vt)) {
+      return false;
+    }
   }
 
   if (vt.isValid()) {
     *type = AstExprType(vt);
   } else {
     *type = AstExprType(ExprType::Void);
   }
 
--- a/layout/xul/nsMenuPopupFrame.cpp
+++ b/layout/xul/nsMenuPopupFrame.cpp
@@ -2497,14 +2497,14 @@ void nsMenuPopupFrame::CheckForAnchorCha
     SetPopupPosition(nullptr, true, false, true);
   }
 }
 
 nsIWidget* nsMenuPopupFrame::GetParentMenuWidget() {
   nsMenuFrame* menuFrame = do_QueryFrame(GetParent());
   if (menuFrame) {
     nsMenuParent* parentPopup = menuFrame->GetMenuParent();
-    if (parentPopup && parentPopup->IsMenu()) {
+    if (parentPopup && (parentPopup->IsMenu() || parentPopup->IsMenuBar())) {
       return static_cast<nsMenuPopupFrame*>(parentPopup)->GetWidget();
     }
   }
   return nullptr;
 }
deleted file mode 100644
--- a/testing/web-platform/meta/selection/Document-open.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[Document-open.html]
-  [Selection must be replaced with a new object after document.open()]
-    expected: FAIL
-
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/firefox.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/firefox.py
@@ -140,17 +140,16 @@ def env_extras(**kwargs):
 
 def env_options():
     # The server host is set to 127.0.0.1 as Firefox is configured (through the
     # network.dns.localDomains preference set below) to resolve the test
     # domains to localhost without relying on the network stack.
     #
     # https://github.com/web-platform-tests/wpt/pull/9480
     return {"server_host": "127.0.0.1",
-            "bind_address": False,
             "supports_debugger": True}
 
 
 def run_info_extras(**kwargs):
 
     def get_bool_pref(pref):
         for key, value in kwargs.get('extra_prefs', []):
             if pref == key:
--- a/testing/web-platform/tests/tools/wptserve/wptserve/utils.py
+++ b/testing/web-platform/tests/tools/wptserve/wptserve/utils.py
@@ -95,16 +95,17 @@ def is_bad_port(port):
         6666,  # irc (alternate)
         6667,  # irc (default)
         6668,  # irc (alternate)
         6669,  # irc (alternate)
         6697,  # irc+tls
     ]
 
 def get_port(host=''):
+    host = host or '127.0.0.1'
     port = 0
     while True:
         free_socket = _open_socket(host, 0)
         port = free_socket.getsockname()[1]
         free_socket.close()
         if not is_bad_port(port):
             break
     return port
--- a/toolkit/locales/en-US/chrome/global/charsetMenu.properties
+++ b/toolkit/locales/en-US/chrome/global/charsetMenu.properties
@@ -71,16 +71,30 @@ Big5             = Chinese, Traditional
 # Cyrillic
 windows-1251.key = C
 windows-1251     = Cyrillic (Windows)
 ISO-8859-5       = Cyrillic (ISO)
 KOI8-R           = Cyrillic (KOI8-R)
 KOI8-U           = Cyrillic (KOI8-U)
 IBM866           = Cyrillic (DOS)
 
+# UI string in anticipation of Cyrillic analog of bug 1543077;
+# deliberately not in use yet
+
+# LOCALIZATION NOTE (Cyrillic.key): If taken into use, this string will appear
+# instead of the string for windows-1251.key, so the use of the same
+# accelerator is deliberate.
+Cyrillic.key     = C
+# LOCALIZATION NOTE (Cyrillic): If taken into use, this string will appear
+# as a single item instead of the five items windows-1251, ISO-8859-5,
+# KOI8-R, KOI8-U, and IBM866, so this string does not need to make sense
+# together with those strings and should be translated the way those were
+# but omitting the part in parentheses.
+Cyrillic         = Cyrillic
+
 # Greek
 windows-1253.key = G
 windows-1253     = Greek (Windows)
 ISO-8859-7.key   =          O
 ISO-8859-7       = Greek (ISO)
 
 # Hebrew
 windows-1255.key = H
@@ -93,16 +107,29 @@ ISO-8859-8       = Hebrew, Visual
 # Japanese
 Shift_JIS.key    = J
 Shift_JIS        = Japanese (Shift_JIS)
 EUC-JP.key       =   p
 EUC-JP           = Japanese (EUC-JP)
 ISO-2022-JP.key  =     n
 ISO-2022-JP      = Japanese (ISO-2022-JP)
 
+# UI string in anticipation of bug 1543077; deliberately not in use yet
+
+# LOCALIZATION NOTE (Japanese.key): If taken into use, this string will appear
+# instead of the string for Shift_JIS.key, so the use of the same
+# accelerator is deliberate.
+Japanese.key     = J
+# LOCALIZATION NOTE (Japanese): If taken into use, this string will appear
+# as a single item in place of the strings for the three items Shift_JIS,
+# EUC-JP, and ISO-2022-JP, so this string does not need to make sense together
+# with those strings and should be translated the way those were
+# but omitting the part in parentheses.
+Japanese         = Japanese
+
 # Korean
 EUC-KR.key       = K
 EUC-KR           = Korean
 
 # Thai
 windows-874.key  =    i
 windows-874      = Thai
 
--- a/widget/gtk/nsWindow.cpp
+++ b/widget/gtk/nsWindow.cpp
@@ -320,16 +320,17 @@ class CurrentX11TimeGetter {
 
 static NS_DEFINE_IID(kCDragServiceCID, NS_DRAGSERVICE_CID);
 
 // The window from which the focus manager asks us to dispatch key events.
 static nsWindow* gFocusWindow = nullptr;
 static bool gBlockActivateEvent = false;
 static bool gGlobalsInitialized = false;
 static bool gRaiseWindows = true;
+static GList* gCurrentPopupWindows = nullptr;
 
 #if GTK_CHECK_VERSION(3, 4, 0)
 static uint32_t gLastTouchID = 0;
 #endif
 
 #define NS_WINDOW_TITLE_MAX_LENGTH 4095
 
 // If after selecting profile window, the startup fail, please refer to
@@ -1127,16 +1128,90 @@ void nsWindow::Move(double aX, double aY
 
   NotifyRollupGeometryChange();
 }
 
 bool nsWindow::IsWaylandPopup() {
   return !mIsX11Display && mIsTopLevel && mWindowType == eWindowType_popup;
 }
 
+void nsWindow::CloseWaylandTooltips() {
+  GList* popup = gCurrentPopupWindows;
+  GList* next;
+  while (popup) {
+    // CloseWaylandWindow() manipulates the gCurrentPopupWindows list.
+    next = popup->next;
+    nsWindow* window = static_cast<nsWindow*>(popup->data);
+    if (window->mPopupType == ePopupTypeTooltip) {
+      window->CloseWaylandWindow();
+    }
+    popup = next;
+  }
+}
+
+// Wayland keeps strong popup window hierarchy. We need to track active
+// (visible) popup windows and make sure we hide popup on the same level
+// before we open another one on that level. It means that every open
+// popup needs to have an unique parent.
+GtkWidget* nsWindow::ConfigureWaylandPopupWindows() {
+  // Check if we're already configured.
+  if (gCurrentPopupWindows && g_list_find(gCurrentPopupWindows, this)) {
+    return GTK_WIDGET(gtk_window_get_transient_for(GTK_WINDOW(mShell)));
+  }
+
+  // If we're opening a new window we don't want to attach it to a tooltip
+  // as it's short lived temporary window.
+  CloseWaylandTooltips();
+
+  GtkWindow* parentWidget = nullptr;
+  if (gCurrentPopupWindows) {
+    if (mPopupType == ePopupTypeTooltip) {
+      // Attach tooltip window to the latest popup window
+      // to have both visible.
+      nsWindow* window = static_cast<nsWindow*>(gCurrentPopupWindows->data);
+      parentWidget = GTK_WINDOW(window->GetGtkWidget());
+    } else {
+      nsIFrame* frame = GetFrame();
+      if (!frame) {
+        // We're not fully created yet - just return our default parent.
+        return GTK_WIDGET(gtk_window_get_transient_for(GTK_WINDOW(mShell)));
+      }
+      nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(frame);
+      nsWindow* window =
+          static_cast<nsWindow*>(menuPopupFrame->GetParentMenuWidget());
+
+      if (!window) {
+        // We're toplevel popup menu attached to another menu. Just use our
+        // latest popup as a parent.
+        window = static_cast<nsWindow*>(gCurrentPopupWindows->data);
+        parentWidget = GTK_WINDOW(window->GetGtkWidget());
+      } else {
+        // We're a regular menu in the same frame hierarchy.
+        // Close child popups which are on a different level.
+        parentWidget = GTK_WINDOW(window->GetGtkWidget());
+        do {
+          nsWindow* window = static_cast<nsWindow*>(gCurrentPopupWindows->data);
+          if (GTK_WINDOW(window->GetGtkWidget()) == parentWidget) {
+            break;
+          }
+          window->CloseWaylandWindow();
+        } while (gCurrentPopupWindows != nullptr);
+      }
+    }
+  }
+
+  if (parentWidget) {
+    gtk_window_set_transient_for(GTK_WINDOW(mShell), parentWidget);
+  } else {
+    parentWidget = gtk_window_get_transient_for(GTK_WINDOW(mShell));
+  }
+  gCurrentPopupWindows = g_list_prepend(gCurrentPopupWindows, this);
+  return GTK_WIDGET(parentWidget);
+}
+
 #ifdef DEBUG
 static void NativeMoveResizeWaylandPopupCallback(
     GdkWindow* window, const GdkRectangle* flipped_rect,
     const GdkRectangle* final_rect, gboolean flipped_x, gboolean flipped_y,
     void* unused) {
   LOG(("%s flipped %d %d\n", __FUNCTION__, flipped_rect->x, flipped_rect->y));
   LOG(("%s final %d %d\n", __FUNCTION__, final_rect->x, final_rect->y));
 }
@@ -1156,23 +1231,17 @@ void nsWindow::NativeMoveResizeWaylandPo
   GdkWindow* gdkWindow = gtk_widget_get_window(GTK_WIDGET(mShell));
   // gdk_window_move_to_rect() is not available, we don't have a valid GdkWindow
   // (we're not realized yet) - try plain gtk_window_move() at least.
   if (!sGdkWindowMoveToRect || !gdkWindow) {
     gtk_window_move(GTK_WINDOW(mShell), aPosition->x, aPosition->y);
     return;
   }
 
-  GtkWindow* parentWindow = GetPopupParentWindow();
-  if (parentWindow) {
-    gtk_window_set_transient_for(GTK_WINDOW(mShell), parentWindow);
-  } else {
-    parentWindow = gtk_window_get_transient_for(GTK_WINDOW(mShell));
-  }
-
+  GtkWidget* parentWindow = ConfigureWaylandPopupWindows();
   LOG(("nsWindow::NativeMoveResizeWaylandPopup [%p] Set popup parent %p\n",
        (void*)this, parentWindow));
 
   int x_parent, y_parent;
   gdk_window_get_origin(gtk_widget_get_window(GTK_WIDGET(parentWindow)),
                         &x_parent, &y_parent);
 
   GdkRectangle rect = {aPosition->x - x_parent, aPosition->y - y_parent, 1, 1};
@@ -4044,53 +4113,57 @@ void nsWindow::NativeMoveResize() {
 #endif
 
   // Does it need to be shown because bounds were previously insane?
   if (mNeedsShow && mIsShown) {
     NativeShow(true);
   }
 }
 
+void nsWindow::CloseWaylandWindow() {
+#ifdef MOZ_WAYLAND
+  if (mContainer && moz_container_has_wl_egl_window(mContainer)) {
+    // Because wl_egl_window is destroyed on moz_container_unmap(),
+    // the current compositor cannot use it anymore. To avoid crash,
+    // destroy the compositor & recreate a new compositor on next
+    // expose event.
+    DestroyLayerManager();
+  }
+#endif
+
+  if (mWindowType == eWindowType_popup) {
+    gCurrentPopupWindows = g_list_remove(gCurrentPopupWindows, this);
+  }
+  gtk_widget_hide(mShell);
+}
+
 void nsWindow::NativeShow(bool aAction) {
   if (aAction) {
     // unset our flag now that our window has been shown
     mNeedsShow = false;
 
     if (mIsTopLevel) {
       // Set up usertime/startupID metadata for the created window.
       if (mWindowType != eWindowType_invisible) {
         SetUserTimeAndStartupIDForActivatedWindow(mShell);
       }
       // Update popup window hierarchy run-time on Wayland.
       if (IsWaylandPopup()) {
-        GtkWindow* parentWindow = GetPopupParentWindow();
-        if (parentWindow) {
-          LOG(("nsWindow::NativeShow [%p] Set popup parent %p\n", (void*)this,
-               parentWindow));
-          gtk_window_set_transient_for(GTK_WINDOW(mShell), parentWindow);
-        }
+        ConfigureWaylandPopupWindows();
       }
       gtk_widget_show(mShell);
     } else if (mContainer) {
       gtk_widget_show(GTK_WIDGET(mContainer));
     } else if (mGdkWindow) {
       gdk_window_show_unraised(mGdkWindow);
     }
   } else {
-#ifdef MOZ_WAYLAND
-    if (mContainer && moz_container_has_wl_egl_window(mContainer)) {
-      // Because wl_egl_window is destroyed on moz_container_unmap(),
-      // the current compositor cannot use it anymore. To avoid crash,
-      // destroy the compositor & recreate a new compositor on next
-      // expose event.
-      DestroyLayerManager();
-    }
-#endif
-
-    if (mIsTopLevel) {
+    if (!mIsX11Display) {
+      CloseWaylandWindow();
+    } else if (mIsTopLevel) {
       // Workaround window freezes on GTK versions before 3.21.2 by
       // ensuring that configure events get dispatched to windows before
       // they are unmapped. See bug 1225044.
       if (gtk_check_version(3, 21, 2) != nullptr && mPendingConfigures > 0) {
         GtkAllocation allocation;
         gtk_widget_get_allocation(GTK_WIDGET(mShell), &allocation);
 
         GdkEventConfigure event;
@@ -4104,17 +4177,16 @@ void nsWindow::NativeShow(bool aAction) 
         event.height = allocation.height;
 
         auto shellClass = GTK_WIDGET_GET_CLASS(mShell);
         for (unsigned int i = 0; i < mPendingConfigures; i++) {
           Unused << shellClass->configure_event(mShell, &event);
         }
         mPendingConfigures = 0;
       }
-
       gtk_widget_hide(mShell);
 
       ClearTransparencyBitmap();  // Release some resources
     } else if (mContainer) {
       gtk_widget_hide(GTK_WIDGET(mContainer));
     } else if (mGdkWindow) {
       gdk_window_hide(mGdkWindow);
     }
@@ -6898,59 +6970,45 @@ static nsIFrame* FindTitlebarFrame(nsIFr
 
     if (nsIFrame* foundFrame = FindTitlebarFrame(childFrame)) {
       return foundFrame;
     }
   }
   return nullptr;
 }
 
+nsIFrame* nsWindow::GetFrame(void) {
+  nsView* view = nsView::GetViewFor(this);
+  if (!view) {
+    return nullptr;
+  }
+  return view->GetFrame();
+}
+
 void nsWindow::ForceTitlebarRedraw(void) {
   MOZ_ASSERT(mDrawInTitlebar, "We should not redraw invisible titlebar.");
 
   if (!mWidgetListener || !mWidgetListener->GetPresShell()) {
     return;
   }
-  nsView* view = nsView::GetViewFor(this);
-  if (!view) {
-    return;
-  }
-  nsIFrame* frame = view->GetFrame();
+
+  nsIFrame* frame = GetFrame();
   if (!frame) {
     return;
   }
 
   frame = FindTitlebarFrame(frame);
   if (frame) {
     nsLayoutUtils::PostRestyleEvent(frame->GetContent()->AsElement(),
                                     RestyleHint{0}, nsChangeHint_RepaintFrame);
   }
 }
 
-GtkWindow* nsWindow::GetPopupParentWindow() {
-  nsView* view = nsView::GetViewFor(this);
-  if (!view) {
-    return nullptr;
-  }
-  nsIFrame* frame = view->GetFrame();
-  if (!frame) {
-    return nullptr;
-  }
-  nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(frame);
-  nsWindow* window =
-      static_cast<nsWindow*>(menuPopupFrame->GetParentMenuWidget());
-  return window ? GTK_WINDOW(window->GetGtkWidget()) : nullptr;
-}
-
 GtkTextDirection nsWindow::GetTextDirection() {
-  nsView* view = nsView::GetViewFor(this);
-  if (!view) {
-    return GTK_TEXT_DIR_LTR;
-  }
-  nsIFrame* frame = view->GetFrame();
+  nsIFrame* frame = GetFrame();
   if (!frame) {
     return GTK_TEXT_DIR_LTR;
   }
 
   WritingMode wm = frame->GetWritingMode();
   bool isFrameRTL = !(wm.IsVertical() ? wm.IsVerticalLR() : wm.IsBidiLTR());
   return isFrameRTL ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR;
 }
--- a/widget/gtk/nsWindow.h
+++ b/widget/gtk/nsWindow.h
@@ -267,16 +267,17 @@ class nsWindow final : public nsBaseWidg
                                                 int32_t aVertical) override;
 
   MozContainer* GetMozContainer() { return mContainer; }
   // GetMozContainerWidget returns the MozContainer even for undestroyed
   // descendant windows
   GtkWidget* GetMozContainerWidget();
   GdkWindow* GetGdkWindow() { return mGdkWindow; }
   GtkWidget* GetGtkWidget() { return mShell; }
+  nsIFrame* GetFrame();
   bool IsDestroyed() { return mIsDestroyed; }
 
   void DispatchDragEvent(mozilla::EventMessage aMsg,
                          const LayoutDeviceIntPoint& aRefPoint, guint aTime);
   static void UpdateDragStatus(GdkDragContext* aDragContext,
                                nsIDragService* aDragService);
 
   WidgetEventTime GetWidgetEventTime(guint32 aEventTime);
@@ -604,20 +605,20 @@ class nsWindow final : public nsBaseWidg
   void SetCompositorWidgetDelegate(CompositorWidgetDelegate* delegate) override;
 
   void CleanLayerManagerRecursive();
 
   virtual int32_t RoundsWidgetCoordinatesTo() override;
 
   void ForceTitlebarRedraw();
 
-  // This is used by Wayland backend to keep strict popup window hierarchy.
-  GtkWindow* GetPopupParentWindow();
-
   bool IsWaylandPopup();
+  GtkWidget* ConfigureWaylandPopupWindows();
+  void CloseWaylandTooltips();
+  void CloseWaylandWindow();
 
   /**
    * |mIMContext| takes all IME related stuff.
    *
    * This is owned by the top-level nsWindow or the topmost child
    * nsWindow embedded in a non-Gecko widget.
    *
    * The instance is created when the top level widget is created.  And when
--- a/widget/tests/window_composition_text_querycontent.xul
+++ b/widget/tests/window_composition_text_querycontent.xul
@@ -7895,18 +7895,17 @@ function* runIMEContentObserverTest()
     dumpUnexpectedNotifications(description, 3);
 
     // []
     description = aDescription + "setting the value property to ''";
     notifications = [];
     aElement.value = "";
     yield waitUntilNotificationsReceived();
     ensureToRemovePrecedingPositionChangeNotification();
-    // XXX Removing invisible <br> or something? The removed length is a line breaker length longer.
-    checkTextChangeNotification(notifications[0], description, { offset: 0, removedLength: getNativeText("ab\ncd\nef\ngh\n").length + kLFLen, addedLength: 0 });
+    checkTextChangeNotification(notifications[0], description, { offset: 0, removedLength: getNativeText("ab\ncd\nef\ngh\n").length, addedLength: 0 });
     checkSelectionChangeNotification(notifications[1], description, { offset: 0, text: "" });
     checkPositionChangeNotification(notifications[2], description);
     ensureToRemovePostPositionChangeNotification(description, 3);
     dumpUnexpectedNotifications(description, 3);
   }
 
   function* testWithHTMLEditor(aDescription, aElement, aDefaultParagraphSeparator)
   {