Browse Source

sync with latest release

Frank-Rainer Grahl 8 months ago
parent
commit
fa34bc88f2
32 changed files with 5650 additions and 1 deletions
  1. 68 0
      frg/work-js/comm-release/patches/1877001-port1407891-25319.patch
  2. 1 1
      frg/work-js/comm-release/patches/series
  3. 109 0
      frg/work-js/mozilla-release/patches/1391277-59a1.patch
  4. 41 0
      frg/work-js/mozilla-release/patches/1396798-1-57a1.patch
  5. 85 0
      frg/work-js/mozilla-release/patches/1396798-2-57a1.patch
  6. 55 0
      frg/work-js/mozilla-release/patches/1398691-57a1.patch
  7. 31 0
      frg/work-js/mozilla-release/patches/1398692-1-57a1.patch
  8. 75 0
      frg/work-js/mozilla-release/patches/1398692-2-57a1.patch
  9. 108 0
      frg/work-js/mozilla-release/patches/1403641-1-58a1.patch
  10. 80 0
      frg/work-js/mozilla-release/patches/1403641-2-58a1.patch
  11. 83 0
      frg/work-js/mozilla-release/patches/1403814-1-58a1.patch
  12. 80 0
      frg/work-js/mozilla-release/patches/1403814-2-58a1.patch
  13. 582 0
      frg/work-js/mozilla-release/patches/1403814-3-58a1.patch
  14. 32 0
      frg/work-js/mozilla-release/patches/1403870-1-58a1.patch
  15. 73 0
      frg/work-js/mozilla-release/patches/1403870-2-58a1.patch
  16. 546 0
      frg/work-js/mozilla-release/patches/1407085-59a1.patch
  17. 702 0
      frg/work-js/mozilla-release/patches/1407891-1-58a1.patch
  18. 74 0
      frg/work-js/mozilla-release/patches/1407891-2-58a1.patch
  19. 158 0
      frg/work-js/mozilla-release/patches/1408451-58a1.patch
  20. 33 0
      frg/work-js/mozilla-release/patches/1414609-58a1.patch
  21. 84 0
      frg/work-js/mozilla-release/patches/1415612-59a1.patch
  22. 330 0
      frg/work-js/mozilla-release/patches/1430558-61a1.patch
  23. 356 0
      frg/work-js/mozilla-release/patches/1431949-61a1.patch
  24. 92 0
      frg/work-js/mozilla-release/patches/1437730-1-61a1.patch
  25. 156 0
      frg/work-js/mozilla-release/patches/1437730-2-61a1.patch
  26. 168 0
      frg/work-js/mozilla-release/patches/1437730-3-61a1.patch
  27. 860 0
      frg/work-js/mozilla-release/patches/1447736-61a1.patch
  28. 30 0
      frg/work-js/mozilla-release/patches/1448320-61a1.patch
  29. 292 0
      frg/work-js/mozilla-release/patches/1451211-61a1.patch
  30. 121 0
      frg/work-js/mozilla-release/patches/1454888-61a1.patch
  31. 116 0
      frg/work-js/mozilla-release/patches/1627944-83a1.patch
  32. 29 0
      frg/work-js/mozilla-release/patches/series

+ 68 - 0
frg/work-js/comm-release/patches/1877001-port1407891-25319.patch

@@ -0,0 +1,68 @@
+# HG changeset patch
+# User Ian Neal <iann_cvs@blueyonder.co.uk>
+# Date 1706455788 0
+# Parent  f0772c45aface46dbe04f598b6692269e5ef78ba
+Bug 1877001 - Allow view-image to open a data: URI by setting a flag on the loadinfo. r=frg a=frg
+
+diff --git a/suite/base/content/nsContextMenu.js b/suite/base/content/nsContextMenu.js
+--- a/suite/base/content/nsContextMenu.js
++++ b/suite/base/content/nsContextMenu.js
+@@ -1077,16 +1077,17 @@ nsContextMenu.prototype = {
+                      });
+      });
+     } else {
+       urlSecurityCheck(this.mediaURL,
+                        this.target.nodePrincipal,
+                        Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
+       openUILinkIn(this.mediaURL, where,
+                    { referrerURI: doc.documentURIObject,
++                     forceAllowDataURI: true,
+                      triggeringPrincipal: this.target.nodePrincipal,
+                    });
+     }
+   },
+ 
+   saveVideoFrameAsImage: function () {
+     urlSecurityCheck(this.mediaURL, this.browser.contentPrincipal,
+                      Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
+diff --git a/suite/base/content/utilityOverlay.js b/suite/base/content/utilityOverlay.js
+--- a/suite/base/content/utilityOverlay.js
++++ b/suite/base/content/utilityOverlay.js
+@@ -1499,16 +1499,17 @@ function openLinkIn(url, where, params)
+   var aAllowThirdPartyFixup = params.allowThirdPartyFixup;
+   var aPostData             = params.postData;
+   var aCharset              = params.charset;
+   var aReferrerURI          = params.referrerURI;
+   var aReferrerPolicy       = ("referrerPolicy" in params ?
+         params.referrerPolicy : Ci.nsIHttpChannel.REFERRER_POLICY_UNSET);
+   var aRelatedToCurrent     = params.relatedToCurrent;
+   var aAllowMixedContent    = params.allowMixedContent;
++  var aForceAllowDataURI    = params.forceAllowDataURI;
+   var aInBackground         = params.inBackground;
+   var aAvoidBrowserFocus    = params.avoidBrowserFocus;
+   var aDisallowInheritPrincipal = params.disallowInheritPrincipal;
+   var aInitiatingDoc = params.initiatingDoc ? params.initiatingDoc : document;
+   var aIsPrivate            = params.private;
+   var aNoReferrer           = params.noReferrer;
+   var aUserContextId        = params.userContextId;
+   var aPrincipal            = params.originPrincipal;
+@@ -1628,16 +1629,19 @@ function openLinkIn(url, where, params)
+ 
+     if (aAllowThirdPartyFixup) {
+       flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+       flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
+     }
+     if (aDisallowInheritPrincipal) {
+       flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_OWNER;
+     }
++    if (aForceAllowDataURI) {
++      flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FORCE_ALLOW_DATA_URI;
++    }
+ 
+     if (aForceAboutBlankViewerInCurrent) {
+       w.gBrowser.selectedBrowser.createAboutBlankContentViewer(aPrincipal);
+     }
+ 
+     w.getBrowser().loadURIWithFlags(url, {
+       triggeringPrincipal: aTriggeringPrincipal,
+       flags,

+ 1 - 1
frg/work-js/comm-release/patches/series

@@ -2146,7 +2146,7 @@ WIP-9999999-lintglobals.patch
 TOP-1378089-4-bookmarks-wip-25319.patch
 TOP-1378089-4-bookmarks-wip-25319.patch
 TOP-1872623-cancelbookmark-25319.patch
 TOP-1872623-cancelbookmark-25319.patch
 1466297-62a1.patch
 1466297-62a1.patch
-
+1877001-port1407891-25319.patch
 
 
 
 
 
 

+ 109 - 0
frg/work-js/mozilla-release/patches/1391277-59a1.patch

@@ -0,0 +1,109 @@
+# HG changeset patch
+# User Honza Bambas <honzab.moz@firemni.cz>
+# Date 1515661020 -7200
+# Node ID 8e08a2cf1b2d3e509d971d52e65377fd6290e3f5
+# Parent  fe90ecd695b7d45b1595f1fe829c3e9ce8e05d54
+Bug 1391277 - Investigative logging in CSP: log when 'upgrade-insecure-requests' CSP is added to the CSP context, r=bz
+
+diff --git a/dom/html/HTMLMediaElement.cpp b/dom/html/HTMLMediaElement.cpp
+--- a/dom/html/HTMLMediaElement.cpp
++++ b/dom/html/HTMLMediaElement.cpp
+@@ -7933,8 +7933,11 @@ HTMLMediaElement::ReportCanPlayTelemetry
+             thread->AsyncShutdown();
+           }));
+       }),
+     NS_DISPATCH_NORMAL);
+ }
+ 
+ } // namespace dom
+ } // namespace mozilla
++
++#undef LOG
++#undef LOG_EVENT
+diff --git a/dom/html/HTMLMetaElement.cpp b/dom/html/HTMLMetaElement.cpp
+--- a/dom/html/HTMLMetaElement.cpp
++++ b/dom/html/HTMLMetaElement.cpp
+@@ -3,20 +3,25 @@
+ /* 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 "mozilla/AsyncEventDispatcher.h"
+ #include "mozilla/dom/HTMLMetaElement.h"
+ #include "mozilla/dom/HTMLMetaElementBinding.h"
+ #include "mozilla/dom/nsCSPService.h"
++#include "mozilla/Logging.h"
+ #include "nsContentUtils.h"
+ #include "nsStyleConsts.h"
+ #include "nsIContentSecurityPolicy.h"
+ 
++static mozilla::LazyLogModule gMetaElementLog("nsMetaElement");
++#define LOG(msg) MOZ_LOG(gMetaElementLog, mozilla::LogLevel::Debug, msg)
++#define LOG_ENABLED() MOZ_LOG_TEST(gMetaElementLog, mozilla::LogLevel::Debug)
++
+ NS_IMPL_NS_NEW_HTML_ELEMENT(Meta)
+ 
+ namespace mozilla {
+ namespace dom {
+ 
+ HTMLMetaElement::HTMLMetaElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+   : nsGenericHTMLElement(aNodeInfo)
+ {
+@@ -112,16 +117,27 @@ HTMLMetaElement::BindToTree(nsIDocument*
+       NS_ENSURE_SUCCESS(rv, rv);
+       content = nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(content);
+ 
+       nsIPrincipal* principal = aDocument->NodePrincipal();
+       nsCOMPtr<nsIContentSecurityPolicy> csp;
+       nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(aDocument);
+       principal->EnsureCSP(domDoc, getter_AddRefs(csp));
+       if (csp) {
++        if (LOG_ENABLED()) {
++          nsAutoCString documentURIspec;
++          nsIURI* documentURI = aDocument->GetDocumentURI();
++          if (documentURI) {
++            documentURI->GetAsciiSpec(documentURIspec);
++          }
++
++          LOG(("HTMLMetaElement %p sets CSP '%s' on document=%p, document-uri=%s",
++               this, NS_ConvertUTF16toUTF8(content).get(), aDocument, documentURIspec.get()));
++        }
++
+         // Multiple CSPs (delivered through either header of meta tag) need to be
+         // joined together, see:
+         // https://w3c.github.io/webappsec/specs/content-security-policy/#delivery-html-meta-element
+         rv = csp->AppendPolicy(content,
+                                false, // csp via meta tag can not be report only
+                                true); // delivered through the meta tag
+         NS_ENSURE_SUCCESS(rv, rv);
+         aDocument->ApplySettingsFromCSP(false);
+diff --git a/dom/security/nsCSPContext.cpp b/dom/security/nsCSPContext.cpp
+--- a/dom/security/nsCSPContext.cpp
++++ b/dom/security/nsCSPContext.cpp
+@@ -418,16 +418,26 @@ nsCSPContext::AppendPolicy(const nsAStri
+                  NS_ConvertUTF16toUTF8(aPolicyString).get()));
+ 
+   // Use the mSelfURI from setRequestContext, see bug 991474
+   NS_ASSERTION(mSelfURI, "mSelfURI required for AppendPolicy, but not set");
+   nsCSPPolicy* policy = nsCSPParser::parseContentSecurityPolicy(aPolicyString, mSelfURI,
+                                                                 aReportOnly, this,
+                                                                 aDeliveredViaMetaTag);
+   if (policy) {
++    if (policy->hasDirective(nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE)) {
++      nsAutoCString selfURIspec, referrer;
++      if (mSelfURI) {
++        mSelfURI->GetAsciiSpec(selfURIspec);
++      }
++      referrer = NS_ConvertUTF16toUTF8(mReferrer);
++      CSPCONTEXTLOG(("nsCSPContext::AppendPolicy added UPGRADE_IF_INSECURE_DIRECTIVE self-uri=%s referrer=%s",
++                     selfURIspec.get(), referrer.get()));
++    }
++
+     mPolicies.AppendElement(policy);
+     // reset cache since effective policy changes
+     mShouldLoadCache.Clear();
+   }
+   return NS_OK;
+ }
+ 
+ NS_IMETHODIMP

+ 41 - 0
frg/work-js/mozilla-release/patches/1396798-1-57a1.patch

@@ -0,0 +1,41 @@
+# HG changeset patch
+# User Christoph Kerschbaumer <ckerschb@christophkerschbaumer.com>
+# Date 1504708025 -7200
+# Node ID ff412c116b9baabf094ab4e97065d5d7dedf921f
+# Parent  db315d8db677a16abae55dd07190b82b09c19cdd
+Bug 1396798: Do not block toplevel data: navigation to image (except svgs). r=smaug
+
+diff --git a/dom/security/nsContentSecurityManager.cpp b/dom/security/nsContentSecurityManager.cpp
+--- a/dom/security/nsContentSecurityManager.cpp
++++ b/dom/security/nsContentSecurityManager.cpp
+@@ -45,23 +45,27 @@ nsContentSecurityManager::AllowTopLevelN
+   if (aContentPolicyType != nsIContentPolicy::TYPE_DOCUMENT) {
+     return true;
+   }
+   bool isDataURI =
+     (NS_SUCCEEDED(aURI->SchemeIs("data", &isDataURI)) && isDataURI);
+   if (!isDataURI) {
+     return true;
+   }
++  // Whitelist data: images as long as they are not SVGs
++  nsAutoCString filePath;
++  aURI->GetFilePath(filePath);
++  if (StringBeginsWith(filePath, NS_LITERAL_CSTRING("image/")) &&
++      !StringBeginsWith(filePath, NS_LITERAL_CSTRING("image/svg+xml"))) {
++    return true;
++  }
+   if (!aLoadFromExternal &&
+       nsContentUtils::IsSystemPrincipal(aTriggeringPrincipal)) {
+     return true;
+   }
+-
+-  nsAutoCString spec;
+-  aURI->GetSpec(spec);
+   NS_ConvertUTF8toUTF16 specUTF16(aURI->GetSpecOrDefault());
+   if (specUTF16.Length() > 50) {
+     specUTF16.Truncate(50);
+     specUTF16.AppendLiteral("...");
+   }
+   const char16_t* params[] = { specUTF16.get() };
+   nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+                                   NS_LITERAL_CSTRING("DATA_URI_BLOCKED"),

+ 85 - 0
frg/work-js/mozilla-release/patches/1396798-2-57a1.patch

@@ -0,0 +1,85 @@
+# HG changeset patch
+# User Christoph Kerschbaumer <ckerschb@christophkerschbaumer.com>
+# Date 1504707378 -7200
+# Node ID 79bf8a92a0ea07b5a6a3c167a5586afc6dab30b4
+# Parent  d6deaf1c4986ca61664a140833b393b85e889276
+Bug 1396798: Test toplevel data: URI navigation to images. r=smaug
+
+diff --git a/dom/security/test/general/mochitest.ini b/dom/security/test/general/mochitest.ini
+--- a/dom/security/test/general/mochitest.ini
++++ b/dom/security/test/general/mochitest.ini
+@@ -21,16 +21,18 @@ support-files =
+   file_same_site_cookies_about_inclusion.html
+   file_same_site_cookies_about.sjs
+ 
+ [test_contentpolicytype_targeted_link_iframe.html]
+ [test_nosniff.html]
+ [test_block_script_wrong_mime.html]
+ [test_block_toplevel_data_navigation.html]
+ skip-if = toolkit == 'android' # intermittent failure
++[test_block_toplevel_data_img_navigation.html]
++skip-if = toolkit == 'android' # intermittent failure
+ [test_same_site_cookies_subrequest.html]
+ [test_same_site_cookies_toplevel_nav.html]
+ [test_same_site_cookies_cross_origin_context.html]
+ [test_same_site_cookies_from_script.html]
+ [test_same_site_cookies_redirect.html]
+ [test_same_site_cookies_toplevel_set_cookie.html]
+ [test_same_site_cookies_iframe.html]
+ [test_same_site_cookies_about.html]
+diff --git a/dom/security/test/general/test_block_toplevel_data_img_navigation.html b/dom/security/test/general/test_block_toplevel_data_img_navigation.html
+new file mode 100644
+--- /dev/null
++++ b/dom/security/test/general/test_block_toplevel_data_img_navigation.html
+@@ -0,0 +1,51 @@
++<!DOCTYPE HTML>
++<html>
++<head>
++  <meta charset="utf-8">
++  <title>Bug 1396798: Do not block toplevel data: navigation to image (except svgs)</title>
++  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
++  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
++</head>
++<body>
++<script class="testbody" type="text/javascript">
++SpecialPowers.setBoolPref("security.data_uri.block_toplevel_data_uri_navigations", true);
++SimpleTest.registerCleanupFunction(() => {
++  SpecialPowers.clearUserPref("security.data_uri.block_toplevel_data_uri_navigations");
++});
++
++SimpleTest.waitForExplicitFinish();
++SimpleTest.requestFlakyTimeout("have to test that top level data:image loading is blocked/allowed");
++
++function test_toplevel_data_image() {
++  const DATA_PNG =
++    "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
++  let win1 = window.open(DATA_PNG);
++  let wrappedWin1 = SpecialPowers.wrap(win1);
++  setTimeout(function () {
++    let images = wrappedWin1.document.getElementsByTagName('img'); 
++    is(images.length, 1, "Loading data:image/png should be allowed");
++    is(images[0].src, DATA_PNG, "Sanity: img src matches");
++    wrappedWin1.close();
++    test_toplevel_data_image_svg();
++  }, 1000);
++}
++
++function test_toplevel_data_image_svg() {
++  const DATA_SVG =
++    "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDE2IDE2Ij4KICA8cGF0aCBkPSJNOCwxMkwzLDcsNCw2bDQsNCw0LTQsMSwxWiIgZmlsbD0iIzZBNkE2QSIgLz4KPC9zdmc+Cg==";
++  let win2 = window.open(DATA_SVG);
++  let wrappedWin2 = SpecialPowers.wrap(win2);
++  setTimeout(function () {
++    isnot(wrappedWin2.document.documentElement.localName, "svg",
++          "Loading data:image/svg+xml should be blocked");
++    wrappedWin2.close();
++    SimpleTest.finish();
++  }, 1000);
++}
++
++// fire up the tests
++test_toplevel_data_image();
++
++</script>
++</body>
++</html>

+ 55 - 0
frg/work-js/mozilla-release/patches/1398691-57a1.patch

@@ -0,0 +1,55 @@
+# HG changeset patch
+# User Christoph Kerschbaumer <ckerschb@christophkerschbaumer.com>
+# Date 1505192798 -7200
+# Node ID ba39904a38ebdde78a04b792172254c3ddb1dd1b
+# Parent  af3cf9a9ef1972787c9169a86f3088205c7a55fb
+Bug 1398691 - Unescape data: URI for console message when blocking toplevel data: URI navigations. r=smaug
+
+diff --git a/dom/security/nsContentSecurityManager.cpp b/dom/security/nsContentSecurityManager.cpp
+--- a/dom/security/nsContentSecurityManager.cpp
++++ b/dom/security/nsContentSecurityManager.cpp
+@@ -1,15 +1,16 @@
+ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+ /* 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/. */
+ 
+ #include "nsContentSecurityManager.h"
++#include "nsEscape.h"
+ #include "nsIChannel.h"
+ #include "nsIHttpChannelInternal.h"
+ #include "nsIStreamListener.h"
+ #include "nsILoadInfo.h"
+ #include "nsIOService.h"
+ #include "nsContentUtils.h"
+ #include "nsCORSListenerProxy.h"
+ #include "nsIStreamListener.h"
+@@ -56,21 +57,23 @@ nsContentSecurityManager::AllowTopLevelN
+   if (StringBeginsWith(filePath, NS_LITERAL_CSTRING("image/")) &&
+       !StringBeginsWith(filePath, NS_LITERAL_CSTRING("image/svg+xml"))) {
+     return true;
+   }
+   if (!aLoadFromExternal &&
+       nsContentUtils::IsSystemPrincipal(aTriggeringPrincipal)) {
+     return true;
+   }
+-  NS_ConvertUTF8toUTF16 specUTF16(aURI->GetSpecOrDefault());
+-  if (specUTF16.Length() > 50) {
+-    specUTF16.Truncate(50);
+-    specUTF16.AppendLiteral("...");
++  nsAutoCString dataSpec;
++  aURI->GetSpec(dataSpec);
++  if (dataSpec.Length() > 50) {
++    dataSpec.Truncate(50);
++    dataSpec.AppendLiteral("...");
+   }
++  NS_ConvertUTF8toUTF16 specUTF16(NS_UnescapeURL(dataSpec));
+   const char16_t* params[] = { specUTF16.get() };
+   nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+                                   NS_LITERAL_CSTRING("DATA_URI_BLOCKED"),
+                                   // no doc available, log to browser console
+                                   nullptr,
+                                   nsContentUtils::eSECURITY_PROPERTIES,
+                                   "BlockTopLevelDataURINavigation",
+                                   params, ArrayLength(params));

+ 31 - 0
frg/work-js/mozilla-release/patches/1398692-1-57a1.patch

@@ -0,0 +1,31 @@
+# HG changeset patch
+# User Christoph Kerschbaumer <ckerschb@christophkerschbaumer.com>
+# Date 1505367281 -7200
+# Node ID 0b67372c4de0f5087149897d4829bc5d5c86fc26
+# Parent  e854835edcfbe670ad52f0af02f5dead48b11f1e
+Bug 1398692: Allow toplevel navigation to a data:application/pdf. r=bz
+
+diff --git a/dom/security/nsContentSecurityManager.cpp b/dom/security/nsContentSecurityManager.cpp
+--- a/dom/security/nsContentSecurityManager.cpp
++++ b/dom/security/nsContentSecurityManager.cpp
+@@ -53,16 +53,20 @@ nsContentSecurityManager::AllowTopLevelN
+   }
+   // Whitelist data: images as long as they are not SVGs
+   nsAutoCString filePath;
+   aURI->GetFilePath(filePath);
+   if (StringBeginsWith(filePath, NS_LITERAL_CSTRING("image/")) &&
+       !StringBeginsWith(filePath, NS_LITERAL_CSTRING("image/svg+xml"))) {
+     return true;
+   }
++  // Whitelist data: PDFs
++  if (StringBeginsWith(filePath, NS_LITERAL_CSTRING("application/pdf"))) {
++    return true;
++  }
+   if (!aLoadFromExternal &&
+       nsContentUtils::IsSystemPrincipal(aTriggeringPrincipal)) {
+     return true;
+   }
+   nsAutoCString dataSpec;
+   aURI->GetSpec(dataSpec);
+   if (dataSpec.Length() > 50) {
+     dataSpec.Truncate(50);

+ 75 - 0
frg/work-js/mozilla-release/patches/1398692-2-57a1.patch

@@ -0,0 +1,75 @@
+# HG changeset patch
+# User Christoph Kerschbaumer <ckerschb@christophkerschbaumer.com>
+# Date 1505367427 -7200
+# Node ID 54e49813d0c511e326826c1add34f53f8f72061d
+# Parent  21755ae660f44b75ffaac32d72ef07e496688c5a
+Bug 1398692: Test toplevel navigation to a data:application/pdf. r=bz
+
+diff --git a/dom/security/test/general/mochitest.ini b/dom/security/test/general/mochitest.ini
+--- a/dom/security/test/general/mochitest.ini
++++ b/dom/security/test/general/mochitest.ini
+@@ -23,16 +23,18 @@ support-files =
+ 
+ [test_contentpolicytype_targeted_link_iframe.html]
+ [test_nosniff.html]
+ [test_block_script_wrong_mime.html]
+ [test_block_toplevel_data_navigation.html]
+ skip-if = toolkit == 'android' # intermittent failure
+ [test_block_toplevel_data_img_navigation.html]
+ skip-if = toolkit == 'android' # intermittent failure
++[test_allow_opening_data_pdf.html]
++skip-if = toolkit == 'android'
+ [test_same_site_cookies_subrequest.html]
+ [test_same_site_cookies_toplevel_nav.html]
+ [test_same_site_cookies_cross_origin_context.html]
+ [test_same_site_cookies_from_script.html]
+ [test_same_site_cookies_redirect.html]
+ [test_same_site_cookies_toplevel_set_cookie.html]
+ [test_same_site_cookies_iframe.html]
+ [test_same_site_cookies_about.html]
+diff --git a/dom/security/test/general/test_allow_opening_data_pdf.html b/dom/security/test/general/test_allow_opening_data_pdf.html
+new file mode 100644
+--- /dev/null
++++ b/dom/security/test/general/test_allow_opening_data_pdf.html
+@@ -0,0 +1,41 @@
++<!DOCTYPE HTML>
++<html>
++<head>
++  <meta charset="utf-8">
++  <title>Bug 1398692: Allow toplevel navigation to a data:application/pdf</title>
++  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
++  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
++</head>
++<body>
++<script class="testbody" type="text/javascript">
++
++SimpleTest.waitForExplicitFinish();
++
++function test_toplevel_data_pdf() {
++  // The PDF contains one page and it is a 3/72" square, the minimum allowed by the spec
++  const DATA_PDF =
++    "data:application/pdf;base64,JVBERi0xLjANCjEgMCBvYmo8PC9UeXBlL0NhdGFsb2cvUGFnZXMgMiAwIFI+PmVuZG9iaiAyIDAgb2JqPDwvVHlwZS9QYWdlcy9LaWRzWzMgMCBSXS9Db3VudCAxPj5lbmRvYmogMyAwIG9iajw8L1R5cGUvUGFnZS9NZWRpYUJveFswIDAgMyAzXT4+ZW5kb2JqDQp4cmVmDQowIDQNCjAwMDAwMDAwMDAgNjU1MzUgZg0KMDAwMDAwMDAxMCAwMDAwMCBuDQowMDAwMDAwMDUzIDAwMDAwIG4NCjAwMDAwMDAxMDIgMDAwMDAgbg0KdHJhaWxlcjw8L1NpemUgNC9Sb290IDEgMCBSPj4NCnN0YXJ0eHJlZg0KMTQ5DQolRU9G";
++
++  let win = window.open(DATA_PDF);
++  let wrappedWin = SpecialPowers.wrap(win);
++
++  // Unfortunately we can't detect whether the PDF has loaded or not using some
++  // event, hence we are constantly polling location.href till we see that
++  // the data: URI appears. Test times out on failure.
++  var pdfLoaded = setInterval(function() {
++    if (wrappedWin.document.location.href.startsWith("data:application/pdf")) {
++      clearInterval(pdfLoaded);
++      ok(true, "navigating to data:application/pdf allowed");
++      wrappedWin.close();
++      SimpleTest.finish();
++    }
++  }, 200);
++}
++
++SpecialPowers.pushPrefEnv({
++  set: [["security.data_uri.block_toplevel_data_uri_navigations", true]]
++}, test_toplevel_data_pdf);
++
++</script>
++</body>
++</html>

+ 108 - 0
frg/work-js/mozilla-release/patches/1403641-1-58a1.patch

@@ -0,0 +1,108 @@
+# HG changeset patch
+# User Christoph Kerschbaumer <ckerschb@christophkerschbaumer.com>
+# Date 1507099436 -7200
+# Node ID dcdbe0c04f23aa33586976911340a9d8ce750529
+# Parent  20b5ce45e8d124971d34678b22468113c7b68429
+Bug 1403641: Allow data: URI downloads even if data: URI navigations are blocked. r=bz
+
+diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp
+--- a/docshell/base/nsDocShell.cpp
++++ b/docshell/base/nsDocShell.cpp
+@@ -9830,17 +9830,18 @@ nsDocShell::InternalLoad(nsIURI* aURI,
+     contentType = nsIContentPolicy::TYPE_DOCUMENT;
+     isTargetTopLevelDocShell = true;
+   }
+ 
+   if (!nsContentSecurityManager::AllowTopLevelNavigationToDataURI(
+         aURI,
+         contentType,
+         aTriggeringPrincipal,
+-        (aLoadType == LOAD_NORMAL_EXTERNAL))) {
++        (aLoadType == LOAD_NORMAL_EXTERNAL),
++        !aFileName.IsVoid())) {
+     // logging to console happens within AllowTopLevelNavigationToDataURI
+     return NS_OK;
+   }
+ 
+   // If there's no targetDocShell, that means we are about to create a new
+   // window (or aWindowTarget is empty). Perform a content policy check before
+   // creating the window. Please note for all other docshell loads
+   // content policy checks are performed within the contentSecurityManager
+diff --git a/dom/security/nsContentSecurityManager.cpp b/dom/security/nsContentSecurityManager.cpp
+--- a/dom/security/nsContentSecurityManager.cpp
++++ b/dom/security/nsContentSecurityManager.cpp
+@@ -25,30 +25,31 @@ NS_IMPL_ISUPPORTS(nsContentSecurityManag
+                   nsIContentSecurityManager,
+                   nsIChannelEventSink)
+ 
+ /* static */ bool
+ nsContentSecurityManager::AllowTopLevelNavigationToDataURI(
+   nsIURI* aURI,
+   nsContentPolicyType aContentPolicyType,
+   nsIPrincipal* aTriggeringPrincipal,
+-  bool aLoadFromExternal)
++  bool aLoadFromExternal,
++  bool aIsDownLoad)
+ {
+   // Let's block all toplevel document navigations to a data: URI.
+   // In all cases where the toplevel document is navigated to a
+   // data: URI the triggeringPrincipal is a codeBasePrincipal, or
+   // a NullPrincipal. In other cases, e.g. typing a data: URL into
+   // the URL-Bar, the triggeringPrincipal is a SystemPrincipal;
+   // we don't want to block those loads. Only exception, loads coming
+   // from an external applicaton (e.g. Thunderbird) don't load
+   // using a codeBasePrincipal, but we want to block those loads.
+   if (!mozilla::net::nsIOService::BlockToplevelDataUriNavigations()) {
+     return true;
+   }
+-  if (aContentPolicyType != nsIContentPolicy::TYPE_DOCUMENT) {
++  if (aContentPolicyType != nsIContentPolicy::TYPE_DOCUMENT || aIsDownLoad) {
+     return true;
+   }
+   bool isDataURI =
+     (NS_SUCCEEDED(aURI->SchemeIs("data", &isDataURI)) && isDataURI);
+   if (!isDataURI) {
+     return true;
+   }
+   // Whitelist data: images as long as they are not SVGs
+@@ -575,16 +576,17 @@ nsContentSecurityManager::AsyncOnChannel
+     nsCOMPtr<nsIURI> uri;
+     nsresult rv = NS_GetFinalChannelURI(aNewChannel, getter_AddRefs(uri));
+     NS_ENSURE_SUCCESS(rv, rv);
+     nsCOMPtr<nsIPrincipal> nullTriggeringPrincipal = NullPrincipal::Create();
+     if (!nsContentSecurityManager::AllowTopLevelNavigationToDataURI(
+           uri,
+           newLoadInfo->GetExternalContentPolicyType(),
+           nullTriggeringPrincipal,
++          false,
+           false)) {
+         // logging to console happens within AllowTopLevelNavigationToDataURI
+       aOldChannel->Cancel(NS_ERROR_DOM_BAD_URI);
+       return NS_ERROR_DOM_BAD_URI;
+     }
+   }
+ 
+   // Also verify that the redirecting server is allowed to redirect to the
+diff --git a/dom/security/nsContentSecurityManager.h b/dom/security/nsContentSecurityManager.h
+--- a/dom/security/nsContentSecurityManager.h
++++ b/dom/security/nsContentSecurityManager.h
+@@ -30,17 +30,18 @@ public:
+   nsContentSecurityManager() {}
+ 
+   static nsresult doContentSecurityCheck(nsIChannel* aChannel,
+                                          nsCOMPtr<nsIStreamListener>& aInAndOutListener);
+ 
+   static bool AllowTopLevelNavigationToDataURI(nsIURI* aURI,
+                                                nsContentPolicyType aContentPolicyType,
+                                                nsIPrincipal* aTriggeringPrincipal,
+-                                               bool aLoadFromExternal);
++                                               bool aLoadFromExternal,
++                                               bool aIsDownload);
+ 
+ private:
+   static nsresult CheckChannel(nsIChannel* aChannel);
+ 
+   virtual ~nsContentSecurityManager() {}
+ 
+ };
+ 

+ 80 - 0
frg/work-js/mozilla-release/patches/1403641-2-58a1.patch

