merge fx-team to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 19 Jul 2016 16:09:20 +0200
changeset 347598 5a91e5b49be3c1ba401b057e90c92d7488e3647d
parent 347581 37cc0da01187534ea9f8d384c0c9c5c57eb67b56 (current diff)
parent 347597 e2ddf53209916302a81ca331f88b4df8df0ca7c7 (diff)
child 347649 3383b0da1a14340ec6096aca542eb73b0f7341d5
push id1230
push userjlund@mozilla.com
push dateMon, 31 Oct 2016 18:13:35 +0000
treeherdermozilla-release@5e06e3766db2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone50.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 fx-team to mozilla-central a=merge
dom/html/HTMLMediaElement.cpp
toolkit/components/telemetry/Histograms.json
--- a/browser/components/downloads/content/allDownloadsViewOverlay.js
+++ b/browser/components/downloads/content/allDownloadsViewOverlay.js
@@ -297,18 +297,21 @@ HistoryDownloadElementShell.prototype = 
     return status;
   },
 
   onStateChanged() {
     this._updateState();
 
     if (this.element.selected) {
       goUpdateDownloadCommands();
+    } else {
+      // If a state change occurs in an item that is not currently selected,
+      // this is the only command that may be affected.
+      goUpdateCommand("downloadsCmd_clearDownloads");
     }
-    goUpdateCommand("downloadsCmd_clearDownloads");
   },
 
   onChanged() {
     // This cannot be placed within onStateChanged because
     // when a download goes from hasBlockedData to !hasBlockedData
     // it will still remain in the same state.
     this.element.classList.toggle("temporary-block",
                                   !!this.download.hasBlockedData);
@@ -1131,18 +1134,17 @@ DownloadsPlacesView.prototype = {
 
   onDownloadRemoved(download) {
     this._removeSessionDownloadFromView(download);
   },
 
   // nsIController
   supportsCommand(aCommand) {
     // Firstly, determine if this is a command that we can handle.
-    if (!aCommand.startsWith("cmd_") &&
-        !aCommand.startsWith("downloadsCmd_")) {
+    if (!DownloadsViewUI.isCommandName(aCommand)) {
       return false;
     }
     if (!(aCommand in this) &&
         !(aCommand in HistoryDownloadElementShell.prototype)) {
       return false;
     }
     // If this function returns true, other controllers won't get a chance to
     // process the command even if isCommandEnabled returns false, so it's
@@ -1413,16 +1415,16 @@ for (let methodName of ["load", "applyFi
     throw new Error("|" + methodName +
                     "| is not implemented by the downloads view.");
   }
 }
 
 function goUpdateDownloadCommands() {
   function updateCommandsForObject(object) {
     for (let name in object) {
-      if (name.startsWith("cmd_") || name.startsWith("downloadsCmd_")) {
+      if (DownloadsViewUI.isCommandName(name)) {
         goUpdateCommand(name);
       }
     }
   }
-  updateCommandsForObject(this);
+  updateCommandsForObject(DownloadsPlacesView.prototype);
   updateCommandsForObject(HistoryDownloadElementShell.prototype);
 }
--- a/browser/components/downloads/content/downloads.css
+++ b/browser/components/downloads/content/downloads.css
@@ -172,31 +172,40 @@ richlistitem.download button {
 #downloadsPanel-mainView .download-state[state="8"] .downloadConfirmBlock,
 #downloadsPanel-mainView .download-state[state="8"] .downloadChooseUnblock,
 #downloadsPanel-mainView .download-state[state="8"] .downloadChooseOpen,
 #downloadsPanel-mainView .download-state[state="8"] .downloadRetry,
 #downloadsPanel-mainView .download-state[state="8"] .downloadShow {
   display: none;
 }
 
+/* Make the panel wide enough to show the download list items without improperly
+   truncating them. */
+#downloadsPanel-multiView > .panel-viewcontainer,
+#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack,
+#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack > .panel-mainview {
+  overflow: visible;
+  max-width: unset;
+}
+
 /* Show the "show blocked info" button. */
 #downloadsPanel-mainView .download-state[state="8"] .downloadShowBlockedInfo {
   display: inline;
 }
 
 /** When the main view is showing... **/
 
 /* The subview should be off to the right and not visible at all. */
 #downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype=main] > .panel-subviews {
-  transform: translateX(100%);
+  transform: translateX(101%);
   transition: transform var(--panelui-subview-transition-duration);
 }
 
 #downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype=main] > .panel-subviews:-moz-locale-dir(rtl) {
-  transform: translateX(-100%);
+  transform: translateX(-101%);
 }
 
 /** When the subview is showing... **/
 
 /* Hide the buttons of all downloads except the one that triggered the
    subview. */
 #downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] .download-state:not([showingsubview]) .downloadButton {
   display: none;
--- a/browser/components/downloads/content/downloadsOverlay.xul
+++ b/browser/components/downloads/content/downloadsOverlay.xul
@@ -129,17 +129,17 @@
           <spacer flex="1"/>
           <vbox id="downloadsFooter">
             <hbox id="downloadsSummary"
                   align="center"
                   orient="horizontal"
                   onkeydown="DownloadsSummary.onKeyDown(event);"
                   onclick="DownloadsSummary.onClick(event);">
               <image class="downloadTypeIcon" />
-              <vbox>
+              <vbox id="downloadsSummaryChildBox">
                 <description id="downloadsSummaryDescription"
                              style="min-width: &downloadsSummary.minWidth2;"/>
                 <progressmeter id="downloadsSummaryProgress"
                                class="downloadProgress"
                                min="0"
                                max="100"
                                mode="normal" />
                 <description id="downloadsSummaryDetails"
--- a/browser/extensions/pocket/content/main.js
+++ b/browser/extensions/pocket/content/main.js
@@ -130,22 +130,17 @@ var pktUI = (function() {
     function showSignUp() {
         // AB test: Direct logged-out users to tab vs panel
         if (pktApi.getSignupPanelTabTestVariant() == 'tab')
         {
             let site = Services.prefs.getCharPref("extensions.pocket.site");
             openTabWithUrl('https://' + site + '/firefox_learnmore?src=ff_ext&s=ffi&t=buttonclick', true);
 
             // force the panel closed before it opens
-            // wrapped in setTimeout to avoid race condition after logging out
-            // if this test goes to 100%, we should move this logic up before
-            // the panel is actually opened
-            setTimeout(function() {
-                getPanel().hidePopup();
-            }, 0);
+            getPanel().hidePopup();
 
             return;
         }
 
         // Control: Show panel as normal
         getFirefoxAccountSignedInUser(function(userdata)
         {
             var fxasignedin = (typeof userdata == 'object' && userdata !== null) ? '1' : '0';
@@ -327,17 +322,19 @@ var pktUI = (function() {
           iframe.style.height = options.height + "px";
         }
     }
 
     /**
      * Called when the signup and saved panel was hidden
      */
     function panelDidHide() {
-
+        // clear the onShow and onHide values
+        _currentPanelDidShow = null;
+        _currentPanelDidHide = null;
     }
 
     /**
      * Register all of the messages needed for the panels
      */
     function registerEventMessages() {
         var iframe = getPanelFrame();
 
--- a/browser/themes/shared/downloads/downloads.inc.css
+++ b/browser/themes/shared/downloads/downloads.inc.css
@@ -27,21 +27,33 @@
 #emptyDownloads {
   padding: 10px 20px;
   /* The panel can be wider than this description after the blocked subview is
      shown, so center the text. */
   text-align: center;
 }
 
 #downloadsSummary {
-  padding: 8px 38px 8px 12px;
+  --summary-padding-end: 38px;
+  --summary-padding-start: 12px;
+  padding: 8px var(--summary-padding-end) 8px var(--summary-padding-start);
   cursor: pointer;
   -moz-user-focus: normal;
 }
 
+#downloadsSummary:-moz-locale-dir(rtl) {
+  padding-right: var(--summary-padding-start);
+  padding-left: var(--summary-padding-end);
+}
+
+#downloadsSummaryChildBox {
+  -moz-margin-start: var(--summary-padding-start);
+  -moz-margin-end: var(--summary-padding-end);
+}
+
 #downloadsSummary > .downloadTypeIcon {
   list-style-image: url("chrome://browser/skin/downloads/download-summary.png");
 }
 
 %ifdef XP_MACOSX
 @media (min-resolution: 2dppx) {
   #downloadsSummary > .downloadTypeIcon {
     list-style-image: url("chrome://browser/skin/downloads/download-summary@2x.png");
@@ -82,17 +94,17 @@ richlistitem[type="download"] {
 richlistitem[type="download"]:first-child {
   border-top: 1px solid transparent;
 }
 
 richlistitem[type="download"]:last-child {
   border-bottom: 1px solid transparent;
 }
 
-.downloadStackIcon {
+.downloadTypeIcon {
   --inline-offset: 8px;
   --block-offset: 4px;
   --icon-size: 32px;
 }
 
 .downloadTypeIcon {
   margin-inline-end: 8px;
   /* Prevent flickering when changing states. */
--- a/caps/moz.build
+++ b/caps/moz.build
@@ -1,16 +1,17 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 MOCHITEST_MANIFESTS += ['tests/mochitest/mochitest.ini']
 MOCHITEST_CHROME_MANIFESTS += ['tests/mochitest/chrome.ini']
+BROWSER_CHROME_MANIFESTS += ['tests/mochitest/browser.ini']
 XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
 
 # Hack to make this file available as a resource:// URI.
 TESTING_JS_MODULES += [
     'tests/mochitest/resource_test_file.html',
 ]
 
 XPIDL_SOURCES += [
new file mode 100644
--- /dev/null
+++ b/caps/tests/mochitest/browser.ini
@@ -0,0 +1,1 @@
+[browser_checkloaduri.js]
new file mode 100644
--- /dev/null
+++ b/caps/tests/mochitest/browser_checkloaduri.js
@@ -0,0 +1,121 @@
+"use strict";
+
+let ssm = Services.scriptSecurityManager;
+
+const URLs = new Map([
+  ["http://www.example.com", [
+  // For each of these entries, the booleans represent whether the parent URI can:
+  // - load them
+  // - load them without principal inheritance
+  // - whether the URI can be created at all (some protocol handlers will
+  //   refuse to create certain variants)
+    ["http://www.example2.com", true, true, true],
+    ["feed:http://www.example2.com", false, false, true],
+    ["https://www.example2.com", true, true, true],
+    ["chrome://foo/content/bar.xul", false, false, true],
+    ["feed:chrome://foo/content/bar.xul", false, false, false],
+    ["view-source:http://www.example2.com", false, false, true],
+    ["view-source:feed:http://www.example2.com", false, false, true],
+    ["feed:view-source:http://www.example2.com", false, false, false],
+    ["data:text/html,Hi", true, false, true],
+    ["javascript:alert('hi')", true, false, true],
+  ]],
+  ["feed:http://www.example.com", [
+    ["http://www.example2.com", true, true, true],
+    ["feed:http://www.example2.com", true, true, true],
+    ["https://www.example2.com", true, true, true],
+    ["feed:https://www.example2.com", false, false, true],
+    ["chrome://foo/content/bar.xul", false, false, true],
+    ["feed:chrome://foo/content/bar.xul", false, false, false],
+    ["view-source:http://www.example2.com", false, false, true],
+    ["view-source:feed:http://www.example2.com", false, false, true],
+    ["feed:view-source:http://www.example2.com", false, false, false],
+    ["data:text/html,Hi", true, false, true],
+    ["javascript:alert('hi')", true, false, true],
+  ]],
+  ["view-source:http://www.example.com", [
+    ["http://www.example2.com", true, true, true],
+    ["feed:http://www.example2.com", false, false, true],
+    ["https://www.example2.com", true, true, true],
+    ["feed:https://www.example2.com", false, false, true],
+    ["chrome://foo/content/bar.xul", false, false, true],
+    ["feed:chrome://foo/content/bar.xul", false, false, false],
+    ["view-source:http://www.example2.com", true, true, true],
+    ["view-source:feed:http://www.example2.com", false, false, true],
+    ["feed:view-source:http://www.example2.com", false, false, false],
+    ["data:text/html,Hi", true, false, true],
+    ["javascript:alert('hi')", true, false, true],
+  ]],
+]);
+
+function testURL(source, target, canLoad, canLoadWithoutInherit, canCreate, flags) {
+  let threw = false;
+  let targetURI;
+  try {
+    targetURI = makeURI(target);
+  } catch (ex) {
+    ok(!canCreate, "Shouldn't be passing URIs that we can't create. Failed to create: " + target);
+    return;
+  }
+  ok(canCreate, "Created a URI for " + target + " which should " +
+     (canCreate ? "" : "not ") + "be possible.");
+  try {
+    ssm.checkLoadURIWithPrincipal(source, targetURI, flags);
+  } catch (ex) {
+    info(ex.message);
+    threw = true;
+  }
+  let inheritDisallowed = flags & ssm.DISALLOW_INHERIT_PRINCIPAL;
+  let shouldThrow = inheritDisallowed ? !canLoadWithoutInherit : !canLoad;
+  ok(threw == shouldThrow,
+     "Should " + (shouldThrow ? "" : "not ") + "throw an error when loading " +
+     target + " from " + source.URI.spec +
+     (inheritDisallowed ? " without" : " with") + " principal inheritance.");
+}
+
+add_task(function* () {
+  let baseFlags = ssm.STANDARD | ssm.DONT_REPORT_ERRORS;
+  for (let [sourceString, targetsAndExpectations] of URLs) {
+    let source = ssm.createCodebasePrincipal(makeURI(sourceString), {});
+    for (let [target, canLoad, canLoadWithoutInherit, canCreate] of targetsAndExpectations) {
+      testURL(source, target, canLoad, canLoadWithoutInherit, canCreate, baseFlags);
+      testURL(source, target, canLoad, canLoadWithoutInherit, canCreate,
+              baseFlags | ssm.DISALLOW_INHERIT_PRINCIPAL);
+    }
+  }
+
+  // Now test blob URIs, which we need to do in-content.
+  yield BrowserTestUtils.withNewTab("http://www.example.com/", function* (browser) {
+    yield ContentTask.spawn(
+      browser,
+      testURL.toString(),
+      function* (testURLFn) {
+        let testURL = eval("(" + testURLFn + ")");
+        let ssm = Services.scriptSecurityManager;
+        let baseFlags = ssm.STANDARD | ssm.DONT_REPORT_ERRORS;
+        let makeURI = Cu.import("resource://gre/modules/BrowserUtils.jsm", {}).BrowserUtils.makeURI;
+        let b = new content.Blob(["I am a blob"]);
+        let contentBlobURI = content.URL.createObjectURL(b);
+        let contentPrincipal = content.document.nodePrincipal;
+        // Loading this blob URI from the content page should work:
+        testURL(contentPrincipal, contentBlobURI, true, true, true, baseFlags);
+        testURL(contentPrincipal, contentBlobURI, true, true, true,
+                baseFlags | ssm.DISALLOW_INHERIT_PRINCIPAL);
+
+        testURL(contentPrincipal, "view-source:" + contentBlobURI, false, false, true,
+                baseFlags);
+        testURL(contentPrincipal, "view-source:" + contentBlobURI, false, false, true,
+                baseFlags | ssm.DISALLOW_INHERIT_PRINCIPAL);
+
+        // Feed URIs for blobs can't be created, so need to pass false as the fourth param.
+        for (let prefix of ["feed:", "view-source:feed:", "feed:view-source:"]) {
+          testURL(contentPrincipal, prefix + contentBlobURI, false, false, false,
+                  baseFlags);
+          testURL(contentPrincipal, prefix + contentBlobURI, false, false, false,
+                  baseFlags | ssm.DISALLOW_INHERIT_PRINCIPAL);
+        }
+      }
+    );
+
+  });
+});
--- a/devtools/client/shared/widgets/HTMLTooltip.js
+++ b/devtools/client/shared/widgets/HTMLTooltip.js
@@ -565,17 +565,17 @@ HTMLTooltip.prototype = {
     panel.setAttribute("animate", false);
     panel.setAttribute("consumeoutsideclicks", false);
     panel.setAttribute("noautofocus", true);
     panel.setAttribute("ignorekeys", true);
 
     // Use type="arrow" to prevent side effects (see Bug 1285206)
     panel.setAttribute("type", "arrow");
 
-    panel.setAttribute("level", "float");
+    panel.setAttribute("level", "top");
     panel.setAttribute("class", "tooltip-xul-wrapper");
 
     return panel;
   },
 
   _showXulWrapperAt: function (left, top) {
     let onPanelShown = listenOnce(this.xulPanelWrapper, "popupshown");
     this.xulPanelWrapper.openPopupAtScreen(left, top, false);
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -1026,17 +1026,16 @@ void HTMLMediaElement::NoSupportedMediaS
   NS_ASSERTION(mNetworkState == NETWORK_LOADING,
                "Not loading during source selection?");
 
   mError = new MediaError(this, nsIDOMMediaError::MEDIA_ERR_SRC_NOT_SUPPORTED);
   ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_NO_SOURCE);
   DispatchAsyncEvent(NS_LITERAL_STRING("error"));
   ChangeDelayLoadStatus(false);
   UpdateAudioChannelPlayingState();
-  OpenUnsupportedMediaWithExtenalAppIfNeeded();
 }
 
 typedef void (HTMLMediaElement::*SyncSectionFn)();
 
 // Runs a "synchronous section", a function that must run once the event loop
 // has reached a "stable state". See:
 // http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#synchronous-section
 class nsSyncSection : public nsMediaEvent
@@ -2693,20 +2692,16 @@ HTMLMediaElement::PlayInternal(bool aCal
 
   // We changed mPaused and mAutoplaying which can affect AddRemoveSelfReference
   // and our preload status.
   AddRemoveSelfReference();
   UpdatePreloadAction();
   UpdateSrcMediaStreamPlaying();
   UpdateAudioChannelPlayingState();
 
-  // The check here is to handle the case that the media element starts playing
-  // after it loaded fail. eg. preload the data before playing.
-  OpenUnsupportedMediaWithExtenalAppIfNeeded();
-
   return NS_OK;
 }
 
 NS_IMETHODIMP HTMLMediaElement::Play()
 {
   return PlayInternal(/* aCallerIsChrome = */ true);
 }
 
@@ -3197,52 +3192,54 @@ nsresult HTMLMediaElement::InitializeDec
 }
 
 nsresult HTMLMediaElement::InitializeDecoderForChannel(nsIChannel* aChannel,
                                                        nsIStreamListener** aListener)
 {
   NS_ASSERTION(mLoadingSrc, "mLoadingSrc must already be set");
   NS_ASSERTION(mDecoder == nullptr, "Shouldn't have a decoder");
 
-  aChannel->GetContentType(mMimeType);
-  NS_ASSERTION(!mMimeType.IsEmpty(), "We should have the Content-Type.");
+  nsAutoCString mimeType;
+
+  aChannel->GetContentType(mimeType);
+  NS_ASSERTION(!mimeType.IsEmpty(), "We should have the Content-Type.");
 
   DecoderDoctorDiagnostics diagnostics;
   RefPtr<MediaDecoder> decoder =
-    DecoderTraits::CreateDecoder(mMimeType, this, &diagnostics);
+    DecoderTraits::CreateDecoder(mimeType, this, &diagnostics);
   diagnostics.StoreFormatDiagnostics(OwnerDoc(),
-                                     NS_ConvertASCIItoUTF16(mMimeType),
+                                     NS_ConvertASCIItoUTF16(mimeType),
                                      decoder != nullptr,
                                      __func__);
   if (!decoder) {
     nsAutoString src;
     GetCurrentSrc(src);
-    NS_ConvertUTF8toUTF16 mimeUTF16(mMimeType);
+    NS_ConvertUTF8toUTF16 mimeUTF16(mimeType);
     const char16_t* params[] = { mimeUTF16.get(), src.get() };
     ReportLoadError("MediaLoadUnsupportedMimeType", params, ArrayLength(params));
     return NS_ERROR_FAILURE;
   }
 
-  LOG(LogLevel::Debug, ("%p Created decoder %p for type %s", this, decoder.get(), mMimeType.get()));
+  LOG(LogLevel::Debug, ("%p Created decoder %p for type %s", this, decoder.get(), mimeType.get()));
 
   RefPtr<MediaResource> resource =
     MediaResource::Create(decoder->GetResourceCallback(), aChannel);
 
   if (!resource)
     return NS_ERROR_OUT_OF_MEMORY;
 
   if (mChannelLoader) {
     mChannelLoader->Done();
     mChannelLoader = nullptr;
   }
 
   // We postpone the |FinishDecoderSetup| function call until we get
   // |OnConnected| signal from MediaStreamController which is held by
   // RtspMediaResource.
-  if (DecoderTraits::DecoderWaitsForOnConnected(mMimeType)) {
+  if (DecoderTraits::DecoderWaitsForOnConnected(mimeType)) {
     decoder->SetResource(resource);
     SetDecoder(decoder);
     if (aListener) {
       *aListener = nullptr;
     }
     return NS_OK;
   } else {
     return FinishDecoderSetup(decoder, resource, aListener);
@@ -5930,53 +5927,16 @@ HTMLMediaElement::IsAudible() const
   // Silent audio track.
   if (!mIsAudioTrackAudible) {
     return false;
   }
 
   return true;
 }
 
-bool
-HTMLMediaElement::HaveFailedWithSourceNotSupportedError() const
-{
-  if (!mError) {
-    return false;
-  }
-
-  uint16_t errorCode;
-  mError->GetCode(&errorCode);
-  return (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_NO_SOURCE &&
-          errorCode == nsIDOMMediaError::MEDIA_ERR_SRC_NOT_SUPPORTED);
-}
-
-void
-HTMLMediaElement::OpenUnsupportedMediaWithExtenalAppIfNeeded()
-{
-  if (!Preferences::GetBool("media.openUnsupportedTypeWithExternalApp")) {
-    return;
-  }
-
-  if (!HaveFailedWithSourceNotSupportedError()) {
-    return;
-  }
-
-  // If media doesn't start playing, we don't need to open it.
-  if (mPaused) {
-    return;
-  }
-
-  LOG(LogLevel::Debug, ("Open unsupported type \'%s\' with external apps.",
-      mMimeType.get()));
-  nsContentUtils::DispatchTrustedEvent(OwnerDoc(), static_cast<nsIContent*>(this),
-                                       NS_LITERAL_STRING("OpenMediaWithExternalApp"),
-                                       true,
-                                       true);
-}
-
 static const char* VisibilityString(Visibility aVisibility) {
   switch(aVisibility) {
     case Visibility::UNTRACKED: {
       return "UNTRACKED";
     }
     case Visibility::NONVISIBLE: {
       return "NONVISIBLE";
     }
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -1188,19 +1188,16 @@ protected:
   void ResumeFromAudioChannelBlocked();
 
   bool IsSuspendedByAudioChannel() const;
   void SetAudioChannelSuspended(SuspendTypes aSuspend);
 
   bool IsAllowedToPlay();
 
   bool IsAudible() const;
-  bool HaveFailedWithSourceNotSupportedError() const;
-
-  void OpenUnsupportedMediaWithExtenalAppIfNeeded();
 
   class nsAsyncEventRunner;
   using nsGenericHTMLElement::DispatchEvent;
   // For nsAsyncEventRunner.
   nsresult DispatchEvent(const nsAString& aName);
 
   // The current decoder. Load() has been called on this decoder.
   // At most one of mDecoder and mSrcStream can be non-null.
@@ -1631,16 +1628,14 @@ private:
   // be seeked even before the media is loaded.
   double mDefaultPlaybackStartPosition;
 
   // True if the audio track is not silent.
   bool mIsAudioTrackAudible;
 
   // True if media element is audible for users.
   bool mAudible;
-
-  nsAutoCString mMimeType;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_HTMLMediaElement_h
--- a/mobile/android/app/lint.xml
+++ b/mobile/android/app/lint.xml
@@ -10,16 +10,21 @@
          `android:debuggable` when building with mach so it's
          not actually hard-coded. We can probably remove this
          warning when we switch to gradle. -->
     <issue id="HardcodedDebugMode" severity="ignore" />
 
     <!-- We have our own l10n system & don't use the platform's plurals. -->
     <issue id="PluralsCandidate" severity="ignore" />
 
+    <!-- We don't want to have to follow the SDK release schedule: we can keep
+	 the warning in order to not forget that there's a new SDK, but there's
+	 no need to break on update. -->
+    <issue id="OldTargetApi" severity="warning" />
+
     <!-- We want all lint warnings to be fatal errors.
          Right now, we set these to lint warnings so:
 
          DO NOT ADD TO THIS LIST.
 
          We did this so we can land lint in automation
          and not fail everything. -->
     <issue id="AppCompatResource" severity="warning" />
@@ -131,17 +136,16 @@
     <issue id="MissingVersion" severity="error" />
     <issue id="MockLocation" severity="error" />
     <issue id="MultipleUsesSdk" severity="error" />
     <issue id="NamespaceTypo" severity="error" />
     <issue id="NestedScrolling" severity="error" />
     <issue id="NfcTechWhitespace" severity="error" />
     <issue id="NotSibling" severity="error" />
     <issue id="ObsoleteLayoutParam" severity="error" />
-    <issue id="OldTargetApi" severity="error" />
     <issue id="OnClick" severity="error" />
     <issue id="Orientation" severity="error" />
     <issue id="Override" severity="error" />
     <issue id="OverrideAbstract" severity="error" />
     <issue id="PackagedPrivateKey" severity="error" />
     <issue id="ParcelCreator" severity="error" />
     <issue id="Performance" severity="error" />
     <issue id="Proguard" severity="error" />
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -915,10 +915,8 @@ pref("identity.sync.tokenserver.uri", "h
 
 // Enable Presentation API
 pref("dom.presentation.enabled", true);
 pref("dom.presentation.discovery.enabled", true);
 pref("dom.presentation.discovery.legacy.enabled", true); // for TV 2.5 backward capability
 
 pref("dom.audiochannel.audioCompeting", true);
 pref("dom.audiochannel.mediaControl", true);
-
-pref("media.openUnsupportedTypeWithExternalApp", true);
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoAppShell.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoAppShell.java
@@ -1654,17 +1654,17 @@ public class GeckoAppShell
             } catch (IOException ex) {
                 // Do nothing.
             }
         }
 
         ArrayList<String> directories = new ArrayList<String>();
         PackageManager pm = getApplicationContext().getPackageManager();
         List<ResolveInfo> plugins = pm.queryIntentServices(new Intent(PLUGIN_ACTION),
-                PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
+                PackageManager.GET_META_DATA);
 
         synchronized (mPackageInfoCache) {
 
             // clear the list of existing packageInfo objects
             mPackageInfoCache.clear();
 
 
             for (ResolveInfo info : plugins) {
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoInputConnection.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoInputConnection.java
@@ -11,16 +11,17 @@ import java.lang.reflect.Proxy;
 import java.util.concurrent.SynchronousQueue;
 
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.util.Clipboard;
 import org.mozilla.gecko.util.GamepadUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.ThreadUtils.AssertBehavior;
 
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.media.AudioManager;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.SystemClock;
 import android.text.Editable;
@@ -463,16 +464,20 @@ class GeckoInputConnection
         if (v == null) {
             return false;
         }
         final Configuration config = v.getContext().getResources().getConfiguration();
         return config.keyboard != Configuration.KEYBOARD_NOKEYS;
     }
 
     // Android N: @Override // InputConnection
+    // We need to suppress lint complaining about the lack override here in the meantime: it wants us to build
+    // against sdk 24, even though we're using 23, and therefore complains about the lack of override.
+    // Once we update to 24, we can use the actual override annotation and remove the lint suppression.
+    @SuppressLint("Override")
     public Handler getHandler() {
         if (isPhysicalKeyboardPresent()) {
             return ThreadUtils.getUiHandler();
         }
 
         return getBackgroundHandler();
     }
 
--- a/mobile/android/base/java/org/mozilla/gecko/home/BookmarksListView.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/BookmarksListView.java
@@ -19,16 +19,18 @@ import org.mozilla.gecko.home.HomePager.
 import android.content.Context;
 import android.database.Cursor;
 import android.util.AttributeSet;
 import android.view.KeyEvent;
 import android.view.View;
 import android.widget.AdapterView;
 import android.widget.HeaderViewListAdapter;
 import android.widget.ListAdapter;
+
+import org.mozilla.gecko.reader.SavedReaderViewHelper;
 import org.mozilla.gecko.util.NetworkUtils;
 
 /**
  * A ListView of bookmarks.
  */
 public class BookmarksListView extends HomeListView
                                implements AdapterView.OnItemClickListener {
     public static final String LOGTAG = "GeckoBookmarksListView";
@@ -164,17 +166,26 @@ public class BookmarksListView extends H
             }
 
             final String extra = getTelemetryExtraForFolder(folderId, baseFolderID);
             Telemetry.sendUIEvent(TelemetryContract.Event.SHOW, TelemetryContract.Method.LIST_ITEM, extra);
         } else {
             // Otherwise, just open the URL
             final String url = cursor.getString(cursor.getColumnIndexOrThrow(Bookmarks.URL));
 
-            Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.LIST_ITEM, "bookmarks");
+            final SavedReaderViewHelper rvh = SavedReaderViewHelper.getSavedReaderViewHelper(getContext());
+
+            final String extra;
+            if (rvh.isURLCached(url)) {
+                extra = "bookmarks-reader";
+            } else {
+                extra = "bookmarks";
+            }
+
+            Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.LIST_ITEM, extra);
             Telemetry.addToHistogram("FENNEC_LOAD_SAVED_PAGE", NetworkUtils.isConnected(getContext()) ? 2 : 3);
 
             // This item is a TwoLinePageRow, so we allow switch-to-tab.
             getOnUrlOpenListener().onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
         }
     }
 
     @Override
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -4688,17 +4688,16 @@ var BrowserEventHandler = {
       Services.obs.addObserver(this, "dom-touch-listener-added", false);
       BrowserApp.deck.addEventListener("touchstart", this, true);
     } else {
       BrowserApp.deck.addEventListener("touchend", this, true);
     }
 
     BrowserApp.deck.addEventListener("DOMUpdatePageReport", PopupBlockerObserver.onUpdatePageReport, false);
     BrowserApp.deck.addEventListener("MozMouseHittest", this, true);
-    BrowserApp.deck.addEventListener("OpenMediaWithExternalApp", this, true);
 
     InitLater(() => BrowserApp.deck.addEventListener("click", InputWidgetHelper, true));
     InitLater(() => BrowserApp.deck.addEventListener("click", SelectHelper, true));
 
     // ReaderViews support backPress listeners.
     Messaging.addListener(() => {
       return Reader.onBackPress(BrowserApp.selectedTab.id);
     }, "Browser:OnBackPressed");
@@ -4712,19 +4711,16 @@ var BrowserEventHandler = {
       case 'touchend':
         if (this._inCluster) {
           aEvent.preventDefault();
         }
         break;
       case 'MozMouseHittest':
         this._handleRetargetedTouchStart(aEvent);
         break;
-      case 'OpenMediaWithExternalApp':
-        ExternalApps.openExternal(aEvent.target);
-        break;
     }
   },
 
   _handleTouchStart: function(aEvent) {
     if (!BrowserApp.isBrowserContentDocumentDisplayed() || aEvent.touches.length > 1 || aEvent.defaultPrevented)
       return;
 
     let target = aEvent.target;
--- a/mobile/android/thirdparty/com/adjust/sdk/ActivityHandler.java
+++ b/mobile/android/thirdparty/com/adjust/sdk/ActivityHandler.java
@@ -708,21 +708,43 @@ public class ActivityHandler extends Han
         packageHandler.sendFirstPackage();
 
         if (updateActivityState(System.currentTimeMillis())) {
             writeActivityState();
         }
     }
 
     private void readActivityState() {
-        activityState = Util.readObject(adjustConfig.context, ACTIVITY_STATE_FILENAME, ACTIVITY_STATE_NAME);
+        try {
+	    /**
+             * Mozilla:
+             * readObject is a generic object, and can therefore return arbitrary generic objects
+             * that might not match the expected type. Therefore there will be an implicit cast
+	     * here, which can fail. Therefore we have to add the catch (ClassCastException)
+             * Note: this has been fixed in upstream, we only need this for the version we are still shipping.
+             */
+            activityState = Util.readObject(adjustConfig.context, ACTIVITY_STATE_FILENAME, ACTIVITY_STATE_NAME);
+        } catch (ClassCastException e) {
+            activityState = null;
+        }
     }
 
     private void readAttribution() {
-        attribution = Util.readObject(adjustConfig.context, ATTRIBUTION_FILENAME, ATTRIBUTION_NAME);
+        try {
+	    /**
+             * Mozilla: (same as in readActivityState() )
+             * readObject is a generic object, and can therefore return arbitrary generic objects
+             * that might not match the expected type. Therefore there will be an implicit cast
+	     * here, which can fail. Therefore we have to add the catch (ClassCastException)
+             * Note: this has been fixed in upstream, we only need this for the version we are still shipping.
+             */
+            attribution = Util.readObject(adjustConfig.context, ATTRIBUTION_FILENAME, ATTRIBUTION_NAME);
+        } catch (ClassCastException e) {
+            activityState = null;
+        }
     }
 
     private void writeActivityState() {
         Util.writeObject(activityState, adjustConfig.context, ACTIVITY_STATE_FILENAME, ACTIVITY_STATE_NAME);
     }
 
     private void writeAttribution() {
         Util.writeObject(attribution, adjustConfig.context, ATTRIBUTION_FILENAME, ATTRIBUTION_NAME);
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -5607,37 +5607,37 @@
     "expires_in_version": "never",
     "kind": "boolean",
     "description": "Count the number of times the user clicked 'allow' on the hidden-plugin infobar."
   },
   "POPUP_NOTIFICATION_STATS": {
     "releaseChannelCollection": "opt-out",
     "alert_emails": ["firefox-dev@mozilla.org"],
     "bug_numbers": [1207089],
-    "expires_in_version": "50",
+    "expires_in_version": "55",
     "kind": "enumerated",
     "keyed": true,
     "n_values": 40,
     "description": "(Bug 1207089) Usage of popup notifications, keyed by ID (0 = Offered, 1..4 = Action, 5 = Click outside, 6 = Leave page, 7 = Use 'X', 8 = Not now, 10 = Open submenu, 11 = Learn more. Add 20 if happened after reopen.)"
   },
   "POPUP_NOTIFICATION_MAIN_ACTION_MS": {
     "alert_emails": ["firefox-dev@mozilla.org"],
     "bug_numbers": [1207089],
-    "expires_in_version": "52",
+    "expires_in_version": "55",
     "kind": "exponential",
     "keyed": true,
     "low": 100,
     "high": 600000,
     "n_buckets": 40,
     "description": "(Bug 1207089) Time in ms between initially requesting a popup notification and triggering the main action, keyed by ID"
   },
   "POPUP_NOTIFICATION_DISMISSAL_MS": {
     "alert_emails": ["firefox-dev@mozilla.org"],
     "bug_numbers": [1207089],
-    "expires_in_version": "52",
+    "expires_in_version": "55",
     "kind": "exponential",
     "keyed": true,
     "low": 200,
     "high": 20000,
     "n_buckets": 50,
     "description": "(Bug 1207089) Time in ms between displaying a popup notification and dismissing it without an action the first time, keyed by ID"
   },
   "PRINT_PREVIEW_OPENED_COUNT": {