@@ -0,0 +1,80 @@
+# HG changeset patch
+# User Christoph Kerschbaumer <ckerschb@christophkerschbaumer.com>
+# Date 1507099476 -7200
+# Node ID 8146509b7d8098eea54d2ae41b5e57200e8ec4e2
+# Parent  1ef60a90620b358b25549bac58bcb35e45e6e616
+Bug 1403641: Test data: URI download. r=bz
+
+diff --git a/dom/security/test/general/browser.ini b/dom/security/test/general/browser.ini
+--- a/dom/security/test/general/browser.ini
++++ b/dom/security/test/general/browser.ini
+@@ -1,5 +1,8 @@
+ [DEFAULT]
+ [browser_test_toplevel_data_navigations.js]
+ support-files =
+   file_toplevel_data_navigations.sjs
+   file_toplevel_data_meta_redirect.html
++[browser_test_data_download.js]
++support-files =
++  file_data_download.html
+diff --git a/dom/security/test/general/browser_test_data_download.js b/dom/security/test/general/browser_test_data_download.js
+new file mode 100644
+--- /dev/null
++++ b/dom/security/test/general/browser_test_data_download.js
+@@ -0,0 +1,37 @@
++"use strict";
++
++const kTestPath = getRootDirectory(gTestPath)
++                  .replace("chrome://mochitests/content", "http://example.com")
++const kTestURI = kTestPath + "file_data_download.html";
++
++function addWindowListener(aURL, aCallback) {
++  Services.wm.addListener({
++    onOpenWindow(aXULWindow) {
++      info("window opened, waiting for focus");
++      Services.wm.removeListener(this);
++      var domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
++                                .getInterface(Ci.nsIDOMWindow);
++      waitForFocus(function() {
++        is(domwindow.document.location.href, aURL, "should have seen the right window open");
++        aCallback(domwindow);
++      }, domwindow);
++    },
++    onCloseWindow(aXULWindow) { },
++    onWindowTitleChange(aXULWindow, aNewTitle) { }
++  });
++}
++
++function test() {
++  waitForExplicitFinish();
++  Services.prefs.setBoolPref("security.data_uri.block_toplevel_data_uri_navigations", true);
++  registerCleanupFunction(function() {
++    Services.prefs.clearUserPref("security.data_uri.block_toplevel_data_uri_navigations");
++  });
++  addWindowListener("chrome://mozapps/content/downloads/unknownContentType.xul", function(win) {
++    is(win.document.getElementById("location").value, "data-foo.html",
++       "file name of download should match");
++     win.close();
++     finish();
++  });
++  gBrowser.loadURI(kTestURI);
++}
+diff --git a/dom/security/test/general/file_data_download.html b/dom/security/test/general/file_data_download.html
+new file mode 100644
+--- /dev/null
++++ b/dom/security/test/general/file_data_download.html
+@@ -0,0 +1,14 @@
++<!DOCTYPE HTML>
++<html>
++<head>
++  <title>Test download attribute for data: URI</title>
++</head>
++<body>
++  <a href="data:text/html,<body>data download</body>" download="data-foo.html" id="testlink">download data</a>
++  <script>
++    // click the link to have the downoad panel appear
++    let testlink = document.getElementById("testlink");
++    testlink.click();
++  </script>
++  </body>
++</html>

+ 83 - 0
frg/work-js/mozilla-release/patches/1403814-1-58a1.patch

@@ -0,0 +1,83 @@
+# HG changeset patch
+# User Christoph Kerschbaumer <ckerschb@christophkerschbaumer.com>
+# Date 1507754832 -7200
+# Node ID acbbffb64a063475792c693b61c20f62af203ead
+# Parent  f7d7506157be9906ef54487201d8f3c728a4d0d5
+Bug 1403814: Test navigation to data:text/csv. r=smaug
+
+diff --git a/dom/security/test/general/browser.ini b/dom/security/test/general/browser.ini
+--- a/dom/security/test/general/browser.ini
++++ b/dom/security/test/general/browser.ini
+@@ -1,8 +1,11 @@
+ [DEFAULT]
+ [browser_test_toplevel_data_navigations.js]
+ support-files =
+   file_toplevel_data_navigations.sjs
+   file_toplevel_data_meta_redirect.html
+ [browser_test_data_download.js]
+ support-files =
+   file_data_download.html
++[browser_test_data_text_csv.js]
++support-files =
++  file_data_text_csv.html
+diff --git a/dom/security/test/general/browser_test_data_text_csv.js b/dom/security/test/general/browser_test_data_text_csv.js
+new file mode 100644
+--- /dev/null
++++ b/dom/security/test/general/browser_test_data_text_csv.js
+@@ -0,0 +1,37 @@
++"use strict";
++
++const kTestPath = getRootDirectory(gTestPath)
++                  .replace("chrome://mochitests/content", "http://example.com")
++const kTestURI = kTestPath + "file_data_text_csv.html";
++
++function addWindowListener(aURL, aCallback) {
++  Services.wm.addListener({
++    onOpenWindow(aXULWindow) {
++      info("window opened, waiting for focus");
++      Services.wm.removeListener(this);
++      var domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
++                                .getInterface(Ci.nsIDOMWindow);
++      waitForFocus(function() {
++        is(domwindow.document.location.href, aURL, "should have seen the right window open");
++        aCallback(domwindow);
++      }, domwindow);
++    },
++    onCloseWindow(aXULWindow) { },
++    onWindowTitleChange(aXULWindow, aNewTitle) { }
++  });
++}
++
++function test() {
++  waitForExplicitFinish();
++  Services.prefs.setBoolPref("security.data_uri.block_toplevel_data_uri_navigations", true);
++  registerCleanupFunction(function() {
++    Services.prefs.clearUserPref("security.data_uri.block_toplevel_data_uri_navigations");
++  });
++  addWindowListener("chrome://mozapps/content/downloads/unknownContentType.xul", function(win) {
++    is(win.document.getElementById("location").value, "text/csv;foo,bar,foobar",
++       "file name of download should match");
++     win.close();
++     finish();
++  });
++  gBrowser.loadURI(kTestURI);
++}
+diff --git a/dom/security/test/general/file_data_text_csv.html b/dom/security/test/general/file_data_text_csv.html
+new file mode 100644
+--- /dev/null
++++ b/dom/security/test/general/file_data_text_csv.html
+@@ -0,0 +1,14 @@
++<!DOCTYPE HTML>
++<html>
++<head>
++  <title>Test open data:text/csv</title>
++</head>
++<body>
++  <a href="data:text/csv;foo,bar,foobar" id="testlink">test text/csv</a>
++  <script>
++    // click the link to have the downoad panel appear
++    let testlink = document.getElementById("testlink");
++    testlink.click();
++  </script>
++  </body>
++</html>

+ 80 - 0
frg/work-js/mozilla-release/patches/1403814-2-58a1.patch

@@ -0,0 +1,80 @@
+# HG changeset patch
+# User Christoph Kerschbaumer <ckerschb@christophkerschbaumer.com>
+# Date 1509711777 -3600
+# Node ID 5d25efb36d34042e37075af204e109d29bc9bded
+# Parent  aaf441e669a5e09dba5adfa2b2649f23bba9140c
+Bug 1403814 - Update tests for toplevel data URI blocking because we know block after we have received the response. r=smaug
+
+diff --git a/dom/security/test/general/test_block_toplevel_data_img_navigation.html b/dom/security/test/general/test_block_toplevel_data_img_navigation.html
+--- a/dom/security/test/general/test_block_toplevel_data_img_navigation.html
++++ b/dom/security/test/general/test_block_toplevel_data_img_navigation.html
+@@ -29,23 +29,25 @@ function test_toplevel_data_image() {
+     test_toplevel_data_image_svg();
+   }, 1000);
+ }
+ 
+ function test_toplevel_data_image_svg() {
+   const DATA_SVG =
+     "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDE2IDE2Ij4KICA8cGF0aCBkPSJNOCwxMkwzLDcsNCw2bDQsNCw0LTQsMSwxWiIgZmlsbD0iIzZBNkE2QSIgLz4KPC9zdmc+Cg==";
+   let win2 = window.open(DATA_SVG);
+-  let wrappedWin2 = SpecialPowers.wrap(win2);
+-  setTimeout(function () {
+-    isnot(wrappedWin2.document.documentElement.localName, "svg",
+-          "Loading data:image/svg+xml should be blocked");
+-    wrappedWin2.close();
+-    SimpleTest.finish();
+-  }, 1000);
++  // Unfortunately we can't detect whether the window was closed using some event,
++  // hence we are constantly polling till we see that win == null.
++  // Test times out on failure.
++  var win2Closed = setInterval(function() {
++    if (win2 == null || win2.closed) {
++      clearInterval(win2Closed);
++      ok(true, "Loading data:image/svg+xml should be blocked");
++      SimpleTest.finish();
++    }
++  }, 200);
+ }
+-
+ // fire up the tests
+ test_toplevel_data_image();
+ 
+ </script>
+ </body>
+ </html>
+diff --git a/dom/security/test/general/test_block_toplevel_data_navigation.html b/dom/security/test/general/test_block_toplevel_data_navigation.html
+--- a/dom/security/test/general/test_block_toplevel_data_navigation.html
++++ b/dom/security/test/general/test_block_toplevel_data_navigation.html
+@@ -16,26 +16,22 @@ SimpleTest.registerCleanupFunction(() =>
+ 
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestFlakyTimeout("have to test that top level data: URI navgiation is blocked");
+ 
+ function test1() {
+   // simple data: URI click navigation should be prevented
+   let TEST_FILE = "file_block_toplevel_data_navigation.html";
+   let win1 = window.open(TEST_FILE);
+-  var readyStateCheckInterval = setInterval(function() {
+-    let state = win1.document.readyState;
+-    if (state === "interactive" || state === "complete") {
+-      clearInterval(readyStateCheckInterval);
+-      ok(win1.document.body.innerHTML.includes("test1:"),
+-         "toplevel data: URI navigation through click() should be blocked");
+-      win1.close();
+-      test2();
+-    }
+-  }, 200);
++  setTimeout(function () {
++    ok(SpecialPowers.wrap(win1).document.body.innerHTML.includes("test1:"),
++      "toplevel data: URI navigation through click() should be blocked");
++    win1.close();
++    test2();
++  }, 1000);
+ }
+ 
+ function test2() {
+   // data: URI in iframe which opens data: URI in _blank should be blocked 
+   let win2 = window.open("file_block_toplevel_data_navigation2.html");
+   window.addEventListener("message", receiveMessage);
+   function receiveMessage(event) {
+     window.removeEventListener("message", receiveMessage);

+ 582 - 0
frg/work-js/mozilla-release/patches/1403814-3-58a1.patch

@@ -0,0 +1,582 @@
+# HG changeset patch
+# User Christoph Kerschbaumer <ckerschb@christophkerschbaumer.com>
+# Date 1509711791 -3600
+# Node ID 0c4ecb84046395afd7c5ed4a250b64991c9b0da7
+# Parent  16b908d193a9d66d36ee9ebae640cd89550c9851
+Bug 1403814 - Block toplevel data: URI navigations only if openend in the browser. r=smaug
+
+diff --git a/docshell/base/nsDSURIContentListener.cpp b/docshell/base/nsDSURIContentListener.cpp
+--- a/docshell/base/nsDSURIContentListener.cpp
++++ b/docshell/base/nsDSURIContentListener.cpp
+@@ -9,16 +9,17 @@
+ #include "nsIChannel.h"
+ #include "nsServiceManagerUtils.h"
+ #include "nsDocShellCID.h"
+ #include "nsIWebNavigationInfo.h"
+ #include "nsIDocument.h"
+ #include "nsIDOMWindow.h"
+ #include "nsIHttpChannel.h"
+ #include "nsError.h"
++#include "nsContentSecurityManager.h"
+ #include "nsDocShellLoadTypes.h"
+ #include "nsIMultiPartChannel.h"
+ 
+ using namespace mozilla;
+ 
+ nsDSURIContentListener::nsDSURIContentListener(nsDocShell* aDocShell)
+   : mDocShell(aDocShell)
+   , mExistingJPEGRequest(nullptr)
+@@ -82,16 +83,24 @@ nsDSURIContentListener::DoContent(const 
+   *aAbortProcess = false;
+ 
+   // determine if the channel has just been retargeted to us...
+   nsLoadFlags loadFlags = 0;
+   nsCOMPtr<nsIChannel> aOpenedChannel = do_QueryInterface(aRequest);
+ 
+   if (aOpenedChannel) {
+     aOpenedChannel->GetLoadFlags(&loadFlags);
++
++    // block top-level data URI navigations if triggered by the web
++    if (!nsContentSecurityManager::AllowTopLevelNavigationToDataURI(aOpenedChannel)) {
++      // logging to console happens within AllowTopLevelNavigationToDataURI
++      aRequest->Cancel(NS_ERROR_DOM_BAD_URI);
++      *aAbortProcess = true;
++      return NS_OK; 
++    }
+   }
+ 
+   if (loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI) {
+     // XXX: Why does this not stop the content too?
+     docShell->Stop(nsIWebNavigation::STOP_NETWORK);
+     NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE);
+     docShell->SetLoadType(aIsContentPreferred ? LOAD_LINK : LOAD_NORMAL);
+ 
+diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp
+--- a/docshell/base/nsDocShell.cpp
++++ b/docshell/base/nsDocShell.cpp
+@@ -9826,29 +9826,16 @@ nsDocShell::InternalLoad(nsIURI* aURI,
+       // an iframe since that's more common.
+       contentType = nsIContentPolicy::TYPE_INTERNAL_IFRAME;
+     }
+   } else {
+     contentType = nsIContentPolicy::TYPE_DOCUMENT;
+     isTargetTopLevelDocShell = true;
+   }
+ 
+-  nsIDocument* doc = mContentViewer ? mContentViewer->GetDocument()
+-                                    : nullptr;
+-  if (!nsContentSecurityManager::AllowTopLevelNavigationToDataURI(
+-        aURI,
+-        contentType,
+-        aTriggeringPrincipal,
+-        doc,
+-        (aLoadType == LOAD_NORMAL_EXTERNAL),
+-        !aFileName.IsVoid())) {
+-    // logging to console happens within AllowTopLevelNavigationToDataURI
+-    return NS_OK;
+-  }
+-
+   // If there's no targetDocShell, that means we are about to create a new
+   // window (or aWindowTarget is empty). Perform a content policy check before
+   // creating the window. Please note for all other docshell loads
+   // content policy checks are performed within the contentSecurityManager
+   // when the channel is about to be openend.
+   if (!targetDocShell && !aWindowTarget.IsEmpty()) {
+     MOZ_ASSERT(contentType == nsIContentPolicy::TYPE_DOCUMENT,
+                "opening a new window requires type to be TYPE_DOCUMENT");
+@@ -11002,16 +10989,17 @@ nsDocShell::DoURILoad(nsIURI* aURI,
+       new LoadInfo(loadingWindow, aTriggeringPrincipal, topLevelLoadingContext,
+                    securityFlags) :
+       new LoadInfo(loadingPrincipal, aTriggeringPrincipal, loadingNode,
+                    securityFlags, aContentPolicyType);
+ 
+   if (aPrincipalToInherit) {
+     loadInfo->SetPrincipalToInherit(aPrincipalToInherit);
+   }
++  loadInfo->SetLoadTriggeredFromExternal(aLoadFromExternal);
+ 
+   // We have to do this in case our OriginAttributes are different from the
+   // OriginAttributes of the parent document. Or in case there isn't a
+   // parent document.
+   bool isTopLevelDoc = mItemType == typeContent &&
+                        (aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT ||
+                         GetIsMozBrowser());
+ 
+diff --git a/docshell/base/nsDocShell.cpp.1403814-3.later b/docshell/base/nsDocShell.cpp.1403814-3.later
+new file mode 100644
+--- /dev/null
++++ b/docshell/base/nsDocShell.cpp.1403814-3.later
+@@ -0,0 +1,22 @@
++--- nsDocShell.cpp
+++++ nsDocShell.cpp
++@@ -10103,16 +10090,19 @@ nsDocShell::InternalLoad(nsIURI* aURI,
++ 
++         nsCOMPtr<nsIDocShellTreeItem> parent;
++         treeItem->GetSameTypeParent(getter_AddRefs(parent));
++         parent.swap(treeItem);
++       } while (treeItem);
++     }
++   }
++ 
+++  nsIDocument* doc = mContentViewer ? mContentViewer->GetDocument()
+++                                    : nullptr;
+++
++   const bool isDocumentAuxSandboxed = doc &&
++     (doc->GetSandboxFlags() & SANDBOXED_AUXILIARY_NAVIGATION);
++ 
++   if (aURI && mLoadURIDelegate &&
++       (!targetDocShell || targetDocShell == static_cast<nsIDocShell*>(this))) {
++     // Dispatch only load requests for the current or a new window to the
++     // delegate, e.g., to allow for GeckoView apps to handle the load event
++     // outside of Gecko.
+diff --git a/dom/security/nsContentSecurityManager.cpp b/dom/security/nsContentSecurityManager.cpp
+--- a/dom/security/nsContentSecurityManager.cpp
++++ b/dom/security/nsContentSecurityManager.cpp
+@@ -12,78 +12,87 @@
+ #include "nsILoadInfo.h"
+ #include "nsIOService.h"
+ #include "nsContentUtils.h"
+ #include "nsCORSListenerProxy.h"
+ #include "nsIStreamListener.h"
+ #include "nsCDefaultURIFixup.h"
+ #include "nsIURIFixup.h"
+ #include "nsIImageLoadingContent.h"
+-#include "NullPrincipal.h"
+ 
+ #include "mozilla/dom/Element.h"
++#include "mozilla/dom/TabChild.h"
+ 
+ NS_IMPL_ISUPPORTS(nsContentSecurityManager,
+                   nsIContentSecurityManager,
+                   nsIChannelEventSink)
+ 
+ /* static */ bool
+-nsContentSecurityManager::AllowTopLevelNavigationToDataURI(
+-  nsIURI* aURI,
+-  nsContentPolicyType aContentPolicyType,
+-  nsIPrincipal* aTriggeringPrincipal,
+-  nsIDocument* aDoc,
+-  bool aLoadFromExternal,
+-  bool aIsDownLoad)
++nsContentSecurityManager::AllowTopLevelNavigationToDataURI(nsIChannel* aChannel)
+ {
+   // Let's block all toplevel document navigations to a data: URI.
+   // In all cases where the toplevel document is navigated to a
+   // data: URI the triggeringPrincipal is a codeBasePrincipal, or
+   // a NullPrincipal. In other cases, e.g. typing a data: URL into
+   // the URL-Bar, the triggeringPrincipal is a SystemPrincipal;
+   // we don't want to block those loads. Only exception, loads coming
+   // from an external applicaton (e.g. Thunderbird) don't load
+   // using a codeBasePrincipal, but we want to block those loads.
+   if (!mozilla::net::nsIOService::BlockToplevelDataUriNavigations()) {
+     return true;
+   }
+-  if (aContentPolicyType != nsIContentPolicy::TYPE_DOCUMENT || aIsDownLoad) {
++  nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
++  if (!loadInfo) {
+     return true;
+   }
++  if (loadInfo->GetExternalContentPolicyType() != nsIContentPolicy::TYPE_DOCUMENT) {
++    return true;
++  }
++  nsCOMPtr<nsIURI> uri;
++  nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
++  NS_ENSURE_SUCCESS(rv, true);
+   bool isDataURI =
+-    (NS_SUCCEEDED(aURI->SchemeIs("data", &isDataURI)) && isDataURI);
++    (NS_SUCCEEDED(uri->SchemeIs("data", &isDataURI)) && isDataURI);
+   if (!isDataURI) {
+     return true;
+   }
+   // Whitelist data: images as long as they are not SVGs
+   nsAutoCString filePath;
+-  aURI->GetFilePath(filePath);
++  uri->GetFilePath(filePath);
+   if (StringBeginsWith(filePath, NS_LITERAL_CSTRING("image/")) &&
+       !StringBeginsWith(filePath, NS_LITERAL_CSTRING("image/svg+xml"))) {
+     return true;
+   }
+   // Whitelist data: PDFs
+   if (StringBeginsWith(filePath, NS_LITERAL_CSTRING("application/pdf"))) {
+     return true;
+   }
+-  if (!aLoadFromExternal &&
+-      nsContentUtils::IsSystemPrincipal(aTriggeringPrincipal)) {
++  // Redirecting to a toplevel data: URI is not allowed, hence we make
++  // sure the RedirectChain is empty.
++  if (!loadInfo->GetLoadTriggeredFromExternal() &&
++      nsContentUtils::IsSystemPrincipal(loadInfo->TriggeringPrincipal()) &&
++      loadInfo->RedirectChain().IsEmpty()) {
+     return true;
+   }
+   nsAutoCString dataSpec;
+-  aURI->GetSpec(dataSpec);
++  uri->GetSpec(dataSpec);
+   if (dataSpec.Length() > 50) {
+     dataSpec.Truncate(50);
+     dataSpec.AppendLiteral("...");
+   }
++  nsCOMPtr<nsITabChild> tabChild = do_QueryInterface(loadInfo->ContextForTopLevelLoad());
++  nsCOMPtr<nsIDocument> doc;
++  if (tabChild) {
++    doc = static_cast<mozilla::dom::TabChild*>(tabChild.get())->GetDocument();
++  }
+   NS_ConvertUTF8toUTF16 specUTF16(NS_UnescapeURL(dataSpec));
+   const char16_t* params[] = { specUTF16.get() };
+   nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+                                   NS_LITERAL_CSTRING("DATA_URI_BLOCKED"),
+-                                  aDoc,
++                                  doc,
+                                   nsContentUtils::eSECURITY_PROPERTIES,
+                                   "BlockTopLevelDataURINavigation",
+                                   params, ArrayLength(params));
+   return false;
+ }
+ 
+ static nsresult
+ ValidateSecurityFlags(nsILoadInfo* aLoadInfo)
+@@ -562,39 +571,16 @@ nsContentSecurityManager::AsyncOnChannel
+   if (loadInfo && loadInfo->GetEnforceSecurity()) {
+     nsresult rv = CheckChannel(aNewChannel);
+     if (NS_FAILED(rv)) {
+       aOldChannel->Cancel(rv);
+       return rv;
+     }
+   }
+ 
+-  // Redirecting to a toplevel data: URI is not allowed, hence we pass
+-  // a NullPrincipal as the TriggeringPrincipal to
+-  // AllowTopLevelNavigationToDataURI() which definitely blocks any
+-  // data: URI load.
+-  nsCOMPtr<nsILoadInfo> newLoadInfo = aNewChannel->GetLoadInfo();
+-  if (newLoadInfo) {
+-    nsCOMPtr<nsIURI> uri;
+-    nsresult rv = NS_GetFinalChannelURI(aNewChannel, getter_AddRefs(uri));
+-    NS_ENSURE_SUCCESS(rv, rv);
+-    nsCOMPtr<nsIPrincipal> nullTriggeringPrincipal = NullPrincipal::Create();
+-    if (!nsContentSecurityManager::AllowTopLevelNavigationToDataURI(
+-          uri,
+-          newLoadInfo->GetExternalContentPolicyType(),
+-          nullTriggeringPrincipal,
+-          nullptr, // no doc available, log to browser console
+-          false,
+-          false)) {
+-        // logging to console happens within AllowTopLevelNavigationToDataURI
+-      aOldChannel->Cancel(NS_ERROR_DOM_BAD_URI);
+-      return NS_ERROR_DOM_BAD_URI;
+-    }
+-  }
+-
+   // Also verify that the redirecting server is allowed to redirect to the
+   // given URI
+   nsCOMPtr<nsIPrincipal> oldPrincipal;
+   nsContentUtils::GetSecurityManager()->
+     GetChannelResultPrincipal(aOldChannel, getter_AddRefs(oldPrincipal));
+ 
+   nsCOMPtr<nsIURI> newURI;
+   Unused << NS_GetFinalChannelURI(aNewChannel, getter_AddRefs(newURI));
+diff --git a/dom/security/nsContentSecurityManager.h b/dom/security/nsContentSecurityManager.h
+--- a/dom/security/nsContentSecurityManager.h
++++ b/dom/security/nsContentSecurityManager.h
+@@ -28,22 +28,17 @@ public:
+   NS_DECL_NSICONTENTSECURITYMANAGER
+   NS_DECL_NSICHANNELEVENTSINK
+ 
+   nsContentSecurityManager() {}
+ 
+   static nsresult doContentSecurityCheck(nsIChannel* aChannel,
+                                          nsCOMPtr<nsIStreamListener>& aInAndOutListener);
+ 
+-  static bool AllowTopLevelNavigationToDataURI(nsIURI* aURI,
+-                                               nsContentPolicyType aContentPolicyType,
+-                                               nsIPrincipal* aTriggeringPrincipal,
+-                                               nsIDocument* aDoc,
+-                                               bool aLoadFromExternal,
+-                                               bool aIsDownload);
++  static bool AllowTopLevelNavigationToDataURI(nsIChannel* aChannel);
+ 
+ private:
+   static nsresult CheckChannel(nsIChannel* aChannel);
+ 
+   virtual ~nsContentSecurityManager() {}
+ 
+ };
+ 
+diff --git a/ipc/glue/BackgroundUtils.cpp b/ipc/glue/BackgroundUtils.cpp
+--- a/ipc/glue/BackgroundUtils.cpp
++++ b/ipc/glue/BackgroundUtils.cpp
+@@ -390,17 +390,18 @@ LoadInfoToLoadInfoArgs(nsILoadInfo *aLoa
+       aLoadInfo->GetIsInThirdPartyContext(),
+       aLoadInfo->GetOriginAttributes(),
+       redirectChainIncludingInternalRedirects,
+       redirectChain,
+       ancestorPrincipals,
+       aLoadInfo->AncestorOuterWindowIDs(),
+       aLoadInfo->CorsUnsafeHeaders(),
+       aLoadInfo->GetForcePreflight(),
+-      aLoadInfo->GetIsPreflight()
++      aLoadInfo->GetIsPreflight(),
++      aLoadInfo->GetLoadTriggeredFromExternal()
+       );
+ 
+   return NS_OK;
+ }
+ 
+ nsresult
+ LoadInfoArgsToLoadInfo(const OptionalLoadInfoArgs& aOptionalLoadInfoArgs,
+                        nsILoadInfo** outLoadInfo)
+@@ -493,17 +494,18 @@ LoadInfoArgsToLoadInfo(const OptionalLoa
+                           loadInfoArgs.isInThirdPartyContext(),
+                           loadInfoArgs.originAttributes(),
+                           redirectChainIncludingInternalRedirects,
+                           redirectChain,
+                           std::move(ancestorPrincipals),
+                           loadInfoArgs.ancestorOuterWindowIDs(),
+                           loadInfoArgs.corsUnsafeHeaders(),
+                           loadInfoArgs.forcePreflight(),
+-                          loadInfoArgs.isPreflight()
++                          loadInfoArgs.isPreflight(),
++                          loadInfoArgs.loadTriggeredFromExternal()
+                           );
+ 
+    loadInfo.forget(outLoadInfo);
+    return NS_OK;
+ }
+ 
+ } // namespace ipc
+ } // namespace mozilla
+diff --git a/netwerk/base/LoadInfo.cpp b/netwerk/base/LoadInfo.cpp
+--- a/netwerk/base/LoadInfo.cpp
++++ b/netwerk/base/LoadInfo.cpp
+@@ -4,16 +4,17 @@
+  * License, v. 2.0. If a copy of the MPL was not distributed with this
+  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+ 
+ #include "mozilla/LoadInfo.h"
+ 
+ #include "mozilla/Assertions.h"
+ #include "mozilla/dom/ClientIPCTypes.h"
+ #include "mozilla/dom/ClientSource.h"
++#include "mozilla/dom/TabChild.h"
+ #include "mozilla/dom/ToJSValue.h"
+ #include "mozIThirdPartyUtil.h"
+ #include "nsFrameLoader.h"
+ #include "nsIContentSecurityPolicy.h"
+ #include "nsIDocShell.h"
+ #include "nsIDocument.h"
+ #include "nsIDOMDocument.h"
+ #include "nsIFrameLoader.h"
+@@ -65,16 +66,17 @@ LoadInfo::LoadInfo(nsIPrincipal* aLoadin
+   , mParentOuterWindowID(0)
+   , mTopOuterWindowID(0)
+   , mFrameOuterWindowID(0)
+   , mEnforceSecurity(false)
+   , mInitialSecurityCheckDone(false)
+   , mIsThirdPartyContext(false)
+   , mForcePreflight(false)
+   , mIsPreflight(false)
++  , mLoadTriggeredFromExternal(false)
+ {
+   MOZ_ASSERT(mLoadingPrincipal);
+   MOZ_ASSERT(mTriggeringPrincipal);
+ 
+ #ifdef DEBUG
+   // TYPE_DOCUMENT loads initiated by javascript tests will go through
+   // nsIOService and use the wrong constructor.  Don't enforce the
+   // !TYPE_DOCUMENT check in those cases
+@@ -243,16 +245,17 @@ LoadInfo::LoadInfo(nsPIDOMWindowOuter* a
+   , mParentOuterWindowID(0)
+   , mTopOuterWindowID(0)
+   , mFrameOuterWindowID(0)
+   , mEnforceSecurity(false)
+   , mInitialSecurityCheckDone(false)
+   , mIsThirdPartyContext(false) // NB: TYPE_DOCUMENT implies not third-party.
+   , mForcePreflight(false)
+   , mIsPreflight(false)
++  , mLoadTriggeredFromExternal(false)
+ {
+   // Top-level loads are never third-party
+   // Grab the information we can out of the window.
+   MOZ_ASSERT(aOuterWindow);
+   MOZ_ASSERT(mTriggeringPrincipal);
+ 
+   // if the load is sandboxed, we can not also inherit the principal
+   if (mSecurityFlags & nsILoadInfo::SEC_SANDBOXED) {
+@@ -318,16 +321,17 @@ LoadInfo::LoadInfo(const LoadInfo& rhs)
+   , mRedirectChainIncludingInternalRedirects(
+       rhs.mRedirectChainIncludingInternalRedirects)
+   , mRedirectChain(rhs.mRedirectChain)
+   , mAncestorPrincipals(rhs.mAncestorPrincipals)
+   , mAncestorOuterWindowIDs(rhs.mAncestorOuterWindowIDs)
+   , mCorsUnsafeHeaders(rhs.mCorsUnsafeHeaders)
+   , mForcePreflight(rhs.mForcePreflight)
+   , mIsPreflight(rhs.mIsPreflight)
++  , mLoadTriggeredFromExternal(rhs.mLoadTriggeredFromExternal)
+ {
+ }
+ 
+ LoadInfo::LoadInfo(nsIPrincipal* aLoadingPrincipal,
+                    nsIPrincipal* aTriggeringPrincipal,
+                    nsIPrincipal* aPrincipalToInherit,
+                    nsIPrincipal* aSandboxedLoadingPrincipal,
+                    nsIURI* aResultPrincipalURI,
+@@ -348,17 +352,18 @@ LoadInfo::LoadInfo(nsIPrincipal* aLoadin
+                    bool aIsThirdPartyContext,
+                    const OriginAttributes& aOriginAttributes,
+                    RedirectHistoryArray& aRedirectChainIncludingInternalRedirects,
+                    RedirectHistoryArray& aRedirectChain,
+                    nsTArray<nsCOMPtr<nsIPrincipal>>&& aAncestorPrincipals,
+                    const nsTArray<uint64_t>& aAncestorOuterWindowIDs,
+                    const nsTArray<nsCString>& aCorsUnsafeHeaders,
+                    bool aForcePreflight,
+-                   bool aIsPreflight)
++                   bool aIsPreflight,
++                   bool aLoadTriggeredFromExternal)
+   : mLoadingPrincipal(aLoadingPrincipal)
+   , mTriggeringPrincipal(aTriggeringPrincipal)
+   , mPrincipalToInherit(aPrincipalToInherit)
+   , mResultPrincipalURI(aResultPrincipalURI)
+   , mSecurityFlags(aSecurityFlags)
+   , mInternalContentPolicyType(aContentPolicyType)
+   , mTainting(aTainting)
+   , mUpgradeInsecureRequests(aUpgradeInsecureRequests)
+@@ -374,16 +379,17 @@ LoadInfo::LoadInfo(nsIPrincipal* aLoadin
+   , mInitialSecurityCheckDone(aInitialSecurityCheckDone)
+   , mIsThirdPartyContext(aIsThirdPartyContext)
+   , mOriginAttributes(aOriginAttributes)
+   , mAncestorPrincipals(std::move(aAncestorPrincipals))
+   , mAncestorOuterWindowIDs(aAncestorOuterWindowIDs)
+   , mCorsUnsafeHeaders(aCorsUnsafeHeaders)
+   , mForcePreflight(aForcePreflight)
+   , mIsPreflight(aIsPreflight)
++  , mLoadTriggeredFromExternal(aLoadTriggeredFromExternal)
+ {
+   // Only top level TYPE_DOCUMENT loads can have a null loadingPrincipal
+   MOZ_ASSERT(mLoadingPrincipal || aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT);
+   MOZ_ASSERT(mTriggeringPrincipal);
+ 
+   mRedirectChainIncludingInternalRedirects.SwapElements(
+     aRedirectChainIncludingInternalRedirects);
+ 
+@@ -1002,16 +1008,33 @@ LoadInfo::SetUpgradeInsecureRequests()
+ NS_IMETHODIMP
+ LoadInfo::GetIsPreflight(bool* aIsPreflight)
+ {
+   *aIsPreflight = mIsPreflight;
+   return NS_OK;
+ }
+ 
+ NS_IMETHODIMP
++LoadInfo::SetLoadTriggeredFromExternal(bool aLoadTriggeredFromExternal)
++{
++  MOZ_ASSERT(!aLoadTriggeredFromExternal ||
++             mInternalContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT,
++             "can only set load triggered from external for TYPE_DOCUMENT");
++  mLoadTriggeredFromExternal = aLoadTriggeredFromExternal;
++  return NS_OK;
++}
++
++NS_IMETHODIMP
++LoadInfo::GetLoadTriggeredFromExternal(bool* aLoadTriggeredFromExternal)
++{
++  *aLoadTriggeredFromExternal = mLoadTriggeredFromExternal;
++  return NS_OK;
++}
++
++NS_IMETHODIMP
+ LoadInfo::GetTainting(uint32_t* aTaintingOut)
+ {
+   MOZ_ASSERT(aTaintingOut);
+   *aTaintingOut = static_cast<uint32_t>(mTainting);
+   return NS_OK;
+ }
+ 
+ NS_IMETHODIMP
+diff --git a/netwerk/base/LoadInfo.h b/netwerk/base/LoadInfo.h
+--- a/netwerk/base/LoadInfo.h
++++ b/netwerk/base/LoadInfo.h
+@@ -116,17 +116,18 @@ private:
+            bool aIsThirdPartyRequest,
+            const OriginAttributes& aOriginAttributes,
+            RedirectHistoryArray& aRedirectChainIncludingInternalRedirects,
+            RedirectHistoryArray& aRedirectChain,
+            nsTArray<nsCOMPtr<nsIPrincipal>>&& aAncestorPrincipals,
+            const nsTArray<uint64_t>& aAncestorOuterWindowIDs,
+            const nsTArray<nsCString>& aUnsafeHeaders,
+            bool aForcePreflight,
+-           bool aIsPreflight);
++           bool aIsPreflight,
++           bool aLoadTriggeredFromExternal);
+   LoadInfo(const LoadInfo& rhs);
+ 
+   NS_IMETHOD GetRedirects(JSContext* aCx, JS::MutableHandle<JS::Value> aRedirects,
+                           const RedirectHistoryArray& aArra);
+ 
+   friend nsresult
+   mozilla::ipc::LoadInfoArgsToLoadInfo(
+     const mozilla::net::OptionalLoadInfoArgs& aLoadInfoArgs,
+@@ -175,15 +176,16 @@ private:
+   OriginAttributes                 mOriginAttributes;
+   RedirectHistoryArray             mRedirectChainIncludingInternalRedirects;
+   RedirectHistoryArray             mRedirectChain;
+   nsTArray<nsCOMPtr<nsIPrincipal>> mAncestorPrincipals;
+   nsTArray<uint64_t>               mAncestorOuterWindowIDs;
+   nsTArray<nsCString>              mCorsUnsafeHeaders;
+   bool                             mForcePreflight;
+   bool                             mIsPreflight;
++  bool                             mLoadTriggeredFromExternal;
+ };
+ 
+ } // namespace net
+ } // namespace mozilla
+ 
+ #endif // mozilla_LoadInfo_h
+ 
+diff --git a/netwerk/base/nsILoadInfo.idl b/netwerk/base/nsILoadInfo.idl
+--- a/netwerk/base/nsILoadInfo.idl
++++ b/netwerk/base/nsILoadInfo.idl
+@@ -633,16 +633,23 @@ interface nsILoadInfo : nsISupports
+    * Please note, once the flag is set to true it must remain true
+    * throughout the lifetime of the channel. Trying to set it
+    * to anything else than true will be discarded.
+    *
+    */
+   [infallible] attribute boolean initialSecurityCheckDone;
+ 
+   /**
++   * Returns true if the load was triggered from an external application
++   * (e.g. Thunderbird). Please note that this flag will only ever be true
++   * if the load is of TYPE_DOCUMENT. 
++   */
++  [infallible] attribute boolean loadTriggeredFromExternal;
++
++  /**
+    * Whenever a channel gets redirected, append the redirect history entry of
+    * the channel which contains principal referrer and remote address [before
+    * the channels got redirected] to the loadinfo, so that at every point this
+    * array provides us information about all the redirects this channel went
+    * through.
+    * @param entry, the nsIRedirectHistoryEntry before the channel
+    *         got redirected.
+    * @param aIsInternalRedirect should be true if the channel is going
+diff --git a/netwerk/ipc/NeckoChannelParams.ipdlh b/netwerk/ipc/NeckoChannelParams.ipdlh
+--- a/netwerk/ipc/NeckoChannelParams.ipdlh
++++ b/netwerk/ipc/NeckoChannelParams.ipdlh
+@@ -65,16 +65,17 @@ struct LoadInfoArgs
+    * See nsILoadInfo.idl for details.
+    */
+   PrincipalInfo[]             ancestorPrincipals;
+   uint64_t[]                  ancestorOuterWindowIDs;
+ 
+   nsCString[]                 corsUnsafeHeaders;
+   bool                        forcePreflight;
+   bool                        isPreflight;
++  bool                        loadTriggeredFromExternal;
+ };
+ 
+ /**
+  * Not every channel necessarily has a loadInfo attached.
+  */
+ union OptionalLoadInfoArgs
+ {
+   void_t;

+ 32 - 0
frg/work-js/mozilla-release/patches/1403870-1-58a1.patch

@@ -0,0 +1,32 @@
+# HG changeset patch
+# User Christoph Kerschbaumer <ckerschb@christophkerschbaumer.com>
+# Date 1509711988 -3600
+# Node ID 4acac146285e05979767dbab0296aad8e1ea0ddd
+# Parent  dc41bc1d319a55bdf89f02e87f1fdee06b33871a
+Bug 1403870: Allow toplevel data URI navigation data:application/json. r=smaug
+
+diff --git a/dom/security/nsContentSecurityManager.cpp b/dom/security/nsContentSecurityManager.cpp
+--- a/dom/security/nsContentSecurityManager.cpp
++++ b/dom/security/nsContentSecurityManager.cpp
+@@ -56,18 +56,19 @@ nsContentSecurityManager::AllowTopLevelN
+   }
+   // Whitelist data: images as long as they are not SVGs
+   nsAutoCString filePath;
+   uri->GetFilePath(filePath);
+   if (StringBeginsWith(filePath, NS_LITERAL_CSTRING("image/")) &&
+       !StringBeginsWith(filePath, NS_LITERAL_CSTRING("image/svg+xml"))) {
+     return true;
+   }
+-  // Whitelist data: PDFs
+-  if (StringBeginsWith(filePath, NS_LITERAL_CSTRING("application/pdf"))) {
++  // Whitelist data: PDFs and JSON
++  if (StringBeginsWith(filePath, NS_LITERAL_CSTRING("application/pdf")) ||
++      StringBeginsWith(filePath, NS_LITERAL_CSTRING("application/json"))) {
+     return true;
+   }
+   // Redirecting to a toplevel data: URI is not allowed, hence we make
+   // sure the RedirectChain is empty.
+   if (!loadInfo->GetLoadTriggeredFromExternal() &&
+       nsContentUtils::IsSystemPrincipal(loadInfo->TriggeringPrincipal()) &&
+       loadInfo->RedirectChain().IsEmpty()) {
+     return true;

+ 73 - 0
frg/work-js/mozilla-release/patches/1403870-2-58a1.patch

@@ -0,0 +1,73 @@
+# HG changeset patch
+# User Christoph Kerschbaumer <ckerschb@christophkerschbaumer.com>
+# Date 1509712021 -3600
+# Node ID b66649cd261a06335e8b052b1cab15b78744465e
+# Parent  62b9a11993c47eaea11b677f49180e16f3b20fc6
+Bug 1403870: Test toplevel data URI navigation to application/json is allowed. r=smaug
+
+diff --git a/dom/security/test/general/mochitest.ini b/dom/security/test/general/mochitest.ini
+--- a/dom/security/test/general/mochitest.ini
++++ b/dom/security/test/general/mochitest.ini
+@@ -25,16 +25,18 @@ support-files =
+ [test_nosniff.html]
+ [test_block_script_wrong_mime.html]
+ [test_block_toplevel_data_navigation.html]
+ skip-if = toolkit == 'android' # intermittent failure
+ [test_block_toplevel_data_img_navigation.html]
+ skip-if = toolkit == 'android' # intermittent failure
+ [test_allow_opening_data_pdf.html]
+ skip-if = toolkit == 'android'
++[test_allow_opening_data_json.html]
++skip-if = toolkit == 'android'
+ [test_same_site_cookies_subrequest.html]
+ [test_same_site_cookies_toplevel_nav.html]
+ [test_same_site_cookies_cross_origin_context.html]
+ [test_same_site_cookies_from_script.html]
+ [test_same_site_cookies_redirect.html]
+ [test_same_site_cookies_toplevel_set_cookie.html]
+ [test_same_site_cookies_iframe.html]
+ [test_same_site_cookies_about.html]
+diff --git a/dom/security/test/general/test_allow_opening_data_json.html b/dom/security/test/general/test_allow_opening_data_json.html
+new file mode 100644
+--- /dev/null
++++ b/dom/security/test/general/test_allow_opening_data_json.html
+@@ -0,0 +1,39 @@
++<!DOCTYPE HTML>
++<html>
++<head>
++  <meta charset="utf-8">
++  <title>Bug 1403814: Allow toplevel data URI navigation data:application/json</title>
++  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
++  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
++</head>
++<body>
++<script class="testbody" type="text/javascript">
++
++SimpleTest.waitForExplicitFinish();
++
++function test_toplevel_data_json() {
++  const DATA_JSON = "data:application/json,{'my_json_key':'my_json_value'}";
++
++  let win = window.open(DATA_JSON);
++  let wrappedWin = SpecialPowers.wrap(win);
++
++  // Unfortunately we can't detect whether the JSON has loaded or not using some
++  // event, hence we are constantly polling location.href till we see that
++  // the data: URI appears. Test times out on failure.
++  var jsonLoaded = setInterval(function() {
++    if (wrappedWin.document.location.href.startsWith("data:application/json")) {
++      clearInterval(jsonLoaded);
++      ok(true, "navigating to data:application/json allowed");
++      wrappedWin.close();
++      SimpleTest.finish();
++    }
++  }, 200);
++}
++
++SpecialPowers.pushPrefEnv({
++  set: [["security.data_uri.block_toplevel_data_uri_navigations", true]]
++}, test_toplevel_data_json);
++
++</script>
++</body>
++</html>

+ 546 - 0
frg/work-js/mozilla-release/patches/1407085-59a1.patch

@@ -0,0 +1,546 @@
+# HG changeset patch
+# User Masayuki Nakano <masayuki@d-toybox.com>
+# Date 1513669580 -32400
+# Node ID 5323dfb26361028737850236fd7fc7c73200a383
+# Parent  f64c0050f151adaf8d3b027c60c9f0e87c7f2285
+Bug 1407085 - nsAutoCompleteController shouldn't restore original value after somebody changes the input value even when Escape key is pressed r=mak
+
+When Escape key is pressed, nsAutoCompleteController needs to restore last
+string which was default value of the input or typed by the user.  However,
+somebody may change the value, e.g., an event listener which handles
+Escape key.  In this case, nsAutoCompleteController shouldn't restore the
+last string.
+
+Unfortunately, when JS sets input value, DOM "input" event won't be fired.
+Therefore, nsAutoCompleteController doesn't have a chance to modify
+mSearchString in this case.  Therefore, nsAutoCompleteController needs to
+store expected input string for checking if somebody modified the input value.
+For solving this issue, this patch adds a new member, mSetValue which is
+modified when the input value is modified by nsAutoCompleteController itself
+or mSearchString is modified.
+
+Even with this patch, if user temporarily selects an item of the popup and
+JS sets same value as the selected item from JS, nsAutoCompleteController
+restores the input value with mSearchString.  However, this must be rare
+case and I don't have idea to fix this issue with simple patches.
+
+MozReview-Commit-ID: lig8c7xvD7
+
+diff --git a/toolkit/components/autocomplete/nsAutoCompleteController.cpp b/toolkit/components/autocomplete/nsAutoCompleteController.cpp
+--- a/toolkit/components/autocomplete/nsAutoCompleteController.cpp
++++ b/toolkit/components/autocomplete/nsAutoCompleteController.cpp
+@@ -21,30 +21,16 @@
+ #include "mozilla/Services.h"
+ #include "mozilla/ModuleUtils.h"
+ #include "mozilla/Unused.h"
+ 
+ static const char *kAutoCompleteSearchCID = "@mozilla.org/autocomplete/search;1?name=";
+ 
+ using namespace mozilla;
+ 
+-namespace {
+-
+-void
+-SetTextValue(nsIAutoCompleteInput* aInput,
+-             const nsString& aValue,
+-             uint16_t aReason) {
+-  nsresult rv = aInput->SetTextValueWithReason(aValue, aReason);
+-  if (NS_FAILED(rv)) {
+-    aInput->SetTextValue(aValue);
+-  }
+-}
+-
+-} // anon namespace
+-
+ NS_IMPL_CYCLE_COLLECTION_CLASS(nsAutoCompleteController)
+ 
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsAutoCompleteController)
+   tmp->SetInput(nullptr);
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsAutoCompleteController)
+   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInput)
+   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSearches)
+@@ -77,16 +63,28 @@ nsAutoCompleteController::nsAutoComplete
+ {
+ }
+ 
+ nsAutoCompleteController::~nsAutoCompleteController()
+ {
+   SetInput(nullptr);
+ }
+ 
++void
++nsAutoCompleteController::SetValueOfInputTo(const nsString& aValue,
++                                            uint16_t aReason)
++{
++  mSetValue = aValue;
++  nsCOMPtr<nsIAutoCompleteInput> input(mInput);
++  nsresult rv = input->SetTextValueWithReason(aValue, aReason);
++  if (NS_FAILED(rv)) {
++    input->SetTextValue(aValue);
++  }
++}
++
+ ////////////////////////////////////////////////////////////////////////
+ //// nsIAutoCompleteController
+ 
+ NS_IMETHODIMP
+ nsAutoCompleteController::GetSearchStatus(uint16_t *aSearchStatus)
+ {
+   *aSearchStatus = mSearchStatus;
+   return NS_OK;
+@@ -144,17 +142,19 @@ nsAutoCompleteController::SetInput(nsIAu
+ 
+   // Nothing more to do if the input was just being set to null.
+   if (!mInput) {
+     return NS_OK;
+   }
+   nsCOMPtr<nsIAutoCompleteInput> input(mInput);
+ 
+   // Reset the current search string.
+-  input->GetTextValue(mSearchString);
++  nsAutoString value;
++  input->GetTextValue(value);
++  SetSearchStringInternal(value);
+ 
+   // Clear out this reference in case the new input's popup has no tree
+   mTree = nullptr;
+ 
+   // Initialize our list of search objects
+   uint32_t searchCount;
+   input->GetSearchCount(&searchCount);
+   mResults.SetCapacity(searchCount);
+@@ -203,33 +203,33 @@ nsAutoCompleteController::ResetInternalS
+ {
+   // Clear out the current search context
+   if (mInput) {
+     nsAutoString value;
+     mInput->GetTextValue(value);
+     // Stop all searches in case they are async.
+     Unused << StopSearch();
+     Unused << ClearResults();
+-    mSearchString = value;
++    SetSearchStringInternal(value);
+   }
+ 
+   mPlaceholderCompletionString.Truncate();
+   mDefaultIndexCompleted = false;
+   mProhibitAutoFill = false;
+   mSearchStatus = nsIAutoCompleteController::STATUS_NONE;
+   mRowCount = 0;
+   mCompletedSelectionIndex = -1;
+ 
+   return NS_OK;
+ }
+ 
+ NS_IMETHODIMP
+ nsAutoCompleteController::StartSearch(const nsAString &aSearchString)
+ {
+-  mSearchString = aSearchString;
++  SetSearchStringInternal(aSearchString);
+   StartSearches();
+   return NS_OK;
+ }
+ 
+ NS_IMETHODIMP
+ nsAutoCompleteController::HandleText(bool *_retval)
+ {
+   *_retval = false;
+@@ -315,17 +315,17 @@ nsAutoCompleteController::HandleText(boo
+       ClearResults();
+     }
+     mProhibitAutoFill = true;
+     mPlaceholderCompletionString.Truncate();
+   } else {
+     mProhibitAutoFill = false;
+   }
+ 
+-  mSearchString = newValue;
++  SetSearchStringInternal(newValue);
+ 
+   // Don't search if the value is empty
+   if (newValue.Length() == 0) {
+     // If autocomplete popup was closed by compositionstart event handler,
+     // we should reopen it forcibly even if the value is empty.
+     if (popupClosedByCompositionStart && handlingCompositionCommit) {
+       bool cancel;
+       HandleKeyNavigation(nsIDOMKeyEvent::DOM_VK_DOWN, &cancel);
+@@ -515,31 +515,31 @@ nsAutoCompleteController::HandleKeyNavig
+             // If the result is the previously autofilled string, then restore
+             // the search string and selection that existed when the result was
+             // autofilled.  Else, fill the result and move the caret to the end.
+             int32_t start;
+             if (value.Equals(mPlaceholderCompletionString,
+                              nsCaseInsensitiveStringComparator())) {
+               start = mSearchString.Length();
+               value = mPlaceholderCompletionString;
+-              SetTextValue(input, value,
+-                           nsIAutoCompleteInput::TEXTVALUE_REASON_COMPLETEDEFAULT);
++              SetValueOfInputTo(
++                value, nsIAutoCompleteInput::TEXTVALUE_REASON_COMPLETEDEFAULT);
+             } else {
+               start = value.Length();
+-              SetTextValue(input, value,
+-                           nsIAutoCompleteInput::TEXTVALUE_REASON_COMPLETESELECTED);
++              SetValueOfInputTo(
++                value, nsIAutoCompleteInput::TEXTVALUE_REASON_COMPLETESELECTED);
+             }
+ 
+             input->SelectTextRange(start, value.Length());
+           }
+           mCompletedSelectionIndex = selectedIndex;
+         } else {
+           // Nothing is selected, so fill in the last typed value
+-          SetTextValue(input, mSearchString,
+-                       nsIAutoCompleteInput::TEXTVALUE_REASON_REVERT);
++          SetValueOfInputTo(
++            mSearchString, nsIAutoCompleteInput::TEXTVALUE_REASON_REVERT);
+           input->SelectTextRange(mSearchString.Length(), mSearchString.Length());
+           mCompletedSelectionIndex = -1;
+         }
+       }
+     } else {
+ #ifdef XP_MACOSX
+       // on Mac, only show the popup if the caret is at the start or end of
+       // the input and there is no selection, so that the default defined key
+@@ -581,17 +581,17 @@ nsAutoCompleteController::HandleKeyNavig
+             return NS_OK;
+           }
+ 
+           // Some script may have changed the value of the text field since our
+           // last keypress or after our focus handler and we don't want to search
+           // for a stale string.
+           nsAutoString value;
+           input->GetTextValue(value);
+-          mSearchString = value;
++          SetSearchStringInternal(value);
+ 
+           StartSearches();
+         }
+       }
+     }
+   } else if (   aKey == nsIDOMKeyEvent::DOM_VK_LEFT
+              || aKey == nsIDOMKeyEvent::DOM_VK_RIGHT
+ #ifndef XP_MACOSX
+@@ -627,18 +627,18 @@ nsAutoCompleteController::HandleKeyNavig
+       int32_t selectedIndex;
+       popup->GetSelectedIndex(&selectedIndex);
+       bool shouldComplete;
+       input->GetCompleteDefaultIndex(&shouldComplete);
+       if (selectedIndex >= 0) {
+         // The pop-up is open and has a selection, take its value
+         nsAutoString value;
+         if (NS_SUCCEEDED(GetResultValueAt(selectedIndex, false, value))) {
+-          SetTextValue(input, value,
+-                       nsIAutoCompleteInput::TEXTVALUE_REASON_COMPLETESELECTED);
++          SetValueOfInputTo(
++            value, nsIAutoCompleteInput::TEXTVALUE_REASON_COMPLETESELECTED);
+           input->SelectTextRange(value.Length(), value.Length());
+         }
+       }
+       else if (shouldComplete) {
+         // We usually try to preserve the casing of what user has typed, but
+         // if he wants to autocomplete, we will replace the value with the
+         // actual autocomplete result. Note that the autocomplete input can also
+         // be showing e.g. "bar >> foo bar" if the search matched "bar", a
+@@ -653,33 +653,33 @@ nsAutoCompleteController::HandleKeyNavig
+           int32_t pos = inputValue.Find(" >> ");
+           if (pos > 0) {
+             inputValue.Right(suggestedValue, inputValue.Length() - pos - 4);
+           } else {
+             suggestedValue = inputValue;
+           }
+ 
+           if (value.Equals(suggestedValue, nsCaseInsensitiveStringComparator())) {
+-            SetTextValue(input, value,
+-                         nsIAutoCompleteInput::TEXTVALUE_REASON_COMPLETEDEFAULT);
++            SetValueOfInputTo(
++              value, nsIAutoCompleteInput::TEXTVALUE_REASON_COMPLETEDEFAULT);
+             input->SelectTextRange(value.Length(), value.Length());
+           }
+         }
+       }
+ 
+       // Close the pop-up even if nothing was selected
+       ClearSearchTimer();
+       ClosePopup();
+     }
+     // Update last-searched string to the current input, since the input may
+     // have changed.  Without this, subsequent backspaces look like text
+     // additions, not text deletions.
+     nsAutoString value;
+     input->GetTextValue(value);
+-    mSearchString = value;
++    SetSearchStringInternal(value);
+   }
+ 
+   return NS_OK;
+ }
+ 
+ NS_IMETHODIMP
+ nsAutoCompleteController::HandleDelete(bool *_retval)
+ {
+@@ -839,17 +839,17 @@ nsAutoCompleteController::GetFinalComple
+   NS_ENSURE_SUCCESS(rv, rv);
+ 
+   return result->GetFinalCompleteValueAt(rowIndex, _retval);
+ }
+ 
+ NS_IMETHODIMP
+ nsAutoCompleteController::SetSearchString(const nsAString &aSearchString)
+ {
+-  mSearchString = aSearchString;
++  SetSearchStringInternal(aSearchString);
+   return NS_OK;
+ }
+ 
+ NS_IMETHODIMP
+ nsAutoCompleteController::GetSearchString(nsAString &aSearchString)
+ {
+   aSearchString = mSearchString;
+   return NS_OK;
+@@ -1572,19 +1572,19 @@ nsAutoCompleteController::EnterMatch(boo
+     }
+   }
+ 
+   nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
+   NS_ENSURE_STATE(obsSvc);
+   obsSvc->NotifyObservers(input, "autocomplete-will-enter-text", nullptr);
+ 
+   if (!value.IsEmpty()) {
+-    SetTextValue(input, value, nsIAutoCompleteInput::TEXTVALUE_REASON_ENTERMATCH);
++    SetValueOfInputTo(value, nsIAutoCompleteInput::TEXTVALUE_REASON_ENTERMATCH);
+     input->SelectTextRange(value.Length(), value.Length());
+-    mSearchString = value;
++    SetSearchStringInternal(value);
+   }
+ 
+   obsSvc->NotifyObservers(input, "autocomplete-did-enter-text", nullptr);
+   ClosePopup();
+ 
+   bool cancel;
+   input->OnTextEntered(aEvent, &cancel);
+ 
+@@ -1595,33 +1595,41 @@ nsresult
+ nsAutoCompleteController::RevertTextValue()
+ {
+   // StopSearch() can call PostSearchCleanup() which might result
+   // in a blur event, which could null out mInput, so we need to check it
+   // again.  See bug #408463 for more details
+   if (!mInput)
+     return NS_OK;
+ 
+-  nsAutoString oldValue(mSearchString);
+   nsCOMPtr<nsIAutoCompleteInput> input(mInput);
+ 
++  // If current input value is different from what we have set, it means
++  // somebody modified the value like JS of the web content.  In such case,
++  // we shouldn't overwrite it with the old value.
++  nsAutoString currentValue;
++  input->GetTextValue(currentValue);
++  if (currentValue != mSetValue) {
++    SetSearchStringInternal(currentValue);
++    return NS_OK;
++  }
++
+   bool cancel = false;
+   input->OnTextReverted(&cancel);
+ 
+   if (!cancel) {
+     nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
+     NS_ENSURE_STATE(obsSvc);
+     obsSvc->NotifyObservers(input, "autocomplete-will-revert-text", nullptr);
+ 
+-    nsAutoString inputValue;
+-    input->GetTextValue(inputValue);
+     // Don't change the value if it is the same to prevent sending useless events.
+     // NOTE: how can |RevertTextValue| be called with inputValue != oldValue?
+-    if (!oldValue.Equals(inputValue)) {
+-      SetTextValue(input, oldValue, nsIAutoCompleteInput::TEXTVALUE_REASON_REVERT);
++    if (mSearchString != currentValue) {
++      SetValueOfInputTo(
++        mSearchString, nsIAutoCompleteInput::TEXTVALUE_REASON_REVERT);
+     }
+ 
+     obsSvc->NotifyObservers(input, "autocomplete-did-revert-text", nullptr);
+   }
+ 
+   return NS_OK;
+ }
+ 
+@@ -1937,18 +1945,18 @@ nsAutoCompleteController::CompleteValue(
+ 
+   if (aValue.IsEmpty() ||
+       StringBeginsWith(aValue, mSearchString,
+                        nsCaseInsensitiveStringComparator())) {
+     // aValue is empty (we were asked to clear mInput), or mSearchString
+     // matches the beginning of aValue.  In either case we can simply
+     // autocomplete to aValue.
+     mPlaceholderCompletionString = aValue;
+-    SetTextValue(input, aValue,
+-                 nsIAutoCompleteInput::TEXTVALUE_REASON_COMPLETEDEFAULT);
++    SetValueOfInputTo(
++      aValue, nsIAutoCompleteInput::TEXTVALUE_REASON_COMPLETEDEFAULT);
+   } else {
+     nsresult rv;
+     nsCOMPtr<nsIIOService> ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
+     NS_ENSURE_SUCCESS(rv, rv);
+     nsAutoCString scheme;
+     if (NS_SUCCEEDED(ios->ExtractScheme(NS_ConvertUTF16toUTF8(aValue), scheme))) {
+       // Trying to autocomplete a URI from somewhere other than the beginning.
+       // Only succeed if the missing portion is "http://"; otherwise do not
+@@ -1960,26 +1968,28 @@ nsAutoCompleteController::CompleteValue(
+           !scheme.LowerCaseEqualsLiteral("http") ||
+           !Substring(aValue, findIndex, mSearchStringLength).Equals(
+             mSearchString, nsCaseInsensitiveStringComparator())) {
+         return NS_OK;
+       }
+ 
+       mPlaceholderCompletionString = mSearchString +
+         Substring(aValue, mSearchStringLength + findIndex, endSelect);
+-      SetTextValue(input, mPlaceholderCompletionString,
+-                   nsIAutoCompleteInput::TEXTVALUE_REASON_COMPLETEDEFAULT);
++      SetValueOfInputTo(
++        mPlaceholderCompletionString,
++        nsIAutoCompleteInput::TEXTVALUE_REASON_COMPLETEDEFAULT);
+ 
+       endSelect -= findIndex; // We're skipping this many characters of aValue.
+     } else {
+       // Autocompleting something other than a URI from the middle.
+       // Use the format "searchstring >> full string" to indicate to the user
+       // what we are going to replace their search string with.
+-      SetTextValue(input, mSearchString + NS_LITERAL_STRING(" >> ") + aValue,
+-                   nsIAutoCompleteInput::TEXTVALUE_REASON_COMPLETEDEFAULT);
++      SetValueOfInputTo(
++        mSearchString + NS_LITERAL_STRING(" >> ") + aValue,
++        nsIAutoCompleteInput::TEXTVALUE_REASON_COMPLETEDEFAULT);
+ 
+       endSelect = mSearchString.Length() + 4 + aValue.Length();
+ 
+       // Reset the last search completion.
+       mPlaceholderCompletionString.Truncate();
+     }
+   }
+ 
+diff --git a/toolkit/components/autocomplete/nsAutoCompleteController.h b/toolkit/components/autocomplete/nsAutoCompleteController.h
+--- a/toolkit/components/autocomplete/nsAutoCompleteController.h
++++ b/toolkit/components/autocomplete/nsAutoCompleteController.h
+@@ -37,16 +37,31 @@ public:
+   NS_DECL_NSITIMERCALLBACK
+   NS_DECL_NSINAMED
+ 
+   nsAutoCompleteController();
+ 
+ protected:
+   virtual ~nsAutoCompleteController();
+ 
++  /**
++   * SetValueOfInputTo() sets value of mInput to aValue and notifies the input
++   * of setting reason.
++   */
++  void SetValueOfInputTo(const nsString& aValue, uint16_t aReason);
++
++  /**
++   * SetSearchStringInternal() sets both mSearchString and mSetValue to
++   * aSearchString.
++   */
++  void SetSearchStringInternal(const nsAString& aSearchString)
++  {
++    mSearchString = mSetValue = aSearchString;
++  }
++
+   nsresult OpenPopup();
+   nsresult ClosePopup();
+ 
+   nsresult StartSearch(uint16_t aSearchType);
+ 
+   nsresult BeforeSearches();
+   nsresult StartSearches();
+   void AfterSearches();
+@@ -133,18 +148,29 @@ protected:
+   // search.  This is needed to allow the searches to reuse the previous result,
+   // since otherwise the first search clears mResults.
+   nsCOMArray<nsIAutoCompleteResult> mResultCache;
+ 
+   nsCOMPtr<nsITimer> mTimer;
+   nsCOMPtr<nsITreeSelection> mSelection;
+   nsCOMPtr<nsITreeBoxObject> mTree;
+ 
++  // mSearchString stores value which is the original value of the input or
++  // typed by the user.  When user is choosing an item from the popup, this
++  // is NOT modified by the item because this is used for reverting the input
++  // value when user cancels choosing an item from the popup.
++  // This should be set through only SetSearchStringInternal().
+   nsString mSearchString;
+   nsString mPlaceholderCompletionString;
++  // mSetValue stores value which is expected in the input.  So, if input's
++  // value and mSetValue are different, it means somebody has changed the
++  // value like JS of the web content.
++  // This is set only by SetValueOfInputTo() or when modifying mSearchString
++  // through SetSearchStringInternal().
++  nsString mSetValue;
+   bool mDefaultIndexCompleted;
+   bool mPopupClosedByCompositionStart;
+ 
+   // Whether autofill is allowed for the next search. May be retrieved by the
+   // search through the "prohibit-autofill" searchParam.
+   bool mProhibitAutoFill;
+ 
+   // Indicates whether the user cleared the autofilled part, returning to the
+diff --git a/toolkit/content/tests/mochitest/mochitest.ini b/toolkit/content/tests/mochitest/mochitest.ini
+--- a/toolkit/content/tests/mochitest/mochitest.ini
++++ b/toolkit/content/tests/mochitest/mochitest.ini
+@@ -1,8 +1,10 @@
++[test_bug1407085.html]
++
+ [test_autocomplete_change_after_focus.html]
+ skip-if = toolkit == "android"
+ [test_mousecapture.xhtml]
+ support-files =
+   file_mousecapture.html
+   file_mousecapture2.html
+   file_mousecapture3.html
+   file_mousecapture4.html
+diff --git a/toolkit/content/tests/mochitest/test_bug1407085.html b/toolkit/content/tests/mochitest/test_bug1407085.html
+new file mode 100644
+--- /dev/null
++++ b/toolkit/content/tests/mochitest/test_bug1407085.html
+@@ -0,0 +1,38 @@
++<!DOCTYPE HTML>
++<html>
++<head>
++  <title>Test for Bug 1407085</title>
++  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
++  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
++  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
++</head>
++<body>
++<p id="display"></p>
++
++<div id="content">
++  <input id="input" value="original value">
++</div>
++
++<pre id="test">
++<script class="testbody" type="text/javascript">
++
++SimpleTest.waitForExplicitFinish();
++SimpleTest.waitForFocus(runTests);
++
++function runTests() {
++  let input = document.getElementById("input");
++  input.focus();
++  input.addEventListener("keydown", () => {
++    input.value = "new value";
++  }, { once: true });
++  synthesizeKey("KEY_Escape", { code: "Escape" });
++  is(input.value, "new value",
++     "New <input> value changed by an Escape key event listener shouldn't be " +
++     "overwritten by original value even if Escape key is pressed");
++  SimpleTest.finish();
++}
++
++</script>
++</pre>
++</body>
++</html>

+ 702 - 0
frg/work-js/mozilla-release/patches/1407891-1-58a1.patch

@@ -0,0 +1,702 @@
+# HG changeset patch
+# User Christoph Kerschbaumer <ckerschb@christophkerschbaumer.com>
+# Date 1510167701 -3600
+# Node ID 69e828da2238c01a3ab7c449d8c13b23792a7555
+# Parent  15ca3e4bbff213ee0d8551d8f01e1934efbb60fd
+Bug 1407891: Allow view-image to open a data: URI by setting a flag on the loadinfo. r=bz
+
+diff --git a/browser/base/content/nsContextMenu.js b/browser/base/content/nsContextMenu.js
+--- a/browser/base/content/nsContextMenu.js
++++ b/browser/base/content/nsContextMenu.js
+@@ -1222,17 +1222,18 @@ nsContextMenu.prototype = {
+                                  referrerURI,
+                                  triggeringPrincipal: systemPrincipal});
+       }, Cu.reportError);
+     } else {
+       urlSecurityCheck(this.mediaURL,
+                        this.browser.contentPrincipal,
+                        Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
+       openUILink(this.mediaURL, e, { disallowInheritPrincipal: true,
+-                                     referrerURI });
++                                     referrerURI,
++                                     forceAllowDataURI: true });
+     }
+   },
+ 
+   saveVideoFrameAsImage() {
+     let mm = this.browser.messageManager;
+     let isPrivate = PrivateBrowsingUtils.isBrowserPrivate(this.browser);
+ 
+     let name = "";
+diff --git a/browser/base/content/utilityOverlay.js b/browser/base/content/utilityOverlay.js
+--- a/browser/base/content/utilityOverlay.js
++++ b/browser/base/content/utilityOverlay.js
+@@ -205,29 +205,31 @@ function openUILinkIn(url, where, aAllow
+     };
+   }
+ 
+   params.fromChrome = true;
+ 
+   openLinkIn(url, where, params);
+ }
+ 
++/* eslint-disable complexity */
+ function openLinkIn(url, where, params) {
+   if (!where || !url)
+     return;
+ 
+   var aFromChrome           = params.fromChrome;
+   var aAllowThirdPartyFixup = params.allowThirdPartyFixup;
+   var aPostData             = params.postData;
+   var aCharset              = params.charset;
+   var aReferrerURI          = params.referrerURI;
+   var aReferrerPolicy       = ("referrerPolicy" in params ?
+       params.referrerPolicy : Ci.nsIHttpChannel.REFERRER_POLICY_UNSET);
+   var aRelatedToCurrent     = params.relatedToCurrent;
+   var aAllowMixedContent    = params.allowMixedContent;
++  var aForceAllowDataURI    = params.forceAllowDataURI;
+   var aInBackground         = params.inBackground;
+   var aDisallowInheritPrincipal = params.disallowInheritPrincipal;
+   var aInitiatingDoc        = params.initiatingDoc;
+   var aIsPrivate            = params.private;
+   var aSkipTabAnimation     = params.skipTabAnimation;
+   var aAllowPinnedTabHostChange = !!params.allowPinnedTabHostChange;
+   var aNoReferrer           = params.noReferrer;
+   var aAllowPopups          = !!params.allowPopups;
+@@ -426,16 +428,19 @@ function openLinkIn(url, where, params) 
+     }
+ 
+     if (aAllowPopups) {
+       flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_POPUPS;
+     }
+     if (aIndicateErrorPageLoad) {
+       flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ERROR_LOAD_CHANGES_RV;
+     }
++    if (aForceAllowDataURI) {
++      flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FORCE_ALLOW_DATA_URI;
++    }
+ 
+     let {URI_INHERITS_SECURITY_CONTEXT} = Ci.nsIProtocolHandler;
+     if (aForceAboutBlankViewerInCurrent &&
+         (!uriObj ||
+          (doGetProtocolFlags(uriObj) & URI_INHERITS_SECURITY_CONTEXT))) {
+       // Unless we know for sure we're not inheriting principals,
+       // force the about:blank viewer to have the right principal:
+       targetBrowser.createAboutBlankContentViewer(aPrincipal);
+diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp
+--- a/docshell/base/nsDocShell.cpp
++++ b/docshell/base/nsDocShell.cpp
+@@ -1306,16 +1306,17 @@ nsDocShell::LoadURI(nsIURI* aURI,
+   bool inheritPrincipal = false;
+   bool principalIsExplicit = false;
+   bool sendReferrer = true;
+   uint32_t referrerPolicy = mozilla::net::RP_Unset;
+   bool isSrcdoc = false;
+   nsCOMPtr<nsISHEntry> shEntry;
+   nsString target;
+   nsAutoString srcdoc;
++  bool forceAllowDataURI = false;
+   nsCOMPtr<nsIDocShell> sourceDocShell;
+   nsCOMPtr<nsIURI> baseURI;
+ 
+   uint32_t loadType = MAKE_LOAD_TYPE(LOAD_NORMAL, aLoadFlags);
+ 
+   NS_ENSURE_ARG(aURI);
+ 
+   if (!StartupTimeline::HasRecord(StartupTimeline::FIRST_LOAD_URI) &&
+@@ -1343,16 +1344,17 @@ nsDocShell::LoadURI(nsIURI* aURI,
+     aLoadInfo->GetPostDataStream(getter_AddRefs(postStream));
+     aLoadInfo->GetHeadersStream(getter_AddRefs(headersStream));
+     aLoadInfo->GetSendReferrer(&sendReferrer);
+     aLoadInfo->GetReferrerPolicy(&referrerPolicy);
+     aLoadInfo->GetIsSrcdocLoad(&isSrcdoc);
+     aLoadInfo->GetSrcdocData(srcdoc);
+     aLoadInfo->GetSourceDocShell(getter_AddRefs(sourceDocShell));
+     aLoadInfo->GetBaseURI(getter_AddRefs(baseURI));
++    aLoadInfo->GetForceAllowDataURI(&forceAllowDataURI);
+   }
+ 
+   MOZ_LOG(gDocShellLeakLog, LogLevel::Debug,
+           ("nsDocShell[%p]: loading %s with flags 0x%08x",
+            this, aURI->GetSpecOrDefault().get(), aLoadFlags));
+ 
+   if (!shEntry &&
+       !LOAD_TYPE_HAS_FLAGS(loadType, LOAD_FLAGS_REPLACE_HISTORY)) {
+@@ -1606,16 +1608,20 @@ nsDocShell::LoadURI(nsIURI* aURI,
+   if (aLoadFlags & LOAD_FLAGS_FORCE_ALLOW_COOKIES) {
+     flags |= INTERNAL_LOAD_FLAGS_FORCE_ALLOW_COOKIES;
+   }
+ 
+   if (isSrcdoc) {
+     flags |= INTERNAL_LOAD_FLAGS_IS_SRCDOC;
+   }
+ 
++  if (forceAllowDataURI) {
++    flags |= INTERNAL_LOAD_FLAGS_FORCE_ALLOW_DATA_URI;
++  }
++
+   return InternalLoad(aURI,
+                       originalURI,
+                       resultPrincipalURI,
+                       keepResultPrincipalURIIfSet,
+                       loadReplace,
+                       referrer,
+                       referrerPolicy,
+                       triggeringPrincipal,
+@@ -4824,16 +4830,19 @@ nsDocShell::LoadURIWithOptions(const cha
+   if (aLoadFlags & LOAD_FLAGS_ALLOW_POPUPS) {
+     popupState = openAllowed;
+     aLoadFlags &= ~LOAD_FLAGS_ALLOW_POPUPS;
+   } else {
+     popupState = openOverridden;
+   }
+   nsAutoPopupStatePusher statePusher(popupState);
+ 
++  bool forceAllowDataURI =
++    aLoadFlags & LOAD_FLAGS_FORCE_ALLOW_DATA_URI;
++
+   // Don't pass certain flags that aren't needed and end up confusing
+   // ConvertLoadTypeToDocShellLoadInfo.  We do need to ensure that they are
+   // passed to LoadURI though, since it uses them.
+   uint32_t extraFlags = (aLoadFlags & EXTRA_LOAD_FLAGS);
+   aLoadFlags &= ~EXTRA_LOAD_FLAGS;
+ 
+   nsCOMPtr<nsIDocShellLoadInfo> loadInfo;
+   rv = CreateLoadInfo(getter_AddRefs(loadInfo));
+@@ -4854,16 +4863,17 @@ nsDocShell::LoadURIWithOptions(const cha
+ 
+   loadInfo->SetLoadType(ConvertLoadTypeToDocShellLoadInfo(loadType));
+   loadInfo->SetPostDataStream(postStream);
+   loadInfo->SetReferrer(aReferringURI);
+   loadInfo->SetReferrerPolicy(aReferrerPolicy);
+   loadInfo->SetHeadersStream(aHeaderStream);
+   loadInfo->SetBaseURI(aBaseURI);
+   loadInfo->SetTriggeringPrincipal(aTriggeringPrincipal);
++  loadInfo->SetForceAllowDataURI(forceAllowDataURI);
+ 
+   if (fixupInfo) {
+     nsAutoString searchProvider, keyword;
+     fixupInfo->GetKeywordProviderName(searchProvider);
+     fixupInfo->GetKeywordAsSent(keyword);
+     MaybeNotifyKeywordSearchLoading(searchProvider, keyword);
+   }
+ 
+@@ -10011,16 +10021,17 @@ nsDocShell::InternalLoad(nsIURI* aURI,
+         loadInfo->SetLoadReplace(aLoadReplace);
+         loadInfo->SetTriggeringPrincipal(aTriggeringPrincipal);
+         loadInfo->SetInheritPrincipal(
+           aFlags & INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL);
+         // Explicit principal because we do not want any guesses as to what the
+         // principal to inherit is: it should be aTriggeringPrincipal.
+         loadInfo->SetPrincipalIsExplicit(true);
+         loadInfo->SetLoadType(ConvertLoadTypeToDocShellLoadInfo(LOAD_LINK));
++        loadInfo->SetForceAllowDataURI(aFlags & INTERNAL_LOAD_FLAGS_FORCE_ALLOW_DATA_URI);
+ 
+         rv = win->Open(NS_ConvertUTF8toUTF16(spec),
+                        aWindowTarget, // window name
+                        EmptyString(), // Features
+                        loadInfo,
+                        true, // aForceNoOpener
+                        getter_AddRefs(newWin));
+         MOZ_ASSERT(!newWin);
+@@ -10671,17 +10682,19 @@ nsDocShell::InternalLoad(nsIURI* aURI,
+   net::PredictorLearn(aURI, nullptr,
+                       nsINetworkPredictor::LEARN_LOAD_TOPLEVEL, attrs);
+   net::PredictorPredict(aURI, nullptr,
+                         nsINetworkPredictor::PREDICT_LOAD, attrs, nullptr);
+ 
+   nsCOMPtr<nsIRequest> req;
+   rv = DoURILoad(aURI, aOriginalURI, aResultPrincipalURI,
+                  aKeepResultPrincipalURIIfSet, aLoadReplace,
+-                 loadFromExternal, aReferrer,
++                 loadFromExternal,
++                 (aFlags & INTERNAL_LOAD_FLAGS_FORCE_ALLOW_DATA_URI),
++                 aReferrer,
+                  !(aFlags & INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER),
+                  aReferrerPolicy,
+                  aTriggeringPrincipal, principalToInherit, aTypeHint,
+                  aFileName, aPostData, aPostDataLength, aHeadersData,
+                  aFirstParty, aDocShell, getter_AddRefs(req),
+                  (aFlags & INTERNAL_LOAD_FLAGS_FIRST_LOAD) != 0,
+                  (aFlags & INTERNAL_LOAD_FLAGS_BYPASS_CLASSIFIER) != 0,
+                  (aFlags & INTERNAL_LOAD_FLAGS_FORCE_ALLOW_COOKIES) != 0,
+@@ -10810,16 +10823,17 @@ IsConsideredSameOriginForUIR(nsIPrincipa
+ 
+ nsresult
+ nsDocShell::DoURILoad(nsIURI* aURI,
+                       nsIURI* aOriginalURI,
+                       Maybe<nsCOMPtr<nsIURI>> const& aResultPrincipalURI,
+                       bool aKeepResultPrincipalURIIfSet,
+                       bool aLoadReplace,
+                       bool aLoadFromExternal,
++                      bool aForceAllowDataURI,
+                       nsIURI* aReferrerURI,
+                       bool aSendReferrer,
+                       uint32_t aReferrerPolicy,
+                       nsIPrincipal* aTriggeringPrincipal,
+                       nsIPrincipal* aPrincipalToInherit,
+                       const char* aTypeHint,
+                       const nsAString& aFileName,
+                       nsIInputStream* aPostData,
+@@ -10990,16 +11004,17 @@ nsDocShell::DoURILoad(nsIURI* aURI,
+                    securityFlags) :
+       new LoadInfo(loadingPrincipal, aTriggeringPrincipal, loadingNode,
+                    securityFlags, aContentPolicyType);
+ 
+   if (aPrincipalToInherit) {
+     loadInfo->SetPrincipalToInherit(aPrincipalToInherit);
+   }
+   loadInfo->SetLoadTriggeredFromExternal(aLoadFromExternal);
++  loadInfo->SetForceAllowDataURI(aForceAllowDataURI);
+ 
+   // We have to do this in case our OriginAttributes are different from the
+   // OriginAttributes of the parent document. Or in case there isn't a
+   // parent document.
+   bool isTopLevelDoc = mItemType == typeContent &&
+                        (aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT ||
+                         GetIsMozBrowser());
+ 
+diff --git a/docshell/base/nsDocShell.h b/docshell/base/nsDocShell.h
+--- a/docshell/base/nsDocShell.h
++++ b/docshell/base/nsDocShell.h
+@@ -452,16 +452,17 @@ protected:
+   // load. If aOriginalURI is null, aURI will be set as the originalURI.
+   // If aLoadReplace is true, LOAD_REPLACE flag will be set to the nsIChannel.
+   nsresult DoURILoad(nsIURI* aURI,
+                      nsIURI* aOriginalURI,
+                      mozilla::Maybe<nsCOMPtr<nsIURI>> const& aResultPrincipalURI,
+                      bool aKeepResultPrincipalURIIfSet,
+                      bool aLoadReplace,
+                      bool aLoadFromExternal,
++                     bool aForceAllowDataURI,
+                      nsIURI* aReferrer,
+                      bool aSendReferrer,
+                      uint32_t aReferrerPolicy,
+                      nsIPrincipal* aTriggeringPrincipal,
+                      nsIPrincipal* aPrincipalToInherit,
+                      const char* aTypeHint,
+                      const nsAString& aFileName,
+                      nsIInputStream* aPostData,
+diff --git a/docshell/base/nsDocShellLoadInfo.cpp b/docshell/base/nsDocShellLoadInfo.cpp
+--- a/docshell/base/nsDocShellLoadInfo.cpp
++++ b/docshell/base/nsDocShellLoadInfo.cpp
+@@ -63,16 +63,17 @@ SetMaybeResultPrincipalURI(nsIDocShellLo
+ } // mozilla
+ 
+ nsDocShellLoadInfo::nsDocShellLoadInfo()
+   : mResultPrincipalURIIsSome(false)
+   , mKeepResultPrincipalURIIfSet(false)
+   , mLoadReplace(false)
+   , mInheritPrincipal(false)
+   , mPrincipalIsExplicit(false)
++  , mForceAllowDataURI(false)
+   , mSendReferrer(true)
+   , mReferrerPolicy(mozilla::net::RP_Unset)
+   , mLoadType(nsIDocShellLoadInfo::loadNormal)
+   , mIsSrcdocLoad(false)
+ {
+ }
+ 
+ nsDocShellLoadInfo::~nsDocShellLoadInfo()
+@@ -221,16 +222,30 @@ nsDocShellLoadInfo::GetPrincipalIsExplic
+ NS_IMETHODIMP
+ nsDocShellLoadInfo::SetPrincipalIsExplicit(bool aPrincipalIsExplicit)
+ {
+   mPrincipalIsExplicit = aPrincipalIsExplicit;
+   return NS_OK;
+ }
+ 
+ NS_IMETHODIMP
++nsDocShellLoadInfo::GetForceAllowDataURI(bool* aForceAllowDataURI)
++{
++  *aForceAllowDataURI = mForceAllowDataURI;
++  return NS_OK;
++}
++
++NS_IMETHODIMP
++nsDocShellLoadInfo::SetForceAllowDataURI(bool aForceAllowDataURI)
++{
++  mForceAllowDataURI = aForceAllowDataURI;
++  return NS_OK;
++}
++
++NS_IMETHODIMP
+ nsDocShellLoadInfo::GetLoadType(nsDocShellInfoLoadType* aLoadType)
+ {
+   NS_ENSURE_ARG_POINTER(aLoadType);
+ 
+   *aLoadType = mLoadType;
+   return NS_OK;
+ }
+ 
+diff --git a/docshell/base/nsDocShellLoadInfo.h b/docshell/base/nsDocShellLoadInfo.h
+--- a/docshell/base/nsDocShellLoadInfo.h
++++ b/docshell/base/nsDocShellLoadInfo.h
+@@ -38,16 +38,17 @@ protected:
+   bool mResultPrincipalURIIsSome;
+   bool mKeepResultPrincipalURIIfSet; // if http-equiv="refresh" cause reload we
+                                      // do not want to replace
+                                      // ResultPrinicpalURI if it was already
+                                      // set.
+   bool mLoadReplace;
+   bool mInheritPrincipal;
+   bool mPrincipalIsExplicit;
++  bool mForceAllowDataURI;
+   bool mSendReferrer;
+   nsDocShellInfoReferrerPolicy mReferrerPolicy;
+   nsDocShellInfoLoadType mLoadType;
+   nsCOMPtr<nsISHEntry> mSHEntry;
+   nsString mTarget;
+   nsCOMPtr<nsIInputStream> mPostDataStream;
+   nsCOMPtr<nsIInputStream> mHeadersStream;
+   bool mIsSrcdocLoad;
+diff --git a/docshell/base/nsIDocShell.idl b/docshell/base/nsIDocShell.idl
+--- a/docshell/base/nsIDocShell.idl
++++ b/docshell/base/nsIDocShell.idl
+@@ -121,16 +121,19 @@ interface nsIDocShell : nsIDocShellTreeI
+   const long INTERNAL_LOAD_FLAGS_BYPASS_CLASSIFIER       = 0x10;
+   const long INTERNAL_LOAD_FLAGS_FORCE_ALLOW_COOKIES     = 0x20;
+ 
+   // Whether the load should be treated as srcdoc load, rather than a URI one.
+   const long INTERNAL_LOAD_FLAGS_IS_SRCDOC               = 0x40;
+ 
+   const long INTERNAL_LOAD_FLAGS_NO_OPENER               = 0x100;
+ 
++  // Whether a top-level data URI navigation is allowed for that load
++  const long INTERNAL_LOAD_FLAGS_FORCE_ALLOW_DATA_URI    = 0x200;
++
+   // NB: 0x80 is available.
+ 
+   /**
+    * Loads the given URI.  This method is identical to loadURI(...) except
+    * that its parameter list is broken out instead of being packaged inside
+    * of an nsIDocShellLoadInfo object...
+    *
+    * @param aURI                 - The URI to load.
+diff --git a/docshell/base/nsIDocShellLoadInfo.idl b/docshell/base/nsIDocShellLoadInfo.idl
+--- a/docshell/base/nsIDocShellLoadInfo.idl
++++ b/docshell/base/nsIDocShellLoadInfo.idl
+@@ -69,16 +69,22 @@ interface nsIDocShellLoadInfo : nsISuppo
+     /** If this attribute is true only ever use the principal specified
+      *  by the triggeringPrincipal and inheritPrincipal attributes.
+      *  If there are security reasons for why this is unsafe, such
+      *  as trying to use a systemprincipal as the triggeringPrincipal
+      *  for a content docshell the load fails.
+      */
+     attribute boolean principalIsExplicit;
+ 
++    /**
++     * If this attribute is true, then a top-level navigation
++     * to a data URI will be allowed.
++     */
++    attribute boolean forceAllowDataURI;
++
+     /* these are load type enums... */
+     const long loadNormal = 0;                     // Normal Load
+     const long loadNormalReplace = 1;              // Normal Load but replaces current history slot
+     const long loadHistory = 2;                    // Load from history
+     const long loadReloadNormal = 3;               // Reload
+     const long loadReloadBypassCache = 4;
+     const long loadReloadBypassProxy = 5;
+     const long loadReloadBypassProxyAndCache = 6;
+diff --git a/docshell/base/nsIWebNavigation.idl b/docshell/base/nsIWebNavigation.idl
+--- a/docshell/base/nsIWebNavigation.idl
++++ b/docshell/base/nsIWebNavigation.idl
+@@ -202,16 +202,22 @@ interface nsIWebNavigation : nsISupports
+   const unsigned long LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP = 0x100000;
+ 
+   /**
+    * This flag specifies that common scheme typos should be corrected.
+    */
+   const unsigned long LOAD_FLAGS_FIXUP_SCHEME_TYPOS = 0x200000;
+ 
+   /**
++   * Allows a top-level data: navigation to occur. E.g. view-image
++   * is an explicit user action which should be allowed.
++   */
++  const unsigned long LOAD_FLAGS_FORCE_ALLOW_DATA_URI = 0x400000;
++
++  /**
+    * Loads a given URI.  This will give priority to loading the requested URI
+    * in the object implementing	this interface.  If it can't be loaded here
+    * however, the URI dispatcher will go through its normal process of content
+    * loading.
+    *
+    * @param aURI
+    *        The URI string to load.  For HTTP and FTP URLs and possibly others,
+    *        characters above U+007F will be converted to UTF-8 and then URL-
+diff --git a/dom/security/nsContentSecurityManager.cpp b/dom/security/nsContentSecurityManager.cpp
+--- a/dom/security/nsContentSecurityManager.cpp
++++ b/dom/security/nsContentSecurityManager.cpp
+@@ -41,16 +41,20 @@ nsContentSecurityManager::AllowTopLevelN
+   }
+   nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
+   if (!loadInfo) {
+     return true;
+   }
+   if (loadInfo->GetExternalContentPolicyType() != nsIContentPolicy::TYPE_DOCUMENT) {
+     return true;
+   }
++  if (loadInfo->GetForceAllowDataURI()) {
++    // if the loadinfo explicitly allows the data URI navigation, let's allow it now
++    return true;
++  }
+   nsCOMPtr<nsIURI> uri;
+   nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
+   NS_ENSURE_SUCCESS(rv, true);
+   bool isDataURI =
+     (NS_SUCCEEDED(uri->SchemeIs("data", &isDataURI)) && isDataURI);
+   if (!isDataURI) {
+     return true;
+   }
+diff --git a/ipc/glue/BackgroundUtils.cpp b/ipc/glue/BackgroundUtils.cpp
+--- a/ipc/glue/BackgroundUtils.cpp
++++ b/ipc/glue/BackgroundUtils.cpp
+@@ -374,16 +374,17 @@ LoadInfoToLoadInfoArgs(nsILoadInfo *aLoa
+       sandboxedLoadingPrincipalInfo,
+       optionalResultPrincipalURI,
+       aLoadInfo->GetSecurityFlags(),
+       aLoadInfo->InternalContentPolicyType(),
+       static_cast<uint32_t>(aLoadInfo->GetTainting()),
+       aLoadInfo->GetUpgradeInsecureRequests(),
+       aLoadInfo->GetVerifySignedContent(),
+       aLoadInfo->GetEnforceSRI(),
++      aLoadInfo->GetForceAllowDataURI(),
+       aLoadInfo->GetForceInheritPrincipalDropped(),
+       aLoadInfo->GetInnerWindowID(),
+       aLoadInfo->GetOuterWindowID(),
+       aLoadInfo->GetParentOuterWindowID(),
+       aLoadInfo->GetTopOuterWindowID(),
+       aLoadInfo->GetFrameOuterWindowID(),
+       aLoadInfo->GetEnforceSecurity(),
+       aLoadInfo->GetInitialSecurityCheckDone(),
+@@ -478,16 +479,17 @@ LoadInfoArgsToLoadInfo(const OptionalLoa
+                           sandboxedLoadingPrincipal,
+                           resultPrincipalURI,
+                           loadInfoArgs.securityFlags(),
+                           loadInfoArgs.contentPolicyType(),
+                           static_cast<LoadTainting>(loadInfoArgs.tainting()),
+                           loadInfoArgs.upgradeInsecureRequests(),
+                           loadInfoArgs.verifySignedContent(),
+                           loadInfoArgs.enforceSRI(),
++                          loadInfoArgs.forceAllowDataURI(),
+                           loadInfoArgs.forceInheritPrincipalDropped(),
+                           loadInfoArgs.innerWindowID(),
+                           loadInfoArgs.outerWindowID(),
+                           loadInfoArgs.parentOuterWindowID(),
+                           loadInfoArgs.topOuterWindowID(),
+                           loadInfoArgs.frameOuterWindowID(),
+                           loadInfoArgs.enforceSecurity(),
+                           loadInfoArgs.initialSecurityCheckDone(),
+diff --git a/netwerk/base/LoadInfo.cpp b/netwerk/base/LoadInfo.cpp
+--- a/netwerk/base/LoadInfo.cpp
++++ b/netwerk/base/LoadInfo.cpp
+@@ -55,16 +55,17 @@ LoadInfo::LoadInfo(nsIPrincipal* aLoadin
+   , mLoadingContext(do_GetWeakReference(aLoadingContext))
+   , mContextForTopLevelLoad(nullptr)
+   , mSecurityFlags(aSecurityFlags)
+   , mInternalContentPolicyType(aContentPolicyType)
+   , mTainting(LoadTainting::Basic)
+   , mUpgradeInsecureRequests(false)
+   , mVerifySignedContent(false)
+   , mEnforceSRI(false)
++  , mForceAllowDataURI(false)
+   , mForceInheritPrincipalDropped(false)
+   , mInnerWindowID(0)
+   , mOuterWindowID(0)
+   , mParentOuterWindowID(0)
+   , mTopOuterWindowID(0)
+   , mFrameOuterWindowID(0)
+   , mEnforceSecurity(false)
+   , mInitialSecurityCheckDone(false)
+@@ -234,16 +235,17 @@ LoadInfo::LoadInfo(nsPIDOMWindowOuter* a
+   , mPrincipalToInherit(nullptr)
+   , mContextForTopLevelLoad(do_GetWeakReference(aContextForTopLevelLoad))
+   , mSecurityFlags(aSecurityFlags)
+   , mInternalContentPolicyType(nsIContentPolicy::TYPE_DOCUMENT)
+   , mTainting(LoadTainting::Basic)
+   , mUpgradeInsecureRequests(false)
+   , mVerifySignedContent(false)
+   , mEnforceSRI(false)
++  , mForceAllowDataURI(false)
+   , mForceInheritPrincipalDropped(false)
+   , mInnerWindowID(0)
+   , mOuterWindowID(0)
+   , mParentOuterWindowID(0)
+   , mTopOuterWindowID(0)
+   , mFrameOuterWindowID(0)
+   , mEnforceSecurity(false)
+   , mInitialSecurityCheckDone(false)
+@@ -303,16 +305,17 @@ LoadInfo::LoadInfo(const LoadInfo& rhs)
+   , mLoadingContext(rhs.mLoadingContext)
+   , mContextForTopLevelLoad(rhs.mContextForTopLevelLoad)
+   , mSecurityFlags(rhs.mSecurityFlags)
+   , mInternalContentPolicyType(rhs.mInternalContentPolicyType)
+   , mTainting(rhs.mTainting)
+   , mUpgradeInsecureRequests(rhs.mUpgradeInsecureRequests)
+   , mVerifySignedContent(rhs.mVerifySignedContent)
+   , mEnforceSRI(rhs.mEnforceSRI)
++  , mForceAllowDataURI(rhs.mForceAllowDataURI)
+   , mForceInheritPrincipalDropped(rhs.mForceInheritPrincipalDropped)
+   , mInnerWindowID(rhs.mInnerWindowID)
+   , mOuterWindowID(rhs.mOuterWindowID)
+   , mParentOuterWindowID(rhs.mParentOuterWindowID)
+   , mTopOuterWindowID(rhs.mTopOuterWindowID)
+   , mFrameOuterWindowID(rhs.mFrameOuterWindowID)
+   , mEnforceSecurity(rhs.mEnforceSecurity)
+   , mInitialSecurityCheckDone(rhs.mInitialSecurityCheckDone)
+@@ -336,16 +339,17 @@ LoadInfo::LoadInfo(nsIPrincipal* aLoadin
+                    nsIPrincipal* aSandboxedLoadingPrincipal,
+                    nsIURI* aResultPrincipalURI,
+                    nsSecurityFlags aSecurityFlags,
+                    nsContentPolicyType aContentPolicyType,
+                    LoadTainting aTainting,
+                    bool aUpgradeInsecureRequests,
+                    bool aVerifySignedContent,
+                    bool aEnforceSRI,
++                   bool aForceAllowDataURI,
+                    bool aForceInheritPrincipalDropped,
+                    uint64_t aInnerWindowID,
+                    uint64_t aOuterWindowID,
+                    uint64_t aParentOuterWindowID,
+                    uint64_t aTopOuterWindowID,
+                    uint64_t aFrameOuterWindowID,
+                    bool aEnforceSecurity,
+                    bool aInitialSecurityCheckDone,
+@@ -364,16 +368,17 @@ LoadInfo::LoadInfo(nsIPrincipal* aLoadin
+   , mPrincipalToInherit(aPrincipalToInherit)
+   , mResultPrincipalURI(aResultPrincipalURI)
+   , mSecurityFlags(aSecurityFlags)
+   , mInternalContentPolicyType(aContentPolicyType)
+   , mTainting(aTainting)
+   , mUpgradeInsecureRequests(aUpgradeInsecureRequests)
+   , mVerifySignedContent(aVerifySignedContent)
+   , mEnforceSRI(aEnforceSRI)
++  , mForceAllowDataURI(aForceAllowDataURI)
+   , mForceInheritPrincipalDropped(aForceInheritPrincipalDropped)
+   , mInnerWindowID(aInnerWindowID)
+   , mOuterWindowID(aOuterWindowID)
+   , mParentOuterWindowID(aParentOuterWindowID)
+   , mTopOuterWindowID(aTopOuterWindowID)
+   , mFrameOuterWindowID(aFrameOuterWindowID)
+   , mEnforceSecurity(aEnforceSecurity)
+   , mInitialSecurityCheckDone(aInitialSecurityCheckDone)
+@@ -751,16 +756,33 @@ LoadInfo::SetEnforceSRI(bool aEnforceSRI
+ NS_IMETHODIMP
+ LoadInfo::GetEnforceSRI(bool* aResult)
+ {
+   *aResult = mEnforceSRI;
+   return NS_OK;
+ }
+ 
+ NS_IMETHODIMP
++LoadInfo::SetForceAllowDataURI(bool aForceAllowDataURI)
++{
++  MOZ_ASSERT(!mForceAllowDataURI ||
++             mInternalContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT,
++             "can only allow data URI navigation for TYPE_DOCUMENT");
++  mForceAllowDataURI = aForceAllowDataURI;
++  return NS_OK;
++}
++
++NS_IMETHODIMP
++LoadInfo::GetForceAllowDataURI(bool* aForceAllowDataURI)
++{
++  *aForceAllowDataURI = mForceAllowDataURI;
++  return NS_OK;
++}
++
++NS_IMETHODIMP
+ LoadInfo::GetForceInheritPrincipalDropped(bool* aResult)
+ {
+   *aResult = mForceInheritPrincipalDropped;
+   return NS_OK;
+ }
+ 
+ NS_IMETHODIMP
+ LoadInfo::GetInnerWindowID(uint64_t* aResult)
+diff --git a/netwerk/base/LoadInfo.h b/netwerk/base/LoadInfo.h
+--- a/netwerk/base/LoadInfo.h
++++ b/netwerk/base/LoadInfo.h
+@@ -100,16 +100,17 @@ private:
+            nsIPrincipal* aSandboxedLoadingPrincipal,
+            nsIURI* aResultPrincipalURI,
+            nsSecurityFlags aSecurityFlags,
+            nsContentPolicyType aContentPolicyType,
+            LoadTainting aTainting,
+            bool aUpgradeInsecureRequests,
+            bool aVerifySignedContent,
+            bool aEnforceSRI,
++           bool aForceAllowDataURI,
+            bool aForceInheritPrincipalDropped,
+            uint64_t aInnerWindowID,
+            uint64_t aOuterWindowID,
+            uint64_t aParentOuterWindowID,
+            uint64_t aTopOuterWindowID,
+            uint64_t aFrameOuterWindowID,
+            bool aEnforceSecurity,
+            bool aInitialSecurityCheckDone,
+@@ -159,16 +160,17 @@ private:
+   nsWeakPtr                        mLoadingContext;
+   nsWeakPtr                        mContextForTopLevelLoad;
+   nsSecurityFlags                  mSecurityFlags;
+   nsContentPolicyType              mInternalContentPolicyType;
+   LoadTainting                     mTainting;
+   bool                             mUpgradeInsecureRequests;
+   bool                             mVerifySignedContent;
+   bool                             mEnforceSRI;
++  bool                             mForceAllowDataURI;
+   bool                             mForceInheritPrincipalDropped;
+   uint64_t                         mInnerWindowID;
+   uint64_t                         mOuterWindowID;
+   uint64_t                         mParentOuterWindowID;
+   uint64_t                         mTopOuterWindowID;
+   uint64_t                         mFrameOuterWindowID;
+   bool                             mEnforceSecurity;
+   bool                             mInitialSecurityCheckDone;
+diff --git a/netwerk/base/nsILoadInfo.idl b/netwerk/base/nsILoadInfo.idl
+--- a/netwerk/base/nsILoadInfo.idl
++++ b/netwerk/base/nsILoadInfo.idl
+@@ -533,16 +533,21 @@ interface nsILoadInfo : nsISupports
+   [infallible] attribute boolean verifySignedContent;
+ 
+   /**
+    * If true, this load will fail if it has no SRI integrity
+    */
+   [infallible] attribute boolean enforceSRI;
+ 
+   /**
++   * If true, toplevel data: URI navigation is allowed
++   */
++  [infallible] attribute boolean forceAllowDataURI;
++
++  /**
+    * The SEC_FORCE_INHERIT_PRINCIPAL flag may be dropped when a load info
+    * object is created.  Specifically, it will be dropped if the SEC_SANDBOXED
+    * flag is also present.  This flag is set if SEC_FORCE_INHERIT_PRINCIPAL was
+    * dropped.
+    */
+   [infallible] readonly attribute boolean forceInheritPrincipalDropped;
+ 
+   /**
+diff --git a/netwerk/ipc/NeckoChannelParams.ipdlh b/netwerk/ipc/NeckoChannelParams.ipdlh
+--- a/netwerk/ipc/NeckoChannelParams.ipdlh
++++ b/netwerk/ipc/NeckoChannelParams.ipdlh
+@@ -42,16 +42,17 @@ struct LoadInfoArgs
+   OptionalPrincipalInfo       sandboxedLoadingPrincipalInfo;
+   OptionalURIParams           resultPrincipalURI;
+   uint32_t                    securityFlags;
+   uint32_t                    contentPolicyType;
+   uint32_t                    tainting;
+   bool                        upgradeInsecureRequests;
+   bool                        verifySignedContent;
+   bool                        enforceSRI;
++  bool                        forceAllowDataURI;
+   bool                        forceInheritPrincipalDropped;
+   uint64_t                    innerWindowID;
+   uint64_t                    outerWindowID;
+   uint64_t                    parentOuterWindowID;
+   uint64_t                    topOuterWindowID;
+   uint64_t                    frameOuterWindowID;
+   bool                        enforceSecurity;
+   bool                        initialSecurityCheckDone;

+ 74 - 0
frg/work-js/mozilla-release/patches/1407891-2-58a1.patch

@@ -0,0 +1,74 @@
+# HG changeset patch
+# User Christoph Kerschbaumer <ckerschb@christophkerschbaumer.com>
+# Date 1510159406 -3600
+# Node ID 5a5b81ec0ae6e4854788be270674b0f9833a667e
+# Parent  d8231428b757e89e3e3f5bc8751b48c63234f10b
+Bug 1407891: Test navigation for right-click view-image on data:image/svg. r=bz
+
+diff --git a/dom/security/test/general/browser.ini b/dom/security/test/general/browser.ini
+--- a/dom/security/test/general/browser.ini
++++ b/dom/security/test/general/browser.ini
+@@ -4,8 +4,11 @@ support-files =
+   file_toplevel_data_navigations.sjs
+   file_toplevel_data_meta_redirect.html
+ [browser_test_data_download.js]
+ support-files =
+   file_data_download.html
+ [browser_test_data_text_csv.js]
+ support-files =
+   file_data_text_csv.html
++[browser_test_view_image_data_navigation.js]
++support-files =
++  file_view_image_data_navigation.html
+diff --git a/dom/security/test/general/browser_test_view_image_data_navigation.js b/dom/security/test/general/browser_test_view_image_data_navigation.js
+new file mode 100644
+--- /dev/null
++++ b/dom/security/test/general/browser_test_view_image_data_navigation.js
+@@ -0,0 +1,30 @@
++"use strict";
++
++const TEST_PAGE = getRootDirectory(gTestPath) + "file_view_image_data_navigation.html";
++
++add_task(async function test_principal_right_click_open_link_in_new_tab() {
++  await SpecialPowers.pushPrefEnv({
++    "set": [["security.data_uri.block_toplevel_data_uri_navigations", true]],
++  });
++
++  await BrowserTestUtils.withNewTab(TEST_PAGE, async function(browser) {
++    let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, true);
++
++    // simulate right-click->view-image
++    BrowserTestUtils.waitForEvent(document, "popupshown", false, event => {
++      // These are operations that must be executed synchronously with the event.
++      document.getElementById("context-viewimage").doCommand();
++      event.target.hidePopup();
++      return true;
++    });
++    BrowserTestUtils.synthesizeMouseAtCenter("#testimage",
++                                             { type: "contextmenu", button: 2 },
++                                             gBrowser.selectedBrowser);
++    await loadPromise;
++
++    await ContentTask.spawn(gBrowser.selectedBrowser, {}, async function() {
++      ok(content.document.location.toString().startsWith("data:image/svg+xml;"),
++         "data:image/svg navigation allowed through right-click view-image")
++    });
++  });
++});
+diff --git a/dom/security/test/general/file_view_image_data_navigation.html b/dom/security/test/general/file_view_image_data_navigation.html
+new file mode 100644
+--- /dev/null
++++ b/dom/security/test/general/file_view_image_data_navigation.html
+@@ -0,0 +1,12 @@
++<!DOCTYPE HTML>
++<html>
++<head>
++  <meta charset="utf-8">
++  <title>Bug 1407891: Test navigation for right-click view-image on data:image/svg</title>
++</head>
++<body>
++
++<img id="testimage" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDE2IDE2Ij4KICA8cGF0aCBkPSJNOCwxMkwzLDcsNCw2bDQsNCw0LTQsMSwxWiIgZmlsbD0iIzZBNkE2QSIgLz4KPC9zdmc+Cg=="></img>
++
++</body>
++</html>

+ 158 - 0
frg/work-js/mozilla-release/patches/1408451-58a1.patch

@@ -0,0 +1,158 @@
+# HG changeset patch
+# User Christoph Kerschbaumer <ckerschb@christophkerschbaumer.com>
+# Date 1508156332 -7200
+# Node ID 8719c44ef3e2df2124137938d61d7ef83dfac0e9
+# Parent  71a7a3bbcc4ac07004636febba2a3fe8b4848ceb
+Bug 1408451: Log to web console when blocking toplevel data: URI navigations. r=bz
+
+diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp
+--- a/docshell/base/nsDocShell.cpp
++++ b/docshell/base/nsDocShell.cpp
+@@ -9826,20 +9826,23 @@ nsDocShell::InternalLoad(nsIURI* aURI,
+       // an iframe since that's more common.
+       contentType = nsIContentPolicy::TYPE_INTERNAL_IFRAME;
+     }
+   } else {
+     contentType = nsIContentPolicy::TYPE_DOCUMENT;
+     isTargetTopLevelDocShell = true;
+   }
+ 
++  nsIDocument* doc = mContentViewer ? mContentViewer->GetDocument()
++                                    : nullptr;
+   if (!nsContentSecurityManager::AllowTopLevelNavigationToDataURI(
+         aURI,
+         contentType,
+         aTriggeringPrincipal,
++        doc,
+         (aLoadType == LOAD_NORMAL_EXTERNAL),
+         !aFileName.IsVoid())) {
+     // logging to console happens within AllowTopLevelNavigationToDataURI
+     return NS_OK;
+   }
+ 
+   // If there's no targetDocShell, that means we are about to create a new
+   // window (or aWindowTarget is empty). Perform a content policy check before
+diff --git a/docshell/base/nsDocShell.cpp.1408451.later b/docshell/base/nsDocShell.cpp.1408451.later
+new file mode 100644
+--- /dev/null
++++ b/docshell/base/nsDocShell.cpp.1408451.later
+@@ -0,0 +1,21 @@
++--- nsDocShell.cpp
+++++ nsDocShell.cpp
++@@ -10092,18 +10095,16 @@ nsDocShell::InternalLoad(nsIURI* aURI,
++ 
++         nsCOMPtr<nsIDocShellTreeItem> parent;
++         treeItem->GetSameTypeParent(getter_AddRefs(parent));
++         parent.swap(treeItem);
++       } while (treeItem);
++     }
++   }
++ 
++-  const nsIDocument* doc = mContentViewer ? mContentViewer->GetDocument()
++-                                          : nullptr;
++   const bool isDocumentAuxSandboxed = doc &&
++     (doc->GetSandboxFlags() & SANDBOXED_AUXILIARY_NAVIGATION);
++ 
++   if (aURI && mLoadURIDelegate &&
++       (!targetDocShell || targetDocShell == static_cast<nsIDocShell*>(this))) {
++     // Dispatch only load requests for the current or a new window to the
++     // delegate, e.g., to allow for GeckoView apps to handle the load event
++     // outside of Gecko.
+diff --git a/dom/security/nsContentSecurityManager.cpp b/dom/security/nsContentSecurityManager.cpp
+--- a/dom/security/nsContentSecurityManager.cpp
++++ b/dom/security/nsContentSecurityManager.cpp
+@@ -25,16 +25,17 @@ NS_IMPL_ISUPPORTS(nsContentSecurityManag
+                   nsIContentSecurityManager,
+                   nsIChannelEventSink)
+ 
+ /* static */ bool
+ nsContentSecurityManager::AllowTopLevelNavigationToDataURI(
+   nsIURI* aURI,
+   nsContentPolicyType aContentPolicyType,
+   nsIPrincipal* aTriggeringPrincipal,
++  nsIDocument* aDoc,
+   bool aLoadFromExternal,
+   bool aIsDownLoad)
+ {
+   // Let's block all toplevel document navigations to a data: URI.
+   // In all cases where the toplevel document is navigated to a
+   // data: URI the triggeringPrincipal is a codeBasePrincipal, or
+   // a NullPrincipal. In other cases, e.g. typing a data: URL into
+   // the URL-Bar, the triggeringPrincipal is a SystemPrincipal;
+@@ -72,18 +73,17 @@ nsContentSecurityManager::AllowTopLevelN
+   if (dataSpec.Length() > 50) {
+     dataSpec.Truncate(50);
+     dataSpec.AppendLiteral("...");
+   }
+   NS_ConvertUTF8toUTF16 specUTF16(NS_UnescapeURL(dataSpec));
+   const char16_t* params[] = { specUTF16.get() };
+   nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+                                   NS_LITERAL_CSTRING("DATA_URI_BLOCKED"),
+-                                  // no doc available, log to browser console
+-                                  nullptr,
++                                  aDoc,
+                                   nsContentUtils::eSECURITY_PROPERTIES,
+                                   "BlockTopLevelDataURINavigation",
+                                   params, ArrayLength(params));
+   return false;
+ }
+ 
+ static nsresult
+ ValidateSecurityFlags(nsILoadInfo* aLoadInfo)
+@@ -576,16 +576,17 @@ nsContentSecurityManager::AsyncOnChannel
+     nsCOMPtr<nsIURI> uri;
+     nsresult rv = NS_GetFinalChannelURI(aNewChannel, getter_AddRefs(uri));
+     NS_ENSURE_SUCCESS(rv, rv);
+     nsCOMPtr<nsIPrincipal> nullTriggeringPrincipal = NullPrincipal::Create();
+     if (!nsContentSecurityManager::AllowTopLevelNavigationToDataURI(
+           uri,
+           newLoadInfo->GetExternalContentPolicyType(),
+           nullTriggeringPrincipal,
++          nullptr, // no doc available, log to browser console
+           false,
+           false)) {
+         // logging to console happens within AllowTopLevelNavigationToDataURI
+       aOldChannel->Cancel(NS_ERROR_DOM_BAD_URI);
+       return NS_ERROR_DOM_BAD_URI;
+     }
+   }
+ 
+diff --git a/dom/security/nsContentSecurityManager.h b/dom/security/nsContentSecurityManager.h
+--- a/dom/security/nsContentSecurityManager.h
++++ b/dom/security/nsContentSecurityManager.h
+@@ -7,16 +7,17 @@
+ #ifndef nsContentSecurityManager_h___
+ #define nsContentSecurityManager_h___
+ 
+ #include "nsIContentSecurityManager.h"
+ #include "nsIChannel.h"
+ #include "nsIChannelEventSink.h"
+ 
+ class nsIStreamListener;
++class nsIDocument;
+ 
+ #define NS_CONTENTSECURITYMANAGER_CONTRACTID "@mozilla.org/contentsecuritymanager;1"
+ // cdcc1ab8-3cea-4e6c-a294-a651fa35227f
+ #define NS_CONTENTSECURITYMANAGER_CID \
+ { 0xcdcc1ab8, 0x3cea, 0x4e6c, \
+   { 0xa2, 0x94, 0xa6, 0x51, 0xfa, 0x35, 0x22, 0x7f } }
+ 
+ class nsContentSecurityManager : public nsIContentSecurityManager
+@@ -30,16 +31,17 @@ public:
+   nsContentSecurityManager() {}
+ 
+   static nsresult doContentSecurityCheck(nsIChannel* aChannel,
+                                          nsCOMPtr<nsIStreamListener>& aInAndOutListener);
+ 
+   static bool AllowTopLevelNavigationToDataURI(nsIURI* aURI,
+                                                nsContentPolicyType aContentPolicyType,
+                                                nsIPrincipal* aTriggeringPrincipal,
++                                               nsIDocument* aDoc,
+                                                bool aLoadFromExternal,
+                                                bool aIsDownload);
+ 
+ private:
+   static nsresult CheckChannel(nsIChannel* aChannel);
+ 
+   virtual ~nsContentSecurityManager() {}
+ 

+ 33 - 0
frg/work-js/mozilla-release/patches/1414609-58a1.patch

@@ -0,0 +1,33 @@
+# HG changeset patch
+# User abhinav <abhinav.koppula@gmail.com>
+# Date 1509878198 -19800
+# Node ID 34b559b82c0354ba6bbe32c932994db991cb38c3
+# Parent  e3940ff93fae9df7d72dfee9f7eaca7645cce2c0
+Bug 1414609 - Changing z-index of filter autocomplete suggestion box to prevent being hidden behind netmonitor header. r=Honza
+
+MozReview-Commit-ID: BpS8nLI1XTM
+
+diff --git a/devtools/client/themes/common.css b/devtools/client/themes/common.css
+--- a/devtools/client/themes/common.css
++++ b/devtools/client/themes/common.css
+@@ -489,17 +489,19 @@ checkbox:-moz-focusring {
+   outline: none;
+ }
+ 
+ .devtools-searchbox .devtools-autocomplete-popup {
+   position: absolute;
+   top: 100%;
+   width: 100%;
+   line-height: initial !important;
+-  z-index: 999;
++  /* See bug - 1414609. z-index is greater than 1000 so that searchbox's z-index
++  is more than z-index of requests-list-headers-wrapper in netmonitor */
++  z-index: 1001;
+ }
+ 
+ /* Don't add 'double spacing' for inputs that are at beginning / end
+    of a toolbar (since the toolbar has it's own spacing). */
+ .devtools-toolbar > .devtools-textinput:first-child,
+ .devtools-toolbar > .devtools-searchinput:first-child,
+ .devtools-toolbar > .devtools-filterinput:first-child {
+   margin-inline-start: 0;

+ 84 - 0
frg/work-js/mozilla-release/patches/1415612-59a1.patch

@@ -0,0 +1,84 @@
+# HG changeset patch
+# User Christoph Kerschbaumer <ckerschb@christophkerschbaumer.com>
+# Date 1510604702 -3600
+# Node ID 4017a9d65a94457f05df28569d9ac69439dcd566
+# Parent  b1598e107af4c9c6401862bbef754638ab77642c
+Bug 1415612: Allow all plain text types when navigating top-level data URIs. r=bz
+
+diff --git a/dom/security/moz.build b/dom/security/moz.build
+--- a/dom/security/moz.build
++++ b/dom/security/moz.build
+@@ -42,9 +42,10 @@ UNIFIED_SOURCES += [
+ 
+ include('/ipc/chromium/chromium-config.mozbuild')
+ 
+ FINAL_LIBRARY = 'xul'
+ LOCAL_INCLUDES += [
+     '/caps',
+     '/docshell/base',  # for nsDocShell.h
+     '/netwerk/base',
++    '/netwerk/protocol/data', # for nsDataHandler.h
+ ]
+diff --git a/dom/security/nsContentSecurityManager.cpp b/dom/security/nsContentSecurityManager.cpp
+--- a/dom/security/nsContentSecurityManager.cpp
++++ b/dom/security/nsContentSecurityManager.cpp
+@@ -1,16 +1,17 @@
+ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+ /* 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/. */
+ 
+ #include "nsContentSecurityManager.h"
+ #include "nsEscape.h"
++#include "nsDataHandler.h"
+ #include "nsIChannel.h"
+ #include "nsIHttpChannelInternal.h"
+ #include "nsIStreamListener.h"
+ #include "nsILoadInfo.h"
+ #include "nsIOService.h"
+ #include "nsContentUtils.h"
+ #include "nsCORSListenerProxy.h"
+ #include "nsIStreamListener.h"
+@@ -53,26 +54,34 @@ nsContentSecurityManager::AllowTopLevelN
+   nsCOMPtr<nsIURI> uri;
+   nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
+   NS_ENSURE_SUCCESS(rv, true);
+   bool isDataURI =
+     (NS_SUCCEEDED(uri->SchemeIs("data", &isDataURI)) && isDataURI);
+   if (!isDataURI) {
+     return true;
+   }
++
++  nsAutoCString spec;
++  rv = uri->GetSpec(spec);
++  NS_ENSURE_SUCCESS(rv, true);
++  nsAutoCString contentType;
++  bool base64;
++  rv = nsDataHandler::ParseURI(spec, contentType, nullptr, 
++                               base64, nullptr);
++  NS_ENSURE_SUCCESS(rv, true);
++
+   // Whitelist data: images as long as they are not SVGs
+-  nsAutoCString filePath;
+-  uri->GetFilePath(filePath);
+-  if (StringBeginsWith(filePath, NS_LITERAL_CSTRING("image/")) &&
+-      !StringBeginsWith(filePath, NS_LITERAL_CSTRING("image/svg+xml"))) {
++  if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("image/")) &&
++      !contentType.EqualsLiteral("image/svg+xml")) {
+     return true;
+   }
+-  // Whitelist data: PDFs and JSON
+-  if (StringBeginsWith(filePath, NS_LITERAL_CSTRING("application/pdf")) ||
+-      StringBeginsWith(filePath, NS_LITERAL_CSTRING("application/json"))) {
++  // Whitelist all plain text types as well as data: PDFs.
++  if (nsContentUtils::IsPlainTextType(contentType) ||
++      contentType.EqualsLiteral("application/pdf")) {
+     return true;
+   }
+   // Redirecting to a toplevel data: URI is not allowed, hence we make
+   // sure the RedirectChain is empty.
+   if (!loadInfo->GetLoadTriggeredFromExternal() &&
+       nsContentUtils::IsSystemPrincipal(loadInfo->TriggeringPrincipal()) &&
+       loadInfo->RedirectChain().IsEmpty()) {
+     return true;

+ 330 - 0
frg/work-js/mozilla-release/patches/1430558-61a1.patch

@@ -0,0 +1,330 @@
+# HG changeset patch
+# User Julian Descottes <jdescottes@mozilla.com>
+# Date 1515800479 -3600
+# Node ID b318d775a2c8ec2e612c62b07e1c78718d3645de
+# Parent  68b498ece10c8123bb3f5ee66953ab0d4de7b514
+Bug 1430558 - add closing parenthesis automatically in CSS autocompletes;r=gl
+
+MozReview-Commit-ID: LLBrLC3Bq0t
+
+diff --git a/devtools/client/inspector/markup/test/browser_markup_css_completion_style_attribute_02.js b/devtools/client/inspector/markup/test/browser_markup_css_completion_style_attribute_02.js
+--- a/devtools/client/inspector/markup/test/browser_markup_css_completion_style_attribute_02.js
++++ b/devtools/client/inspector/markup/test/browser_markup_css_completion_style_attribute_02.js
+@@ -78,20 +78,20 @@ const TEST_DATA_INNER = [
+   ["\"", "style=\"", 7, 7, false],
+   ["b", "style=\"border", 8, 13, true],
+   ["a", "style=\"background", 9, 17, true],
+   ["VK_RIGHT", "style=\"background", 17, 17, false],
+   [":", "style=\"background:aliceblue", 18, 27, true],
+   ["u", "style=\"background:unset", 19, 23, true],
+   ["r", "style=\"background:url", 20, 21, false],
+   ["l", "style=\"background:url", 21, 21, false],
+-  ["(", "style=\"background:url(", 22, 22, false],
+-  ["'", "style=\"background:url('", 23, 23, false],
+-  ["1", "style=\"background:url('1", 24, 24, false],
+-  ["'", "style=\"background:url('1'", 25, 25, false],
++  ["(", "style=\"background:url()", 22, 22, false],
++  ["'", "style=\"background:url(')", 23, 23, false],
++  ["1", "style=\"background:url('1)", 24, 24, false],
++  ["'", "style=\"background:url('1')", 25, 25, false],
+   [")", "style=\"background:url('1')", 26, 26, false],
+   [";", "style=\"background:url('1');", 27, 27, false],
+   [" ", "style=\"background:url('1'); ", 28, 28, false],
+   ["c", "style=\"background:url('1'); color", 29, 33, true],
+   ["VK_RIGHT", "style=\"background:url('1'); color", 33, 33, false],
+   [":", "style=\"background:url('1'); color:aliceblue", 34, 43, true],
+   ["b", "style=\"background:url('1'); color:beige", 35, 39, true],
+   ["VK_RETURN", "style=\"background:url('1'); color:beige\"", -1, -1, false]
+diff --git a/devtools/client/shared/inplace-editor.js b/devtools/client/shared/inplace-editor.js
+--- a/devtools/client/shared/inplace-editor.js
++++ b/devtools/client/shared/inplace-editor.js
+@@ -21,16 +21,18 @@
+  * See editableField() for more options.
+  */
+ 
+ "use strict";
+ 
+ const Services = require("Services");
+ const focusManager = Services.focus;
+ const {KeyCodes} = require("devtools/client/shared/keycodes");
++const EventEmitter = require("devtools/shared/event-emitter");
++const { findMostRelevantCssPropertyIndex } = require("./suggestion-picker");
+ 
+ loader.lazyRequireGetter(this, "AppConstants", "resource://gre/modules/AppConstants.jsm", true);
+ 
+ const HTML_NS = "http://www.w3.org/1999/xhtml";
+ const CONTENT_TYPES = {
+   PLAIN_TEXT: 0,
+   CSS_VALUE: 1,
+   CSS_MIXED: 2,
+@@ -39,18 +41,20 @@ const CONTENT_TYPES = {
+ 
+ // The limit of 500 autocomplete suggestions should not be reached but is kept
+ // for safety.
+ const MAX_POPUP_ENTRIES = 500;
+ 
+ const FOCUS_FORWARD = focusManager.MOVEFOCUS_FORWARD;
+ const FOCUS_BACKWARD = focusManager.MOVEFOCUS_BACKWARD;
+ 
+-const EventEmitter = require("devtools/shared/event-emitter");
+-const { findMostRelevantCssPropertyIndex } = require("./suggestion-picker");
++const WORD_REGEXP = /\w/;
++const isWordChar = function(str) {
++  return str && WORD_REGEXP.test(str);
++};
+ 
+ /**
+  * Helper to check if the provided key matches one of the expected keys.
+  * Keys will be prefixed with DOM_VK_ and should match a key in KeyCodes.
+  *
+  * @param {String} key
+  *        the key to check (can be a keyCode).
+  * @param {...String} keys
+@@ -1046,16 +1050,20 @@ InplaceEditor.prototype = {
+    * Handle the input field's keypress event.
+    */
+   _onKeyPress: function(event) {
+     let prevent = false;
+ 
+     let key = event.keyCode;
+     let input = this.input;
+ 
++    // We want to autoclose some characters, remember the pressed key in order to process
++    // it later on in maybeSuggestionCompletion().
++    this._pressedKey = event.key;
++
+     let multilineNavigation = !this._isSingleLine() &&
+       isKeyIn(key, "UP", "DOWN", "LEFT", "RIGHT");
+     let isPlainText = this.contentType == CONTENT_TYPES.PLAIN_TEXT;
+     let isPopupOpen = this.popup && this.popup.isOpen;
+ 
+     let increment = 0;
+     if (!isPlainText && !multilineNavigation) {
+       increment = this._getIncrement(event);
+@@ -1288,16 +1296,17 @@ InplaceEditor.prototype = {
+    * @param {Boolean} autoInsert
+    *        Pass true to automatically insert the most relevant suggestion.
+    */
+   _maybeSuggestCompletion: function(autoInsert) {
+     // Input can be null in cases when you intantaneously switch out of it.
+     if (!this.input) {
+       return;
+     }
++
+     let preTimeoutQuery = this.input.value;
+ 
+     // Since we are calling this method from a keypress event handler, the
+     // |input.value| does not include currently typed character. Thus we perform
+     // this method async.
+     this._openPopupTimeout = this.doc.defaultView.setTimeout(() => {
+       if (this._preventSuggestions) {
+         this._preventSuggestions = false;
+@@ -1472,23 +1481,69 @@ InplaceEditor.prototype = {
+         let selectedIndex = autoInsert ? index : -1;
+ 
+         // Open the suggestions popup.
+         this.popup.setItems(finalList);
+         this._openAutocompletePopup(offset, selectedIndex);
+       } else {
+         this._hideAutocompletePopup();
+       }
++
++      this._autocloseParenthesis();
++
+       // This emit is mainly for the purpose of making the test flow simpler.
+       this.emit("after-suggest");
+       this._doValidation();
+     }, 0);
+   },
+ 
+   /**
++   * Automatically add closing parenthesis and skip closing parenthesis when needed.
++   */
++  _autocloseParenthesis: function() {
++    // Split the current value at the cursor index to rebuild the string.
++    let parts = this._splitStringAt(this.input.value, this.input.selectionStart);
++
++    // Lookup the character following the caret to know if the string should be modified.
++    let nextChar = parts[1][0];
++
++    // Autocomplete closing parenthesis if the last key pressed was "(" and the next
++    // character is not a "word" character.
++    if (this._pressedKey == "(" && !isWordChar(nextChar)) {
++      this._updateValue(parts[0] + ")" + parts[1]);
++    }
++
++    // Skip inserting ")" if the next character is already a ")" (note that we actually
++    // insert and remove the extra ")" here, as the input has already been modified).
++    if (this._pressedKey == ")" && nextChar == ")") {
++      this._updateValue(parts[0] + parts[1].substring(1));
++    }
++
++    this._pressedKey = null;
++  },
++
++  /**
++   * Update the current value of the input while preserving the caret position.
++   */
++  _updateValue: function(str) {
++    let start = this.input.selectionStart;
++    this.input.value = str;
++    this.input.setSelectionRange(start, start);
++    this._updateSize();
++  },
++
++  /**
++   * Split the provided string at the provided index. Returns an array of two strings.
++   * _splitStringAt("1234567", 3) will return ["123", "4567"]
++   */
++  _splitStringAt: function(str, index) {
++    return [str.substring(0, index), str.substring(index, str.length)];
++  },
++
++  /**
+    * Check if the current input is displaying more than one line of text.
+    *
+    * @return {Boolean} true if the input has a single line of text
+    */
+   _isSingleLine: function() {
+     let inputRect = this.input.getBoundingClientRect();
+     return inputRect.height < 2 * this.inputCharDimensions.height;
+   },
+diff --git a/devtools/client/shared/test/browser.ini b/devtools/client/shared/test/browser.ini
+--- a/devtools/client/shared/test/browser.ini
++++ b/devtools/client/shared/test/browser.ini
+@@ -142,16 +142,17 @@ skip-if = e10s # Bug 1221911, bug 122228
+ [browser_html_tooltip_hover.js]
+ [browser_html_tooltip_offset.js]
+ [browser_html_tooltip_rtl.js]
+ [browser_html_tooltip_variable-height.js]
+ [browser_html_tooltip_width-auto.js]
+ [browser_html_tooltip_xul-wrapper.js]
+ [browser_inplace-editor-01.js]
+ [browser_inplace-editor-02.js]
++[browser_inplace-editor_autoclose_parentheses.js]
+ [browser_inplace-editor_autocomplete_01.js]
+ [browser_inplace-editor_autocomplete_02.js]
+ [browser_inplace-editor_autocomplete_offset.js]
+ [browser_inplace-editor_autocomplete_css_variable.js]
+ [browser_inplace-editor_maxwidth.js]
+ [browser_keycodes.js]
+ [browser_key_shortcuts.js]
+ [browser_layoutHelpers.js]
+diff --git a/devtools/client/shared/test/browser_inplace-editor_autoclose_parentheses.js b/devtools/client/shared/test/browser_inplace-editor_autoclose_parentheses.js
+new file mode 100644
+--- /dev/null
++++ b/devtools/client/shared/test/browser_inplace-editor_autoclose_parentheses.js
+@@ -0,0 +1,73 @@
++/* vim: set ts=2 et sw=2 tw=80: */
++/* Any copyright is dedicated to the Public Domain.
++   http://creativecommons.org/publicdomain/zero/1.0/ */
++/* import-globals-from helper_inplace_editor.js */
++
++"use strict";
++
++const AutocompletePopup = require("devtools/client/shared/autocomplete-popup");
++const { InplaceEditor } = require("devtools/client/shared/inplace-editor");
++loadHelperScript("helper_inplace_editor.js");
++
++// Test the inplace-editor closes parentheses automatically.
++
++// format :
++//  [
++//    what key to press,
++//    expected input box value after keypress,
++//    selected suggestion index (-1 if popup is hidden),
++//    number of suggestions in the popup (0 if popup is hidden),
++//  ]
++const testData = [
++  ["u", "u", -1, 0],
++  ["r", "ur", -1, 0],
++  ["l", "url", -1, 0],
++  ["(", "url()", -1, 0],
++  ["v", "url(v)", -1, 0],
++  ["a", "url(va)", -1, 0],
++  ["r", "url(var)", -1, 0],
++  ["(", "url(var())", -1, 0],
++  ["-", "url(var(-))", -1, 0],
++  ["-", "url(var(--))", -1, 0],
++  ["a", "url(var(--a))", -1, 0],
++  [")", "url(var(--a))", -1, 0],
++  [")", "url(var(--a))", -1, 0],
++];
++
++add_task(async function() {
++  await addTab("data:text/html;charset=utf-8," +
++    "inplace editor parentheses autoclose");
++  let [host, win, doc] = await createHost();
++
++  let xulDocument = win.top.document;
++  let popup = new AutocompletePopup(xulDocument, { autoSelect: true });
++  await new Promise(resolve => {
++    createInplaceEditorAndClick({
++      start: runPropertyAutocompletionTest,
++      contentType: InplaceEditor.CONTENT_TYPES.CSS_VALUE,
++      property: {
++        name: "background-image"
++      },
++      cssVariables: new Map(),
++      done: resolve,
++      popup: popup
++    }, doc);
++  });
++
++  popup.destroy();
++  host.destroy();
++  gBrowser.removeCurrentTab();
++});
++
++let runPropertyAutocompletionTest = async function(editor) {
++  info("Starting to test for css property completion");
++
++  // No need to test autocompletion here, return an empty array.
++  editor._getCSSValuesForPropertyName = () => [];
++
++  for (let data of testData) {
++    await testCompletion(data, editor);
++  }
++
++  EventUtils.synthesizeKey("VK_RETURN", {}, editor.input.defaultView);
++};
+diff --git a/devtools/client/shared/test/browser_inplace-editor_autocomplete_css_variable.js b/devtools/client/shared/test/browser_inplace-editor_autocomplete_css_variable.js
+--- a/devtools/client/shared/test/browser_inplace-editor_autocomplete_css_variable.js
++++ b/devtools/client/shared/test/browser_inplace-editor_autocomplete_css_variable.js
+@@ -22,26 +22,26 @@ loadHelperScript("helper_inplace_editor.
+ //    selected suggestion index (-1 if popup is hidden),
+ //    number of suggestions in the popup (0 if popup is hidden),
+ //    expected post label corresponding with the input box value,
+ //  ]
+ const testData = [
+   ["v", "v", -1, 0, null],
+   ["a", "va", -1, 0, null],
+   ["r", "var", -1, 0, null],
+-  ["(", "var(", -1, 0, null],
+-  ["-", "var(--abc", 0, 4, "blue"],
+-  ["VK_BACK_SPACE", "var(-", -1, 0, null],
+-  ["-", "var(--abc", 0, 4, "blue"],
+-  ["VK_DOWN", "var(--def", 1, 4, "red"],
+-  ["VK_DOWN", "var(--ghi", 2, 4, "green"],
+-  ["VK_DOWN", "var(--jkl", 3, 4, "yellow"],
+-  ["VK_DOWN", "var(--abc", 0, 4, "blue"],
+-  ["VK_DOWN", "var(--def", 1, 4, "red"],
+-  ["VK_LEFT", "var(--def", -1, 0, null],
++  ["(", "var()", -1, 0, null],
++  ["-", "var(--abc)", 0, 4, "blue"],
++  ["VK_BACK_SPACE", "var(-)", -1, 0, null],
++  ["-", "var(--abc)", 0, 4, "blue"],
++  ["VK_DOWN", "var(--def)", 1, 4, "red"],
++  ["VK_DOWN", "var(--ghi)", 2, 4, "green"],
++  ["VK_DOWN", "var(--jkl)", 3, 4, "yellow"],
++  ["VK_DOWN", "var(--abc)", 0, 4, "blue"],
++  ["VK_DOWN", "var(--def)", 1, 4, "red"],
++  ["VK_LEFT", "var(--def)", -1, 0, null],
+ ];
+ 
+ const CSS_VARIABLES = [
+   ["--abc", "blue"],
+   ["--def", "red"],
+   ["--ghi", "green"],
+   ["--jkl", "yellow"]
+ ];

+ 356 - 0
frg/work-js/mozilla-release/patches/1431949-61a1.patch

@@ -0,0 +1,356 @@
+# HG changeset patch
+# User Sarah Childs <sarahchilds19@gmail.com>
+# Date 1522801942 14400
+# Node ID 3f6eb728f2426264a3edb35957703e3748b8bda3
+# Parent  5bf5228ef8518e714367bb66561c66e07433cc03
+Bug 1431949 - Show variable values in the CSS variable autocomplete popup. r=jdescottes
+
+diff --git a/devtools/client/shared/autocomplete-popup.js b/devtools/client/shared/autocomplete-popup.js
+--- a/devtools/client/shared/autocomplete-popup.js
++++ b/devtools/client/shared/autocomplete-popup.js
+@@ -249,20 +249,26 @@ AutocompletePopup.prototype = {
+   __maxLabelLength: -1,
+ 
+   get _maxLabelLength() {
+     if (this.__maxLabelLength !== -1) {
+       return this.__maxLabelLength;
+     }
+ 
+     let max = 0;
+-    for (let {label, count} of this.items) {
++
++    for (let {label, postLabel, count} of this.items) {
+       if (count) {
+         label += count + "";
+       }
++
++      if (postLabel) {
++        label += postLabel;
++      }
++
+       max = Math.max(label.length, max);
+     }
+ 
+     this.__maxLabelLength = max;
+     return this.__maxLabelLength;
+   },
+ 
+   /**
+@@ -417,39 +423,54 @@ AutocompletePopup.prototype = {
+    *        The item object can have the following properties:
+    *        - label {String} Property which is used as the displayed value.
+    *        - preLabel {String} [Optional] The String that will be displayed
+    *                   before the label indicating that this is the already
+    *                   present text in the input box, and label is the text
+    *                   that will be auto completed. When this property is
+    *                   present, |preLabel.length| starting characters will be
+    *                   removed from label.
++   *        - postLabel {String} [Optional] The string that will be displayed
++   *                  after the label. Currently used to display the value of
++   *                  a desired variable.
+    *        - count {Number} [Optional] The number to represent the count of
+    *                autocompleted label.
+    */
+   appendItem: function(item) {
+     let listItem = this._document.createElementNS(HTML_NS, "li");
+     // Items must have an id for accessibility.
+     listItem.setAttribute("id", "autocomplete-item-" + itemIdCounter++);
+     listItem.className = "autocomplete-item";
+     listItem.setAttribute("data-index", this.items.length);
++
+     if (this.direction) {
+       listItem.setAttribute("dir", this.direction);
+     }
++
+     let label = this._document.createElementNS(HTML_NS, "span");
+     label.textContent = item.label;
+     label.className = "autocomplete-value";
++
+     if (item.preLabel) {
+       let preDesc = this._document.createElementNS(HTML_NS, "span");
+       preDesc.textContent = item.preLabel;
+       preDesc.className = "initial-value";
+       listItem.appendChild(preDesc);
+       label.textContent = item.label.slice(item.preLabel.length);
+     }
++
+     listItem.appendChild(label);
++
++    if (item.postLabel) {
++      let postDesc = this._document.createElementNS(HTML_NS, "span");
++      postDesc.textContent = item.postLabel;
++      postDesc.className = "autocomplete-postlabel";
++      listItem.appendChild(postDesc);
++    }
++
+     if (item.count && item.count > 1) {
+       let countDesc = this._document.createElementNS(HTML_NS, "span");
+       countDesc.textContent = item.count;
+       countDesc.setAttribute("flex", "1");
+       countDesc.className = "autocomplete-count";
+       listItem.appendChild(countDesc);
+     }
+ 
+diff --git a/devtools/client/shared/inplace-editor.js b/devtools/client/shared/inplace-editor.js
+--- a/devtools/client/shared/inplace-editor.js
++++ b/devtools/client/shared/inplace-editor.js
+@@ -1327,17 +1327,20 @@ InplaceEditor.prototype = {
+         // Check if the next character is a valid word character, no suggestion should be
+         // provided when preceeding a word.
+         if (/[\w-]/.test(nextChar)) {
+           // This emit is mainly to make the test flow simpler.
+           this.emit("after-suggest", "nothing to autocomplete");
+           return;
+         }
+       }
++
+       let list = [];
++      let postLabelValues = [];
++
+       if (this.contentType == CONTENT_TYPES.CSS_PROPERTY) {
+         list = this._getCSSPropertyList();
+       } else if (this.contentType == CONTENT_TYPES.CSS_VALUE) {
+         // Get the last query to be completed before the caret.
+         let match = /([^\s,.\/]+$)/.exec(query);
+         if (match) {
+           startCheckQuery = match[0];
+         } else {
+@@ -1345,16 +1348,17 @@ InplaceEditor.prototype = {
+         }
+ 
+         // Check if the query to be completed is a CSS variable.
+         let varMatch = /^var\(([^\s]+$)/.exec(startCheckQuery);
+ 
+         if (varMatch && varMatch.length == 2) {
+           startCheckQuery = varMatch[1];
+           list = this._getCSSVariableNames();
++          postLabelValues = list.map(varName => this._getCSSVariableValue(varName));
+         } else {
+           list = ["!important",
+                   ...this._getCSSValuesForPropertyName(this.property.name)];
+         }
+ 
+         if (query == "") {
+           // Do not suggest '!important' without any manually typed character.
+           list.splice(0, 1);
+@@ -1410,17 +1414,18 @@ InplaceEditor.prototype = {
+ 
+       let finalList = [];
+       let length = list.length;
+       for (let i = 0, count = 0; i < length && count < MAX_POPUP_ENTRIES; i++) {
+         if (startCheckQuery != null && list[i].startsWith(startCheckQuery)) {
+           count++;
+           finalList.push({
+             preLabel: startCheckQuery,
+-            label: list[i]
++            label: list[i],
++            postLabel: postLabelValues[i] ? postLabelValues[i] : ""
+           });
+         } else if (count > 0) {
+           // Since count was incremented, we had already crossed the entries
+           // which would have started with query, assuming that list is sorted.
+           break;
+         } else if (startCheckQuery != null && list[i][0] > startCheckQuery[0]) {
+           // We have crossed all possible matches alphabetically.
+           break;
+@@ -1513,16 +1518,27 @@ InplaceEditor.prototype = {
+   /**
+    * Returns the list of all CSS variables to use for the autocompletion.
+    *
+    * @return {Array} array of CSS variable names (Strings)
+    */
+   _getCSSVariableNames: function() {
+     return Array.from(this.cssVariables.keys()).sort();
+   },
++
++  /**
++  * Returns the variable's value for the given CSS variable name.
++  *
++  * @param {String} varName
++  *        The variable name to retrieve the value of
++  * @return {String} the variable value to the given CSS variable name
++  */
++  _getCSSVariableValue: function(varName) {
++    return this.cssVariables.get(varName);
++  },
+ };
+ 
+ /**
+  * Copy text-related styles from one element to another.
+  */
+ function copyTextStyles(from, to) {
+   let win = from.ownerDocument.defaultView;
+   let style = win.getComputedStyle(from);
+diff --git a/devtools/client/shared/test/browser_inplace-editor_autocomplete_css_variable.js b/devtools/client/shared/test/browser_inplace-editor_autocomplete_css_variable.js
+--- a/devtools/client/shared/test/browser_inplace-editor_autocomplete_css_variable.js
++++ b/devtools/client/shared/test/browser_inplace-editor_autocomplete_css_variable.js
+@@ -16,69 +16,72 @@ loadHelperScript("helper_inplace_editor.
+ // typing in "var"
+ 
+ // format :
+ //  [
+ //    what key to press,
+ //    expected input box value after keypress,
+ //    selected suggestion index (-1 if popup is hidden),
+ //    number of suggestions in the popup (0 if popup is hidden),
++//    expected post label corresponding with the input box value,
+ //  ]
+ const testData = [
+-  ["v", "v", -1, 0],
+-  ["a", "va", -1, 0],
+-  ["r", "var", -1, 0],
+-  ["(", "var(", -1, 0],
+-  ["-", "var(--abc", 0, 2],
+-  ["VK_BACK_SPACE", "var(-", -1, 0],
+-  ["-", "var(--abc", 0, 2],
+-  ["VK_DOWN", "var(--def", 1, 2],
+-  ["VK_DOWN", "var(--abc", 0, 2],
+-  ["VK_LEFT", "var(--abc", -1, 0],
++  ["v", "v", -1, 0, null],
++  ["a", "va", -1, 0, null],
++  ["r", "var", -1, 0, null],
++  ["(", "var(", -1, 0, null],
++  ["-", "var(--abc", 0, 4, "blue"],
++  ["VK_BACK_SPACE", "var(-", -1, 0, null],
++  ["-", "var(--abc", 0, 4, "blue"],
++  ["VK_DOWN", "var(--def", 1, 4, "red"],
++  ["VK_DOWN", "var(--ghi", 2, 4, "green"],
++  ["VK_DOWN", "var(--jkl", 3, 4, "yellow"],
++  ["VK_DOWN", "var(--abc", 0, 4, "blue"],
++  ["VK_DOWN", "var(--def", 1, 4, "red"],
++  ["VK_LEFT", "var(--def", -1, 0, null],
++];
++
++const CSS_VARIABLES = [
++  ["--abc", "blue"],
++  ["--def", "red"],
++  ["--ghi", "green"],
++  ["--jkl", "yellow"]
+ ];
+ 
+ const mockGetCSSValuesForPropertyName = function(propertyName) {
+   return [];
+ };
+ 
+-const mockGetCSSVariableNames = function() {
+-  return [
+-    "--abc",
+-    "--def",
+-  ];
+-};
+-
+ add_task(async function() {
+-  await addTab("data:text/html;charset=utf-8," +
+-    "inplace editor CSS variable autocomplete");
++  await addTab("data:text/html;charset=utf-8,inplace editor CSS variable autocomplete");
+   let [host, win, doc] = await createHost();
+ 
+   let xulDocument = win.top.document;
+   let popup = new AutocompletePopup(xulDocument, { autoSelect: true });
+ 
+   await new Promise(resolve => {
+     createInplaceEditorAndClick({
+       start: runAutocompletionTest,
+       contentType: InplaceEditor.CONTENT_TYPES.CSS_VALUE,
+       property: {
+         name: "color"
+       },
++      cssVariables: new Map(CSS_VARIABLES),
+       done: resolve,
+       popup: popup
+     }, doc);
+   });
+ 
+   popup.destroy();
+   host.destroy();
+   gBrowser.removeCurrentTab();
+ });
+ 
+ let runAutocompletionTest = async function(editor) {
+   info("Starting to test for css variable completion");
+   editor._getCSSValuesForPropertyName = mockGetCSSValuesForPropertyName;
+-  editor._getCSSVariableNames = mockGetCSSVariableNames;
+ 
+   for (let data of testData) {
+     await testCompletion(data, editor);
+   }
+ 
+   EventUtils.synthesizeKey("VK_RETURN", {}, editor.input.defaultView);
+ };
+diff --git a/devtools/client/shared/test/helper_inplace_editor.js b/devtools/client/shared/test/helper_inplace_editor.js
+--- a/devtools/client/shared/test/helper_inplace_editor.js
++++ b/devtools/client/shared/test/helper_inplace_editor.js
+@@ -67,20 +67,21 @@ function createSpan(doc) {
+  * Test helper simulating a key event in an InplaceEditor and checking that the
+  * autocompletion works as expected.
+  *
+  * @param {Array} testData
+  *        - {String} key, the key to send
+  *        - {String} completion, the expected value of the auto-completion
+  *        - {Number} index, the index of the selected suggestion in the popup
+  *        - {Number} total, the total number of suggestions in the popup
++ *        - {String} postLabel, the expected post label for the selected suggestion
+  * @param {InplaceEditor} editor
+  *        The InplaceEditor instance being tested
+  */
+-async function testCompletion([key, completion, index, total], editor) {
++async function testCompletion([key, completion, index, total, postLabel], editor) {
+   info("Pressing key " + key);
+   info("Expecting " + completion);
+ 
+   let onVisibilityChange = null;
+   let open = total > 0;
+   if (editor.popup.isOpen != open) {
+     onVisibilityChange = editor.popup.once(open ? "popup-opened" : "popup-closed");
+   }
+@@ -100,16 +101,24 @@ async function testCompletion([key, comp
+   await onSuggest;
+   await onVisibilityChange;
+   await waitForTime(5);
+ 
+   info("Checking the state");
+   if (completion !== null) {
+     is(editor.input.value, completion, "Correct value is autocompleted");
+   }
++
++  if (postLabel) {
++    let selectedItem = editor.popup.getItems()[index];
++    let selectedElement = editor.popup.elements.get(selectedItem);
++    ok(selectedElement.textContent.includes(postLabel),
++      "Selected popup element contains the expected post-label");
++  }
++
+   if (total === 0) {
+     ok(!(editor.popup && editor.popup.isOpen), "Popup is closed");
+   } else {
+     ok(editor.popup.isOpen, "Popup is open");
+     is(editor.popup.getItems().length, total, "Number of suggestions match");
+     is(editor.popup.selectedIndex, index, "Expected item is selected");
+   }
+ }
+diff --git a/devtools/client/themes/common.css b/devtools/client/themes/common.css
+--- a/devtools/client/themes/common.css
++++ b/devtools/client/themes/common.css
+@@ -96,16 +96,22 @@ html|button, html|select {
+   white-space: pre;
+   overflow: hidden;
+ }
+ 
+ .devtools-autocomplete-listbox .autocomplete-item > .initial-value,
+ .devtools-autocomplete-listbox .autocomplete-item > .autocomplete-value {
+   margin: 0;
+   padding: 0;
++  float: left;
++}
++
++.devtools-autocomplete-listbox .autocomplete-item > .autocomplete-postlabel {
++  font-style: italic;
++  float: right;
+ }
+ 
+ .devtools-autocomplete-listbox .autocomplete-item > .autocomplete-count {
+   text-align: end;
+ }
+ 
+ /* Rest of the dark and light theme */
+ 

+ 92 - 0
frg/work-js/mozilla-release/patches/1437730-1-61a1.patch

@@ -0,0 +1,92 @@
+# HG changeset patch
+# User Daisuke Akatsuka <dakatsuka@mozilla.com>
+# Date 1521526231 -32400
+# Node ID ca779308cf22738367670d6986c4d385a05fc69c
+# Parent  c5f84a8ede569961086b1ad44506ca12d551890f
+Bug 1437730 - Part 1: Update animation state on mutations of added or removed animations. r=gl
+
+MozReview-Commit-ID: KOQlLX3sEj9
+
+diff --git a/devtools/client/inspector/animation/animation.js b/devtools/client/inspector/animation/animation.js
+--- a/devtools/client/inspector/animation/animation.js
++++ b/devtools/client/inspector/animation/animation.js
+@@ -46,16 +46,17 @@ class AnimationInspector {
+     this.setAnimationsPlayState = this.setAnimationsPlayState.bind(this);
+     this.setDetailVisibility = this.setDetailVisibility.bind(this);
+     this.simulateAnimation = this.simulateAnimation.bind(this);
+     this.simulateAnimationForKeyframesProgressBar =
+       this.simulateAnimationForKeyframesProgressBar.bind(this);
+     this.toggleElementPicker = this.toggleElementPicker.bind(this);
+     this.update = this.update.bind(this);
+     this.onAnimationsCurrentTimeUpdated = this.onAnimationsCurrentTimeUpdated.bind(this);
++    this.onAnimationsMutation = this.onAnimationsMutation.bind(this);
+     this.onCurrentTimeTimerUpdated = this.onCurrentTimeTimerUpdated.bind(this);
+     this.onElementPickerStarted = this.onElementPickerStarted.bind(this);
+     this.onElementPickerStopped = this.onElementPickerStopped.bind(this);
+     this.onSidebarResized = this.onSidebarResized.bind(this);
+     this.onSidebarSelect = this.onSidebarSelect.bind(this);
+ 
+     EventEmitter.decorate(this);
+     this.emit = this.emit.bind(this);
+@@ -132,25 +133,29 @@ class AnimationInspector {
+     );
+     this.provider = provider;
+ 
+     this.inspector.selection.on("new-node-front", this.update);
+     this.inspector.sidebar.on("newanimationinspector-selected", this.onSidebarSelect);
+     this.inspector.toolbox.on("inspector-sidebar-resized", this.onSidebarResized);
+     this.inspector.toolbox.on("picker-started", this.onElementPickerStarted);
+     this.inspector.toolbox.on("picker-stopped", this.onElementPickerStopped);
++
++    this.animationsFront.on("mutations", this.onAnimationsMutation);
+   }
+ 
+   destroy() {
+     this.inspector.selection.off("new-node-front", this.update);
+     this.inspector.sidebar.off("newanimationinspector-selected", this.onSidebarSelect);
+     this.inspector.toolbox.off("inspector-sidebar-resized", this.onSidebarResized);
+     this.inspector.toolbox.off("picker-started", this.onElementPickerStarted);
+     this.inspector.toolbox.off("picker-stopped", this.onElementPickerStopped);
+ 
++    this.animationsFront.off("mutations", this.onAnimationsMutation);
++
+     if (this.simulatedAnimation) {
+       this.simulatedAnimation.cancel();
+       this.simulatedAnimation = null;
+     }
+ 
+     if (this.simulatedElement) {
+       this.simulatedElement.remove();
+       this.simulatedElement = null;
+@@ -266,16 +271,31 @@ class AnimationInspector {
+   onCurrentTimeTimerUpdated(currentTime, shouldStop) {
+     if (shouldStop) {
+       this.setAnimationsCurrentTime(currentTime, true);
+     } else {
+       this.onAnimationsCurrentTimeUpdated(currentTime);
+     }
+   }
+ 
++  onAnimationsMutation(changes) {
++    const animations = [...this.state.animations];
++
++    for (const {type, player: animation} of changes) {
++      if (type === "added") {
++        animations.push(animation);
++      } else if (type === "removed") {
++        const index = animations.indexOf(animation);
++        animations.splice(index, 1);
++      }
++    }
++
++    this.updateState(animations);
++  }
++
+   onElementPickerStarted() {
+     this.inspector.store.dispatch(updateElementPickerEnabled(true));
+   }
+ 
+   onElementPickerStopped() {
+     this.inspector.store.dispatch(updateElementPickerEnabled(false));
+   }
+ 

+ 156 - 0
frg/work-js/mozilla-release/patches/1437730-2-61a1.patch

@@ -0,0 +1,156 @@
+# HG changeset patch
+# User Daisuke Akatsuka <dakatsuka@mozilla.com>
+# Date 1521526235 -32400
+# Node ID 5e7233f63d44a65391136d5c2d5c2af66cb21c75
+# Parent  723feb3465f872f27430c770cc99106c5a06d3a1
+Bug 1437730 - Part 2: Update animation inspector to reflect animation modification. r=gl
+
+MozReview-Commit-ID: 35hgVGujptl
+
+diff --git a/devtools/client/inspector/animation/animation.js b/devtools/client/inspector/animation/animation.js
+--- a/devtools/client/inspector/animation/animation.js
++++ b/devtools/client/inspector/animation/animation.js
+@@ -45,16 +45,17 @@ class AnimationInspector {
+     this.setAnimationsPlaybackRate = this.setAnimationsPlaybackRate.bind(this);
+     this.setAnimationsPlayState = this.setAnimationsPlayState.bind(this);
+     this.setDetailVisibility = this.setDetailVisibility.bind(this);
+     this.simulateAnimation = this.simulateAnimation.bind(this);
+     this.simulateAnimationForKeyframesProgressBar =
+       this.simulateAnimationForKeyframesProgressBar.bind(this);
+     this.toggleElementPicker = this.toggleElementPicker.bind(this);
+     this.update = this.update.bind(this);
++    this.onAnimationStateChanged = this.onAnimationStateChanged.bind(this);
+     this.onAnimationsCurrentTimeUpdated = this.onAnimationsCurrentTimeUpdated.bind(this);
+     this.onAnimationsMutation = this.onAnimationsMutation.bind(this);
+     this.onCurrentTimeTimerUpdated = this.onCurrentTimeTimerUpdated.bind(this);
+     this.onElementPickerStarted = this.onElementPickerStarted.bind(this);
+     this.onElementPickerStopped = this.onElementPickerStopped.bind(this);
+     this.onSidebarResized = this.onSidebarResized.bind(this);
+     this.onSidebarSelect = this.onSidebarSelect.bind(this);
+ 
+@@ -138,16 +139,17 @@ class AnimationInspector {
+     this.inspector.toolbox.on("inspector-sidebar-resized", this.onSidebarResized);
+     this.inspector.toolbox.on("picker-started", this.onElementPickerStarted);
+     this.inspector.toolbox.on("picker-stopped", this.onElementPickerStopped);
+ 
+     this.animationsFront.on("mutations", this.onAnimationsMutation);
+   }
+ 
+   destroy() {
++    this.setAnimationStateChangedListenerEnabled(false);
+     this.inspector.selection.off("new-node-front", this.update);
+     this.inspector.sidebar.off("newanimationinspector-selected", this.onSidebarSelect);
+     this.inspector.toolbox.off("inspector-sidebar-resized", this.onSidebarResized);
+     this.inspector.toolbox.off("picker-started", this.onElementPickerStarted);
+     this.inspector.toolbox.off("picker-stopped", this.onElementPickerStopped);
+ 
+     this.animationsFront.off("mutations", this.onAnimationsMutation);
+ 
+@@ -242,16 +244,21 @@ class AnimationInspector {
+   }
+ 
+   isPanelVisible() {
+     return this.inspector && this.inspector.toolbox && this.inspector.sidebar &&
+            this.inspector.toolbox.currentToolId === "inspector" &&
+            this.inspector.sidebar.getCurrentTabID() === "newanimationinspector";
+   }
+ 
++  onAnimationStateChanged() {
++    // Simply update the animations since the state has already been updated.
++    this.updateState([...this.state.animations]);
++  }
++
+   /**
+    * This method should call when the current time is changed.
+    * Then, dispatches the current time to listeners that are registered
+    * by addAnimationsCurrentTimeListener.
+    *
+    * @param {Number} currentTime
+    */
+   onAnimationsCurrentTimeUpdated(currentTime) {
+@@ -350,25 +357,31 @@ class AnimationInspector {
+ 
+     if (shouldRefresh) {
+       this.updateState([...animations]);
+     }
+   }
+ 
+   async setAnimationsPlaybackRate(playbackRate) {
+     const animations = this.state.animations;
++    // "changed" event on each animation will fire respectively when the playback
++    // rate changed. Since for each occurrence of event, change of UI is urged.
++    // To avoid this, disable the listeners once in order to not capture the event.
++    this.setAnimationStateChangedListenerEnabled(false);
+ 
+     try {
+       await this.animationsFront.setPlaybackRates(animations, playbackRate);
+       await this.updateAnimations(animations);
+     } catch (e) {
+       // Expected if we've already been destroyed or other node have been selected
+       // in the meantime.
+       console.error(e);
+       return;
++    } finally {
++      this.setAnimationStateChangedListenerEnabled(true);
+     }
+ 
+     await this.updateState([...animations]);
+   }
+ 
+   async setAnimationsPlayState(doPlay) {
+     try {
+       if (doPlay) {
+@@ -383,16 +396,35 @@ class AnimationInspector {
+       // in the meantime.
+       console.error(e);
+       return;
+     }
+ 
+     await this.updateState([...this.state.animations]);
+   }
+ 
++  /**
++   * Enable/disable the animation state change listener.
++   * If set true, observe "changed" event on current animations.
++   * Otherwise, quit observing the "changed" event.
++   *
++   * @param {Bool} isEnabled
++   */
++  setAnimationStateChangedListenerEnabled(isEnabled) {
++    if (isEnabled) {
++      for (const animation of this.state.animations) {
++        animation.on("changed", this.onAnimationStateChanged);
++      }
++    } else {
++      for (const animation of this.state.animations) {
++        animation.off("changed", this.onAnimationStateChanged);
++      }
++    }
++  }
++
+   setDetailVisibility(isVisible) {
+     this.inspector.store.dispatch(updateDetailVisibility(isVisible));
+   }
+ 
+   /**
+    * Returns simulatable animation by given parameters.
+    * The returned animation is implementing Animation interface of Web Animation API.
+    * https://drafts.csswg.org/web-animations/#the-animation-interface
+@@ -523,15 +555,17 @@ class AnimationInspector {
+     });
+   }
+ 
+   updateState(animations) {
+     this.stopAnimationsCurrentTimeTimer();
+ 
+     this.inspector.store.dispatch(updateAnimations(animations));
+ 
++    this.setAnimationStateChangedListenerEnabled(true);
++
+     if (hasRunningAnimation(animations)) {
+       this.startAnimationsCurrentTimeTimer();
+     }
+   }
+ }
+ 
+ module.exports = AnimationInspector;

+ 168 - 0
frg/work-js/mozilla-release/patches/1437730-3-61a1.patch

@@ -0,0 +1,168 @@
+# HG changeset patch
+# User Daisuke Akatsuka <dakatsuka@mozilla.com>
+# Date 1521526240 -32400
+# Node ID f4213610ea79e5575418602fb73b88ada11fada5
+# Parent  b5d1fb54c29b25524e667c80d50f342fe3bd6f90
+Bug 1437730 - Part 3: Add tests. r=gl
+
+MozReview-Commit-ID: 75xaanT4jwD
+
+diff --git a/devtools/client/inspector/animation/test/browser.ini b/devtools/client/inspector/animation/test/browser.ini
+--- a/devtools/client/inspector/animation/test/browser.ini
++++ b/devtools/client/inspector/animation/test/browser.ini
+@@ -28,16 +28,17 @@ support-files =
+ [browser_animation_current-time-scrubber.js]
+ [browser_animation_empty_on_invalid_nodes.js]
+ [browser_animation_inspector_exists.js]
+ [browser_animation_keyframes-graph_computed-value-path.js]
+ [browser_animation_keyframes-graph_computed-value-path_easing-hint.js]
+ [browser_animation_keyframes-graph_keyframe-marker.js]
+ [browser_animation_keyframes-progress-bar.js]
+ [browser_animation_logic_auto-stop.js]
++[browser_animation_logic_mutations.js]
+ [browser_animation_pause-resume-button.js]
+ [browser_animation_pause-resume-button_spacebar.js]
+ [browser_animation_playback-rate-selector.js]
+ [browser_animation_rewind-button.js]
+ [browser_animation_summary-graph_animation-name.js]
+ [browser_animation_summary-graph_compositor.js]
+ [browser_animation_summary-graph_computed-timing-path.js]
+ [browser_animation_summary-graph_computed-timing-path_different-timescale.js]
+diff --git a/devtools/client/inspector/animation/test/browser_animation_logic_mutations.js b/devtools/client/inspector/animation/test/browser_animation_logic_mutations.js
+new file mode 100644
+--- /dev/null
++++ b/devtools/client/inspector/animation/test/browser_animation_logic_mutations.js
+@@ -0,0 +1,59 @@
++/* Any copyright is dedicated to the Public Domain.
++   http://creativecommons.org/publicdomain/zero/1.0/ */
++
++"use strict";
++
++// Test for following mutations:
++// * add animation
++// * remove animation
++// * modify animation
++
++add_task(async function() {
++  await addTab(URL_ROOT + "doc_simple_animation.html");
++  const { animationInspector, inspector, panel } = await openAnimationInspector();
++
++  info("Checking the mutation for add an animation");
++  const originalAnimationCount = animationInspector.state.animations.length;
++  await setClassAttribute(animationInspector, ".still", "ball no-compositor");
++  is(animationInspector.state.animations.length, originalAnimationCount + 1,
++    "Count of animation should be plus one to original count");
++
++  info("Checking added animation existence even the animation name is duplicated");
++  is(getAnimationNameCount(panel, "no-compositor"), 2,
++   "Count of animation should be plus one to original count");
++
++  info("Checking the mutation for remove an animation");
++  await setClassAttribute(animationInspector, ".compositor-notall", "ball still");
++  is(animationInspector.state.animations.length, originalAnimationCount,
++   "Count of animation should be same to original count since we remove an animation");
++
++  info("Checking the mutation for modify an animation");
++  await selectNodeAndWaitForAnimations(".compositor-all", inspector);
++  await setStyle(animationInspector, ".compositor-all", "animationDuration", "100s");
++  await setStyle(animationInspector, ".compositor-all", "animationIterationCount", 1);
++  const summaryGraphPathEl = getSummaryGraphPathElement(panel, "compositor-all");
++  is(summaryGraphPathEl.viewBox.baseVal.width, 100000,
++    "Width of summary graph path should be 100000 " +
++    "after modifing the duration and iteration count");
++  await setStyle(animationInspector, ".compositor-all", "animationDelay", "100s");
++  is(summaryGraphPathEl.viewBox.baseVal.width, 200000,
++    "Width of summary graph path should be 200000 after modifing the delay");
++  ok(summaryGraphPathEl.parentElement.querySelector(".animation-delay-sign"),
++    "Delay sign element shoud exist");
++});
++
++function getAnimationNameCount(panel, animationName) {
++  return [...panel.querySelectorAll(".animation-name")].reduce(
++    (count, element) => element.textContent === animationName ? count + 1 : count, 0);
++}
++
++function getSummaryGraphPathElement(panel, animationName) {
++  for (const animationNameEl of panel.querySelectorAll(".animation-name")) {
++    if (animationNameEl.textContent === animationName) {
++      return animationNameEl.closest(".animation-summary-graph")
++                            .querySelector(".animation-summary-graph-path");
++    }
++  }
++
++  return null;
++}
+diff --git a/devtools/client/inspector/animation/test/head.js b/devtools/client/inspector/animation/test/head.js
+--- a/devtools/client/inspector/animation/test/head.js
++++ b/devtools/client/inspector/animation/test/head.js
+@@ -324,31 +324,70 @@ const selectNodeAndWaitForAnimations = a
+  */
+ const sendSpaceKeyEvent = async function(animationInspector, panel) {
+   panel.focus();
+   EventUtils.sendKey("SPACE", panel.ownerGlobal);
+   await waitForSummaryAndDetail(animationInspector);
+ };
+ 
+ /**
++ * Set a node class attribute to the given selector.
++ *
++ * @param {AnimationInspector} animationInspector
++ * @param {String} selector
++ * @param {String} cls
++ *        e.g. ".ball.still"
++ */
++const setClassAttribute = async function(animationInspector, selector, cls) {
++  const options = {
++    attributeName: "class",
++    attributeValue: cls,
++    selector,
++  };
++  await executeInContent("devtools:test:setAttribute", options);
++  await waitForSummaryAndDetail(animationInspector);
++};
++
++/**
+  * Set the sidebar width by given parameter.
+  *
+  * @param {String} width
+  *        Change sidebar width by given parameter.
+  * @param {InspectorPanel} inspector
+  *        The instance of InspectorPanel currently loaded in the toolbox
+  * @return {Promise} Resolves when the sidebar size changed.
+  */
+ const setSidebarWidth = async function(width, inspector) {
+   const onUpdated = inspector.toolbox.once("inspector-sidebar-resized");
+   inspector.splitBox.setState({ width });
+   await onUpdated;
+ };
+ 
+ /**
++ * Set a new style property declaration to the node for the given selector.
++ *
++ * @param {AnimationInspector} animationInspector
++ * @param {String} selector
++ * @param {String} propertyName
++ *        e.g. "animationDuration"
++ * @param {String} propertyValue
++ *        e.g. "5.5s"
++ */
++const setStyle = async function(animationInspector,
++                                selector, propertyName, propertyValue) {
++  const options = {
++    propertyName,
++    propertyValue,
++    selector,
++  };
++  await executeInContent("devtools:test:setStyle", options);
++  await waitForSummaryAndDetail(animationInspector);
++};
++
++/**
+  * Wait for rendering.
+  *
+  * @param {AnimationInspector} animationInspector
+  */
+ const waitForRendering = async function(animationInspector) {
+   await Promise.all([
+     waitForAllAnimationTargets(animationInspector),
+     waitForAllSummaryGraph(animationInspector),

+ 860 - 0
frg/work-js/mozilla-release/patches/1447736-61a1.patch

@@ -0,0 +1,860 @@
+# HG changeset patch
+# User Patrick Brosset <pbrosset@mozilla.com>
+# Date 1522250422 -7200
+# Node ID c2b1e0b5cc31efed6945c9769a2784da1233e844
+# Parent  5bf5228ef8518e714367bb66561c66e07433cc03
+Bug 1447736 - Refresh rule/computed-view even when parents and siblings change; r=jdescottes
+
+MozReview-Commit-ID: 9RZyWwnpgUj
+
+diff --git a/devtools/client/inspector/computed/computed.js b/devtools/client/inspector/computed/computed.js
+--- a/devtools/client/inspector/computed/computed.js
++++ b/devtools/client/inspector/computed/computed.js
+@@ -1409,26 +1409,23 @@ function ComputedViewTool(inspector, win
+   this.document = window.document;
+ 
+   this.computedView = new CssComputedView(this.inspector, this.document,
+     this.inspector.pageStyle);
+ 
+   this.onSelected = this.onSelected.bind(this);
+   this.refresh = this.refresh.bind(this);
+   this.onPanelSelected = this.onPanelSelected.bind(this);
+-  this.onMutations = this.onMutations.bind(this);
+-  this.onResized = this.onResized.bind(this);
+ 
+   this.inspector.selection.on("detached-front", this.onDetachedFront);
+   this.inspector.selection.on("new-node-front", this.onSelected);
+   this.inspector.selection.on("pseudoclass", this.refresh);
+   this.inspector.sidebar.on("computedview-selected", this.onPanelSelected);
+   this.inspector.pageStyle.on("stylesheet-updated", this.refresh);
+-  this.inspector.walker.on("mutations", this.onMutations);
+-  this.inspector.walker.on("resize", this.onResized);
++  this.inspector.styleChangeTracker.on("style-changed", this.refresh);
+ 
+   this.computedView.selectElement(null);
+ 
+   this.onSelected();
+ }
+ 
+ ComputedViewTool.prototype = {
+   isSidebarActive: function() {
+@@ -1482,41 +1479,18 @@ ComputedViewTool.prototype = {
+   onPanelSelected: function() {
+     if (this.inspector.selection.nodeFront === this.computedView._viewedElement) {
+       this.refresh();
+     } else {
+       this.onSelected();
+     }
+   },
+ 
+-  /**
+-   * When markup mutations occur, if an attribute of the selected node changes,
+-   * we need to refresh the view as that might change the node's styles.
+-   */
+-  onMutations: function(mutations) {
+-    for (let {type, target} of mutations) {
+-      if (target === this.inspector.selection.nodeFront &&
+-          type === "attributes") {
+-        this.refresh();
+-        break;
+-      }
+-    }
+-  },
+-
+-  /**
+-   * When the window gets resized, this may cause media-queries to match, and
+-   * therefore, different styles may apply.
+-   */
+-  onResized: function() {
+-    this.refresh();
+-  },
+-
+   destroy: function() {
+-    this.inspector.walker.off("mutations", this.onMutations);
+-    this.inspector.walker.off("resize", this.onResized);
++    this.inspector.styleChangeTracker.off("style-changed", this.refresh);
+     this.inspector.sidebar.off("computedview-selected", this.refresh);
+     this.inspector.selection.off("pseudoclass", this.refresh);
+     this.inspector.selection.off("new-node-front", this.onSelected);
+     this.inspector.selection.off("detached-front", this.onDetachedFront);
+     this.inspector.sidebar.off("computedview-selected", this.onPanelSelected);
+     if (this.inspector.pageStyle) {
+       this.inspector.pageStyle.off("stylesheet-updated", this.refresh);
+     }
+diff --git a/devtools/client/inspector/inspector.js b/devtools/client/inspector/inspector.js
+--- a/devtools/client/inspector/inspector.js
++++ b/devtools/client/inspector/inspector.js
+@@ -12,16 +12,17 @@ const Services = require("Services");
+ const promise = require("promise");
+ const EventEmitter = require("devtools/shared/event-emitter");
+ const {executeSoon} = require("devtools/shared/DevToolsUtils");
+ const {PrefObserver} = require("devtools/client/shared/prefs");
+ const Telemetry = require("devtools/client/shared/telemetry");
+ const HighlightersOverlay = require("devtools/client/inspector/shared/highlighters-overlay");
+ const ReflowTracker = require("devtools/client/inspector/shared/reflow-tracker");
+ const Store = require("devtools/client/inspector/store");
++const InspectorStyleChangeTracker = require("devtools/client/inspector/shared/style-change-tracker");
+ 
+ // Use privileged promise in panel documents to prevent having them to freeze
+ // during toolbox destruction. See bug 1402779.
+ const Promise = require("Promise");
+ 
+ loader.lazyRequireGetter(this, "initCssProperties", "devtools/shared/fronts/css-properties", true);
+ loader.lazyRequireGetter(this, "HTMLBreadcrumbs", "devtools/client/inspector/breadcrumbs", true);
+ loader.lazyRequireGetter(this, "KeyShortcuts", "devtools/client/shared/key-shortcuts");
+@@ -99,16 +100,17 @@ function Inspector(toolbox) {
+ 
+   // Map [panel id => panel instance]
+   // Stores all the instances of sidebar panels like rule view, computed view, ...
+   this._panels = new Map();
+ 
+   this.highlighters = new HighlightersOverlay(this);
+   this.prefsObserver = new PrefObserver("devtools.");
+   this.reflowTracker = new ReflowTracker(this._target);
++  this.styleChangeTracker = new InspectorStyleChangeTracker(this);
+   this.store = Store();
+   this.telemetry = new Telemetry();
+ 
+   // Store the URL of the target page prior to navigation in order to ensure
+   // telemetry counts in the Grid Inspector are not double counted on reload.
+   this.previousURL = this.target.url;
+ 
+   this.showSplitSidebarToggle = Services.prefs.getBoolPref(
+@@ -1293,16 +1295,17 @@ Inspector.prototype = {
+     this.selection.off("new-node-front", this.onNewSelection);
+     this.selection.off("detached-front", this.onDetached);
+ 
+     let markupDestroyer = this._destroyMarkup();
+ 
+     this.highlighters.destroy();
+     this.prefsObserver.destroy();
+     this.reflowTracker.destroy();
++    this.styleChangeTracker.destroy();
+     this.search.destroy();
+ 
+     this._toolbox = null;
+     this.breadcrumbs = null;
+     this.highlighters = null;
+     this.isSplitRuleViewEnabled = null;
+     this.panelDoc = null;
+     this.panelWin.inspector = null;
+diff --git a/devtools/client/inspector/rules/rules.js b/devtools/client/inspector/rules/rules.js
+--- a/devtools/client/inspector/rules/rules.js
++++ b/devtools/client/inspector/rules/rules.js
+@@ -1743,32 +1743,30 @@ function getShapePoint(node) {
+ function RuleViewTool(inspector, window) {
+   this.inspector = inspector;
+   this.document = window.document;
+ 
+   this.view = new CssRuleView(this.inspector, this.document);
+ 
+   this.clearUserProperties = this.clearUserProperties.bind(this);
+   this.refresh = this.refresh.bind(this);
+-  this.onMutations = this.onMutations.bind(this);
++  this.onDetachedFront = this.onDetachedFront.bind(this);
+   this.onPanelSelected = this.onPanelSelected.bind(this);
+-  this.onResized = this.onResized.bind(this);
+   this.onSelected = this.onSelected.bind(this);
+   this.onViewRefreshed = this.onViewRefreshed.bind(this);
+ 
+   this.view.on("ruleview-refreshed", this.onViewRefreshed);
+   this.inspector.selection.on("detached-front", this.onDetachedFront);
+   this.inspector.selection.on("new-node-front", this.onSelected);
+   this.inspector.selection.on("pseudoclass", this.refresh);
+   this.inspector.target.on("navigate", this.clearUserProperties);
+   this.inspector.ruleViewSideBar.on("ruleview-selected", this.onPanelSelected);
+   this.inspector.sidebar.on("ruleview-selected", this.onPanelSelected);
+   this.inspector.pageStyle.on("stylesheet-updated", this.refresh);
+-  this.inspector.walker.on("mutations", this.onMutations);
+-  this.inspector.walker.on("resize", this.onResized);
++  this.inspector.styleChangeTracker.on("style-changed", this.refresh);
+ 
+   this.onSelected();
+ }
+ 
+ RuleViewTool.prototype = {
+   isSidebarActive: function() {
+     if (!this.view) {
+       return false;
+@@ -1831,41 +1829,18 @@ RuleViewTool.prototype = {
+       this.onSelected();
+     }
+   },
+ 
+   onViewRefreshed: function() {
+     this.inspector.emit("rule-view-refreshed");
+   },
+ 
+-  /**
+-   * When markup mutations occur, if an attribute of the selected node changes,
+-   * we need to refresh the view as that might change the node's styles.
+-   */
+-  onMutations: function(mutations) {
+-    for (let {type, target} of mutations) {
+-      if (target === this.inspector.selection.nodeFront &&
+-          type === "attributes") {
+-        this.refresh();
+-        break;
+-      }
+-    }
+-  },
+-
+-  /**
+-   * When the window gets resized, this may cause media-queries to match, and
+-   * therefore, different styles may apply.
+-   */
+-  onResized: function() {
+-    this.refresh();
+-  },
+-
+   destroy: function() {
+-    this.inspector.walker.off("mutations", this.onMutations);
+-    this.inspector.walker.off("resize", this.onResized);
++    this.inspector.styleChangeTracker.off("style-changed", this.refresh);
+     this.inspector.selection.off("detached-front", this.onDetachedFront);
+     this.inspector.selection.off("pseudoclass", this.refresh);
+     this.inspector.selection.off("new-node-front", this.onSelected);
+     this.inspector.target.off("navigate", this.clearUserProperties);
+     this.inspector.sidebar.off("ruleview-selected", this.onPanelSelected);
+     if (this.inspector.pageStyle) {
+       this.inspector.pageStyle.off("stylesheet-updated", this.refresh);
+     }
+diff --git a/devtools/client/inspector/rules/test/browser_rules_refresh-on-attribute-change_02.js b/devtools/client/inspector/rules/test/browser_rules_refresh-on-attribute-change_02.js
+--- a/devtools/client/inspector/rules/test/browser_rules_refresh-on-attribute-change_02.js
++++ b/devtools/client/inspector/rules/test/browser_rules_refresh-on-attribute-change_02.js
+@@ -8,146 +8,146 @@
+ // rule-view
+ 
+ const TEST_URI = `
+   <div id="testid" class="testclass" style="margin-top: 1px; padding-top: 5px;">
+     Styled Node
+   </div>
+ `;
+ 
++// The series of test cases to run. Each case is an object with the following properties:
++// - {String} desc The test case description
++// - {Function} setup The setup function to execute for this case
++// - {Array} properties The properties to expect as a result. Each of them is an object:
++//   - {String} name The expected property name
++//   - {String} value The expected property value
++//   - {Boolean} overridden The expected property overridden state
++//   - {Boolean} enabled The expected property enabled state
++const TEST_DATA = [{
++  desc: "Adding a second margin-top value in the element selector",
++  setup: async function({ view }) {
++    await addProperty(view, 0, "margin-top", "5px");
++  },
++  properties: [
++    { name: "margin-top", value: "1px", overridden: true, enabled: true },
++    { name: "padding-top", value: "5px", overridden: false, enabled: true },
++    { name: "margin-top", value: "5px", overridden: false, enabled: true }
++  ]
++}, {
++  desc: "Setting the element style to its original value",
++  setup: async function({ inspector, testActor }) {
++    await changeElementStyle("#testid", "margin-top: 1px; padding-top: 5px", inspector,
++                             testActor);
++  },
++  properties: [
++    { name: "margin-top", value: "1px", overridden: false, enabled: true },
++    { name: "padding-top", value: "5px", overridden: false, enabled: true },
++    { name: "margin-top", value: "5px", overridden: false, enabled: false }
++  ]
++}, {
++  desc: "Set the margin-top back to 5px, the previous property should be re-enabled",
++  setup: async function({ inspector, testActor }) {
++    await changeElementStyle("#testid", "margin-top: 5px; padding-top: 5px;", inspector,
++                            testActor);
++  },
++  properties: [
++    { name: "margin-top", value: "1px", overridden: false, enabled: false },
++    { name: "padding-top", value: "5px", overridden: false, enabled: true },
++    { name: "margin-top", value: "5px", overridden: false, enabled: true }
++  ]
++}, {
++  desc: "Set the margin property to a value that doesn't exist in the editor, which " +
++        "should reuse the currently re-enabled property (the second one)",
++  setup: async function({ inspector, testActor }) {
++    await changeElementStyle("#testid", "margin-top: 15px; padding-top: 5px;", inspector,
++                             testActor);
++  },
++  properties: [
++    { name: "margin-top", value: "1px", overridden: false, enabled: false },
++    { name: "padding-top", value: "5px", overridden: false, enabled: true },
++    { name: "margin-top", value: "15px", overridden: false, enabled: true }
++  ]
++}, {
++  desc: "Remove the padding-top attribute. Should disable the padding property but not " +
++        "remove it",
++  setup: async function({ inspector, testActor }) {
++    await changeElementStyle("#testid", "margin-top: 5px;", inspector, testActor);
++  },
++  properties: [
++    { name: "margin-top", value: "1px", overridden: false, enabled: false },
++    { name: "padding-top", value: "5px", overridden: false, enabled: false },
++    { name: "margin-top", value: "5px", overridden: false, enabled: true }
++  ]
++}, {
++  desc: "Put the padding-top attribute back in, should re-enable the padding property",
++  setup: async function({ inspector, testActor }) {
++    await changeElementStyle("#testid", "margin-top: 5px; padding-top: 25px", inspector,
++                             testActor);
++  },
++  properties: [
++    { name: "margin-top", value: "1px", overridden: false, enabled: false },
++    { name: "padding-top", value: "25px", overridden: false, enabled: true },
++    { name: "margin-top", value: "5px", overridden: false, enabled: true }
++  ]
++}, {
++  desc: "Add an entirely new property",
++  setup: async function({ inspector, testActor }) {
++    await changeElementStyle("#testid",
++                            "margin-top: 5px; padding-top: 25px; padding-left: 20px;",
++                            inspector, testActor);
++  },
++  properties: [
++    { name: "margin-top", value: "1px", overridden: false, enabled: false },
++    { name: "padding-top", value: "25px", overridden: false, enabled: true },
++    { name: "margin-top", value: "5px", overridden: false, enabled: true },
++    { name: "padding-left", value: "20px", overridden: false, enabled: true }
++  ]
++}, {
++  desc: "Add an entirely new property again",
++  setup: async function({ inspector, testActor }) {
++    await changeElementStyle("#testid", "color: red", inspector, testActor);
++  },
++  properties: [
++    { name: "margin-top", value: "1px", overridden: false, enabled: false },
++    { name: "padding-top", value: "25px", overridden: false, enabled: false },
++    { name: "margin-top", value: "5px", overridden: false, enabled: false },
++    { name: "padding-left", value: "20px", overridden: false, enabled: false },
++    { name: "color", value: "red", overridden: false, enabled: true }
++  ]
++}];
++
+ add_task(async function() {
+   await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+-  let {inspector, view, testActor} = await openRuleView();
++  let { inspector, view, testActor } = await openRuleView();
+   await selectNode("#testid", inspector);
+ 
+-  await testPropertyChanges(inspector, view);
+-  await testPropertyChange0(inspector, view, "#testid", testActor);
+-  await testPropertyChange1(inspector, view, "#testid", testActor);
+-  await testPropertyChange2(inspector, view, "#testid", testActor);
+-  await testPropertyChange3(inspector, view, "#testid", testActor);
+-  await testPropertyChange4(inspector, view, "#testid", testActor);
+-  await testPropertyChange5(inspector, view, "#testid", testActor);
+-  await testPropertyChange6(inspector, view, "#testid", testActor);
++  for (let { desc, setup, properties } of TEST_DATA) {
++    info(desc);
++
++    await setup({ inspector, view, testActor });
++
++    let rule = view._elementStyle.rules[0];
++    is(rule.editor.element.querySelectorAll(".ruleview-property").length,
++       properties.length, "The correct number of properties was found");
++
++    properties.forEach(({ name, value, overridden, enabled }, index) => {
++      validateTextProp(rule.textProps[index], overridden, enabled, name, value);
++    });
++  }
+ });
+ 
+-async function testPropertyChanges(inspector, ruleView) {
+-  info("Adding a second margin-top value in the element selector");
+-  let ruleEditor = ruleView._elementStyle.rules[0].editor;
+-  let onRefreshed = inspector.once("rule-view-refreshed");
+-  ruleEditor.addProperty("margin-top", "5px", "", true);
+-  await onRefreshed;
+-
+-  let rule = ruleView._elementStyle.rules[0];
+-  validateTextProp(rule.textProps[0], false, "margin-top", "1px",
+-    "Original margin property active");
+-}
+-
+-async function testPropertyChange0(inspector, ruleView, selector, testActor) {
+-  await changeElementStyle(selector, "margin-top: 1px; padding-top: 5px",
+-    inspector, testActor);
+-
+-  let rule = ruleView._elementStyle.rules[0];
+-  is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3,
+-    "Correct number of properties");
+-  validateTextProp(rule.textProps[0], true, "margin-top", "1px",
+-    "First margin property re-enabled");
+-  validateTextProp(rule.textProps[2], false, "margin-top", "5px",
+-    "Second margin property disabled");
+-}
+-
+-async function testPropertyChange1(inspector, ruleView, selector, testActor) {
+-  info("Now set it back to 5px, the 5px value should be re-enabled.");
+-  await changeElementStyle(selector, "margin-top: 5px; padding-top: 5px;",
+-    inspector, testActor);
+-
+-  let rule = ruleView._elementStyle.rules[0];
+-  is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3,
+-    "Correct number of properties");
+-  validateTextProp(rule.textProps[0], false, "margin-top", "1px",
+-    "First margin property re-enabled");
+-  validateTextProp(rule.textProps[2], true, "margin-top", "5px",
+-    "Second margin property disabled");
+-}
+-
+-async function testPropertyChange2(inspector, ruleView, selector, testActor) {
+-  info("Set the margin property to a value that doesn't exist in the editor.");
+-  info("Should reuse the currently-enabled element (the second one.)");
+-  await changeElementStyle(selector, "margin-top: 15px; padding-top: 5px;",
+-    inspector, testActor);
+-
+-  let rule = ruleView._elementStyle.rules[0];
+-  is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3,
+-    "Correct number of properties");
+-  validateTextProp(rule.textProps[0], false, "margin-top", "1px",
+-    "First margin property re-enabled");
+-  validateTextProp(rule.textProps[2], true, "margin-top", "15px",
+-    "Second margin property disabled");
+-}
+-
+-async function testPropertyChange3(inspector, ruleView, selector, testActor) {
+-  info("Remove the padding-top attribute. Should disable the padding " +
+-    "property but not remove it.");
+-  await changeElementStyle(selector, "margin-top: 5px;", inspector, testActor);
+-
+-  let rule = ruleView._elementStyle.rules[0];
+-  is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3,
+-    "Correct number of properties");
+-  validateTextProp(rule.textProps[1], false, "padding-top", "5px",
+-    "Padding property disabled");
+-}
+-
+-async function testPropertyChange4(inspector, ruleView, selector, testActor) {
+-  info("Put the padding-top attribute back in, should re-enable the " +
+-    "padding property.");
+-  await changeElementStyle(selector, "margin-top: 5px; padding-top: 25px",
+-    inspector, testActor);
+-
+-  let rule = ruleView._elementStyle.rules[0];
+-  is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3,
+-    "Correct number of properties");
+-  validateTextProp(rule.textProps[1], true, "padding-top", "25px",
+-    "Padding property enabled");
+-}
+-
+-async function testPropertyChange5(inspector, ruleView, selector, testActor) {
+-  info("Add an entirely new property");
+-  await changeElementStyle(selector,
+-    "margin-top: 5px; padding-top: 25px; padding-left: 20px;",
+-    inspector, testActor);
+-
+-  let rule = ruleView._elementStyle.rules[0];
+-  is(rule.editor.element.querySelectorAll(".ruleview-property").length, 4,
+-    "Added a property");
+-  validateTextProp(rule.textProps[3], true, "padding-left", "20px",
+-    "Padding property enabled");
+-}
+-
+-async function testPropertyChange6(inspector, ruleView, selector, testActor) {
+-  info("Add an entirely new property again");
+-  await changeElementStyle(selector, "background: red " +
+-    "url(\"chrome://branding/content/about-logo.png\") repeat scroll 0% 0%",
+-    inspector, testActor);
+-
+-  let rule = ruleView._elementStyle.rules[0];
+-  is(rule.editor.element.querySelectorAll(".ruleview-property").length, 5,
+-    "Added a property");
+-  validateTextProp(rule.textProps[4], true, "background",
+-                   "red url(\"chrome://branding/content/about-logo.png\") repeat scroll 0% 0%",
+-                   "shortcut property correctly set");
+-}
+-
+ async function changeElementStyle(selector, style, inspector, testActor) {
++  info(`Setting ${selector}'s element style to ${style}`);
+   let onRefreshed = inspector.once("rule-view-refreshed");
+   await testActor.setAttribute(selector, "style", style);
+   await onRefreshed;
+ }
+ 
+-function validateTextProp(prop, enabled, name, value, desc) {
+-  is(prop.enabled, enabled, desc + ": enabled.");
+-  is(prop.name, name, desc + ": name.");
+-  is(prop.value, value, desc + ": value.");
++function validateTextProp(prop, overridden, enabled, name, value) {
++  is(prop.name, name, `${name} property name is correct`);
++  is(prop.editor.nameSpan.textContent, name, `${name} property name is correct in UI`);
+ 
+-  is(prop.editor.enable.hasAttribute("checked"), enabled,
+-    desc + ": enabled checkbox.");
+-  is(prop.editor.nameSpan.textContent, name, desc + ": name span.");
+-  is(prop.editor.valueSpan.textContent,
+-    value, desc + ": value span.");
++  is(prop.value, value, `${name} property value is correct`);
++  is(prop.editor.valueSpan.textContent, value, `${name} property value is correct in UI`);
++
++  is(prop.enabled, enabled, `${name} property enabled state correct`);
++  is(prop.overridden, overridden, `${name} property overridden state correct`);
+ }
+diff --git a/devtools/client/inspector/rules/test/head.js b/devtools/client/inspector/rules/test/head.js
+--- a/devtools/client/inspector/rules/test/head.js
++++ b/devtools/client/inspector/rules/test/head.js
+@@ -242,42 +242,60 @@ var openCubicBezierAndChangeCoords = asy
+ };
+ 
+ /**
+  * Simulate adding a new property in an existing rule in the rule-view.
+  *
+  * @param {CssRuleView} view
+  *        The instance of the rule-view panel
+  * @param {Number} ruleIndex
+- *        The index of the rule to use. Note that if ruleIndex is 0, you might
+- *        want to also listen to markupmutation events in your test since
+- *        that's going to change the style attribute of the selected node.
++ *        The index of the rule to use.
+  * @param {String} name
+  *        The name for the new property
+  * @param {String} value
+  *        The value for the new property
+  * @param {String} commitValueWith
+  *        Which key should be used to commit the new value. VK_RETURN is used by
+  *        default, but tests might want to use another key to test cancelling
+  *        for exemple.
+  * @param {Boolean} blurNewProperty
+  *        After the new value has been added, a new property would have been
+  *        focused. This parameter is true by default, and that causes the new
+  *        property to be blurred. Set to false if you don't want this.
+  * @return {TextProperty} The instance of the TextProperty that was added
+  */
+ var addProperty = async function(view, ruleIndex, name, value,
+-                                        commitValueWith = "VK_RETURN",
+-                                        blurNewProperty = true) {
++                                 commitValueWith = "VK_RETURN",
++                                 blurNewProperty = true) {
+   info("Adding new property " + name + ":" + value + " to rule " + ruleIndex);
+ 
+   let ruleEditor = getRuleViewRuleEditor(view, ruleIndex);
+   let editor = await focusNewRuleViewProperty(ruleEditor);
+   let numOfProps = ruleEditor.rule.textProps.length;
+ 
++  let onMutations = new Promise(r => {
++    // If we're adding the property to a non-element style rule, we don't need to wait
++    // for mutations.
++    if (ruleIndex !== 0) {
++      r();
++    }
++
++    // Otherwise, adding the property to the element style rule causes 2 mutations to the
++    // style attribute on the element: first when the name is added with an empty value,
++    // and then when the value is added.
++    let receivedMutations = 0;
++    view.inspector.walker.on("mutations", function onWalkerMutations(mutations) {
++      receivedMutations += mutations.length;
++      if (receivedMutations >= 2) {
++        view.inspector.walker.off("mutations", onWalkerMutations);
++        r();
++      }
++    });
++  });
++
+   info("Adding name " + name);
+   editor.input.value = name;
+   let onNameAdded = view.once("ruleview-changed");
+   EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
+   await onNameAdded;
+ 
+   // Focus has moved to the value inplace-editor automatically.
+   editor = inplaceEditor(view.styleDocument.activeElement);
+@@ -296,16 +314,19 @@ var addProperty = async function(view, r
+   editor.input.value = value;
+   view.debounce.flush();
+   await onPreview;
+ 
+   let onValueAdded = view.once("ruleview-changed");
+   EventUtils.synthesizeKey(commitValueWith, {}, view.styleWindow);
+   await onValueAdded;
+ 
++  info("Waiting for DOM mutations in case the property was added to the element style");
++  await onMutations;
++
+   if (blurNewProperty) {
+     view.styleDocument.activeElement.blur();
+   }
+ 
+   return textProp;
+ };
+ 
+ /**
+diff --git a/devtools/client/inspector/shared/moz.build b/devtools/client/inspector/shared/moz.build
+--- a/devtools/client/inspector/shared/moz.build
++++ b/devtools/client/inspector/shared/moz.build
+@@ -4,14 +4,15 @@
+ # 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/.
+ 
+ DevToolsModules(
+     'dom-node-preview.js',
+     'highlighters-overlay.js',
+     'node-types.js',
+     'reflow-tracker.js',
++    'style-change-tracker.js',
+     'style-inspector-menu.js',
+     'tooltips-overlay.js',
+     'utils.js'
+ )
+ 
+ BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
+diff --git a/devtools/client/inspector/shared/style-change-tracker.js b/devtools/client/inspector/shared/style-change-tracker.js
+new file mode 100644
+--- /dev/null
++++ b/devtools/client/inspector/shared/style-change-tracker.js
+@@ -0,0 +1,98 @@
++/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
++/* vim: set ts=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/. */
++
++"use strict";
++
++const EventEmitter = require("devtools/shared/event-emitter");
++
++/**
++ * The InspectorStyleChangeTracker simply emits an event when it detects any changes in
++ * the page that may cause the current inspector selection to have different style applied
++ * to it.
++ * It currently tracks:
++ * - markup mutations, because they may cause different CSS rules to apply to the current
++ *   node.
++ * - window resize, because they may cause media query changes and therefore also
++ *   different CSS rules to apply to the current node.
++ */
++class InspectorStyleChangeTracker {
++  constructor(inspector) {
++    this.walker = inspector.walker;
++    this.selection = inspector.selection;
++
++    this.onMutations = this.onMutations.bind(this);
++    this.onResized = this.onResized.bind(this);
++
++    this.walker.on("mutations", this.onMutations);
++    this.walker.on("resize", this.onResized);
++
++    EventEmitter.decorate(this);
++  }
++
++  destroy() {
++    this.walker.off("mutations", this.onMutations);
++    this.walker.off("resize", this.onResized);
++
++    this.walker = this.selection = null;
++  }
++
++  /**
++   * When markup mutations occur, if an attribute of the selected node, one of its
++   * ancestors or siblings changes, we need to consider this as potentially causing a
++   * style change for the current node.
++   */
++  onMutations(mutations) {
++    const canMutationImpactCurrentStyles = ({ type, target }) => {
++      // Only attributes mutations are interesting here.
++      if (type !== "attributes") {
++        return false;
++      }
++
++      // Is the mutation on the current selected node?
++      let currentNode = this.selection.nodeFront;
++      if (target === currentNode) {
++        return true;
++      }
++
++      // Is the mutation on one of the current selected node's siblings?
++      // We can't know the order of nodes on the client-side without calling
++      // walker.children, so don't attempt to check the previous or next element siblings.
++      // It's good enough to know that one sibling changed.
++      let parent = currentNode.parentNode();
++      let siblings = parent.treeChildren();
++      if (siblings.includes(target)) {
++        return true;
++      }
++
++      // Is the mutation on one of the current selected node's parents?
++      while (parent) {
++        if (target === parent) {
++          return true;
++        }
++        parent = parent.parentNode();
++      }
++
++      return false;
++    };
++
++    for (let mutation of mutations) {
++      if (canMutationImpactCurrentStyles(mutation)) {
++        this.emit("style-changed");
++        break;
++      }
++    }
++  }
++
++  /**
++   * When the window gets resized, this may cause media-queries to match, and we therefore
++   * need to consider this as a style change for the current node.
++   */
++  onResized() {
++    this.emit("style-changed");
++  }
++}
++
++module.exports = InspectorStyleChangeTracker;
+diff --git a/devtools/client/inspector/shared/test/browser.ini b/devtools/client/inspector/shared/test/browser.ini
+--- a/devtools/client/inspector/shared/test/browser.ini
++++ b/devtools/client/inspector/shared/test/browser.ini
+@@ -1,13 +1,14 @@
+ [DEFAULT]
+ tags = devtools
+ subsuite = devtools
+ support-files =
+   doc_author-sheet.html
++  doc_content_style_changes.html
+   doc_content_stylesheet.html
+   doc_content_stylesheet.xul
+   doc_content_stylesheet_imported.css
+   doc_content_stylesheet_imported2.css
+   doc_content_stylesheet_linked.css
+   doc_content_stylesheet_script.css
+   doc_content_stylesheet_xul.css
+   doc_frame_script.js
+@@ -25,16 +26,17 @@ subsuite = clipboard
+ skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
+ [browser_styleinspector_context-menu-copy-urls.js]
+ subsuite = clipboard
+ skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
+ [browser_styleinspector_csslogic-content-stylesheets.js]
+ skip-if = e10s && debug # Bug 1250058 (docshell leak when opening 2 toolboxes)
+ [browser_styleinspector_output-parser.js]
+ [browser_styleinspector_refresh_when_active.js]
++[browser_styleinspector_refresh_when_style_changes.js]
+ [browser_styleinspector_tooltip-background-image.js]
+ [browser_styleinspector_tooltip-closes-on-new-selection.js]
+ skip-if = e10s # Bug 1111546 (e10s)
+ [browser_styleinspector_tooltip-longhand-fontfamily.js]
+ [browser_styleinspector_tooltip-multiple-background-images.js]
+ [browser_styleinspector_tooltip-shorthand-fontfamily.js]
+ [browser_styleinspector_tooltip-size.js]
+ [browser_styleinspector_transform-highlighter-01.js]
+diff --git a/devtools/client/inspector/shared/test/browser_styleinspector_refresh_when_style_changes.js b/devtools/client/inspector/shared/test/browser_styleinspector_refresh_when_style_changes.js
+new file mode 100644
+--- /dev/null
++++ b/devtools/client/inspector/shared/test/browser_styleinspector_refresh_when_style_changes.js
+@@ -0,0 +1,80 @@
++/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
++/* Any copyright is dedicated to the Public Domain.
++ http://creativecommons.org/publicdomain/zero/1.0/ */
++
++"use strict";
++
++// Test that the rule and computed views refresh when style changes that impact the
++// current selection occur.
++// This test does not need to worry about the correctness of the styles and rules
++// displayed in these views (other tests do this) but only cares that they do catch the
++// change.
++
++const TEST_URI = TEST_URL_ROOT + "doc_content_style_changes.html";
++
++const TEST_DATA = [{
++  target: "#test",
++  className: "green-class",
++  force: true
++}, {
++  target: "#test",
++  className: "green-class",
++  force: false
++}, {
++  target: "#parent",
++  className: "purple-class",
++  force: true
++}, {
++  target: "#parent",
++  className: "purple-class",
++  force: false
++}, {
++  target: "#sibling",
++  className: "blue-class",
++  force: true
++}, {
++  target: "#sibling",
++  className: "blue-class",
++  force: false
++}];
++
++add_task(async function() {
++  let tab = await addTab(TEST_URI);
++
++  let { inspector } = await openRuleView();
++  await selectNode("#test", inspector);
++
++  info("Run the test on the rule-view");
++  await runViewTest(inspector, tab, "rule");
++
++  info("Switch to the computed view");
++  let onComputedViewReady = inspector.once("computed-view-refreshed");
++  selectComputedView(inspector);
++  await onComputedViewReady;
++
++  info("Run the test again on the computed view");
++  await runViewTest(inspector, tab, "computed");
++});
++
++async function runViewTest(inspector, tab, viewName) {
++  for (let { target, className, force } of TEST_DATA) {
++    info((force ? "Adding" : "Removing") +
++         ` class ${className} on ${target} and expecting a ${viewName}-view refresh`);
++
++    await toggleClassAndWaitForViewChange(
++      { target, className, force }, inspector, tab, `${viewName}-view-refreshed`);
++  }
++}
++
++async function toggleClassAndWaitForViewChange(whatToMutate, inspector, tab, eventName) {
++  let onRefreshed = inspector.once(eventName);
++
++  await ContentTask.spawn(tab.linkedBrowser, whatToMutate,
++    function({ target, className, force }) {
++      content.document.querySelector(target).classList.toggle(className, force);
++    }
++  );
++
++  await onRefreshed;
++  ok(true, "The view was refreshed after the class was changed");
++}
+diff --git a/devtools/client/inspector/shared/test/doc_content_style_changes.html b/devtools/client/inspector/shared/test/doc_content_style_changes.html
+new file mode 100644
+--- /dev/null
++++ b/devtools/client/inspector/shared/test/doc_content_style_changes.html
+@@ -0,0 +1,28 @@
++<!DOCTYPE html>
++<meta charset="utf-8">
++<style>
++#test {
++  color: red;
++}
++/* Adding/removing the green-class on #test should refresh the rule-view when #test is
++   selected */
++#test.green-class {
++  color: green;
++}
++/* Adding/removing the purple-class on #parent should refresh the rule-view when #test is
++   selected */
++#parent.purple-class #test {
++  color: purple;
++}
++/* Adding/removing the blue-class on #sibling should refresh the rule-view when #test is
++   selected*/
++#sibling.blue-class + #test {
++  color: blue;
++}
++</style>
++<div id="parent">
++  <div>
++    <div id="sibling"></div>
++    <div id="test">test</div>
++  </div>
++</div>

+ 30 - 0
frg/work-js/mozilla-release/patches/1448320-61a1.patch

@@ -0,0 +1,30 @@
+# HG changeset patch
+# User Julian Descottes <jdescottes@mozilla.com>
+# Date 1521812872 -3600
+# Node ID 4e884e689aada1cfec78a41e8d89bf0229f0d8fc
+# Parent  aca13577ea3ef4d9778e1f2bde38c06ff582be6d
+Bug 1448320 - bind onDetachedFront in RuleView constructor;r=pbro
+
+MozReview-Commit-ID: BuOTYTuregT
+
+diff --git a/devtools/client/inspector/rules/rules.js b/devtools/client/inspector/rules/rules.js
+--- a/devtools/client/inspector/rules/rules.js
++++ b/devtools/client/inspector/rules/rules.js
+@@ -1745,16 +1745,17 @@ function RuleViewTool(inspector, window)
+   this.document = window.document;
+ 
+   this.view = new CssRuleView(this.inspector, this.document);
+ 
+   this.clearUserProperties = this.clearUserProperties.bind(this);
+   this.refresh = this.refresh.bind(this);
+   this.onDetachedFront = this.onDetachedFront.bind(this);
+   this.onPanelSelected = this.onPanelSelected.bind(this);
++  this.onDetachedFront = this.onDetachedFront.bind(this);
+   this.onSelected = this.onSelected.bind(this);
+   this.onViewRefreshed = this.onViewRefreshed.bind(this);
+ 
+   this.view.on("ruleview-refreshed", this.onViewRefreshed);
+   this.inspector.selection.on("detached-front", this.onDetachedFront);
+   this.inspector.selection.on("new-node-front", this.onSelected);
+   this.inspector.selection.on("pseudoclass", this.refresh);
+   this.inspector.target.on("navigate", this.clearUserProperties);

+ 292 - 0
frg/work-js/mozilla-release/patches/1451211-61a1.patch

@@ -0,0 +1,292 @@
+# HG changeset patch
+# User Julian Descottes <jdescottes@mozilla.com>
+# Date 1524080056 -7200
+# Node ID b8e88898b0f4cb32857618b67d730643bbc17c7a
+# Parent  6af5d6e66496e4f0179b4976df4d996ea276ccae
+Bug 1451211 - Show a colour swatch next to colour values in the CSS variable autocomplete postlabel. r=jdescottes
+
+MozReview-Commit-ID: 7obhsNOBu4N
+
+diff --git a/devtools/client/shared/autocomplete-popup.js b/devtools/client/shared/autocomplete-popup.js
+--- a/devtools/client/shared/autocomplete-popup.js
++++ b/devtools/client/shared/autocomplete-popup.js
+@@ -5,16 +5,17 @@
+ 
+ "use strict";
+ 
+ const HTML_NS = "http://www.w3.org/1999/xhtml";
+ const Services = require("Services");
+ const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
+ const EventEmitter = require("devtools/shared/event-emitter");
+ const {PrefObserver} = require("devtools/client/shared/prefs");
++const {colorUtils} = require("devtools/shared/css/color");
+ 
+ let itemIdCounter = 0;
+ /**
+  * Autocomplete popup UI implementation.
+  *
+  * @constructor
+  * @param {Document} toolboxDoc
+  *        The toolbox document to attach the autocomplete popup panel.
+@@ -456,18 +457,25 @@ AutocompletePopup.prototype = {
+       listItem.appendChild(preDesc);
+       label.textContent = item.label.slice(item.preLabel.length);
+     }
+ 
+     listItem.appendChild(label);
+ 
+     if (item.postLabel) {
+       let postDesc = this._document.createElementNS(HTML_NS, "span");
++      postDesc.className = "autocomplete-postlabel";
+       postDesc.textContent = item.postLabel;
+-      postDesc.className = "autocomplete-postlabel";
++      // Determines if the postlabel is a valid colour or other value
++      if (this._isValidColor(item.postLabel)) {
++        let colorSwatch = this._document.createElementNS(HTML_NS, "span");
++        colorSwatch.className = "autocomplete-swatch autocomplete-colorswatch";
++        colorSwatch.style.cssText = "background-color: " + item.postLabel;
++        postDesc.insertBefore(colorSwatch, postDesc.childNodes[0]);
++      }
+       listItem.appendChild(postDesc);
+     }
+ 
+     if (item.count && item.count > 1) {
+       let countDesc = this._document.createElementNS(HTML_NS, "span");
+       countDesc.textContent = item.count;
+       countDesc.setAttribute("flex", "1");
+       countDesc.className = "autocomplete-count";
+@@ -599,16 +607,28 @@ AutocompletePopup.prototype = {
+     this._tooltip.panel.classList.toggle(newValue + "-theme", true);
+     this._list.classList.toggle(oldValue + "-theme", false);
+     this._list.classList.toggle(newValue + "-theme", true);
+ 
+     this._currentTheme = newValue;
+   },
+ 
+   /**
++  * Determines if the specified colour object is a valid colour, and if
++  * it is not a "special value"
++  *
++  * @return {Boolean}
++  *         If the object represents a proper colour or not.
++  */
++  _isValidColor: function(color) {
++    let colorObj = new colorUtils.CssColor(color);
++    return (colorObj.valid && (!colorObj.specialValue));
++  },
++
++  /**
+    * Used by tests.
+    */
+   get _panel() {
+     return this._tooltip.panel;
+   },
+ 
+   /**
+    * Used by tests.
+diff --git a/devtools/client/shared/test/browser_inplace-editor_autocomplete_css_variable.js b/devtools/client/shared/test/browser_inplace-editor_autocomplete_css_variable.js
+--- a/devtools/client/shared/test/browser_inplace-editor_autocomplete_css_variable.js
++++ b/devtools/client/shared/test/browser_inplace-editor_autocomplete_css_variable.js
+@@ -10,45 +10,59 @@ const { InplaceEditor } = require("devto
+ loadHelperScript("helper_inplace_editor.js");
+ 
+ // Test the inplace-editor autocomplete popup for variable suggestions.
+ // Using a mocked list of CSS variables to avoid test failures linked to
+ // engine changes (new property, removed property, ...).
+ // Also using a mocked list of CSS properties to avoid autocompletion when
+ // typing in "var"
+ 
++// Used for representing the expectation of a visible color swatch
++const COLORSWATCH = true;
+ // format :
+ //  [
+ //    what key to press,
+ //    expected input box value after keypress,
+ //    selected suggestion index (-1 if popup is hidden),
+ //    number of suggestions in the popup (0 if popup is hidden),
+ //    expected post label corresponding with the input box value,
++//    boolean representing if there should be a colour swatch visible,
+ //  ]
+ const testData = [
+-  ["v", "v", -1, 0, null],
+-  ["a", "va", -1, 0, null],
+-  ["r", "var", -1, 0, null],
+-  ["(", "var()", -1, 0, null],
+-  ["-", "var(--abc)", 0, 4, "blue"],
+-  ["VK_BACK_SPACE", "var(-)", -1, 0, null],
+-  ["-", "var(--abc)", 0, 4, "blue"],
+-  ["VK_DOWN", "var(--def)", 1, 4, "red"],
+-  ["VK_DOWN", "var(--ghi)", 2, 4, "green"],
+-  ["VK_DOWN", "var(--jkl)", 3, 4, "yellow"],
+-  ["VK_DOWN", "var(--abc)", 0, 4, "blue"],
+-  ["VK_DOWN", "var(--def)", 1, 4, "red"],
+-  ["VK_LEFT", "var(--def)", -1, 0, null],
++  ["v", "v", -1, 0, null, !COLORSWATCH],
++  ["a", "va", -1, 0, null, !COLORSWATCH],
++  ["r", "var", -1, 0, null, !COLORSWATCH],
++  ["(", "var()", -1, 0, null, !COLORSWATCH],
++  ["-", "var(--abc)", 0, 9, "inherit", !COLORSWATCH],
++  ["VK_BACK_SPACE", "var(-)", -1, 0, null, !COLORSWATCH],
++  ["-", "var(--abc)", 0, 9, "inherit", !COLORSWATCH],
++  ["VK_DOWN", "var(--def)", 1, 9, "transparent", !COLORSWATCH],
++  ["VK_DOWN", "var(--ghi)", 2, 9, "#00FF00", COLORSWATCH],
++  ["VK_DOWN", "var(--jkl)", 3, 9, "rgb(255, 0, 0)", COLORSWATCH],
++  ["VK_DOWN", "var(--mno)", 4, 9, "hsl(120, 60%, 70%)", COLORSWATCH],
++  ["VK_DOWN", "var(--pqr)", 5, 9, "BlueViolet", COLORSWATCH],
++  ["VK_DOWN", "var(--stu)", 6, 9, "15px", !COLORSWATCH],
++  ["VK_DOWN", "var(--vwx)", 7, 9, "rgba(255, 0, 0, 0.4)", COLORSWATCH],
++  ["VK_DOWN", "var(--yz)", 8, 9, "hsla(120, 60%, 70%, 0.3)", COLORSWATCH],
++  ["VK_DOWN", "var(--abc)", 0, 9, "inherit", !COLORSWATCH],
++  ["VK_DOWN", "var(--def)", 1, 9, "transparent", !COLORSWATCH],
++  ["VK_DOWN", "var(--ghi)", 2, 9, "#00FF00", COLORSWATCH],
++  ["VK_LEFT", "var(--ghi)", -1, 0, null, !COLORSWATCH],
+ ];
+ 
+ const CSS_VARIABLES = [
+-  ["--abc", "blue"],
+-  ["--def", "red"],
+-  ["--ghi", "green"],
+-  ["--jkl", "yellow"]
++  ["--abc", "inherit"],
++  ["--def", "transparent"],
++  ["--ghi", "#00FF00"],
++  ["--jkl", "rgb(255, 0, 0)"],
++  ["--mno", "hsl(120, 60%, 70%)"],
++  ["--pqr", "BlueViolet"],
++  ["--stu", "15px"],
++  ["--vwx", "rgba(255, 0, 0, 0.4)"],
++  ["--yz", "hsla(120, 60%, 70%, 0.3)"],
+ ];
+ 
+ const mockGetCSSValuesForPropertyName = function(propertyName) {
+   return [];
+ };
+ 
+ add_task(async function() {
+   await addTab("data:text/html;charset=utf-8,inplace editor CSS variable autocomplete");
+diff --git a/devtools/client/shared/test/helper_inplace_editor.js b/devtools/client/shared/test/helper_inplace_editor.js
+--- a/devtools/client/shared/test/helper_inplace_editor.js
++++ b/devtools/client/shared/test/helper_inplace_editor.js
+@@ -6,16 +6,17 @@
+ "use strict";
+ 
+ /**
+  * Helper methods for the HTMLTooltip integration tests.
+  */
+ 
+ const HTML_NS = "http://www.w3.org/1999/xhtml";
+ const { editableField } = require("devtools/client/shared/inplace-editor");
++const {colorUtils} = require("devtools/shared/css/color");
+ 
+ /**
+  * Create an inplace editor linked to a span element and click on the span to
+  * to turn to edit mode.
+  *
+  * @param {Object} options
+  *        Options passed to the InplaceEditor/editableField constructor.
+  * @param {Document} doc
+@@ -68,20 +69,22 @@ function createSpan(doc) {
+  * autocompletion works as expected.
+  *
+  * @param {Array} testData
+  *        - {String} key, the key to send
+  *        - {String} completion, the expected value of the auto-completion
+  *        - {Number} index, the index of the selected suggestion in the popup
+  *        - {Number} total, the total number of suggestions in the popup
+  *        - {String} postLabel, the expected post label for the selected suggestion
++ *        - {Boolean} colorSwatch, if there is a swatch of color expected to be visible
+  * @param {InplaceEditor} editor
+  *        The InplaceEditor instance being tested
+  */
+-async function testCompletion([key, completion, index, total, postLabel], editor) {
++async function testCompletion([key, completion, index, total,
++    postLabel, colorSwatch], editor) {
+   info("Pressing key " + key);
+   info("Expecting " + completion);
+ 
+   let onVisibilityChange = null;
+   let open = total > 0;
+   if (editor.popup.isOpen != open) {
+     onVisibilityChange = editor.popup.once(open ? "popup-opened" : "popup-closed");
+   }
+@@ -107,16 +110,31 @@ async function testCompletion([key, comp
+     is(editor.input.value, completion, "Correct value is autocompleted");
+   }
+ 
+   if (postLabel) {
+     let selectedItem = editor.popup.getItems()[index];
+     let selectedElement = editor.popup.elements.get(selectedItem);
+     ok(selectedElement.textContent.includes(postLabel),
+       "Selected popup element contains the expected post-label");
++
++    // Determines if there is a color swatch attached to the label
++    // and if the color swatch's background color matches the post label
++    let swatchSpan = selectedElement.getElementsByClassName(
++      "autocomplete-swatch autocomplete-colorswatch");
++    if (colorSwatch) {
++      ok(swatchSpan.length === 1, "Displayed the expected color swatch");
++      let color = new colorUtils.CssColor(swatchSpan[0].style.backgroundColor);
++      let swatchColor = color.rgba;
++      color.newColor(postLabel);
++      let postColor = color.rgba;
++      ok(swatchColor == postColor, "Color swatch matches postLabel value");
++    } else {
++      ok(swatchSpan.length === 0, "As expected no swatches were available");
++    }
+   }
+ 
+   if (total === 0) {
+     ok(!(editor.popup && editor.popup.isOpen), "Popup is closed");
+   } else {
+     ok(editor.popup.isOpen, "Popup is open");
+     is(editor.popup.getItems().length, total, "Number of suggestions match");
+     is(editor.popup.selectedIndex, index, "Expected item is selected");
+diff --git a/devtools/client/themes/common.css b/devtools/client/themes/common.css
+--- a/devtools/client/themes/common.css
++++ b/devtools/client/themes/common.css
+@@ -108,16 +108,45 @@ html|button, html|select {
+   font-style: italic;
+   float: right;
+ }
+ 
+ .devtools-autocomplete-listbox .autocomplete-item > .autocomplete-count {
+   text-align: end;
+ }
+ 
++.devtools-autocomplete-listbox .autocomplete-swatch {
++  cursor: pointer;
++  border-radius: 50%;
++  width: 1em;
++  height: 1em;
++  vertical-align: middle;
++  /* align the swatch with its value */
++  margin-top: -1px;
++  margin-inline-end: 5px;
++  display: inline-block;
++  position: relative;
++}
++
++.devtools-autocomplete-listbox .autocomplete-colorswatch::before {
++  content: '';
++  background-color: #eee;
++  background-image: linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc),
++                    linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc);
++  background-size: 12px 12px;
++  background-position: 0 0, 6px 6px;
++  position: absolute;
++  border-radius: 50%;
++  top: 0;
++  left: 0;
++  right: 0;
++  bottom: 0;
++  z-index: -1;
++}
++
+ /* Rest of the dark and light theme */
+ 
+ .devtools-autocomplete-popup,
+ .CodeMirror-hints,
+ .CodeMirror-Tern-tooltip {
+   border: 1px solid hsl(210,24%,90%);
+   background-image: linear-gradient(to bottom, hsla(209,18%,100%,0.9), hsl(210,24%,95%));
+ }

+ 121 - 0
frg/work-js/mozilla-release/patches/1454888-61a1.patch

@@ -0,0 +1,121 @@
+# HG changeset patch
+# User Julian Descottes <jdescottes@mozilla.com>
+# Date 1525211103 -7200
+# Node ID 288cf722769784a6da695d6c63391778b0828388
+# Parent  4b2e83f5b78f20d99c860f83963cf045c11d749b
+Bug 1454888 - Autocomplete postLabel overlaps with the scrollbar of the suggestions popup. r=jdescottes
+
+MozReview-Commit-ID: FpfqEvdYg3z
+
+diff --git a/devtools/client/shared/autocomplete-popup.js b/devtools/client/shared/autocomplete-popup.js
+--- a/devtools/client/shared/autocomplete-popup.js
++++ b/devtools/client/shared/autocomplete-popup.js
+@@ -260,32 +260,34 @@ AutocompletePopup.prototype = {
+       if (count) {
+         label += count + "";
+       }
+ 
+       if (postLabel) {
+         label += postLabel;
+       }
+ 
+-      max = Math.max(label.length, max);
++      const length = label.length + (postLabel ? 3 : 0);
++      max = Math.max(length, max);
+     }
+ 
+     this.__maxLabelLength = max;
+     return this.__maxLabelLength;
+   },
+ 
+   /**
+    * Update the panel size to fit the content.
+    */
+   _updateSize: function() {
+     if (!this._tooltip) {
+       return;
+     }
+ 
+     this._list.style.width = (this._maxLabelLength + 3) + "ch";
++
+     let selectedItem = this.selectedItem;
+     if (selectedItem) {
+       this._scrollElementIntoViewIfNeeded(this.elements.get(selectedItem));
+     }
+   },
+ 
+   _scrollElementIntoViewIfNeeded: function(element) {
+     let quads = element.getBoxQuads({relativeTo: this._tooltip.panel});
+diff --git a/devtools/client/themes/common.css b/devtools/client/themes/common.css
+--- a/devtools/client/themes/common.css
++++ b/devtools/client/themes/common.css
+@@ -50,45 +50,48 @@ html|button, html|select {
+ }
+ 
+ /* Autocomplete Popup */
+ 
+ .devtools-autocomplete-popup {
+   box-shadow: 0 1px 0 hsla(209,29%,72%,.25) inset;
+   background-color: transparent;
+   border-radius: 3px;
+-  overflow-x: hidden;
+-  max-height: 20rem;
++  overflow: hidden;
+ 
+   /* Devtools autocompletes display technical english keywords and should be displayed
+      using LTR direction. */
+   direction: ltr !important;
+ }
+ 
+ /* Reset list styles. */
+ .devtools-autocomplete-popup ul {
+   list-style: none;
+ }
+ 
+ .devtools-autocomplete-popup ul,
+ .devtools-autocomplete-popup li {
+   margin: 0;
+ }
+ 
+-:root[platform="linux"] .devtools-autocomplete-popup {
++:root[platform="linux"] .devtools-autocomplete-listbox {
+   /* Root font size is bigger on Linux, adjust rem-based values. */
+   max-height: 16rem;
+ }
+ 
+ .devtools-autocomplete-listbox {
+   -moz-appearance: none !important;
+   background-color: transparent;
+   border-width: 0px !important;
+   margin: 0;
+   padding: 2px;
++  overflow-x: hidden;
++  max-height: 20rem;
++  height: 100%;
++  box-sizing: border-box;
+ }
+ 
+ .devtools-autocomplete-listbox .autocomplete-item {
+   width: 100%;
+   background-color: transparent;
+   border-radius: 4px;
+   padding: 1px 0;
+   cursor: default;
+@@ -102,16 +105,17 @@ html|button, html|select {
+   margin: 0;
+   padding: 0;
+   float: left;
+ }
+ 
+ .devtools-autocomplete-listbox .autocomplete-item > .autocomplete-postlabel {
+   font-style: italic;
+   float: right;
++  padding-right: 3px;
+ }
+ 
+ .devtools-autocomplete-listbox .autocomplete-item > .autocomplete-count {
+   text-align: end;
+ }
+ 
+ .devtools-autocomplete-listbox .autocomplete-swatch {
+   cursor: pointer;

+ 116 - 0
frg/work-js/mozilla-release/patches/1627944-83a1.patch

@@ -0,0 +1,116 @@
+# HG changeset patch
+# User Sebastian Streich <sstreich@mozilla.com>
+# Date 1600793965 0
+# Node ID 666690ce97bc198a517d412cf2a5521d8e1e3b24
+# Parent  57cb9b9bd46b68ee82249cf391b86dd98bd6aaba
+Bug 1627944 - Restrict toplevel data uri's to non-doc creating or downloads r=ckerschb,annevk
+
+Differential Revision: https://phabricator.services.mozilla.com/D86836
+
+diff --git a/dom/security/nsContentSecurityManager.cpp b/dom/security/nsContentSecurityManager.cpp
+--- a/dom/security/nsContentSecurityManager.cpp
++++ b/dom/security/nsContentSecurityManager.cpp
+@@ -13,16 +13,17 @@
+ #include "nsILoadInfo.h"
+ #include "nsIOService.h"
+ #include "nsContentUtils.h"
+ #include "nsCORSListenerProxy.h"
+ #include "nsIStreamListener.h"
+ #include "nsCDefaultURIFixup.h"
+ #include "nsIURIFixup.h"
+ #include "nsIImageLoadingContent.h"
++#include "nsMimeTypes.h"
+ 
+ #include "mozilla/dom/Element.h"
+ #include "mozilla/dom/TabChild.h"
+ 
+ NS_IMPL_ISUPPORTS(nsContentSecurityManager,
+                   nsIContentSecurityManager,
+                   nsIChannelEventSink)
+ 
+@@ -69,19 +70,20 @@ nsContentSecurityManager::AllowTopLevelN
+                                base64, nullptr);
+   NS_ENSURE_SUCCESS(rv, true);
+ 
+   // Whitelist data: images as long as they are not SVGs
+   if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("image/")) &&
+       !contentType.EqualsLiteral("image/svg+xml")) {
+     return true;
+   }
+-  // Whitelist all plain text types as well as data: PDFs.
+-  if (nsContentUtils::IsPlainTextType(contentType) ||
+-      contentType.EqualsLiteral("application/pdf")) {
++  // Allow all data: PDFs. or JSON documents
++  if (contentType.EqualsLiteral(APPLICATION_JSON) ||
++      contentType.EqualsLiteral(TEXT_JSON) ||
++      contentType.EqualsLiteral(APPLICATION_PDF)) {
+     return true;
+   }
+   // Redirecting to a toplevel data: URI is not allowed, hence we make
+   // sure the RedirectChain is empty.
+   if (!loadInfo->GetLoadTriggeredFromExternal() &&
+       nsContentUtils::IsSystemPrincipal(loadInfo->TriggeringPrincipal()) &&
+       loadInfo->RedirectChain().IsEmpty()) {
+     return true;
+diff --git a/dom/security/test/general/test_block_toplevel_data_navigation.html b/dom/security/test/general/test_block_toplevel_data_navigation.html
+--- a/dom/security/test/general/test_block_toplevel_data_navigation.html
++++ b/dom/security/test/general/test_block_toplevel_data_navigation.html
+@@ -69,16 +69,29 @@ function test5() {
+   // navigating to a URI which redirects to a data: URI using window.open() should be blocked
+   let win5 = window.open("file_block_toplevel_data_redirect.sjs");
+   setTimeout(function () {
+     // Please note that the data: URI will be displayed in the URL-Bar but not
+     // loaded, hence we rather rely on document.body than document.location
+     is(SpecialPowers.wrap(win5).document.body.innerHTML, "",
+       "navigating to URI which redirects to a data: URI using window.open() should be blocked");
+     win5.close();
++    test6();
++  }, 1000);
++}
++
++function test6() {
++  // navigating to a data: URI without a Content Type should be blocked
++  let win6 = window.open("data:DataURIsWithNoContentTypeShouldBeBlocked");
++  setTimeout(function () {
++    // Please note that the data: URI will be displayed in the URL-Bar but not
++    // loaded, hence we rather rely on document.body than document.location
++    is(win6.document.body.innerHTML, "",
++       "navigating to a data: URI without a Content Type should be blocked");
++    win6.close();
+     SimpleTest.finish();
+   }, 1000);
+ }
+ 
+ // fire up the tests
+ test1();
+ 
+ </script>
+diff --git a/uriloader/exthandler/nsExternalHelperAppService.cpp b/uriloader/exthandler/nsExternalHelperAppService.cpp
+--- a/uriloader/exthandler/nsExternalHelperAppService.cpp
++++ b/uriloader/exthandler/nsExternalHelperAppService.cpp
+@@ -1631,16 +1631,25 @@ NS_IMETHODIMP nsExternalAppHandler::OnSt
+   // Inform channel it is open on behalf of a download to throttle it during
+   // page loads and prevent its caching.
+   nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(aChannel);
+   if (httpInternal) {
+     rv = httpInternal->SetChannelIsForDownload(true);
+     MOZ_ASSERT(NS_SUCCEEDED(rv));
+   }
+ 
++  bool isDataScheme = false;
++  mSourceUrl->SchemeIs("data", &isDataScheme);
++  if (isDataScheme) {
++    // In case we're downloading a data:// uri
++    // we don't want to apply AllowTopLevelNavigationToDataURI.
++    nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
++    loadInfo->SetForceAllowDataURI(true);
++  }
++
+   // now that the temp file is set up, find out if we need to invoke a dialog
+   // asking the user what they want us to do with this content...
+ 
+   // We can get here for three reasons: "can't handle", "sniffed type", or
+   // "server sent content-disposition:attachment".  In the first case we want
+   // to honor the user's "always ask" pref; in the other two cases we want to
+   // honor it only if the default action is "save".  Opening attachments in
+   // helper apps by default breaks some websites (especially if the attachment

+ 29 - 0
frg/work-js/mozilla-release/patches/series

@@ -6668,6 +6668,11 @@ NOBUG-removemobilethemes-25318.patch
 1394554-1-57a1.patch
 1394554-1-57a1.patch
 1394554-2-57a1.patch
 1394554-2-57a1.patch
 1394554-3-57a1.patch
 1394554-3-57a1.patch
+1396798-1-57a1.patch
+1396798-2-57a1.patch
+1398691-57a1.patch
+1398692-1-57a1.patch
+1398692-2-57a1.patch
 1401242-57a1.patch
 1401242-57a1.patch
 1394804-1-57a1.patch
 1394804-1-57a1.patch
 1394804-2-57a1.patch
 1394804-2-57a1.patch
@@ -6692,6 +6697,8 @@ NOBUG-removemobilethemes-25318.patch
 1403389-58a1.patch
 1403389-58a1.patch
 1348960-58a1.patch
 1348960-58a1.patch
 1404197-58a1.patch
 1404197-58a1.patch
+1403641-1-58a1.patch
+1403641-2-58a1.patch
 1405983-1-58a1.patch
 1405983-1-58a1.patch
 1405983-2-58a1.patch
 1405983-2-58a1.patch
 1402389-58a1.patch
 1402389-58a1.patch
@@ -6707,6 +6714,7 @@ NOBUG-removemobilethemes-25318.patch
 1407830-2-58a1.patch
 1407830-2-58a1.patch
 1407830-3-58a1.patch
 1407830-3-58a1.patch
 1387827-58a1.patch
 1387827-58a1.patch
+1408451-58a1.patch
 1409684-58a1.patch
 1409684-58a1.patch
 1339800-58a1.patch
 1339800-58a1.patch
 1401207-1-58a1.patch
 1401207-1-58a1.patch
@@ -6734,6 +6742,11 @@ NOBUG-removemobilethemes-25318.patch
 1412023-58a1.patch
 1412023-58a1.patch
 1412359-1-58a1.patch
 1412359-1-58a1.patch
 1412359-2-58a1.patch
 1412359-2-58a1.patch
+1403814-1-58a1.patch
+1403814-2-58a1.patch
+1403814-3-58a1.patch
+1403870-1-58a1.patch
+1403870-2-58a1.patch
 1413765-1-58a1.patch
 1413765-1-58a1.patch
 1413765-2-58a1.patch
 1413765-2-58a1.patch
 1408919-58a1.patch
 1408919-58a1.patch
@@ -6742,9 +6755,12 @@ NOBUG-removemobilethemes-25318.patch
 1403449-58a1.patch
 1403449-58a1.patch
 1401958-58a1.patch
 1401958-58a1.patch
 1408928-58a1.patch
 1408928-58a1.patch
+1414609-58a1.patch
 1408790-58a1.patch
 1408790-58a1.patch
 1412029-1only-58a1.patch
 1412029-1only-58a1.patch
 1408198-58a1.patch
 1408198-58a1.patch
+1407891-1-58a1.patch
+1407891-2-58a1.patch
 1394841-1-58a1.patch
 1394841-1-58a1.patch
 1394841-2-58a1.patch
 1394841-2-58a1.patch
 1394841-3-58a1.patch
 1394841-3-58a1.patch
@@ -6762,6 +6778,7 @@ NOBUG-removemobilethemes-25318.patch
 1371293-3-59a1.patch
 1371293-3-59a1.patch
 1371293-4-59a1.patch
 1371293-4-59a1.patch
 1371293-7-59a1.patch
 1371293-7-59a1.patch
+1415612-59a1.patch
 1416960-59a1.patch
 1416960-59a1.patch
 1387511-1-59a1.patch
 1387511-1-59a1.patch
 1387511-2-59a1.patch
 1387511-2-59a1.patch
@@ -6876,6 +6893,7 @@ NOBUG-removemobilethemes-25318.patch
 1404392-59a1.patch
 1404392-59a1.patch
 1428014-59a1.patch
 1428014-59a1.patch
 1428191-59a1.patch
 1428191-59a1.patch
+1407085-59a1.patch
 1381463-59a1.patch
 1381463-59a1.patch
 1401953-59a1.patch
 1401953-59a1.patch
 1398904-59a1.patch
 1398904-59a1.patch
@@ -6895,6 +6913,7 @@ NOBUG-removemobilethemes-25318.patch
 1420934-59a1.patch
 1420934-59a1.patch
 1406841-59a1.patch
 1406841-59a1.patch
 1429271-59a1.patch
 1429271-59a1.patch
+1391277-59a1.patch
 1428722-1-59a1.patch
 1428722-1-59a1.patch
 1428722-2-59a1.patch
 1428722-2-59a1.patch
 1428722-3-59a1.patch
 1428722-3-59a1.patch
@@ -7287,6 +7306,9 @@ NOBUG-20180313-inspector-61a1.patch
 1447154-2-61a1.patch
 1447154-2-61a1.patch
 1444594-61a1.patch
 1444594-61a1.patch
 1447528-61a1.patch
 1447528-61a1.patch
+1437730-1-61a1.patch
+1437730-2-61a1.patch
+1437730-3-61a1.patch
 1444007-61a1.patch
 1444007-61a1.patch
 1443846-1-61a1.patch
 1443846-1-61a1.patch
 1443846-2-61a1.patch
 1443846-2-61a1.patch
@@ -7296,11 +7318,17 @@ NOBUG-20180313-inspector-61a1.patch
 1414286-2-61a1.patch
 1414286-2-61a1.patch
 1444033-61a1.patch
 1444033-61a1.patch
 1442153-61a1.patch
 1442153-61a1.patch
+1447736-61a1.patch
+1448320-61a1.patch
+1431949-61a1.patch
+1430558-61a1.patch
 1448194-61a1.patch
 1448194-61a1.patch
 1447180-61a1.patch
 1447180-61a1.patch
 1450163-61a1.patch
 1450163-61a1.patch
+1451211-61a1.patch
 1403188-61a1.patch
 1403188-61a1.patch
 1425866-61a1.patch
 1425866-61a1.patch
+1454888-61a1.patch
 1450182-61a1.patch
 1450182-61a1.patch
 1451683-62a1.patch
 1451683-62a1.patch
 1463924-62a1.patch
 1463924-62a1.patch
@@ -7321,6 +7349,7 @@ NOBUG-20180313-inspector-61a1.patch
 1639815-7-78a1.patch
 1639815-7-78a1.patch
 1639815-8-78a1.patch
 1639815-8-78a1.patch
 1654470-81a1.patch
 1654470-81a1.patch
+1627944-83a1.patch
 1667896-83a1.patch
 1667896-83a1.patch
 1670130-83a1.patch
 1670130-83a1.patch
 1480005-3-86a1.patch
 1480005-3-86a1.patch