Browse Source

sync with release

Frank-Rainer Grahl 7 months ago
parent
commit
1a096d3cc6
100 changed files with 25064 additions and 32 deletions
  1. 84 0
      frg/work-js/comm-release/patches/1445374-61a1.patch
  2. 1 1
      frg/work-js/comm-release/patches/series
  3. 1 0
      frg/work-js/comm-release/patches/series-test
  4. 551 0
      frg/work-js/mozilla-release/patches/1037335-1-59a1.patch
  5. 41 0
      frg/work-js/mozilla-release/patches/1037335-2-59a1.patch
  6. 57 0
      frg/work-js/mozilla-release/patches/1037335-3no4or5-59a1.patch
  7. 158 0
      frg/work-js/mozilla-release/patches/1037335-6-59a1.patch
  8. 411 0
      frg/work-js/mozilla-release/patches/1293277-1-59a1.patch
  9. 79 0
      frg/work-js/mozilla-release/patches/1293277-2-59a1.patch
  10. 124 0
      frg/work-js/mozilla-release/patches/1293277-3-59a1.patch
  11. 856 0
      frg/work-js/mozilla-release/patches/1293277-4-59a1.patch
  12. 3110 0
      frg/work-js/mozilla-release/patches/1293277-5-59a1.patch
  13. 802 0
      frg/work-js/mozilla-release/patches/1293277-6-59a1.patch
  14. 407 0
      frg/work-js/mozilla-release/patches/1293277-7no8-59a1.patch
  15. 26 0
      frg/work-js/mozilla-release/patches/1293277-9-59a1.patch
  16. 2 2
      frg/work-js/mozilla-release/patches/1340901-87a1.patch
  17. 4 4
      frg/work-js/mozilla-release/patches/1393609-60a1.patch
  18. 42 0
      frg/work-js/mozilla-release/patches/1403866-58a1.patch
  19. 31 0
      frg/work-js/mozilla-release/patches/1415056-1-59a1.patch
  20. 33 0
      frg/work-js/mozilla-release/patches/1415056-2-59a1.patch
  21. 393 0
      frg/work-js/mozilla-release/patches/1418243-1no2-59a1.patch
  22. 194 0
      frg/work-js/mozilla-release/patches/1418243-3-59a1.patch
  23. 507 0
      frg/work-js/mozilla-release/patches/1418376-59a1.patch
  24. 341 0
      frg/work-js/mozilla-release/patches/1419536-1-59a1.patch
  25. 342 0
      frg/work-js/mozilla-release/patches/1419536-2-59a1.patch
  26. 752 0
      frg/work-js/mozilla-release/patches/1419536-3-59a1.patch
  27. 441 0
      frg/work-js/mozilla-release/patches/1419536-4-59a1.patch
  28. 36 0
      frg/work-js/mozilla-release/patches/1419536-5-59a1.patch
  29. 126 0
      frg/work-js/mozilla-release/patches/1420221-59a1.patch
  30. 5 5
      frg/work-js/mozilla-release/patches/1420594-1-59a1.patch
  31. 149 0
      frg/work-js/mozilla-release/patches/1420743-1-59a1.patch
  32. 30 0
      frg/work-js/mozilla-release/patches/1420743-2-59a1.patch
  33. 30 0
      frg/work-js/mozilla-release/patches/1422584-59a1.patch
  34. 17 20
      frg/work-js/mozilla-release/patches/1422983-59a1.patch
  35. 703 0
      frg/work-js/mozilla-release/patches/1423328-59a1.patch
  36. 729 0
      frg/work-js/mozilla-release/patches/1423412-1-59a1.patch
  37. 139 0
      frg/work-js/mozilla-release/patches/1423412-2-59a1.patch
  38. 49 0
      frg/work-js/mozilla-release/patches/1423412-3-59a1.patch
  39. 573 0
      frg/work-js/mozilla-release/patches/1423412-4no5-59a1.patch
  40. 188 0
      frg/work-js/mozilla-release/patches/1423913-1-59a1.patch
  41. 185 0
      frg/work-js/mozilla-release/patches/1423913-2-59a1.patch
  42. 98 0
      frg/work-js/mozilla-release/patches/1423913-3-59a1.patch
  43. 400 0
      frg/work-js/mozilla-release/patches/1424338-1-59a1.patch
  44. 330 0
      frg/work-js/mozilla-release/patches/1424338-2-59a1.patch
  45. 746 0
      frg/work-js/mozilla-release/patches/1424338-3-59a1.patch
  46. 968 0
      frg/work-js/mozilla-release/patches/1424338-4-59a1.patch
  47. 294 0
      frg/work-js/mozilla-release/patches/1424338-5-59a1.patch
  48. 509 0
      frg/work-js/mozilla-release/patches/1424338-6-59a1.patch
  49. 105 0
      frg/work-js/mozilla-release/patches/1424338-7-59a1.patch
  50. 395 0
      frg/work-js/mozilla-release/patches/1424452-59a1.patch
  51. 55 0
      frg/work-js/mozilla-release/patches/1424943-59a1.patch
  52. 196 0
      frg/work-js/mozilla-release/patches/1425316-1-59a1.patch
  53. 134 0
      frg/work-js/mozilla-release/patches/1425316-2-59a1.patch
  54. 146 0
      frg/work-js/mozilla-release/patches/1425316-3-59a1.patch
  55. 270 0
      frg/work-js/mozilla-release/patches/1425316-4-59a1.patch
  56. 138 0
      frg/work-js/mozilla-release/patches/1425316-5-59a1.patch
  57. 107 0
      frg/work-js/mozilla-release/patches/1425316-6-59a1.patch
  58. 676 0
      frg/work-js/mozilla-release/patches/1425574-1-59a1.patch
  59. 156 0
      frg/work-js/mozilla-release/patches/1425574-2-59a1.patch
  60. 157 0
      frg/work-js/mozilla-release/patches/1425574-3-59a1.patch
  61. 500 0
      frg/work-js/mozilla-release/patches/1425574-4-59a1.patch
  62. 157 0
      frg/work-js/mozilla-release/patches/1425574-5-59a1.patch
  63. 25 0
      frg/work-js/mozilla-release/patches/1425574-6-59a1.patch
  64. 148 0
      frg/work-js/mozilla-release/patches/1425574-7-59a1.patch
  65. 459 0
      frg/work-js/mozilla-release/patches/1425574-8-59a1.patch
  66. 190 0
      frg/work-js/mozilla-release/patches/1425574-9-59a1.patch
  67. 38 0
      frg/work-js/mozilla-release/patches/1425614-1-59a1.patch
  68. 51 0
      frg/work-js/mozilla-release/patches/1425614-2-59a1.patch
  69. 47 0
      frg/work-js/mozilla-release/patches/1425704-59a1.patch
  70. 193 0
      frg/work-js/mozilla-release/patches/1425965-1-59a1.patch
  71. 209 0
      frg/work-js/mozilla-release/patches/1425965-2-59a1.patch
  72. 210 0
      frg/work-js/mozilla-release/patches/1425965-3-59a1.patch
  73. 507 0
      frg/work-js/mozilla-release/patches/1425965-4-59a1.patch
  74. 108 0
      frg/work-js/mozilla-release/patches/1425965-5-59a1.patch
  75. 115 0
      frg/work-js/mozilla-release/patches/1425965-6-59a1.patch
  76. 35 0
      frg/work-js/mozilla-release/patches/1425965-7-59a1.patch
  77. 172 0
      frg/work-js/mozilla-release/patches/1425975-01-59a1.patch
  78. 194 0
      frg/work-js/mozilla-release/patches/1425975-02-59a1.patch
  79. 130 0
      frg/work-js/mozilla-release/patches/1425975-03-59a1.patch
  80. 59 0
      frg/work-js/mozilla-release/patches/1425975-04-59a1.patch
  81. 44 0
      frg/work-js/mozilla-release/patches/1425975-05-59a1.patch
  82. 273 0
      frg/work-js/mozilla-release/patches/1425975-06-59a1.patch
  83. 478 0
      frg/work-js/mozilla-release/patches/1425975-07no08-59a1.patch
  84. 95 0
      frg/work-js/mozilla-release/patches/1425975-09-59a1.patch
  85. 84 0
      frg/work-js/mozilla-release/patches/1425975-10-59a1.patch
  86. 77 0
      frg/work-js/mozilla-release/patches/1425975-11-59a1.patch
  87. 52 0
      frg/work-js/mozilla-release/patches/1425975-12-59a1.patch
  88. 155 0
      frg/work-js/mozilla-release/patches/1425975-13-59a1.patch
  89. 109 0
      frg/work-js/mozilla-release/patches/1425975-14-59a1.patch
  90. 182 0
      frg/work-js/mozilla-release/patches/1425975-15-59a1.patch
  91. 248 0
      frg/work-js/mozilla-release/patches/1425975-16-59a1.patch
  92. 134 0
      frg/work-js/mozilla-release/patches/1425975-17-59a1.patch
  93. 33 0
      frg/work-js/mozilla-release/patches/1425975-18-59a1.patch
  94. 66 0
      frg/work-js/mozilla-release/patches/1425975-19-59a1.patch
  95. 51 0
      frg/work-js/mozilla-release/patches/1425993-59a1.patch
  96. 51 0
      frg/work-js/mozilla-release/patches/1426250-1-59a1.patch
  97. 44 0
      frg/work-js/mozilla-release/patches/1426250-2-59a1.patch
  98. 406 0
      frg/work-js/mozilla-release/patches/1426250-3-59a1.patch
  99. 151 0
      frg/work-js/mozilla-release/patches/1426253-1-59a1.patch
  100. 155 0
      frg/work-js/mozilla-release/patches/1426253-2-59a1.patch

+ 84 - 0
frg/work-js/comm-release/patches/1445374-61a1.patch

@@ -0,0 +1,84 @@
+# HG changeset patch
+# User Jorg K <jorgk@jorgk.com>
+# Date 1520965094 -3600
+# Node ID 9bd75170c7aea42af5de3c7de1da9ff9a0b84354
+# Parent  733708a0681b8222037d1e3d4f9a3d780c2739c4
+Bug 1445374 - Port Bug 1443079: nsIScriptError::Init() has another argument now. rs=bustage-fix DONTBUILD
+
+diff --git a/calendar/base/backend/libical/calUtils.cpp b/calendar/base/backend/libical/calUtils.cpp
+--- a/calendar/base/backend/libical/calUtils.cpp
++++ b/calendar/base/backend/libical/calUtils.cpp
+@@ -11,25 +11,25 @@ extern "C" {
+ }
+ 
+ namespace cal {
+ 
+ nsresult logError(const nsAString& msg) {
+     nsresult rc;
+     nsCOMPtr<nsIScriptError> const scriptError(do_CreateInstance("@mozilla.org/scripterror;1", &rc));
+     NS_ENSURE_SUCCESS(rc, rc);
+-    rc = scriptError->Init(msg, EmptyString(), EmptyString(), 0, 0, nsIScriptError::errorFlag, "calendar");
++    rc = scriptError->Init(msg, EmptyString(), EmptyString(), 0, 0, nsIScriptError::errorFlag, "calendar", false);
+     return getConsoleService()->LogMessage(scriptError);
+ }
+ 
+ nsresult logWarning(const nsAString& msg) {
+     nsresult rc;
+     nsCOMPtr<nsIScriptError> const scriptError(do_CreateInstance("@mozilla.org/scripterror;1", &rc));
+     NS_ENSURE_SUCCESS(rc, rc);
+-    rc = scriptError->Init(msg, EmptyString(), EmptyString(), 0, 0, nsIScriptError::warningFlag, "calendar");
++    rc = scriptError->Init(msg, EmptyString(), EmptyString(), 0, 0, nsIScriptError::warningFlag, "calendar", false);
+     return getConsoleService()->LogMessage(scriptError);
+ }
+ 
+ nsresult log(char16_t const* msg) {
+     return getConsoleService()->LogStringMessage(msg);
+ }
+ 
+ nsCOMPtr<calITimezone> detectTimezone(icaltimetype const& icalt,
+diff --git a/mailnews/base/util/nsMsgDBFolder.cpp b/mailnews/base/util/nsMsgDBFolder.cpp
+--- a/mailnews/base/util/nsMsgDBFolder.cpp
++++ b/mailnews/base/util/nsMsgDBFolder.cpp
+@@ -3594,17 +3594,18 @@ NS_IMETHODIMP nsMsgDBFolder::GetPretties
+ 
+     if (cs) {
+       nsCString msg(__FUNCTION__);
+       msg.AppendLiteral(" is deprecated and will be removed soon.");
+ 
+       nsCOMPtr<nsIScriptError> e = do_CreateInstance(NS_SCRIPTERROR_CONTRACTID);
+       if (e && NS_SUCCEEDED(e->Init(NS_ConvertUTF8toUTF16(msg), EmptyString(),
+                                     EmptyString(), 0, 0,
+-                                    nsIScriptError::warningFlag, "mailnews"))) {
++                                    nsIScriptError::warningFlag, "mailnews",
++                                    false))) {
+         cs->LogMessage(e);
+       }
+     }
+   }
+   NS_WARNING("You are trying to use the deprecated attribute 'prettiestName'.");
+ 
+   if (NS_SUCCEEDED(GetPrettyName(name)))
+     return NS_OK;
+diff --git a/mailnews/base/util/nsMsgUtils.cpp b/mailnews/base/util/nsMsgUtils.cpp
+--- a/mailnews/base/util/nsMsgUtils.cpp
++++ b/mailnews/base/util/nsMsgUtils.cpp
+@@ -100,17 +100,18 @@ NS_MSG_BASE void MsgLogToConsole4(const 
+   if (NS_WARN_IF(!console))
+     return;
+   if (NS_FAILED(scriptError->Init(aErrorText,
+                                   aFilename,
+                                   EmptyString(),
+                                   aLinenumber,
+                                   0,
+                                   aFlag,
+-                                  "mailnews")))
++                                  "mailnews",
++                                  false)))
+     return;
+   console->LogMessage(scriptError);
+   return;
+ }
+ 
+ using namespace mozilla;
+ using namespace mozilla::net;
+ 

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

@@ -2149,7 +2149,7 @@ TOP-1872623-cancelbookmark-25319.patch
 1877001-port1407891-25319.patch
 1353704-69a1.patch
 1879726-port1398229-25319.patch
-
+1445374-61a1.patch
 
 253-release-top.patch
 9999999-disablemaint-comm-25318.patch

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

@@ -2149,3 +2149,4 @@ TOP-1872623-cancelbookmark-25319.patch
 1877001-port1407891-25319.patch
 1353704-69a1.patch
 1879726-port1398229-25319.patch
+1445374-61a1.patch

+ 551 - 0
frg/work-js/mozilla-release/patches/1037335-1-59a1.patch

@@ -0,0 +1,551 @@
+# HG changeset patch
+# User Chung-Sheng Fu <cfu@mozilla.com>
+# Date 1511967180 -7200
+# Node ID 48ba62a0feb0c7431807e0d586c0e310da21f721
+# Parent  8d6c019576925b5adba7880c62dde3cb006ea9f4
+Bug 1037335 - Implement security policy violation event. r=ckerschb,smaug
+
+MozReview-Commit-ID: 4BYThUXduI4
+
+diff --git a/dom/interfaces/security/nsIContentSecurityPolicy.idl b/dom/interfaces/security/nsIContentSecurityPolicy.idl
+--- a/dom/interfaces/security/nsIContentSecurityPolicy.idl
++++ b/dom/interfaces/security/nsIContentSecurityPolicy.idl
+@@ -1,23 +1,20 @@
+ /* This Source Code Form is subject to the terms of the Mozilla Public
+  * License, v. 2.0. If a copy of the MPL was not distributed with this
+  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+ 
+ #include "nsISerializable.idl"
+ #include "nsIContentPolicy.idl"
+ 
+ interface nsIURI;
+-interface nsIChannel;
+ interface nsIDocShell;
+ interface nsIDOMDocument;
+ interface nsIEventTarget;
+ interface nsIPrincipal;
+-interface nsIScriptElement;
+-interface nsIURI;
+ 
+ /**
+  * nsIContentSecurityPolicy
+  * Describes an XPCOM component used to model and enforce CSPs.  Instances of
+  * this class may have multiple policies within them, but there should only be
+  * one of these per document/principal.
+  */
+ 
+diff --git a/dom/security/nsCSPContext.cpp b/dom/security/nsCSPContext.cpp
+--- a/dom/security/nsCSPContext.cpp
++++ b/dom/security/nsCSPContext.cpp
+@@ -838,47 +838,39 @@ StripURIForReporting(nsIURI* aURI,
+     aURI->GetPrePath(outStrippedURI);
+     return;
+   }
+ 
+   // 3) Return uri, with any fragment component removed.
+   aURI->GetSpecIgnoringRef(outStrippedURI);
+ }
+ 
+-/**
+- * Sends CSP violation reports to all sources listed under report-uri.
+- *
+- * @param aBlockedContentSource
+- *        Either a CSP Source (like 'self', as string) or nsIURI: the source
+- *        of the violation.
+- * @param aOriginalUri
+- *        The original URI if the blocked content is a redirect, else null
+- * @param aViolatedDirective
+- *        the directive that was violated (string).
+- * @param aSourceFile
+- *        name of the file containing the inline script violation
+- * @param aScriptSample
+- *        a sample of the violating inline script
+- * @param aLineNum
+- *        source line number of the violation (if available)
+- */
+ nsresult
+-nsCSPContext::SendReports(nsISupports* aBlockedContentSource,
+-                          nsIURI* aOriginalURI,
+-                          nsAString& aViolatedDirective,
+-                          uint32_t aViolatedPolicyIndex,
+-                          nsAString& aSourceFile,
+-                          nsAString& aScriptSample,
+-                          uint32_t aLineNum)
++nsCSPContext::GatherSecurityPolicyViolationEventData(
++  nsISupports* aBlockedContentSource,
++  nsIURI* aOriginalURI,
++  nsAString& aViolatedDirective,
++  uint32_t aViolatedPolicyIndex,
++  nsAString& aSourceFile,
++  nsAString& aScriptSample,
++  uint32_t aLineNum,
++  mozilla::dom::SecurityPolicyViolationEventInit& aViolationEventInit)
+ {
+   NS_ENSURE_ARG_MAX(aViolatedPolicyIndex, mPolicies.Length() - 1);
+ 
+-  dom::CSPReport report;
+   nsresult rv;
+ 
++  // document-uri
++  nsAutoCString reportDocumentURI;
++  StripURIForReporting(mSelfURI, mSelfURI, reportDocumentURI);
++  aViolationEventInit.mDocumentURI = NS_ConvertUTF8toUTF16(reportDocumentURI);
++
++  // referrer
++  aViolationEventInit.mReferrer = mReferrer;
++
+   // blocked-uri
+   if (aBlockedContentSource) {
+     nsAutoCString reportBlockedURI;
+     nsCOMPtr<nsIURI> uri = do_QueryInterface(aBlockedContentSource);
+     // could be a string or URI
+     if (uri) {
+       StripURIForReporting(uri, mSelfURI, reportBlockedURI);
+     } else {
+@@ -887,89 +879,150 @@ nsCSPContext::SendReports(nsISupports* a
+         cstr->GetData(reportBlockedURI);
+       }
+     }
+     if (reportBlockedURI.IsEmpty()) {
+       // this can happen for frame-ancestors violation where the violating
+       // ancestor is cross-origin.
+       NS_WARNING("No blocked URI (null aBlockedContentSource) for CSP violation report.");
+     }
+-    report.mCsp_report.mBlocked_uri = NS_ConvertUTF8toUTF16(reportBlockedURI);
++    aViolationEventInit.mBlockedURI = NS_ConvertUTF8toUTF16(reportBlockedURI);
+   }
+ 
+-  // document-uri
+-  nsAutoCString reportDocumentURI;
+-  StripURIForReporting(mSelfURI, mSelfURI, reportDocumentURI);
+-  report.mCsp_report.mDocument_uri = NS_ConvertUTF8toUTF16(reportDocumentURI);
++  // violated-directive
++  aViolationEventInit.mViolatedDirective = aViolatedDirective;
++
++  // effective-directive
++  aViolationEventInit.mEffectiveDirective = aViolatedDirective;
+ 
+   // original-policy
+   nsAutoString originalPolicy;
+   rv = this->GetPolicyString(aViolatedPolicyIndex, originalPolicy);
+   NS_ENSURE_SUCCESS(rv, rv);
+-  report.mCsp_report.mOriginal_policy = originalPolicy;
+-
+-  // referrer
+-  if (!mReferrer.IsEmpty()) {
+-    report.mCsp_report.mReferrer = mReferrer;
+-  }
+-
+-  // violated-directive
+-  report.mCsp_report.mViolated_directive = aViolatedDirective;
++  aViolationEventInit.mOriginalPolicy = originalPolicy;
+ 
+   // source-file
+   if (!aSourceFile.IsEmpty()) {
+     // if aSourceFile is a URI, we have to make sure to strip fragments
+     nsCOMPtr<nsIURI> sourceURI;
+     NS_NewURI(getter_AddRefs(sourceURI), aSourceFile);
+     if (sourceURI) {
+       nsAutoCString spec;
+       sourceURI->GetSpecIgnoringRef(spec);
+       aSourceFile = NS_ConvertUTF8toUTF16(spec);
+     }
++    aViolationEventInit.mSourceFile = aSourceFile;
++  }
++
++  // sample
++  aViolationEventInit.mSample = aScriptSample;
++
++  // disposition
++  aViolationEventInit.mDisposition = mPolicies[aViolatedPolicyIndex]->getReportOnlyFlag()
++    ? mozilla::dom::SecurityPolicyViolationEventDisposition::Report
++    : mozilla::dom::SecurityPolicyViolationEventDisposition::Enforce;
++
++  // status-code
++  uint16_t statusCode = 0;
++  {
++    nsCOMPtr<nsIDocument> doc = do_QueryReferent(mLoadingContext);
++    if (doc) {
++      nsCOMPtr<nsIHttpChannel> channel = do_QueryInterface(doc->GetChannel());
++      if (channel) {
++        uint32_t responseStatus = 0;
++        nsresult rv = channel->GetResponseStatus(&responseStatus);
++        if (NS_SUCCEEDED(rv) && (responseStatus <= UINT16_MAX)) {
++          statusCode = static_cast<uint16_t>(responseStatus);
++        }
++      }
++    }
++  }
++  aViolationEventInit.mStatusCode = statusCode;
++
++  // line-number
++  aViolationEventInit.mLineNumber = aLineNum;
++
++  // column-number
++  // TODO: Set correct column number.
++  aViolationEventInit.mColumnNumber = 0;
++
++  aViolationEventInit.mBubbles = true;
++  aViolationEventInit.mComposed = true;
++
++  return NS_OK;
++}
++
++nsresult
++nsCSPContext::SendReports(
++  const mozilla::dom::SecurityPolicyViolationEventInit& aViolationEventInit,
++  uint32_t aViolatedPolicyIndex)
++{
++  NS_ENSURE_ARG_MAX(aViolatedPolicyIndex, mPolicies.Length() - 1);
++
++  dom::CSPReport report;
++
++  // blocked-uri
++  report.mCsp_report.mBlocked_uri = aViolationEventInit.mBlockedURI;
++
++  // document-uri
++  report.mCsp_report.mDocument_uri = aViolationEventInit.mDocumentURI;
++
++  // original-policy
++  report.mCsp_report.mOriginal_policy = aViolationEventInit.mOriginalPolicy;
++
++  // referrer
++  report.mCsp_report.mReferrer = aViolationEventInit.mReferrer;
++
++  // violated-directive
++  report.mCsp_report.mViolated_directive = aViolationEventInit.mViolatedDirective;
++
++  // source-file
++  if (!aViolationEventInit.mSourceFile.IsEmpty()) {
+     report.mCsp_report.mSource_file.Construct();
+-    report.mCsp_report.mSource_file.Value() = aSourceFile;
++    report.mCsp_report.mSource_file.Value() = aViolationEventInit.mSourceFile;
+   }
+ 
+   // script-sample
+-  if (!aScriptSample.IsEmpty()) {
++  if (!aViolationEventInit.mSample.IsEmpty()) {
+     report.mCsp_report.mScript_sample.Construct();
+-    report.mCsp_report.mScript_sample.Value() = aScriptSample;
++    report.mCsp_report.mScript_sample.Value() = aViolationEventInit.mSample;
+   }
+ 
+   // line-number
+-  if (aLineNum != 0) {
++  if (aViolationEventInit.mLineNumber != 0) {
+     report.mCsp_report.mLine_number.Construct();
+-    report.mCsp_report.mLine_number.Value() = aLineNum;
++    report.mCsp_report.mLine_number.Value() = aViolationEventInit.mLineNumber;
+   }
+ 
+   nsString csp_report;
+   if (!report.ToJSON(csp_report)) {
+     return NS_ERROR_FAILURE;
+   }
+ 
+   // ---------- Assembled, now send it to all the report URIs ----------- //
+ 
+   nsTArray<nsString> reportURIs;
+   mPolicies[aViolatedPolicyIndex]->getReportURIs(reportURIs);
+ 
+-
+   nsCOMPtr<nsIDocument> doc = do_QueryReferent(mLoadingContext);
+   nsCOMPtr<nsIURI> reportURI;
+   nsCOMPtr<nsIChannel> reportChannel;
+ 
++  nsresult rv;
+   for (uint32_t r = 0; r < reportURIs.Length(); r++) {
+     nsAutoCString reportURICstring = NS_ConvertUTF16toUTF8(reportURIs[r]);
+     // try to create a new uri from every report-uri string
+     rv = NS_NewURI(getter_AddRefs(reportURI), reportURIs[r]);
+     if (NS_FAILED(rv)) {
+       const char16_t* params[] = { reportURIs[r].get() };
+       CSPCONTEXTLOG(("Could not create nsIURI for report URI %s",
+                      reportURICstring.get()));
+       logToConsole("triedToSendReport", params, ArrayLength(params),
+-                   aSourceFile, aScriptSample, aLineNum, 0, nsIScriptError::errorFlag);
++                   aViolationEventInit.mSourceFile, aViolationEventInit.mSample,
++                   aViolationEventInit.mLineNumber, 0, nsIScriptError::errorFlag);
+       continue; // don't return yet, there may be more URIs
+     }
+ 
+     // try to create a new channel for every report-uri
+     nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL | nsIChannel::LOAD_CLASSIFY_URI;
+     if (doc) {
+       rv = NS_NewChannel(getter_AddRefs(reportChannel),
+                          reportURI,
+@@ -1000,17 +1053,18 @@ nsCSPContext::SendReports(nsISupports* a
+     // log a warning to console if scheme is not http or https
+     bool isHttpScheme =
+       (NS_SUCCEEDED(reportURI->SchemeIs("http", &isHttpScheme)) && isHttpScheme) ||
+       (NS_SUCCEEDED(reportURI->SchemeIs("https", &isHttpScheme)) && isHttpScheme);
+ 
+     if (!isHttpScheme) {
+       const char16_t* params[] = { reportURIs[r].get() };
+       logToConsole("reportURInotHttpsOrHttp2", params, ArrayLength(params),
+-                   aSourceFile, aScriptSample, aLineNum, 0, nsIScriptError::errorFlag);
++                   aViolationEventInit.mSourceFile, aViolationEventInit.mSample,
++                   aViolationEventInit.mLineNumber, 0, nsIScriptError::errorFlag);
+       continue;
+     }
+ 
+     // make sure this is an anonymous request (no cookies) so in case the
+     // policy URI is injected, it can't be abused for CSRF.
+     nsLoadFlags flags;
+     rv = reportChannel->GetLoadFlags(&flags);
+     NS_ENSURE_SUCCESS(rv, rv);
+@@ -1065,24 +1119,45 @@ nsCSPContext::SendReports(nsISupports* a
+     // SetRequestContext is not given a channel).  This should fail quietly and
+     // not return an error since it's really ok if reports don't go out, but
+     // it's good to log the error locally.
+ 
+     if (NS_FAILED(rv)) {
+       const char16_t* params[] = { reportURIs[r].get() };
+       CSPCONTEXTLOG(("AsyncOpen failed for report URI %s", NS_ConvertUTF16toUTF8(params[0]).get()));
+       logToConsole("triedToSendReport", params, ArrayLength(params),
+-                   aSourceFile, aScriptSample, aLineNum, 0, nsIScriptError::errorFlag);
++                   aViolationEventInit.mSourceFile, aViolationEventInit.mSample,
++                   aViolationEventInit.mLineNumber, 0, nsIScriptError::errorFlag);
+     } else {
+       CSPCONTEXTLOG(("Sent violation report to URI %s", reportURICstring.get()));
+     }
+   }
+   return NS_OK;
+ }
+ 
++nsresult
++nsCSPContext::FireViolationEvent(
++  const mozilla::dom::SecurityPolicyViolationEventInit& aViolationEventInit)
++{
++  nsCOMPtr<nsIDocument> doc = do_QueryReferent(mLoadingContext);
++  if (!doc) {
++    return NS_OK;
++  }
++
++  RefPtr<mozilla::dom::Event> event =
++    mozilla::dom::SecurityPolicyViolationEvent::Constructor(
++      doc,
++      NS_LITERAL_STRING("securitypolicyviolation"),
++      aViolationEventInit);
++  event->SetTrusted(true);
++
++  bool rv;
++  return doc->DispatchEvent(event, &rv);
++}
++
+ /**
+  * Dispatched from the main thread to send reports for one CSP violation.
+  */
+ class CSPReportSenderRunnable final : public Runnable
+ {
+   public:
+     CSPReportSenderRunnable(nsISupports* aBlockedContentSource,
+                             nsIURI* aOriginalURI,
+@@ -1119,28 +1194,34 @@ class CSPReportSenderRunnable final : pu
+         mObserverSubject = do_QueryInterface(supportscstr);
+       }
+     }
+ 
+     NS_IMETHOD Run() override
+     {
+       MOZ_ASSERT(NS_IsMainThread());
+ 
++      // 0) prepare violation data
++      mozilla::dom::SecurityPolicyViolationEventInit init;
++      mCSPContext->GatherSecurityPolicyViolationEventData(
++        mBlockedContentSource, mOriginalURI,
++        mViolatedDirective, mViolatedPolicyIndex,
++        mSourceFile, mScriptSample, mLineNum,
++        init);
++
+       // 1) notify observers
+       nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
+       NS_ASSERTION(observerService, "needs observer service");
+       nsresult rv = observerService->NotifyObservers(mObserverSubject,
+                                                      CSP_VIOLATION_TOPIC,
+                                                      mViolatedDirective.get());
+       NS_ENSURE_SUCCESS(rv, rv);
+ 
+       // 2) send reports for the policy that was violated
+-      mCSPContext->SendReports(mBlockedContentSource, mOriginalURI,
+-                               mViolatedDirective, mViolatedPolicyIndex,
+-                               mSourceFile, mScriptSample, mLineNum);
++      mCSPContext->SendReports(init, mViolatedPolicyIndex);
+ 
+       // 3) log to console (one per policy violation)
+       // mBlockedContentSource could be a URI or a string.
+       nsCOMPtr<nsIURI> blockedURI = do_QueryInterface(mBlockedContentSource);
+       // if mBlockedContentSource is not a URI, it could be a string
+       nsCOMPtr<nsISupportsCString> blockedString = do_QueryInterface(mBlockedContentSource);
+ 
+       nsCString blockedDataStr;
+@@ -1163,16 +1244,20 @@ class CSPReportSenderRunnable final : pu
+         nsString blockedDataChar16 = NS_ConvertUTF8toUTF16(blockedDataStr);
+         const char16_t* params[] = { mViolatedDirective.get(),
+                                      blockedDataChar16.get() };
+         mCSPContext->logToConsole(mReportOnlyFlag ? "CSPROViolationWithURI" :
+                                                     "CSPViolationWithURI",
+                                   params, ArrayLength(params), mSourceFile, mScriptSample,
+                                   mLineNum, 0, nsIScriptError::errorFlag);
+       }
++
++      // 4) fire violation event
++      mCSPContext->FireViolationEvent(init);
++
+       return NS_OK;
+     }
+ 
+   private:
+     nsCOMPtr<nsISupports>   mBlockedContentSource;
+     nsCOMPtr<nsIURI>        mOriginalURI;
+     uint32_t                mViolatedPolicyIndex;
+     bool                    mReportOnlyFlag;
+diff --git a/dom/security/nsCSPContext.h b/dom/security/nsCSPContext.h
+--- a/dom/security/nsCSPContext.h
++++ b/dom/security/nsCSPContext.h
+@@ -3,16 +3,17 @@
+ /* This Source Code Form is subject to the terms of the Mozilla Public
+  * License, v. 2.0. If a copy of the MPL was not distributed with this
+  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+ 
+ #ifndef nsCSPContext_h___
+ #define nsCSPContext_h___
+ 
+ #include "mozilla/dom/nsCSPUtils.h"
++#include "mozilla/dom/SecurityPolicyViolationEvent.h"
+ #include "nsDataHashtable.h"
+ #include "nsIChannel.h"
+ #include "nsIChannelEventSink.h"
+ #include "nsIClassInfo.h"
+ #include "nsIContentSecurityPolicy.h"
+ #include "nsIInterfaceRequestor.h"
+ #include "nsISerializable.h"
+ #include "nsIStreamListener.h"
+@@ -53,23 +54,53 @@ class nsCSPContext : public nsIContentSe
+                       const char16_t** aParams,
+                       uint32_t aParamsLength,
+                       const nsAString& aSourceName,
+                       const nsAString& aSourceLine,
+                       uint32_t aLineNumber,
+                       uint32_t aColumnNumber,
+                       uint32_t aSeverityFlag);
+ 
+-    nsresult SendReports(nsISupports* aBlockedContentSource,
+-                         nsIURI* aOriginalURI,
+-                         nsAString& aViolatedDirective,
+-                         uint32_t aViolatedPolicyIndex,
+-                         nsAString& aSourceFile,
+-                         nsAString& aScriptSample,
+-                         uint32_t aLineNum);
++
++
++    /**
++     * Construct SecurityPolicyViolationEventInit structure.
++     *
++     * @param aBlockedContentSource
++     *        Either a CSP Source (like 'self', as string) or nsIURI: the source
++     *        of the violation.
++     * @param aOriginalUri
++     *        The original URI if the blocked content is a redirect, else null
++     * @param aViolatedDirective
++     *        the directive that was violated (string).
++     * @param aSourceFile
++     *        name of the file containing the inline script violation
++     * @param aScriptSample
++     *        a sample of the violating inline script
++     * @param aLineNum
++     *        source line number of the violation (if available)
++     * @param aViolationEventInit
++     *        The output
++     */
++    nsresult GatherSecurityPolicyViolationEventData(
++      nsISupports* aBlockedContentSource,
++      nsIURI* aOriginalURI,
++      nsAString& aViolatedDirective,
++      uint32_t aViolatedPolicyIndex,
++      nsAString& aSourceFile,
++      nsAString& aScriptSample,
++      uint32_t aLineNum,
++      mozilla::dom::SecurityPolicyViolationEventInit& aViolationEventInit);
++
++    nsresult SendReports(
++      const mozilla::dom::SecurityPolicyViolationEventInit& aViolationEventInit,
++      uint32_t aViolatedPolicyIndex);
++
++    nsresult FireViolationEvent(
++      const mozilla::dom::SecurityPolicyViolationEventInit& aViolationEventInit);
+ 
+     nsresult AsyncReportViolation(nsISupports* aBlockedContentSource,
+                                   nsIURI* aOriginalURI,
+                                   const nsAString& aViolatedDirective,
+                                   uint32_t aViolatedPolicyIndex,
+                                   const nsAString& aObserverSubject,
+                                   const nsAString& aSourceFile,
+                                   const nsAString& aScriptSample,
+diff --git a/dom/webidl/SecurityPolicyViolationEvent.webidl b/dom/webidl/SecurityPolicyViolationEvent.webidl
+new file mode 100644
+--- /dev/null
++++ b/dom/webidl/SecurityPolicyViolationEvent.webidl
+@@ -0,0 +1,41 @@
++/* 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/. */
++
++enum SecurityPolicyViolationEventDisposition
++{
++  "enforce", "report"
++};
++
++[Constructor(DOMString type, optional SecurityPolicyViolationEventInit eventInitDict)]
++interface SecurityPolicyViolationEvent : Event
++{
++    readonly attribute DOMString      documentURI;
++    readonly attribute DOMString      referrer;
++    readonly attribute DOMString      blockedURI;
++    readonly attribute DOMString      violatedDirective;
++    readonly attribute DOMString      effectiveDirective;
++    readonly attribute DOMString      originalPolicy;
++    readonly attribute DOMString      sourceFile;
++    readonly attribute DOMString      sample;
++    readonly attribute SecurityPolicyViolationEventDisposition disposition;
++    readonly attribute unsigned short statusCode;
++    readonly attribute long           lineNumber;
++    readonly attribute long           columnNumber;
++};
++
++dictionary SecurityPolicyViolationEventInit : EventInit
++{
++    DOMString      documentURI = "";
++    DOMString      referrer = "";
++    DOMString      blockedURI = "";
++    DOMString      violatedDirective = "";
++    DOMString      effectiveDirective = "";
++    DOMString      originalPolicy = "";
++    DOMString      sourceFile = "";
++    DOMString      sample = "";
++    SecurityPolicyViolationEventDisposition disposition = "report";
++    unsigned short statusCode = 0;
++    long           lineNumber = 0;
++    long           columnNumber = 0;
++};
+diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build
+--- a/dom/webidl/moz.build
++++ b/dom/webidl/moz.build
+@@ -1051,16 +1051,17 @@ GENERATED_EVENTS_WEBIDL_FILES = [
+     'PageTransitionEvent.webidl',
+     'PerformanceEntryEvent.webidl',
+     'PluginCrashedEvent.webidl',
+     'PopStateEvent.webidl',
+     'PopupBlockedEvent.webidl',
+     'ProgressEvent.webidl',
+     'PromiseRejectionEvent.webidl',
+     'ScrollViewChangeEvent.webidl',
++    'SecurityPolicyViolationEvent.webidl',
+     'StyleRuleChangeEvent.webidl',
+     'StyleSheetApplicableStateChangeEvent.webidl',
+     'StyleSheetChangeEvent.webidl',
+     'TCPServerSocketEvent.webidl',
+     'TCPSocketErrorEvent.webidl',
+     'TCPSocketEvent.webidl',
+     'TrackEvent.webidl',
+     'UDPMessageEvent.webidl',

+ 41 - 0
frg/work-js/mozilla-release/patches/1037335-2-59a1.patch

@@ -0,0 +1,41 @@
+# HG changeset patch
+# User Chung-Sheng Fu <cfu@mozilla.com>
+# Date 1511967240 -7200
+# Node ID 207b671db6ec57f755ef5eeb0aab5307fb61b06f
+# Parent  2e7c128a01b81ccb230aec38e4b6a235c4d7f494
+Bug 1037335 - Add a mochitest for security policy violation event. r=ckerschb
+
+MozReview-Commit-ID: 7l5jJFEtIaT
+
+diff --git a/dom/security/test/csp/mochitest.ini b/dom/security/test/csp/mochitest.ini
+--- a/dom/security/test/csp/mochitest.ini
++++ b/dom/security/test/csp/mochitest.ini
+@@ -343,8 +343,9 @@ support-files =
+   file_spawn_shared_worker.js
+   file_spawn_service_worker.js
+ [test_frame_src.html]
+ support-files =
+   file_frame_src_frame_governs.html
+   file_frame_src_child_governs.html
+   file_frame_src.js
+   file_frame_src_inner.html
++[test_security_policy_violation_event.html]
+diff --git a/dom/security/test/csp/test_security_policy_violation_event.html b/dom/security/test/csp/test_security_policy_violation_event.html
+new file mode 100644
+--- /dev/null
++++ b/dom/security/test/csp/test_security_policy_violation_event.html
+@@ -0,0 +1,14 @@
++<!DOCTYPE html>
++<meta charset="utf-8">
++<meta http-equiv="Content-Security-Policy" content="img-src 'none'">
++<script src="/tests/SimpleTest/SimpleTest.js"></script>
++<script>
++SimpleTest.waitForExplicitFinish();
++document.addEventListener("securitypolicyviolation", (e) => {
++  SimpleTest.is(e.blockedURI, "http://mochi.test:8888/foo/bar.jpg", "blockedURI");
++  SimpleTest.todo_is(e.violatedDirective, "img-src", "violatedDirective")
++  SimpleTest.is(e.originalPolicy, "img-src 'none'", "originalPolicy");
++  SimpleTest.finish();
++});
++</script>
++<img src="http://mochi.test:8888/foo/bar.jpg">

+ 57 - 0
frg/work-js/mozilla-release/patches/1037335-3no4or5-59a1.patch

@@ -0,0 +1,57 @@
+# HG changeset patch
+# User Chung-Sheng Fu <cfu@mozilla.com>
+# Date 1511967240 -7200
+# Node ID 0f09a387cbc4b093aa1535f75634cd1d1930f899
+# Parent  09ea70c5794ea57e4d5387ac068a6b12ff3c4dcc
+Bug 1037335 - Fix test failures. r=ckerschb,smaug
+
+Require review from DOM peer.
+
+MozReview-Commit-ID: HWBKKxxPh0e
+
+diff --git a/dom/events/test/test_all_synthetic_events.html b/dom/events/test/test_all_synthetic_events.html
+--- a/dom/events/test/test_all_synthetic_events.html
++++ b/dom/events/test/test_all_synthetic_events.html
+@@ -406,16 +406,20 @@ const kEventConstructors = {
+   WheelEvent:                                { create: function (aName, aProps) {
+                                                          return new WheelEvent(aName, aProps);
+                                                        },
+                                              },
+   WebGLContextEvent:                         { create: function (aName, aProps) {
+                                                          return new WebGLContextEvent(aName, aProps);
+                                                        },
+                                              },
++  SecurityPolicyViolationEvent:              { create: function (aName, aProps) {
++                                                         return new SecurityPolicyViolationEvent(aName, aProps);
++                                                       },
++                                             },
+ };
+ 
+ for (var name of Object.keys(kEventConstructors)) {
+   if (!kEventConstructors[name].chromeOnly) {
+     continue;
+   }
+   if (window[name]) {
+     ok(false, name + " should be chrome only.");
+diff --git a/dom/tests/mochitest/general/test_interfaces.js b/dom/tests/mochitest/general/test_interfaces.js
+--- a/dom/tests/mochitest/general/test_interfaces.js
++++ b/dom/tests/mochitest/general/test_interfaces.js
+@@ -810,16 +810,18 @@ var interfaceNamesInGlobalScope =
+     "Screen",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+     "ScreenOrientation",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+     "ScriptProcessorNode",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+     "ScrollAreaEvent",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
++    "SecurityPolicyViolationEvent",
++// IMPORTANT: Do not change this list without review from a DOM peer!
+     "Selection",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+     "ServiceWorker",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+     "ServiceWorkerContainer",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+     "ServiceWorkerRegistration",
+ // IMPORTANT: Do not change this list without review from a DOM peer!

+ 158 - 0
frg/work-js/mozilla-release/patches/1037335-6-59a1.patch

@@ -0,0 +1,158 @@
+# HG changeset patch
+# User Chung-Sheng Fu <cfu@mozilla.com>
+# Date 1511967300 -7200
+# Node ID 3f2598dad67c2ed8c521ce68c8f383385a2a088f
+# Parent  c1257a5e7e4446e43ca81d6278715b7ad8b0b8f7
+Bug 1037335 - Add a pref to enable only within Nightly and Early Beta. r=ckerschb,smaug
+
+MozReview-Commit-ID: Bi82dHm53qX
+
+diff --git a/dom/security/nsCSPContext.cpp b/dom/security/nsCSPContext.cpp
+--- a/dom/security/nsCSPContext.cpp
++++ b/dom/security/nsCSPContext.cpp
+@@ -268,28 +268,31 @@ NS_IMPL_CLASSINFO(nsCSPContext,
+                   nsIClassInfo::MAIN_THREAD_ONLY,
+                   NS_CSPCONTEXT_CID)
+ 
+ NS_IMPL_ISUPPORTS_CI(nsCSPContext,
+                      nsIContentSecurityPolicy,
+                      nsISerializable)
+ 
+ int32_t nsCSPContext::sScriptSampleMaxLength;
++bool nsCSPContext::sViolationEventsEnabled = false;
+ 
+ nsCSPContext::nsCSPContext()
+   : mInnerWindowID(0)
+   , mLoadingContext(nullptr)
+   , mLoadingPrincipal(nullptr)
+   , mQueueUpMessages(true)
+ {
+   static bool sInitialized = false;
+   if (!sInitialized) {
+     Preferences::AddIntVarCache(&sScriptSampleMaxLength,
+                                 "security.csp.reporting.script-sample.max-length",
+                                 40);
++    Preferences::AddBoolVarCache(&sViolationEventsEnabled,
++                                 "security.csp.enable_violation_events");
+     sInitialized = true;
+   }
+ 
+   CSPCONTEXTLOG(("nsCSPContext::nsCSPContext"));
+ }
+ 
+ nsCSPContext::~nsCSPContext()
+ {
+@@ -1132,16 +1135,20 @@ nsCSPContext::SendReports(
+   }
+   return NS_OK;
+ }
+ 
+ nsresult
+ nsCSPContext::FireViolationEvent(
+   const mozilla::dom::SecurityPolicyViolationEventInit& aViolationEventInit)
+ {
++  if (!sViolationEventsEnabled) {
++    return NS_OK;
++  }
++
+   nsCOMPtr<nsIDocument> doc = do_QueryReferent(mLoadingContext);
+   if (!doc) {
+     return NS_OK;
+   }
+ 
+   RefPtr<mozilla::dom::Event> event =
+     mozilla::dom::SecurityPolicyViolationEvent::Constructor(
+       doc,
+diff --git a/dom/security/nsCSPContext.h b/dom/security/nsCSPContext.h
+--- a/dom/security/nsCSPContext.h
++++ b/dom/security/nsCSPContext.h
+@@ -139,16 +139,18 @@ class nsCSPContext : public nsIContentSe
+ 
+     static int32_t sScriptSampleMaxLength;
+ 
+     static uint32_t ScriptSampleMaxLength()
+     {
+       return std::max(sScriptSampleMaxLength, 0);
+     }
+ 
++    static bool sViolationEventsEnabled;
++
+     nsString                                   mReferrer;
+     uint64_t                                   mInnerWindowID; // used for web console logging
+     nsTArray<nsCSPPolicy*>                     mPolicies;
+     nsCOMPtr<nsIURI>                           mSelfURI;
+     nsDataHashtable<nsCStringHashKey, int16_t> mShouldLoadCache;
+     nsCOMPtr<nsILoadGroup>                     mCallingChannelLoadGroup;
+     nsWeakPtr                                  mLoadingContext;
+     // The CSP hangs off the principal, so let's store a raw pointer of the principal
+diff --git a/dom/security/test/csp/test_security_policy_violation_event.html b/dom/security/test/csp/test_security_policy_violation_event.html
+--- a/dom/security/test/csp/test_security_policy_violation_event.html
++++ b/dom/security/test/csp/test_security_policy_violation_event.html
+@@ -1,14 +1,19 @@
+ <!DOCTYPE html>
+ <meta charset="utf-8">
+ <meta http-equiv="Content-Security-Policy" content="img-src 'none'">
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script>
+ SimpleTest.waitForExplicitFinish();
++SpecialPowers.pushPrefEnv({
++  set: [
++    ["security.csp.enable_violation_events", true]
++  ]
++});
+ document.addEventListener("securitypolicyviolation", (e) => {
+   SimpleTest.is(e.blockedURI, "http://mochi.test:8888/foo/bar.jpg", "blockedURI");
+   SimpleTest.todo_is(e.violatedDirective, "img-src", "violatedDirective")
+   SimpleTest.is(e.originalPolicy, "img-src 'none'", "originalPolicy");
+   SimpleTest.finish();
+ });
+ </script>
+ <img src="http://mochi.test:8888/foo/bar.jpg">
+diff --git a/dom/webidl/SecurityPolicyViolationEvent.webidl b/dom/webidl/SecurityPolicyViolationEvent.webidl
+--- a/dom/webidl/SecurityPolicyViolationEvent.webidl
++++ b/dom/webidl/SecurityPolicyViolationEvent.webidl
+@@ -2,17 +2,18 @@
+  * 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/. */
+ 
+ enum SecurityPolicyViolationEventDisposition
+ {
+   "enforce", "report"
+ };
+ 
+-[Constructor(DOMString type, optional SecurityPolicyViolationEventInit eventInitDict)]
++[Constructor(DOMString type, optional SecurityPolicyViolationEventInit eventInitDict),
++ Pref="security.csp.enable_violation_events"]
+ interface SecurityPolicyViolationEvent : Event
+ {
+     readonly attribute DOMString      documentURI;
+     readonly attribute DOMString      referrer;
+     readonly attribute DOMString      blockedURI;
+     readonly attribute DOMString      violatedDirective;
+     readonly attribute DOMString      effectiveDirective;
+     readonly attribute DOMString      originalPolicy;
+diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js
+--- a/modules/libpref/init/all.js
++++ b/modules/libpref/init/all.js
+@@ -2627,16 +2627,21 @@ pref("security.checkloaduri", true);
+ pref("security.xpconnect.plugin.unrestricted", true);
+ // security-sensitive dialogs should delay button enabling. In milliseconds.
+ pref("security.dialog_enable_delay", 1000);
+ pref("security.notification_enable_delay", 500);
+ 
+ pref("security.csp.enable", true);
+ pref("security.csp.experimentalEnabled", false);
+ pref("security.csp.enableStrictDynamic", true);
++#ifdef EARLY_BETA_OR_EARLIER
++pref("security.csp.enable_violation_events", true);
++#else
++pref("security.csp.enable_violation_events", false);
++#endif
+ 
+ // Default Content Security Policy to apply to signed contents.
+ pref("security.signed_content.CSP.default", "script-src 'self'; style-src 'self'");
+ 
+ // Mixed content blocking
+ pref("security.mixed_content.block_active_content", false);
+ pref("security.mixed_content.block_display_content", false);
+ 

+ 411 - 0
frg/work-js/mozilla-release/patches/1293277-1-59a1.patch

@@ -0,0 +1,411 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1513111486 18000
+# Node ID e3fb8bd8b10dc5a0a75238a9a3284e4ca5ea2fa3
+# Parent  cc1272b428ef650617c59a004e34a04934a7ed21
+Bug 1293277 P1 Capture StorageAccess when snapshoting ClientSource state. r=baku
+
+diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h
+--- a/dom/base/nsContentUtils.h
++++ b/dom/base/nsContentUtils.h
+@@ -2996,16 +2996,18 @@ public:
+     eDeny = 0,
+     // Allow access to the storage, but only if it is secure to do so in a
+     // private browsing context.
+     ePrivateBrowsing = 1,
+     // Allow access to the storage, but only persist it for the current session
+     eSessionScoped = 2,
+     // Allow access to the storage
+     eAllow = 3,
++    // Keep this at the end.  Used for serialization, but not a valid value.
++    eNumValues = 4,
+   };
+ 
+   /*
+    * Checks if storage for the given window is permitted by a combination of
+    * the user's preferences, and whether the window is a third-party iframe.
+    *
+    * This logic is intended to be shared between the different forms of
+    * persistent storage which are available to web pages. Cookies don't use
+diff --git a/dom/clients/manager/ClientIPCTypes.ipdlh b/dom/clients/manager/ClientIPCTypes.ipdlh
+--- a/dom/clients/manager/ClientIPCTypes.ipdlh
++++ b/dom/clients/manager/ClientIPCTypes.ipdlh
+@@ -5,16 +5,17 @@
+ include protocol PClientSource;
+ include DOMTypes;
+ include PBackgroundSharedTypes;
+ include IPCServiceWorkerDescriptor;
+ include ProtocolTypes;
+ using class mozilla::TimeStamp from "mozilla/TimeStamp.h";
+ using ClientType from "mozilla/dom/ClientIPCUtils.h";
+ using FrameType from "mozilla/dom/ClientIPCUtils.h";
++using nsContentUtils::StorageAccess from "mozilla/dom/ClientIPCUtils.h";
+ using VisibilityState from "mozilla/dom/ClientIPCUtils.h";
+ 
+ namespace mozilla {
+ namespace dom {
+ 
+ struct ClientSourceConstructorArgs
+ {
+   nsID id;
+@@ -32,21 +33,23 @@ struct IPCClientInfo
+   nsCString url;
+   FrameType frameType;
+ };
+ 
+ struct IPCClientWindowState
+ {
+   VisibilityState visibilityState;
+   TimeStamp lastFocusTime;
++  StorageAccess storageAccess;
+   bool focused;
+ };
+ 
+ struct IPCClientWorkerState
+ {
++  StorageAccess storageAccess;
+ };
+ 
+ union IPCClientState
+ {
+   IPCClientWindowState;
+   IPCClientWorkerState;
+ };
+ 
+diff --git a/dom/clients/manager/ClientIPCUtils.h b/dom/clients/manager/ClientIPCUtils.h
+--- a/dom/clients/manager/ClientIPCUtils.h
++++ b/dom/clients/manager/ClientIPCUtils.h
+@@ -9,16 +9,17 @@
+ #include "ipc/IPCMessageUtils.h"
+ 
+ // Fix X11 header brain damage that conflicts with FrameType::None
+ #undef None
+ 
+ #include "mozilla/dom/ClientBinding.h"
+ #include "mozilla/dom/ClientsBinding.h"
+ #include "mozilla/dom/DocumentBinding.h"
++#include "nsContentUtils.h"
+ 
+ namespace IPC {
+   template<>
+   struct ParamTraits<mozilla::dom::ClientType> :
+     public ContiguousEnumSerializer<mozilla::dom::ClientType,
+                                     mozilla::dom::ClientType::Window,
+                                     mozilla::dom::ClientType::EndGuard_>
+   {};
+@@ -31,11 +32,18 @@ namespace IPC {
+   {};
+ 
+   template<>
+   struct ParamTraits<mozilla::dom::VisibilityState> :
+     public ContiguousEnumSerializer<mozilla::dom::VisibilityState,
+                                     mozilla::dom::VisibilityState::Hidden,
+                                     mozilla::dom::VisibilityState::EndGuard_>
+   {};
++
++  template<>
++  struct ParamTraits<nsContentUtils::StorageAccess> :
++    public ContiguousEnumSerializer<nsContentUtils::StorageAccess,
++                                    nsContentUtils::StorageAccess::eDeny,
++                                    nsContentUtils::StorageAccess::eNumValues>
++  {};
+ } // namespace IPC
+ 
+ #endif // _mozilla_dom_ClientIPCUtils_h
+diff --git a/dom/clients/manager/ClientSource.cpp b/dom/clients/manager/ClientSource.cpp
+--- a/dom/clients/manager/ClientSource.cpp
++++ b/dom/clients/manager/ClientSource.cpp
+@@ -73,34 +73,40 @@ nsresult
+ ClientSource::SnapshotWindowState(ClientState* aStateOut)
+ {
+   MOZ_ASSERT(NS_IsMainThread());
+ 
+   nsPIDOMWindowInner* window = GetInnerWindow();
+   if (!window || !window->IsCurrentInnerWindow() ||
+       !window->HasActiveDocument()) {
+     *aStateOut = ClientState(ClientWindowState(VisibilityState::Hidden,
+-                                               TimeStamp(), false));
++                                               TimeStamp(),
++                                               nsContentUtils::StorageAccess::eDeny,
++                                               false));
+     return NS_OK;
+   }
+ 
+   nsIDocument* doc = window->GetExtantDoc();
+   if (NS_WARN_IF(!doc)) {
+     return NS_ERROR_UNEXPECTED;
+   }
+ 
+   ErrorResult rv;
+   bool focused = doc->HasFocus(rv);
+   if (NS_WARN_IF(rv.Failed())) {
+     rv.SuppressException();
+     return rv.StealNSResult();
+   }
+ 
++  nsContentUtils::StorageAccess storage =
++    nsContentUtils::StorageAllowedForDocument(doc);
++
+   *aStateOut = ClientState(ClientWindowState(doc->VisibilityState(),
+-                                             doc->LastFocusTime(), focused));
++                                             doc->LastFocusTime(), storage,
++                                             focused));
+ 
+   return NS_OK;
+ }
+ 
+ WorkerPrivate*
+ ClientSource::GetWorkerPrivate() const
+ {
+   NS_ASSERT_OWNINGTHREAD(ClientSource);
+@@ -584,17 +590,28 @@ ClientSource::SnapshotState(ClientState*
+     MaybeCreateInitialDocument();
+     nsresult rv = SnapshotWindowState(aStateOut);
+     if (NS_FAILED(rv)) {
+       return rv;
+     }
+     return NS_OK;
+   }
+ 
+-  *aStateOut = ClientState(ClientWorkerState());
++  WorkerPrivate* workerPrivate = GetWorkerPrivate();
++  if (!workerPrivate) {
++    return NS_ERROR_DOM_INVALID_STATE_ERR;
++  }
++
++  // Workers only keep a boolean for storage access at the moment.
++  // Map this back to eAllow or eDeny for now.
++  nsContentUtils::StorageAccess storage =
++    workerPrivate->IsStorageAllowed() ? nsContentUtils::StorageAccess::eAllow
++                                      : nsContentUtils::StorageAccess::eDeny;
++
++  *aStateOut = ClientState(ClientWorkerState(storage));
+   return NS_OK;
+ }
+ 
+ nsISerialEventTarget*
+ ClientSource::EventTarget() const
+ {
+   return mEventTarget;
+ }
+diff --git a/dom/clients/manager/ClientState.cpp b/dom/clients/manager/ClientState.cpp
+--- a/dom/clients/manager/ClientState.cpp
++++ b/dom/clients/manager/ClientState.cpp
+@@ -8,19 +8,20 @@
+ 
+ #include "mozilla/dom/ClientIPCTypes.h"
+ 
+ namespace mozilla {
+ namespace dom {
+ 
+ ClientWindowState::ClientWindowState(mozilla::dom::VisibilityState aVisibilityState,
+                                      const TimeStamp& aLastFocusTime,
++                                     nsContentUtils::StorageAccess aStorageAccess,
+                                      bool aFocused)
+   : mData(MakeUnique<IPCClientWindowState>(aVisibilityState, aLastFocusTime,
+-                                           aFocused))
++                                           aStorageAccess, aFocused))
+ {
+ }
+ 
+ ClientWindowState::ClientWindowState(const IPCClientWindowState& aData)
+   : mData(MakeUnique<IPCClientWindowState>(aData))
+ {
+ }
+ 
+@@ -67,24 +68,30 @@ ClientWindowState::LastFocusTime() const
+ }
+ 
+ bool
+ ClientWindowState::Focused() const
+ {
+   return mData->focused();
+ }
+ 
++nsContentUtils::StorageAccess
++ClientWindowState::GetStorageAccess() const
++{
++  return mData->storageAccess();
++}
++
+ const IPCClientWindowState&
+ ClientWindowState::ToIPC() const
+ {
+   return *mData;
+ }
+ 
+-ClientWorkerState::ClientWorkerState()
+-  : mData(MakeUnique<IPCClientWorkerState>())
++ClientWorkerState::ClientWorkerState(nsContentUtils::StorageAccess aStorageAccess)
++  : mData(MakeUnique<IPCClientWorkerState>(aStorageAccess))
+ {
+ }
+ 
+ ClientWorkerState::ClientWorkerState(const IPCClientWorkerState& aData)
+   : mData(MakeUnique<IPCClientWorkerState>(aData))
+ {
+ }
+ 
+@@ -113,16 +120,22 @@ ClientWorkerState::operator=(ClientWorke
+   mData = Move(aRight.mData);
+   return *this;
+ }
+ 
+ ClientWorkerState::~ClientWorkerState()
+ {
+ }
+ 
++nsContentUtils::StorageAccess
++ClientWorkerState::GetStorageAccess() const
++{
++  return mData->storageAccess();
++}
++
+ const IPCClientWorkerState&
+ ClientWorkerState::ToIPC() const
+ {
+   return *mData;
+ }
+ 
+ ClientState::ClientState()
+ {
+@@ -197,16 +210,26 @@ ClientState::IsWorkerState() const
+ }
+ 
+ const ClientWorkerState&
+ ClientState::AsWorkerState() const
+ {
+   return mData.ref().as<ClientWorkerState>();
+ }
+ 
++nsContentUtils::StorageAccess
++ClientState::GetStorageAccess() const
++{
++  if (IsWindowState()) {
++    return AsWindowState().GetStorageAccess();
++  }
++
++  return AsWorkerState().GetStorageAccess();
++}
++
+ const IPCClientState
+ ClientState::ToIPC() const
+ {
+   if (IsWindowState()) {
+     return IPCClientState(AsWindowState().ToIPC());
+   }
+ 
+   return IPCClientState(AsWorkerState().ToIPC());
+diff --git a/dom/clients/manager/ClientState.h b/dom/clients/manager/ClientState.h
+--- a/dom/clients/manager/ClientState.h
++++ b/dom/clients/manager/ClientState.h
+@@ -6,16 +6,17 @@
+ 
+ #ifndef _mozilla_dom_ClientState_h
+ #define _mozilla_dom_ClientState_h
+ 
+ #include "mozilla/dom/DocumentBinding.h"
+ #include "mozilla/Maybe.h"
+ #include "mozilla/TimeStamp.h"
+ #include "mozilla/UniquePtr.h"
++#include "nsContentUtils.h"
+ 
+ namespace mozilla {
+ namespace dom {
+ 
+ class IPCClientState;
+ class IPCClientWindowState;
+ class IPCClientWorkerState;
+ 
+@@ -24,16 +25,17 @@ class IPCClientWorkerState;
+ // is not live updated.
+ class ClientWindowState final
+ {
+   UniquePtr<IPCClientWindowState> mData;
+ 
+ public:
+   ClientWindowState(mozilla::dom::VisibilityState aVisibilityState,
+                     const TimeStamp& aLastFocusTime,
++                    nsContentUtils::StorageAccess aStorageAccess,
+                     bool aFocused);
+ 
+   explicit ClientWindowState(const IPCClientWindowState& aData);
+ 
+   ClientWindowState(const ClientWindowState& aRight);
+   ClientWindowState(ClientWindowState&& aRight);
+ 
+   ClientWindowState&
+@@ -48,46 +50,52 @@ public:
+   VisibilityState() const;
+ 
+   const TimeStamp&
+   LastFocusTime() const;
+ 
+   bool
+   Focused() const;
+ 
++  nsContentUtils::StorageAccess
++  GetStorageAccess() const;
++
+   const IPCClientWindowState&
+   ToIPC() const;
+ };
+ 
+ // This class defines the mutable worker state we support querying
+ // through the ClientManagerService.  It is a snapshot of the state and
+ // is not live updated.  Right now, we don't actually providate any
+ // worker specific state values, but we may in the future.  This
+ // class also services as a placeholder that the state is referring
+ // to a worker in ClientState.
+ class ClientWorkerState final
+ {
+   UniquePtr<IPCClientWorkerState> mData;
+ 
+ public:
+-  ClientWorkerState();
++  explicit ClientWorkerState(nsContentUtils::StorageAccess aStorageAccess);
+ 
+   explicit ClientWorkerState(const IPCClientWorkerState& aData);
+ 
+   ClientWorkerState(const ClientWorkerState& aRight);
+   ClientWorkerState(ClientWorkerState&& aRight);
+ 
+   ClientWorkerState&
+   operator=(const ClientWorkerState& aRight);
+ 
+   ClientWorkerState&
+   operator=(ClientWorkerState&& aRight);
+ 
+   ~ClientWorkerState();
+ 
++  nsContentUtils::StorageAccess
++  GetStorageAccess() const;
++
+   const IPCClientWorkerState&
+   ToIPC() const;
+ };
+ 
+ // This is a union of the various types of mutable state we support
+ // querying in ClientManagerService.  Right now it can contain either
+ // window or worker states.
+ class ClientState final
+@@ -123,16 +131,19 @@ public:
+   AsWindowState() const;
+ 
+   bool
+   IsWorkerState() const;
+ 
+   const ClientWorkerState&
+   AsWorkerState() const;
+ 
++  nsContentUtils::StorageAccess
++  GetStorageAccess() const;
++
+   const IPCClientState
+   ToIPC() const;
+ };
+ 
+ } // namespace dom
+ } // namespace mozilla
+ 
+ #endif // _mozilla_dom_ClientState_h

+ 79 - 0
frg/work-js/mozilla-release/patches/1293277-2-59a1.patch

@@ -0,0 +1,79 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1513111487 18000
+# Node ID 3f23acc150062cfe3715024196c1ca1c318f9c2a
+# Parent  f629397e0fe46466846d0c74700663e7a7febbc3
+Bug 1293277 P2 Cleanup test_openWindow.html mochitest service worker a bit. r=baku
+
+diff --git a/dom/workers/test/serviceworkers/openWindow_worker.js b/dom/workers/test/serviceworkers/openWindow_worker.js
+--- a/dom/workers/test/serviceworkers/openWindow_worker.js
++++ b/dom/workers/test/serviceworkers/openWindow_worker.js
+@@ -55,37 +55,37 @@ onmessage = function(event) {
+   if (event.data == "testNoPopup") {
+     client = event.source;
+ 
+     var results = [];
+     var promises = [];
+     promises.push(testForUrl("about:blank", "TypeError", null, results));
+     promises.push(testForUrl("http://example.com", "InvalidAccessError", null, results));
+     promises.push(testForUrl("_._*`InvalidURL", "InvalidAccessError", null, results));
+-    Promise.all(promises).then(function(e) {
++    event.waitUntil(Promise.all(promises).then(function(e) {
+       client.postMessage(results);
+-    });
++    }));
+   }
+   if (event.data == "NEW_WINDOW") {
+     window_count += 1;
+     if (window_count == expected_window_count) {
+       resolve_got_all_windows();
+     }
+   }
+ 
+   if (event.data == "CHECK_NUMBER_OF_WINDOWS") {
+-    got_all_windows.then(function() {
++    event.waitUntil(got_all_windows.then(function() {
+       return clients.matchAll();
+     }).then(function(cl) {
+       event.source.postMessage({result: cl.length == expected_window_count,
+                                 message: "The number of windows is correct."});
+       for (i = 0; i < cl.length; i++) {
+         cl[i].postMessage("CLOSE");
+       }
+-    });
++    }));
+   }
+ }
+ 
+ onnotificationclick = function(e) {
+   var results = [];
+   var promises = [];
+ 
+   var redirect = "http://mochi.test:8888/tests/dom/workers/test/serviceworkers/redirect.sjs?"
+@@ -96,21 +96,21 @@ onnotificationclick = function(e) {
+ 
+   promises.push(testForUrl("about:blank", "TypeError", null, results));
+   promises.push(testForUrl(different_origin, null, null, results));
+   promises.push(testForUrl(same_origin, null, {url: same_origin}, results));
+   promises.push(testForUrl("open_window/client.html", null, {url: same_origin}, results));
+ 
+   // redirect tests
+   promises.push(testForUrl(redirect + "open_window/client.html", null,
+-			   {url: same_origin}, results));
++                           {url: same_origin}, results));
+   promises.push(testForUrl(redirect + different_origin, null, null, results));
+ 
+   promises.push(testForUrl(redirect_xorigin + "open_window/client.html", null,
+-			   null, results));
++                           null, results));
+   promises.push(testForUrl(redirect_xorigin + same_origin, null,
+-			   {url: same_origin}, results));
++                           {url: same_origin}, results));
+ 
+-  Promise.all(promises).then(function(e) {
++  e.waitUntil(Promise.all(promises).then(function(e) {
+     client.postMessage(results);
+-  });
++  }));
+ }
+ 

+ 124 - 0
frg/work-js/mozilla-release/patches/1293277-3-59a1.patch

@@ -0,0 +1,124 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1513111487 18000
+# Node ID 6d09b4467c62da66ce870de1a90d310cc7abb858
+# Parent  32ae3d3ea376163f854b27352211cbaa2009a57e
+Bug 1293277 P3 Remove the dom.serviceWorkers.openWindow.enabled pref. r=baku
+
+diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js
+--- a/browser/app/profile/firefox.js
++++ b/browser/app/profile/firefox.js
+@@ -1557,17 +1557,16 @@ pref("reader.parse-node-limit", 0);
+ 
+ // On desktop, we want the URLs to be included here for ease of debugging,
+ // and because (normally) these errors are not persisted anywhere.
+ pref("reader.errors.includeURLs", true);
+ 
+ pref("view_source.tab", true);
+ 
+ pref("dom.serviceWorkers.enabled", true);
+-pref("dom.serviceWorkers.openWindow.enabled", true);
+ 
+ // Enable Push API.
+ pref("dom.push.enabled", true);
+ 
+ // These are the thumbnail width/height set in about:newtab.
+ // If you change this, ENSURE IT IS THE SAME SIZE SET
+ // by about:newtab. These values are in CSS pixels.
+ pref("toolkit.pageThumbs.minWidth", 280);
+diff --git a/dom/webidl/Clients.webidl b/dom/webidl/Clients.webidl
+--- a/dom/webidl/Clients.webidl
++++ b/dom/webidl/Clients.webidl
+@@ -10,18 +10,17 @@
+ 
+ [Exposed=ServiceWorker]
+ interface Clients {
+   // The objects returned will be new instances every time
+   [NewObject]
+   Promise<any> get(DOMString id);
+   [NewObject]
+   Promise<sequence<Client>> matchAll(optional ClientQueryOptions options);
+-  [NewObject,
+-   Func="mozilla::dom::ServiceWorkerGlobalScope::OpenWindowEnabled"]
++  [NewObject]
+   Promise<WindowClient?> openWindow(USVString url);
+   [NewObject]
+   Promise<void> claim();
+ };
+ 
+ dictionary ClientQueryOptions {
+   boolean includeUncontrolled = false;
+   ClientType type = "window";
+diff --git a/dom/workers/WorkerPrefs.h b/dom/workers/WorkerPrefs.h
+--- a/dom/workers/WorkerPrefs.h
++++ b/dom/workers/WorkerPrefs.h
+@@ -28,17 +28,16 @@ WORKER_SIMPLE_PREF("canvas.imagebitmap_e
+ WORKER_SIMPLE_PREF("dom.caches.enabled", DOMCachesEnabled, DOM_CACHES)
+ WORKER_SIMPLE_PREF("dom.caches.testing.enabled", DOMCachesTestingEnabled, DOM_CACHES_TESTING)
+ WORKER_SIMPLE_PREF("dom.performance.enable_user_timing_logging", PerformanceLoggingEnabled, PERFORMANCE_LOGGING_ENABLED)
+ WORKER_SIMPLE_PREF("dom.webnotifications.enabled", DOMWorkerNotificationEnabled, DOM_WORKERNOTIFICATION)
+ WORKER_SIMPLE_PREF("dom.webnotifications.serviceworker.enabled", DOMServiceWorkerNotificationEnabled, DOM_SERVICEWORKERNOTIFICATION)
+ WORKER_SIMPLE_PREF("dom.webnotifications.requireinteraction.enabled", DOMWorkerNotificationRIEnabled, DOM_WORKERNOTIFICATIONRI)
+ WORKER_SIMPLE_PREF("dom.serviceWorkers.enabled", ServiceWorkersEnabled, SERVICEWORKERS_ENABLED)
+ WORKER_SIMPLE_PREF("dom.serviceWorkers.testing.enabled", ServiceWorkersTestingEnabled, SERVICEWORKERS_TESTING_ENABLED)
+-WORKER_SIMPLE_PREF("dom.serviceWorkers.openWindow.enabled", OpenWindowEnabled, OPEN_WINDOW_ENABLED)
+ WORKER_SIMPLE_PREF("dom.storageManager.enabled", StorageManagerEnabled, STORAGEMANAGER_ENABLED)
+ WORKER_SIMPLE_PREF("dom.promise_rejection_events.enabled", PromiseRejectionEventsEnabled, PROMISE_REJECTION_EVENTS_ENABLED)
+ WORKER_SIMPLE_PREF("dom.push.enabled", PushEnabled, PUSH_ENABLED)
+ WORKER_SIMPLE_PREF("dom.streams.enabled", StreamsEnabled, STREAMS_ENABLED)
+ WORKER_SIMPLE_PREF("dom.requestcontext.enabled", RequestContextEnabled, REQUESTCONTEXT_ENABLED)
+ WORKER_SIMPLE_PREF("gfx.offscreencanvas.enabled", OffscreenCanvasEnabled, OFFSCREENCANVAS_ENABLED)
+ WORKER_SIMPLE_PREF("dom.webkitBlink.dirPicker.enabled", WebkitBlinkDirectoryPickerEnabled, DOM_WEBKITBLINK_DIRPICKER_WEBKITBLINK)
+ WORKER_SIMPLE_PREF("dom.netinfo.enabled", NetworkInformationEnabled, NETWORKINFORMATION_ENABLED)
+diff --git a/dom/workers/WorkerScope.cpp b/dom/workers/WorkerScope.cpp
+--- a/dom/workers/WorkerScope.cpp
++++ b/dom/workers/WorkerScope.cpp
+@@ -844,25 +844,16 @@ ServiceWorkerGlobalScope::SkipWaiting(Er
+   RefPtr<WorkerScopeSkipWaitingRunnable> runnable =
+     new WorkerScopeSkipWaitingRunnable(promiseProxy,
+                                        NS_ConvertUTF16toUTF8(mScope));
+ 
+   MOZ_ALWAYS_SUCCEEDS(mWorkerPrivate->DispatchToMainThread(runnable.forget()));
+   return promise.forget();
+ }
+ 
+-bool
+-ServiceWorkerGlobalScope::OpenWindowEnabled(JSContext* aCx, JSObject* aObj)
+-{
+-  WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+-  MOZ_ASSERT(worker);
+-  worker->AssertIsOnWorkerThread();
+-  return worker->OpenWindowEnabled();
+-}
+-
+ WorkerDebuggerGlobalScope::WorkerDebuggerGlobalScope(
+                                                   WorkerPrivate* aWorkerPrivate)
+ : mWorkerPrivate(aWorkerPrivate)
+ , mSerialEventTarget(aWorkerPrivate->HybridEventTarget())
+ {
+   mWorkerPrivate->AssertIsOnWorkerThread();
+ 
+   // We should always have an event target when the global is created.
+diff --git a/dom/workers/WorkerScope.h b/dom/workers/WorkerScope.h
+--- a/dom/workers/WorkerScope.h
++++ b/dom/workers/WorkerScope.h
+@@ -295,19 +295,16 @@ public:
+   IMPL_EVENT_HANDLER(notificationclose)
+ 
+   ServiceWorkerGlobalScope(WorkerPrivate* aWorkerPrivate, const nsACString& aScope);
+ 
+   virtual bool
+   WrapGlobalObject(JSContext* aCx,
+                    JS::MutableHandle<JSObject*> aReflector) override;
+ 
+-  static bool
+-  OpenWindowEnabled(JSContext* aCx, JSObject* aObj);
+-
+   void
+   GetScope(nsString& aScope) const
+   {
+     aScope = mScope;
+   }
+ 
+   workers::ServiceWorkerClients*
+   Clients();

+ 856 - 0
frg/work-js/mozilla-release/patches/1293277-4-59a1.patch

@@ -0,0 +1,856 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1513111487 18000
+# Node ID f7b0cd514d46f242222aa50e498b85f89b7e668a
+# Parent  3f8fc0abb71072f9b999a6e7beda7db3a58a2f7d
+Bug 1293277 P4 Add Client and Clients DOM classes, but don't hook them into bindings yet. r=baku
+
+diff --git a/dom/clients/api/Client.cpp b/dom/clients/api/Client.cpp
+new file mode 100644
+--- /dev/null
++++ b/dom/clients/api/Client.cpp
+@@ -0,0 +1,249 @@
++/* -*- 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 "Client.h"
++
++#include "ClientDOMUtil.h"
++#include "mozilla/dom/ClientHandle.h"
++#include "mozilla/dom/ClientIPCTypes.h"
++#include "mozilla/dom/ClientManager.h"
++#include "mozilla/dom/ClientState.h"
++#include "mozilla/dom/Promise.h"
++#include "mozilla/dom/WorkerPrivate.h"
++#include "mozilla/dom/WorkerScope.h"
++#include "nsIGlobalObject.h"
++
++namespace mozilla {
++namespace dom {
++
++using mozilla::dom::workers::Closing;
++using mozilla::dom::workers::GetCurrentThreadWorkerPrivate;
++using mozilla::dom::workers::WorkerHolderToken;
++using mozilla::dom::workers::WorkerPrivate;
++using mozilla::dom::ipc::StructuredCloneData;
++
++NS_IMPL_CYCLE_COLLECTING_ADDREF(mozilla::dom::Client);
++NS_IMPL_CYCLE_COLLECTING_RELEASE(mozilla::dom::Client);
++NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(mozilla::dom::Client, mGlobal);
++
++NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(mozilla::dom::Client)
++  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
++  NS_INTERFACE_MAP_ENTRY(nsISupports)
++NS_INTERFACE_MAP_END
++
++void
++Client::EnsureHandle()
++{
++  NS_ASSERT_OWNINGTHREAD(mozilla::dom::Client);
++  if (!mHandle) {
++    mHandle = ClientManager::CreateHandle(ClientInfo(mData->info()),
++                                          mGlobal->EventTargetFor(TaskCategory::Other));
++  }
++}
++
++Client::Client(nsIGlobalObject* aGlobal, const ClientInfoAndState& aData)
++  : mGlobal(aGlobal)
++  , mData(MakeUnique<ClientInfoAndState>(aData))
++{
++  MOZ_DIAGNOSTIC_ASSERT(mGlobal);
++}
++
++TimeStamp
++Client::CreationTime() const
++{
++  return mData->info().creationTime();
++}
++
++TimeStamp
++Client::LastFocusTime() const
++{
++  if (mData->info().type() != ClientType::Window) {
++    return TimeStamp();
++  }
++  return mData->state().get_IPCClientWindowState().lastFocusTime();
++}
++
++nsContentUtils::StorageAccess
++Client::GetStorageAccess() const
++{
++  ClientState state(ClientState::FromIPC(mData->state()));
++  return state.GetStorageAccess();
++}
++
++JSObject*
++Client::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
++{
++#if 0
++  // TODO: Enable when bindings are updated to point to this class.
++  if (mData->info().type() == ClientType::Window) {
++    return WindowClientBinding::Wrap(aCx, this, aGivenProto);
++  }
++  return ClientBinding::Wrap(aCx, this, aGivenProto);
++#endif
++  return nullptr;
++}
++
++nsIGlobalObject*
++Client::GetParentObject() const
++{
++  return mGlobal;
++}
++
++void
++Client::GetUrl(nsAString& aUrlOut) const
++{
++  CopyUTF8toUTF16(mData->info().url(), aUrlOut);
++}
++
++void
++Client::GetId(nsAString& aIdOut) const
++{
++  char buf[NSID_LENGTH];
++  mData->info().id().ToProvidedString(buf);
++  NS_ConvertASCIItoUTF16 uuid(buf);
++
++  // Remove {} and the null terminator
++  aIdOut.Assign(Substring(uuid, 1, NSID_LENGTH - 3));
++}
++
++ClientType
++Client::Type() const
++{
++  return mData->info().type();
++}
++
++FrameType
++Client::GetFrameType() const
++{
++  return mData->info().frameType();
++}
++
++void
++Client::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
++                    const Sequence<JSObject*>& aTransferable,
++                    ErrorResult& aRv)
++{
++  MOZ_ASSERT(!NS_IsMainThread());
++  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
++  MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
++  MOZ_DIAGNOSTIC_ASSERT(workerPrivate->IsServiceWorker());
++  workerPrivate->AssertIsOnWorkerThread();
++
++  JS::Rooted<JS::Value> transferable(aCx, JS::UndefinedValue());
++  aRv = nsContentUtils::CreateJSValueFromSequenceOfObject(aCx, aTransferable,
++                                                          &transferable);
++  if (aRv.Failed()) {
++    return;
++  }
++
++  StructuredCloneData data;
++  data.Write(aCx, aMessage, transferable, aRv);
++  if (aRv.Failed()) {
++    return;
++  }
++
++  EnsureHandle();
++  mHandle->PostMessage(data, workerPrivate->GetServiceWorkerDescriptor());
++}
++
++VisibilityState
++Client::GetVisibilityState() const
++{
++  return mData->state().get_IPCClientWindowState().visibilityState();
++}
++
++bool
++Client::Focused() const
++{
++  return mData->state().get_IPCClientWindowState().focused();
++}
++
++already_AddRefed<Promise>
++Client::Focus(ErrorResult& aRv)
++{
++  MOZ_ASSERT(!NS_IsMainThread());
++  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
++  MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
++  MOZ_DIAGNOSTIC_ASSERT(workerPrivate->IsServiceWorker());
++  workerPrivate->AssertIsOnWorkerThread();
++
++  RefPtr<Promise> outerPromise = Promise::Create(mGlobal, aRv);
++  if (aRv.Failed()) {
++    return outerPromise.forget();
++  }
++
++  if (!workerPrivate->GlobalScope()->WindowInteractionAllowed()) {
++    outerPromise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR);
++    return outerPromise.forget();
++  }
++
++  // Hold the worker thread alive while we perform the async operation
++  // and also avoid invoking callbacks if the worker starts shutting
++  // down.
++  RefPtr<WorkerHolderToken> token =
++    WorkerHolderToken::Create(GetCurrentThreadWorkerPrivate(), Closing);
++
++  EnsureHandle();
++  RefPtr<ClientStatePromise> innerPromise = mHandle->Focus();
++  RefPtr<Client> self = this;
++
++  innerPromise->Then(mGlobal->EventTargetFor(TaskCategory::Other), __func__,
++    [self, token, outerPromise] (const ClientState& aResult) {
++      if (token->IsShuttingDown()) {
++        return;
++      }
++      RefPtr<Client> newClient =
++        new Client(self->mGlobal, ClientInfoAndState(self->mData->info(), aResult.ToIPC()));
++      outerPromise->MaybeResolve(newClient);
++    }, [self, token, outerPromise] (nsresult aResult) {
++      if (token->IsShuttingDown()) {
++        return;
++      }
++      outerPromise->MaybeReject(aResult);
++    });
++
++  return outerPromise.forget();
++}
++
++already_AddRefed<Promise>
++Client::Navigate(const nsAString& aURL, ErrorResult& aRv)
++{
++  MOZ_ASSERT(!NS_IsMainThread());
++  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
++  MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
++  MOZ_DIAGNOSTIC_ASSERT(workerPrivate->IsServiceWorker());
++  workerPrivate->AssertIsOnWorkerThread();
++
++  RefPtr<Promise> outerPromise = Promise::Create(mGlobal, aRv);
++  if (aRv.Failed()) {
++    return outerPromise.forget();
++  }
++
++  ClientNavigateArgs args(mData->info(), NS_ConvertUTF16toUTF8(aURL),
++                          workerPrivate->GetLocationInfo().mHref);
++  RefPtr<Client> self = this;
++
++  StartClientManagerOp(&ClientManager::Navigate, args,
++    mGlobal->EventTargetFor(TaskCategory::Other),
++    [self, outerPromise] (const ClientOpResult& aResult) {
++      if (aResult.type() != ClientOpResult::TClientInfoAndState) {
++        outerPromise->MaybeResolve(JS::NullHandleValue);
++        return;
++      }
++      RefPtr<Client> newClient =
++        new Client(self->mGlobal, aResult.get_ClientInfoAndState());
++      outerPromise->MaybeResolve(newClient);
++    }, [self, outerPromise] (nsresult aResult) {
++      // TODO: Improve this error in bug 1412856.  Ideally we should throw
++      //       the TypeError in the child process and pass it back to here.
++      outerPromise->MaybeReject(NS_ERROR_TYPE_ERR);
++    });
++
++  return outerPromise.forget();
++}
++
++} // namespace dom
++} // namespace mozilla
+diff --git a/dom/clients/api/Client.h b/dom/clients/api/Client.h
+new file mode 100644
+--- /dev/null
++++ b/dom/clients/api/Client.h
+@@ -0,0 +1,99 @@
++/* -*- 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/. */
++#ifndef _mozilla_dom_Client_h
++#define _mozilla_dom_Client_h
++
++#include "mozilla/dom/ClientBinding.h"
++#include "nsCOMPtr.h"
++#include "nsContentUtils.h"
++#include "nsISupports.h"
++#include "nsWrapperCache.h"
++
++class nsIGlobalObject;
++
++namespace mozilla {
++
++class ErrorResult;
++
++namespace dom {
++
++class ClientHandle;
++class ClientInfoAndState;
++class Promise;
++
++template <typename t> class Sequence;
++
++class Client final : public nsISupports
++                   , public nsWrapperCache
++{
++  nsCOMPtr<nsIGlobalObject> mGlobal;
++  UniquePtr<ClientInfoAndState> mData;
++  RefPtr<ClientHandle> mHandle;
++
++  ~Client() = default;
++
++  void
++  EnsureHandle();
++
++public:
++  Client(nsIGlobalObject* aGlobal, const ClientInfoAndState& aData);
++
++  TimeStamp
++  CreationTime() const;
++
++  TimeStamp
++  LastFocusTime() const;
++
++  nsContentUtils::StorageAccess
++  GetStorageAccess() const;
++
++  // nsWrapperCache interface methods
++  JSObject*
++  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
++
++  // DOM bindings methods
++  nsIGlobalObject*
++  GetParentObject() const;
++
++  // Client Bindings
++  void
++  GetUrl(nsAString& aUrlOut) const;
++
++  void
++  GetId(nsAString& aIdOut) const;
++
++  ClientType
++  Type() const;
++
++  FrameType
++  GetFrameType() const;
++
++  // WindowClient bindings
++  VisibilityState
++  GetVisibilityState() const;
++
++  bool
++  Focused() const;
++
++  already_AddRefed<Promise>
++  Focus(ErrorResult& aRv);
++
++  already_AddRefed<Promise>
++  Navigate(const nsAString& aURL, ErrorResult& aRv);
++
++  void
++  PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
++              const Sequence<JSObject*>& aTransferrable,
++              ErrorResult& aRv);
++
++  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
++  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(mozilla::dom::Client)
++};
++
++} // namespace dom
++} // namespace mozilla
++
++#endif // _mozilla_dom_Client_h
+diff --git a/dom/clients/api/ClientDOMUtil.h b/dom/clients/api/ClientDOMUtil.h
+new file mode 100644
+--- /dev/null
++++ b/dom/clients/api/ClientDOMUtil.h
+@@ -0,0 +1,54 @@
++/* -*- 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/. */
++#ifndef _mozilla_dom_ClientDOMUtil_h
++#define _mozilla_dom_ClientDOMUtil_h
++
++#include "mozilla/dom/ClientIPCTypes.h"
++#include "mozilla/dom/ClientOpPromise.h"
++#include "mozilla/dom/WorkerPrivate.h"
++#include "mozilla/dom/workers/bindings/WorkerHolderToken.h"
++
++class nsIGlobalObject;
++
++namespace mozilla {
++namespace dom {
++
++// Utility method to properly execute a ClientManager operation.  It
++// will properly hold a worker thread alive and avoid executing callbacks
++// if the thread is shutting down.
++template<typename Func, typename Arg, typename Resolve, typename Reject>
++void
++StartClientManagerOp(Func aFunc, const Arg& aArg, nsISerialEventTarget* aTarget,
++                     Resolve aResolve, Reject aReject)
++{
++  using mozilla::dom::workers::Closing;
++  using mozilla::dom::workers::GetCurrentThreadWorkerPrivate;
++  using mozilla::dom::workers::WorkerHolderToken;
++
++  RefPtr<WorkerHolderToken> token;
++  if (!NS_IsMainThread()) {
++    token = WorkerHolderToken::Create(GetCurrentThreadWorkerPrivate(), Closing);
++  }
++
++  RefPtr<ClientOpPromise> promise = aFunc(aArg, aTarget);
++  promise->Then(aTarget, __func__,
++    [aResolve, token](const ClientOpResult& aResult) {
++      if (token && token->IsShuttingDown()) {
++        return;
++      }
++      aResolve(aResult);
++    }, [aReject, token](nsresult aResult) {
++      if (token && token->IsShuttingDown()) {
++        return;
++      }
++      aReject(aResult);
++    });
++}
++
++} // namespace dom
++} // namespace mozilla
++
++#endif // _mozilla_dom_ClientDOMUtil_h
+diff --git a/dom/clients/api/Clients.cpp b/dom/clients/api/Clients.cpp
+new file mode 100644
+--- /dev/null
++++ b/dom/clients/api/Clients.cpp
+@@ -0,0 +1,289 @@
++/* -*- 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 "Clients.h"
++
++#include "ClientDOMUtil.h"
++#include "mozilla/dom/ClientIPCTypes.h"
++#include "mozilla/dom/ClientManager.h"
++#include "mozilla/dom/ClientsBinding.h"
++#include "mozilla/dom/Promise.h"
++#include "mozilla/dom/ServiceWorkerDescriptor.h"
++#include "mozilla/dom/WorkerPrivate.h"
++#include "mozilla/dom/workers/ServiceWorkerManager.h"
++#include "mozilla/SystemGroup.h"
++#include "nsIGlobalObject.h"
++#include "nsString.h"
++
++namespace mozilla {
++namespace dom {
++
++using mozilla::dom::workers::GetCurrentThreadWorkerPrivate;
++using mozilla::dom::workers::WorkerPrivate;
++using mozilla::dom::workers::ServiceWorkerManager;
++using mozilla::ipc::PrincipalInfo;
++
++NS_IMPL_CYCLE_COLLECTING_ADDREF(Clients);
++NS_IMPL_CYCLE_COLLECTING_RELEASE(Clients);
++NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Clients, mGlobal);
++
++NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Clients)
++  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
++  NS_INTERFACE_MAP_ENTRY(nsISupports)
++NS_INTERFACE_MAP_END
++
++Clients::Clients(nsIGlobalObject* aGlobal)
++  : mGlobal(aGlobal)
++{
++  MOZ_DIAGNOSTIC_ASSERT(mGlobal);
++}
++
++JSObject*
++Clients::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
++{
++  // TODO: Enable when bindings are updated to point to this class.
++#if 0
++  return ClientsBinding::Wrap(aCx, this, aGivenProto);
++#endif
++  return nullptr;
++}
++
++nsIGlobalObject*
++Clients::GetParentObject() const
++{
++  return mGlobal;
++}
++
++already_AddRefed<Promise>
++Clients::Get(const nsAString& aClientID, ErrorResult& aRv)
++{
++  MOZ_ASSERT(!NS_IsMainThread());
++  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
++  MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
++  MOZ_DIAGNOSTIC_ASSERT(workerPrivate->IsServiceWorker());
++  workerPrivate->AssertIsOnWorkerThread();
++
++  RefPtr<Promise> outerPromise = Promise::Create(mGlobal, aRv);
++  if (aRv.Failed()) {
++    return outerPromise.forget();
++  }
++
++  nsID id;
++  if (!id.Parse(NS_ConvertUTF16toUTF8(aClientID).get())) {
++    // Invalid ID means we will definitely not find a match, so just
++    // resolve with undefined indicating "not found".
++    outerPromise->MaybeResolveWithUndefined();
++    return outerPromise.forget();
++  }
++
++  const PrincipalInfo& principalInfo = workerPrivate->GetPrincipalInfo();
++  nsCOMPtr<nsISerialEventTarget> target =
++    mGlobal->EventTargetFor(TaskCategory::Other);
++
++  RefPtr<ClientOpPromise> innerPromise =
++    ClientManager::GetInfoAndState(ClientGetInfoAndStateArgs(id, principalInfo),
++                                   target);
++
++  nsCOMPtr<nsIGlobalObject> global = mGlobal;
++  nsCString scope = workerPrivate->ServiceWorkerScope();
++
++  innerPromise->Then(target, __func__,
++    [outerPromise, global, scope] (const ClientOpResult& aResult) {
++      RefPtr<Client> client = new Client(global, aResult.get_ClientInfoAndState());
++      if (client->GetStorageAccess() == nsContentUtils::StorageAccess::eAllow) {
++        outerPromise->MaybeResolve(Move(client));
++        return;
++      }
++      nsCOMPtr<nsIRunnable> r =
++        NS_NewRunnableFunction("Clients::MatchAll() storage denied",
++        [scope] {
++          ServiceWorkerManager::LocalizeAndReportToAllClients(
++            scope, "ServiceWorkerGetClientStorageError", nsTArray<nsString>());
++        });
++      SystemGroup::Dispatch(TaskCategory::Other, r.forget());
++      outerPromise->MaybeResolveWithUndefined();
++    }, [outerPromise] (nsresult aResult) {
++      outerPromise->MaybeResolveWithUndefined();
++    });
++
++  return outerPromise.forget();
++}
++
++namespace {
++
++class MatchAllComparator final
++{
++public:
++  bool
++  LessThan(Client* aLeft, Client* aRight) const
++  {
++    TimeStamp leftFocusTime = aLeft->LastFocusTime();
++    TimeStamp rightFocusTime = aRight->LastFocusTime();
++    // If the focus times are the same, then default to creation order.
++    // MatchAll should return oldest Clients first.
++    if (leftFocusTime == rightFocusTime) {
++      return aLeft->CreationTime() < aRight->CreationTime();
++    }
++
++    // Otherwise compare focus times.  We reverse the logic here so
++    // that the most recently focused window is first in the list.
++    if (!leftFocusTime.IsNull() && rightFocusTime.IsNull()) {
++      return true;
++    }
++    if (leftFocusTime.IsNull() && !rightFocusTime.IsNull()) {
++      return false;
++    }
++    return leftFocusTime > rightFocusTime;
++  }
++
++  bool
++  Equals(Client* aLeft, Client* aRight) const
++  {
++    return aLeft->LastFocusTime() == aRight->LastFocusTime() &&
++           aLeft->CreationTime() == aRight->CreationTime();
++  }
++};
++
++} // anonymous namespace
++
++already_AddRefed<Promise>
++Clients::MatchAll(const ClientQueryOptions& aOptions, ErrorResult& aRv)
++{
++  MOZ_ASSERT(!NS_IsMainThread());
++  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
++  MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
++  MOZ_DIAGNOSTIC_ASSERT(workerPrivate->IsServiceWorker());
++  workerPrivate->AssertIsOnWorkerThread();
++
++  RefPtr<Promise> outerPromise = Promise::Create(mGlobal, aRv);
++  if (aRv.Failed()) {
++    return outerPromise.forget();
++  }
++
++  nsCOMPtr<nsIGlobalObject> global = mGlobal;
++  nsCString scope = workerPrivate->ServiceWorkerScope();
++
++  ClientMatchAllArgs args(workerPrivate->GetServiceWorkerDescriptor().ToIPC(),
++                          aOptions.mType,
++                          aOptions.mIncludeUncontrolled);
++  StartClientManagerOp(&ClientManager::MatchAll, args,
++    mGlobal->EventTargetFor(TaskCategory::Other),
++    [outerPromise, global, scope] (const ClientOpResult& aResult) {
++      nsTArray<RefPtr<Client>> clientList;
++      bool storageDenied = false;
++      for (const ClientInfoAndState& value : aResult.get_ClientList().values()) {
++        RefPtr<Client> client = new Client(global, value);
++        if (client->GetStorageAccess() != nsContentUtils::StorageAccess::eAllow) {
++          storageDenied = true;
++          continue;
++        }
++        clientList.AppendElement(Move(client));
++      }
++      if (storageDenied) {
++        nsCOMPtr<nsIRunnable> r =
++          NS_NewRunnableFunction("Clients::MatchAll() storage denied",
++          [scope] {
++            ServiceWorkerManager::LocalizeAndReportToAllClients(
++              scope, "ServiceWorkerGetClientStorageError", nsTArray<nsString>());
++          });
++        SystemGroup::Dispatch(TaskCategory::Other, r.forget());
++      }
++      clientList.Sort(MatchAllComparator());
++      outerPromise->MaybeResolve(clientList);
++    }, [outerPromise] (nsresult aResult) {
++      outerPromise->MaybeReject(aResult);
++    });
++
++  return outerPromise.forget();
++}
++
++already_AddRefed<Promise>
++Clients::OpenWindow(const nsAString& aURL, ErrorResult& aRv)
++{
++  MOZ_ASSERT(!NS_IsMainThread());
++  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
++  MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
++  MOZ_DIAGNOSTIC_ASSERT(workerPrivate->IsServiceWorker());
++  workerPrivate->AssertIsOnWorkerThread();
++
++  RefPtr<Promise> outerPromise = Promise::Create(mGlobal, aRv);
++  if (aRv.Failed()) {
++    return outerPromise.forget();
++  }
++
++  if (aURL.EqualsLiteral("about:blank")) {
++    // TODO: Improve this error in bug 1412856.
++    outerPromise->MaybeReject(NS_ERROR_DOM_TYPE_ERR);
++    return outerPromise.forget();
++  }
++
++  if (!workerPrivate->GlobalScope()->WindowInteractionAllowed()) {
++    outerPromise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR);
++    return outerPromise.forget();
++  }
++
++  const PrincipalInfo& principalInfo = workerPrivate->GetPrincipalInfo();
++  nsCString baseURL = workerPrivate->GetLocationInfo().mHref;
++  ClientOpenWindowArgs args(principalInfo, NS_ConvertUTF16toUTF8(aURL),
++                            baseURL);
++
++  nsCOMPtr<nsIGlobalObject> global = mGlobal;
++
++  StartClientManagerOp(&ClientManager::OpenWindow, args,
++    mGlobal->EventTargetFor(TaskCategory::Other),
++    [outerPromise, global] (const ClientOpResult& aResult) {
++      if (aResult.type() != ClientOpResult::TClientInfoAndState) {
++        outerPromise->MaybeResolve(JS::NullHandleValue);
++        return;
++      }
++      RefPtr<Client> client =
++        new Client(global, aResult.get_ClientInfoAndState());
++      outerPromise->MaybeResolve(client);
++    }, [outerPromise] (nsresult aResult) {
++      // TODO: Improve this error in bug 1412856.  Ideally we should throw
++      //       the TypeError in the child process and pass it back to here.
++      outerPromise->MaybeReject(NS_ERROR_TYPE_ERR);
++    });
++
++  return outerPromise.forget();
++}
++
++already_AddRefed<Promise>
++Clients::Claim(ErrorResult& aRv)
++{
++  MOZ_ASSERT(!NS_IsMainThread());
++  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
++  MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
++  MOZ_DIAGNOSTIC_ASSERT(workerPrivate->IsServiceWorker());
++  workerPrivate->AssertIsOnWorkerThread();
++
++  RefPtr<Promise> outerPromise = Promise::Create(mGlobal, aRv);
++  if (aRv.Failed()) {
++    return outerPromise.forget();
++  }
++
++  const ServiceWorkerDescriptor& serviceWorker =
++    workerPrivate->GetServiceWorkerDescriptor();
++
++  if (serviceWorker.State() != ServiceWorkerState::Activating &&
++      serviceWorker.State() != ServiceWorkerState::Activated) {
++    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
++    return outerPromise.forget();
++  }
++
++  StartClientManagerOp(&ClientManager::Claim, ClientClaimArgs(serviceWorker.ToIPC()),
++    mGlobal->EventTargetFor(TaskCategory::Other),
++    [outerPromise] (const ClientOpResult& aResult) {
++      outerPromise->MaybeResolveWithUndefined();
++    }, [outerPromise] (nsresult aResult) {
++      outerPromise->MaybeReject(aResult);
++    });
++
++  return outerPromise.forget();
++}
++
++} // namespace dom
++} // namespace mozilla
+diff --git a/dom/clients/api/Clients.h b/dom/clients/api/Clients.h
+new file mode 100644
+--- /dev/null
++++ b/dom/clients/api/Clients.h
+@@ -0,0 +1,61 @@
++/* -*- 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/. */
++#ifndef _mozilla_dom_Clients_h
++#define _mozilla_dom_Clients_h
++
++#include "nsCOMPtr.h"
++#include "nsISupports.h"
++#include "nsWrapperCache.h"
++
++class nsIGlobalObject;
++
++namespace mozilla {
++
++class ErrorResult;
++
++namespace dom {
++
++struct ClientQueryOptions;
++class Promise;
++
++class Clients final : public nsISupports
++                    , public nsWrapperCache
++{
++  nsCOMPtr<nsIGlobalObject> mGlobal;
++
++  ~Clients() = default;
++
++public:
++  explicit Clients(nsIGlobalObject* aGlobal);
++
++  // nsWrapperCache interface methods
++  JSObject*
++  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
++
++  // DOM bindings methods
++  nsIGlobalObject*
++  GetParentObject() const;
++
++  already_AddRefed<Promise>
++  Get(const nsAString& aClientID, ErrorResult& aRv);
++
++  already_AddRefed<Promise>
++  MatchAll(const ClientQueryOptions& aOptions, ErrorResult& aRv);
++
++  already_AddRefed<Promise>
++  OpenWindow(const nsAString& aURL, ErrorResult& aRv);
++
++  already_AddRefed<Promise>
++  Claim(ErrorResult& aRv);
++
++  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
++  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Clients)
++};
++
++} // namespace dom
++} // namespace mozilla
++
++#endif // _mozilla_dom_Clients_h
+diff --git a/dom/clients/api/moz.build b/dom/clients/api/moz.build
+new file mode 100644
+--- /dev/null
++++ b/dom/clients/api/moz.build
+@@ -0,0 +1,32 @@
++# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
++# vim: set filetype=python:
++# This Source Code Form is subject to the terms of the Mozilla Public
++# License, v. 2.0. If a copy of the MPL was not distributed with this
++# file, You can obtain one at http://mozilla.org/MPL/2.0/.
++
++EXPORTS.mozilla.dom += [
++  'Client.h',
++  'Clients.h',
++]
++
++UNIFIED_SOURCES += [
++  'Client.cpp',
++  'Clients.cpp',
++]
++
++include('/ipc/chromium/chromium-config.mozbuild')
++
++LOCAL_INCLUDES += [
++  '/dom/workers',
++]
++
++FINAL_LIBRARY = 'xul'
++
++MOCHITEST_MANIFESTS += [
++]
++
++BROWSER_CHROME_MANIFESTS += [
++]
++
++XPCSHELL_TESTS_MANIFESTS += [
++]
+diff --git a/dom/clients/moz.build b/dom/clients/moz.build
+--- a/dom/clients/moz.build
++++ b/dom/clients/moz.build
+@@ -1,9 +1,10 @@
+ # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+ # vim: set filetype=python:
+ # This Source Code Form is subject to the terms of the Mozilla Public
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
+ # file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ 
+ DIRS += [
++  'api',
+   'manager',
+ ]
+diff --git a/dom/indexedDB/ActorsParent.cpp b/dom/indexedDB/ActorsParent.cpp
+--- a/dom/indexedDB/ActorsParent.cpp
++++ b/dom/indexedDB/ActorsParent.cpp
+@@ -134,16 +134,17 @@ MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLAT
+                                           PRFileDesc,
+                                           PR_Close);
+ 
+ namespace dom {
+ namespace indexedDB {
+ 
+ using namespace mozilla::dom::quota;
+ using namespace mozilla::ipc;
++using mozilla::dom::quota::Client;
+ 
+ namespace {
+ 
+ class ConnectionPool;
+ class Cursor;
+ class Database;
+ struct DatabaseActorInfo;
+ class DatabaseFile;

+ 3110 - 0
frg/work-js/mozilla-release/patches/1293277-5-59a1.patch

@@ -0,0 +1,3110 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1513111487 18000
+# Node ID 63938d2d5492022b7d3808d11a87faf83742ed6b
+# Parent  326a1ff52d1ac22dbd066731eb3b326f490d74d5
+Bug 1293277 P5 Switch bindings over to new Client and Clients classes. r=baku
+
+diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf
+--- a/dom/bindings/Bindings.conf
++++ b/dom/bindings/Bindings.conf
+@@ -148,26 +148,16 @@ DOMInterfaces = {
+     'concrete': False,
+ },
+ 
+ 'ChromeWorker': {
+     'headerFile': 'mozilla/dom/WorkerPrivate.h',
+     'nativeType': 'mozilla::dom::workers::ChromeWorkerPrivate',
+ },
+ 
+-'Client': {
+-    'nativeType': 'mozilla::dom::workers::ServiceWorkerClient',
+-    'headerFile': 'mozilla/dom/workers/bindings/ServiceWorkerClient.h',
+-},
+-
+-'Clients': {
+-    'nativeType': 'mozilla::dom::workers::ServiceWorkerClients',
+-    'headerFile': 'mozilla/dom/workers/bindings/ServiceWorkerClients.h',
+-},
+-
+ 'console': {
+     'nativeType': 'mozilla::dom::Console',
+ },
+ 
+ 'ConvolverNode': {
+     'implicitJSContext': [ 'buffer' ],
+ },
+ 
+@@ -1116,18 +1106,17 @@ DOMInterfaces = {
+     'nativeType': 'mozilla::extensions::WebExtensionContentScript',
+ },
+ 
+ 'WebExtensionPolicy': {
+     'nativeType': 'mozilla::extensions::WebExtensionPolicy',
+ },
+ 
+ 'WindowClient': {
+-    'nativeType': 'mozilla::dom::workers::ServiceWorkerWindowClient',
+-    'headerFile': 'mozilla/dom/workers/bindings/ServiceWorkerWindowClient.h',
++    'nativeType': 'mozilla::dom::Client',
+ },
+ 
+ 'WebGLActiveInfo': {
+     'nativeType': 'mozilla::WebGLActiveInfo',
+     'headerFile': 'WebGLActiveInfo.h'
+ },
+ 
+ 'WebGLBuffer': {
+diff --git a/dom/clients/api/Client.cpp b/dom/clients/api/Client.cpp
+--- a/dom/clients/api/Client.cpp
++++ b/dom/clients/api/Client.cpp
+@@ -71,24 +71,20 @@ Client::GetStorageAccess() const
+ {
+   ClientState state(ClientState::FromIPC(mData->state()));
+   return state.GetStorageAccess();
+ }
+ 
+ JSObject*
+ Client::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+ {
+-#if 0
+-  // TODO: Enable when bindings are updated to point to this class.
+   if (mData->info().type() == ClientType::Window) {
+     return WindowClientBinding::Wrap(aCx, this, aGivenProto);
+   }
+   return ClientBinding::Wrap(aCx, this, aGivenProto);
+-#endif
+-  return nullptr;
+ }
+ 
+ nsIGlobalObject*
+ Client::GetParentObject() const
+ {
+   return mGlobal;
+ }
+ 
+diff --git a/dom/clients/api/Clients.cpp b/dom/clients/api/Clients.cpp
+--- a/dom/clients/api/Clients.cpp
++++ b/dom/clients/api/Clients.cpp
+@@ -39,21 +39,17 @@ Clients::Clients(nsIGlobalObject* aGloba
+   : mGlobal(aGlobal)
+ {
+   MOZ_DIAGNOSTIC_ASSERT(mGlobal);
+ }
+ 
+ JSObject*
+ Clients::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+ {
+-  // TODO: Enable when bindings are updated to point to this class.
+-#if 0
+   return ClientsBinding::Wrap(aCx, this, aGivenProto);
+-#endif
+-  return nullptr;
+ }
+ 
+ nsIGlobalObject*
+ Clients::GetParentObject() const
+ {
+   return mGlobal;
+ }
+ 
+diff --git a/dom/webidl/Client.webidl b/dom/webidl/Client.webidl
+--- a/dom/webidl/Client.webidl
++++ b/dom/webidl/Client.webidl
+@@ -8,30 +8,32 @@
+  *
+  */
+ 
+ [Exposed=ServiceWorker]
+ interface Client {
+   readonly attribute USVString url;
+ 
+   // Remove frameType in bug 1290936
++  [BinaryName="GetFrameType"]
+   readonly attribute FrameType frameType;
+ 
+   readonly attribute ClientType type;
+   readonly attribute DOMString id;
+ 
+   // Implement reserved in bug 1264177
+   // readonly attribute boolean reserved;
+ 
+   [Throws]
+   void postMessage(any message, optional sequence<object> transfer = []);
+ };
+ 
+ [Exposed=ServiceWorker]
+ interface WindowClient : Client {
++  [BinaryName="GetVisibilityState"]
+   readonly attribute VisibilityState visibilityState;
+   readonly attribute boolean focused;
+ 
+   // Implement ancestorOrigins in bug 1264180
+   // [SameObject] readonly attribute FrozenArray<USVString> ancestorOrigins;
+ 
+   [Throws, NewObject]
+   Promise<WindowClient> focus();
+diff --git a/dom/webidl/ServiceWorkerGlobalScope.webidl b/dom/webidl/ServiceWorkerGlobalScope.webidl
+--- a/dom/webidl/ServiceWorkerGlobalScope.webidl
++++ b/dom/webidl/ServiceWorkerGlobalScope.webidl
+@@ -10,17 +10,18 @@
+  *
+  * You are granted a license to use, reproduce and create derivative works of
+  * this document.
+  */
+ 
+ [Global=(Worker,ServiceWorker),
+  Exposed=ServiceWorker]
+ interface ServiceWorkerGlobalScope : WorkerGlobalScope {
+-  [SameObject] readonly attribute Clients clients;
++  [SameObject, BinaryName="GetClients"]
++  readonly attribute Clients clients;
+   [SameObject] readonly attribute ServiceWorkerRegistration registration;
+ 
+   [Throws, NewObject]
+   Promise<void> skipWaiting();
+ 
+   attribute EventHandler oninstall;
+   attribute EventHandler onactivate;
+ 
+diff --git a/dom/workers/ServiceWorker.cpp b/dom/workers/ServiceWorker.cpp
+--- a/dom/workers/ServiceWorker.cpp
++++ b/dom/workers/ServiceWorker.cpp
+@@ -3,22 +3,23 @@
+ /* This Source Code Form is subject to the terms of the Mozilla Public
+  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+  * You can obtain one at http://mozilla.org/MPL/2.0/. */
+ 
+ #include "ServiceWorker.h"
+ 
+ #include "nsIDocument.h"
+ #include "nsPIDOMWindow.h"
+-#include "ServiceWorkerClient.h"
+ #include "ServiceWorkerManager.h"
+ #include "ServiceWorkerPrivate.h"
+ #include "WorkerPrivate.h"
+ 
+ #include "mozilla/Preferences.h"
++#include "mozilla/dom/ClientIPCTypes.h"
++#include "mozilla/dom/ClientState.h"
+ #include "mozilla/dom/Promise.h"
+ #include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h"
+ 
+ #ifdef XP_WIN
+ #undef PostMessage
+ #endif
+ 
+ using mozilla::ErrorResult;
+@@ -97,16 +98,24 @@ ServiceWorker::PostMessage(JSContext* aC
+   if (storageAllowed != nsContentUtils::StorageAccess::eAllow) {
+     ServiceWorkerManager::LocalizeAndReportToAllClients(
+       mInfo->Scope(), "ServiceWorkerPostMessageStorageError",
+       nsTArray<nsString> { NS_ConvertUTF8toUTF16(mInfo->Scope()) });
+     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+     return;
+   }
+ 
+-  UniquePtr<ServiceWorkerClientInfo> clientInfo(new ServiceWorkerClientInfo(window->GetExtantDoc()));
++  Maybe<ClientInfo> clientInfo = window->GetClientInfo();
++  Maybe<ClientState> clientState = window->GetClientState();
++  if (clientInfo.isNothing() || clientState.isNothing()) {
++    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
++    return;
++  }
++
+   ServiceWorkerPrivate* workerPrivate = mInfo->WorkerPrivate();
+-  aRv = workerPrivate->SendMessageEvent(aCx, aMessage, aTransferable, Move(clientInfo));
++  aRv = workerPrivate->SendMessageEvent(aCx, aMessage, aTransferable,
++                                        ClientInfoAndState(clientInfo.ref().ToIPC(),
++                                                           clientState.ref().ToIPC()));
+ }
+ 
+ } // namespace workers
+ } // namespace dom
+ } // namespace mozilla
+diff --git a/dom/workers/ServiceWorkerClient.cpp b/dom/workers/ServiceWorkerClient.cpp
+deleted file mode 100644
+--- a/dom/workers/ServiceWorkerClient.cpp
++++ /dev/null
+@@ -1,286 +0,0 @@
+-/* -*- 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 "ServiceWorkerClient.h"
+-#include "ServiceWorkerContainer.h"
+-
+-#include "mozilla/dom/MessageEvent.h"
+-#include "mozilla/dom/Navigator.h"
+-#include "nsGlobalWindow.h"
+-#include "nsIBrowserDOMWindow.h"
+-#include "nsIDocument.h"
+-#include "ServiceWorker.h"
+-#include "ServiceWorkerPrivate.h"
+-#include "WorkerPrivate.h"
+-
+-using namespace mozilla;
+-using namespace mozilla::dom;
+-using namespace mozilla::dom::workers;
+-
+-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ServiceWorkerClient, mOwner)
+-
+-NS_IMPL_CYCLE_COLLECTING_ADDREF(ServiceWorkerClient)
+-NS_IMPL_CYCLE_COLLECTING_RELEASE(ServiceWorkerClient)
+-
+-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ServiceWorkerClient)
+-  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+-  NS_INTERFACE_MAP_ENTRY(nsISupports)
+-NS_INTERFACE_MAP_END
+-
+-ServiceWorkerClientInfo::ServiceWorkerClientInfo(nsIDocument* aDoc, uint32_t aOrdinal)
+-  : mType(ClientType::Window)
+-  , mOrdinal(aOrdinal)
+-  , mWindowId(0)
+-  , mFrameType(FrameType::None)
+-{
+-  MOZ_ASSERT(aDoc);
+-  nsresult rv = aDoc->GetOrCreateId(mClientId);
+-  if (NS_FAILED(rv)) {
+-    NS_WARNING("Failed to get the UUID of the document.");
+-  }
+-
+-  RefPtr<nsGlobalWindow> innerWindow = nsGlobalWindow::Cast(aDoc->GetInnerWindow());
+-  if (innerWindow) {
+-    // XXXcatalinb: The inner window can be null if the document is navigating
+-    // and was detached.
+-    mWindowId = innerWindow->WindowID();
+-  }
+-
+-  nsCOMPtr<nsIURI> originalURI = aDoc->GetOriginalURI();
+-  if (originalURI) {
+-    nsAutoCString spec;
+-    originalURI->GetSpec(spec);
+-    CopyUTF8toUTF16(spec, mUrl);
+-  }
+-  mVisibilityState = aDoc->VisibilityState();
+-
+-  mLastFocusTime = aDoc->LastFocusTime();
+-
+-  ErrorResult result;
+-  mFocused = aDoc->HasFocus(result);
+-  if (result.Failed()) {
+-    NS_WARNING("Failed to get focus information.");
+-  }
+-
+-  MOZ_ASSERT_IF(mLastFocusTime.IsNull(), !mFocused);
+-  MOZ_ASSERT_IF(mFocused, !mLastFocusTime.IsNull());
+-
+-  RefPtr<nsGlobalWindow> outerWindow = nsGlobalWindow::Cast(aDoc->GetWindow());
+-  if (!outerWindow) {
+-    MOZ_ASSERT(mFrameType == FrameType::None);
+-  } else if (!outerWindow->IsTopLevelWindow()) {
+-    mFrameType = FrameType::Nested;
+-  } else if (outerWindow->HadOriginalOpener()) {
+-    mFrameType = FrameType::Auxiliary;
+-  } else {
+-    mFrameType = FrameType::Top_level;
+-  }
+-}
+-
+-bool
+-ServiceWorkerClientInfo::operator<(const ServiceWorkerClientInfo& aRight) const
+-{
+-  // Note: the mLastFocusTime comparisons are reversed because we need to
+-  // put most recently focused values first.  The mOrdinal comparison is
+-  // normal, though, because otherwise we want normal creation order.
+-
+-  if (mLastFocusTime == aRight.mLastFocusTime) {
+-    return mOrdinal < aRight.mOrdinal;
+-  }
+-
+-  if (mLastFocusTime.IsNull()) {
+-    return false;
+-  }
+-
+-  if (aRight.mLastFocusTime.IsNull()) {
+-    return true;
+-  }
+-
+-  return mLastFocusTime > aRight.mLastFocusTime;
+-}
+-
+-bool
+-ServiceWorkerClientInfo::operator==(const ServiceWorkerClientInfo& aRight) const
+-{
+-  return mLastFocusTime == aRight.mLastFocusTime &&
+-         mOrdinal == aRight.mOrdinal &&
+-         mClientId == aRight.mClientId;
+-}
+-
+-JSObject*
+-ServiceWorkerClient::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+-{
+-  return ClientBinding::Wrap(aCx, this, aGivenProto);
+-}
+-
+-ClientType
+-ServiceWorkerClient::Type() const
+-{
+-  return mType;
+-}
+-
+-namespace {
+-
+-class ServiceWorkerClientPostMessageRunnable final
+-  : public Runnable
+-  , public StructuredCloneHolder
+-{
+-  const uint64_t mSourceID;
+-  const nsCString mSourceScope;
+-  const uint64_t mWindowId;
+-
+-public:
+-  ServiceWorkerClientPostMessageRunnable(uint64_t aSourceID,
+-                                         const nsACString& aSourceScope,
+-                                         uint64_t aWindowId)
+-    : mozilla::Runnable("ServiceWorkerClientPostMessageRunnable")
+-    , StructuredCloneHolder(CloningSupported,
+-                            TransferringSupported,
+-                            StructuredCloneScope::SameProcessDifferentThread)
+-    , mSourceID(aSourceID)
+-    , mSourceScope(aSourceScope)
+-    , mWindowId(aWindowId)
+-  {}
+-
+-  NS_IMETHOD
+-  Run() override
+-  {
+-    AssertIsOnMainThread();
+-    nsGlobalWindow* window = nsGlobalWindow::GetInnerWindowWithId(mWindowId);
+-    if (!window) {
+-      return NS_ERROR_FAILURE;
+-    }
+-
+-    dom::Navigator* navigator = window->Navigator();
+-    if (!navigator) {
+-      return NS_ERROR_FAILURE;
+-    }
+-
+-    RefPtr<ServiceWorkerContainer> container = navigator->ServiceWorker();
+-    AutoJSAPI jsapi;
+-    if (NS_WARN_IF(!jsapi.Init(window))) {
+-      return NS_ERROR_FAILURE;
+-    }
+-    JSContext* cx = jsapi.cx();
+-
+-    return DispatchDOMEvent(cx, window->AsInner(), container);
+-  }
+-
+-private:
+-  NS_IMETHOD
+-  DispatchDOMEvent(JSContext* aCx, nsPIDOMWindowInner* aWindow,
+-                   ServiceWorkerContainer* aTargetContainer)
+-  {
+-    AssertIsOnMainThread();
+-
+-    MOZ_ASSERT(aTargetContainer->GetParentObject(),
+-               "How come we don't have a window here?!");
+-
+-    JS::Rooted<JS::Value> messageData(aCx);
+-    ErrorResult rv;
+-    Read(aTargetContainer->GetParentObject(), aCx, &messageData, rv);
+-    if (NS_WARN_IF(rv.Failed())) {
+-      xpc::Throw(aCx, rv.StealNSResult());
+-      return NS_ERROR_FAILURE;
+-    }
+-
+-    RootedDictionary<MessageEventInit> init(aCx);
+-
+-    nsCOMPtr<nsIPrincipal> principal = aTargetContainer->GetParentObject()->PrincipalOrNull();
+-    NS_WARNING_ASSERTION(principal, "Why is the principal null here?");
+-
+-    bool isNullPrincipal = false;
+-    bool isSystemPrincipal = false;
+-    if (principal) {
+-      isNullPrincipal = principal->GetIsNullPrincipal();
+-      MOZ_ASSERT(!isNullPrincipal);
+-      isSystemPrincipal = principal->GetIsSystemPrincipal();
+-      MOZ_ASSERT(!isSystemPrincipal);
+-    }
+-
+-    init.mData = messageData;
+-    nsAutoCString origin;
+-    if (principal && !isNullPrincipal && !isSystemPrincipal) {
+-      principal->GetOrigin(origin);
+-    }
+-    init.mOrigin = NS_ConvertUTF8toUTF16(origin);
+-
+-
+-    RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+-    if (swm) {
+-      RefPtr<ServiceWorkerRegistrationInfo> reg =
+-        swm->GetRegistration(principal, mSourceScope);
+-      if (reg) {
+-        RefPtr<ServiceWorkerInfo> serviceWorker = reg->GetByID(mSourceID);
+-        if (serviceWorker) {
+-          init.mSource.SetValue().SetAsServiceWorker() =
+-            serviceWorker->GetOrCreateInstance(aWindow);
+-        }
+-      }
+-    }
+-
+-    if (!TakeTransferredPortsAsSequence(init.mPorts)) {
+-      return NS_ERROR_OUT_OF_MEMORY;
+-    }
+-
+-    RefPtr<MessageEvent> event =
+-      MessageEvent::Constructor(aTargetContainer, NS_LITERAL_STRING("message"),
+-                                init);
+-
+-    event->SetTrusted(true);
+-    bool status = false;
+-    aTargetContainer->DispatchEvent(static_cast<dom::Event*>(event.get()),
+-                                    &status);
+-
+-    if (!status) {
+-      return NS_ERROR_FAILURE;
+-    }
+-
+-    return NS_OK;
+-  }
+-};
+-
+-} // namespace
+-
+-void
+-ServiceWorkerClient::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+-                                 const Sequence<JSObject*>& aTransferable,
+-                                 ErrorResult& aRv)
+-{
+-  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+-  MOZ_ASSERT(workerPrivate);
+-  workerPrivate->AssertIsOnWorkerThread();
+-
+-  JS::Rooted<JS::Value> transferable(aCx, JS::UndefinedValue());
+-
+-  aRv = nsContentUtils::CreateJSValueFromSequenceOfObject(aCx, aTransferable,
+-                                                          &transferable);
+-  if (NS_WARN_IF(aRv.Failed())) {
+-    return;
+-  }
+-
+-  // At the moment we only expose Client on ServiceWorker globals.
+-  MOZ_ASSERT(workerPrivate->IsServiceWorker());
+-  uint32_t serviceWorkerID = workerPrivate->ServiceWorkerID();
+-  nsCString scope = workerPrivate->ServiceWorkerScope();
+-
+-  RefPtr<ServiceWorkerClientPostMessageRunnable> runnable =
+-    new ServiceWorkerClientPostMessageRunnable(serviceWorkerID, scope,
+-                                               mWindowId);
+-
+-  runnable->Write(aCx, aMessage, transferable, JS::CloneDataPolicy().denySharedArrayBuffer(),
+-                  aRv);
+-  if (NS_WARN_IF(aRv.Failed())) {
+-    return;
+-  }
+-
+-  aRv = workerPrivate->DispatchToMainThread(runnable.forget());
+-  if (NS_WARN_IF(aRv.Failed())) {
+-    return;
+-  }
+-}
+-
+diff --git a/dom/workers/ServiceWorkerClient.h b/dom/workers/ServiceWorkerClient.h
+deleted file mode 100644
+--- a/dom/workers/ServiceWorkerClient.h
++++ /dev/null
+@@ -1,128 +0,0 @@
+-/* -*- 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/.
+- */
+-
+-#ifndef mozilla_dom_workers_serviceworkerclient_h
+-#define mozilla_dom_workers_serviceworkerclient_h
+-
+-#include "nsCOMPtr.h"
+-#include "nsWrapperCache.h"
+-#include "mozilla/ErrorResult.h"
+-#include "mozilla/dom/BindingDeclarations.h"
+-#include "mozilla/dom/ClientBinding.h"
+-
+-class nsIDocument;
+-
+-namespace mozilla {
+-namespace dom {
+-namespace workers {
+-
+-class ServiceWorkerClient;
+-class ServiceWorkerWindowClient;
+-
+-// Used as a container object for information needed to create
+-// client objects.
+-class ServiceWorkerClientInfo final
+-{
+-  friend class ServiceWorkerClient;
+-  friend class ServiceWorkerWindowClient;
+-
+-public:
+-  explicit ServiceWorkerClientInfo(nsIDocument* aDoc, uint32_t aOrdinal = 0);
+-
+-  const nsString& ClientId() const
+-  {
+-    return mClientId;
+-  }
+-
+-  bool operator<(const ServiceWorkerClientInfo& aRight) const;
+-  bool operator==(const ServiceWorkerClientInfo& aRight) const;
+-
+-private:
+-  const mozilla::dom::ClientType mType;
+-  const uint32_t mOrdinal;
+-  nsString mClientId;
+-  uint64_t mWindowId;
+-  nsString mUrl;
+-
+-  // Window Clients
+-  VisibilityState mVisibilityState;
+-  FrameType mFrameType;
+-  TimeStamp mLastFocusTime;
+-  bool mFocused;
+-};
+-
+-class ServiceWorkerClient : public nsISupports,
+-                            public nsWrapperCache
+-{
+-public:
+-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ServiceWorkerClient)
+-
+-  ServiceWorkerClient(nsISupports* aOwner,
+-                      const ServiceWorkerClientInfo& aClientInfo)
+-    : mOwner(aOwner)
+-    , mType(aClientInfo.mType)
+-    , mId(aClientInfo.mClientId)
+-    , mUrl(aClientInfo.mUrl)
+-    , mWindowId(aClientInfo.mWindowId)
+-    , mFrameType(aClientInfo.mFrameType)
+-  {
+-    MOZ_ASSERT(aOwner);
+-  }
+-
+-  nsISupports*
+-  GetParentObject() const
+-  {
+-    return mOwner;
+-  }
+-
+-  void GetId(nsString& aRetval) const
+-  {
+-    aRetval = mId;
+-  }
+-
+-  void
+-  GetUrl(nsAString& aUrl) const
+-  {
+-    aUrl.Assign(mUrl);
+-  }
+-
+-  mozilla::dom::FrameType
+-  FrameType() const
+-  {
+-    return mFrameType;
+-  }
+-
+-  mozilla::dom::ClientType
+-  Type() const;
+-
+-  void
+-  PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+-              const Sequence<JSObject*>& aTransferable, ErrorResult& aRv);
+-
+-  JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+-
+-protected:
+-  virtual ~ServiceWorkerClient()
+-  { }
+-
+-private:
+-  nsCOMPtr<nsISupports> mOwner;
+-  const ClientType mType;
+-  nsString mId;
+-  nsString mUrl;
+-
+-protected:
+-  uint64_t mWindowId;
+-  mozilla::dom::FrameType mFrameType;
+-};
+-
+-} // namespace workers
+-} // namespace dom
+-} // namespace mozilla
+-
+-#endif // mozilla_dom_workers_serviceworkerclient_h
+diff --git a/dom/workers/ServiceWorkerClients.cpp b/dom/workers/ServiceWorkerClients.cpp
+deleted file mode 100644
+--- a/dom/workers/ServiceWorkerClients.cpp
++++ /dev/null
+@@ -1,941 +0,0 @@
+-/* -*- 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 "ServiceWorkerClients.h"
+-
+-#include "mozilla/dom/Promise.h"
+-#include "mozilla/dom/PromiseWorkerProxy.h"
+-#include "mozilla/JSObjectHolder.h"
+-
+-#include "ServiceWorkerClient.h"
+-#include "ServiceWorkerManager.h"
+-#include "ServiceWorkerPrivate.h"
+-#include "ServiceWorkerWindowClient.h"
+-
+-#include "WorkerPrivate.h"
+-#include "WorkerRunnable.h"
+-#include "WorkerScope.h"
+-
+-#include "nsContentUtils.h"
+-#include "nsIBrowserDOMWindow.h"
+-#include "nsIDocShell.h"
+-#include "nsIDOMChromeWindow.h"
+-#include "nsIDOMWindow.h"
+-#include "nsIWebNavigation.h"
+-#include "nsIWebProgress.h"
+-#include "nsIWebProgressListener.h"
+-#include "nsIWindowMediator.h"
+-#include "nsIWindowWatcher.h"
+-#include "nsNetUtil.h"
+-#include "nsPIWindowWatcher.h"
+-#include "nsWindowWatcher.h"
+-#include "nsWeakReference.h"
+-
+-#ifdef MOZ_WIDGET_ANDROID
+-#include "FennecJNIWrappers.h"
+-#endif
+-
+-using namespace mozilla;
+-using namespace mozilla::dom;
+-using namespace mozilla::dom::workers;
+-
+-NS_IMPL_CYCLE_COLLECTING_ADDREF(ServiceWorkerClients)
+-NS_IMPL_CYCLE_COLLECTING_RELEASE(ServiceWorkerClients)
+-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ServiceWorkerClients, mWorkerScope)
+-
+-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ServiceWorkerClients)
+-  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+-  NS_INTERFACE_MAP_ENTRY(nsISupports)
+-NS_INTERFACE_MAP_END
+-
+-ServiceWorkerClients::ServiceWorkerClients(ServiceWorkerGlobalScope* aWorkerScope)
+-  : mWorkerScope(aWorkerScope)
+-{
+-  MOZ_ASSERT(mWorkerScope);
+-}
+-
+-JSObject*
+-ServiceWorkerClients::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+-{
+-  return ClientsBinding::Wrap(aCx, this, aGivenProto);
+-}
+-
+-namespace {
+-
+-class GetRunnable final : public Runnable
+-{
+-  class ResolvePromiseWorkerRunnable final : public WorkerRunnable
+-  {
+-    RefPtr<PromiseWorkerProxy> mPromiseProxy;
+-    UniquePtr<ServiceWorkerClientInfo> mValue;
+-    nsresult mRv;
+-
+-  public:
+-    ResolvePromiseWorkerRunnable(WorkerPrivate* aWorkerPrivate,
+-                                 PromiseWorkerProxy* aPromiseProxy,
+-                                 UniquePtr<ServiceWorkerClientInfo>&& aValue,
+-                                 nsresult aRv)
+-      : WorkerRunnable(aWorkerPrivate),
+-        mPromiseProxy(aPromiseProxy),
+-        mValue(Move(aValue)),
+-        mRv(Move(aRv))
+-    {
+-      AssertIsOnMainThread();
+-    }
+-
+-    bool
+-    WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+-    {
+-      MOZ_ASSERT(aWorkerPrivate);
+-      aWorkerPrivate->AssertIsOnWorkerThread();
+-
+-      Promise* promise = mPromiseProxy->WorkerPromise();
+-      MOZ_ASSERT(promise);
+-
+-      if (NS_FAILED(mRv)) {
+-        promise->MaybeReject(mRv);
+-      } else if (mValue) {
+-        RefPtr<ServiceWorkerWindowClient> windowClient =
+-          new ServiceWorkerWindowClient(promise->GetParentObject(), *mValue);
+-        promise->MaybeResolve(windowClient.get());
+-      } else {
+-        promise->MaybeResolveWithUndefined();
+-      }
+-      mPromiseProxy->CleanUp();
+-      return true;
+-    }
+-  };
+-
+-  RefPtr<PromiseWorkerProxy> mPromiseProxy;
+-  nsString mClientId;
+-public:
+-  GetRunnable(PromiseWorkerProxy* aPromiseProxy, const nsAString& aClientId)
+-    : mozilla::Runnable("GetRunnable")
+-    , mPromiseProxy(aPromiseProxy)
+-    , mClientId(aClientId)
+-  {
+-  }
+-
+-  NS_IMETHOD
+-  Run() override
+-  {
+-    AssertIsOnMainThread();
+-
+-    MutexAutoLock lock(mPromiseProxy->Lock());
+-    if (mPromiseProxy->CleanedUp()) {
+-      return NS_OK;
+-    }
+-
+-    WorkerPrivate* workerPrivate = mPromiseProxy->GetWorkerPrivate();
+-    MOZ_ASSERT(workerPrivate);
+-
+-    UniquePtr<ServiceWorkerClientInfo> result;
+-    ErrorResult rv;
+-
+-    RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+-    if (!swm) {
+-      rv = NS_ERROR_FAILURE;
+-    } else {
+-      result = swm->GetClient(workerPrivate->GetPrincipal(), mClientId, rv);
+-    }
+-
+-    RefPtr<ResolvePromiseWorkerRunnable> r =
+-      new ResolvePromiseWorkerRunnable(mPromiseProxy->GetWorkerPrivate(),
+-                                       mPromiseProxy, Move(result),
+-                                       rv.StealNSResult());
+-    rv.SuppressException();
+-
+-    r->Dispatch();
+-    return NS_OK;
+-  }
+-};
+-
+-class MatchAllRunnable final : public Runnable
+-{
+-  class ResolvePromiseWorkerRunnable final : public WorkerRunnable
+-  {
+-    RefPtr<PromiseWorkerProxy> mPromiseProxy;
+-    nsTArray<ServiceWorkerClientInfo> mValue;
+-
+-  public:
+-    ResolvePromiseWorkerRunnable(WorkerPrivate* aWorkerPrivate,
+-                                 PromiseWorkerProxy* aPromiseProxy,
+-                                 nsTArray<ServiceWorkerClientInfo>& aValue)
+-      : WorkerRunnable(aWorkerPrivate),
+-        mPromiseProxy(aPromiseProxy)
+-    {
+-      AssertIsOnMainThread();
+-      mValue.SwapElements(aValue);
+-    }
+-
+-    bool
+-    WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+-    {
+-      MOZ_ASSERT(aWorkerPrivate);
+-      aWorkerPrivate->AssertIsOnWorkerThread();
+-
+-      Promise* promise = mPromiseProxy->WorkerPromise();
+-      MOZ_ASSERT(promise);
+-
+-      nsTArray<RefPtr<ServiceWorkerClient>> ret;
+-      for (size_t i = 0; i < mValue.Length(); i++) {
+-        ret.AppendElement(RefPtr<ServiceWorkerClient>(
+-              new ServiceWorkerWindowClient(promise->GetParentObject(),
+-                                            mValue.ElementAt(i))));
+-      }
+-
+-      promise->MaybeResolve(ret);
+-      mPromiseProxy->CleanUp();
+-      return true;
+-    }
+-  };
+-
+-  RefPtr<PromiseWorkerProxy> mPromiseProxy;
+-  const nsCString mScope;
+-  const uint64_t mServiceWorkerID;
+-  const bool mIncludeUncontrolled;
+-public:
+-  MatchAllRunnable(PromiseWorkerProxy* aPromiseProxy,
+-                   const nsCString& aScope,
+-                   uint64_t aServiceWorkerID,
+-                   bool aIncludeUncontrolled)
+-    : mozilla::Runnable("MatchAllRunnable")
+-    , mPromiseProxy(aPromiseProxy)
+-    , mScope(aScope)
+-    , mServiceWorkerID(aServiceWorkerID)
+-    , mIncludeUncontrolled(aIncludeUncontrolled)
+-  {
+-    MOZ_ASSERT(mPromiseProxy);
+-  }
+-
+-  NS_IMETHOD
+-  Run() override
+-  {
+-    AssertIsOnMainThread();
+-
+-    MutexAutoLock lock(mPromiseProxy->Lock());
+-    if (mPromiseProxy->CleanedUp()) {
+-      return NS_OK;
+-    }
+-
+-    nsTArray<ServiceWorkerClientInfo> result;
+-    RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+-    if (swm) {
+-      swm->GetAllClients(mPromiseProxy->GetWorkerPrivate()->GetPrincipal(),
+-                         mScope, mServiceWorkerID, mIncludeUncontrolled,
+-                         result);
+-    }
+-    RefPtr<ResolvePromiseWorkerRunnable> r =
+-      new ResolvePromiseWorkerRunnable(mPromiseProxy->GetWorkerPrivate(),
+-                                       mPromiseProxy, result);
+-
+-    r->Dispatch();
+-    return NS_OK;
+-  }
+-};
+-
+-class ResolveClaimRunnable final : public WorkerRunnable
+-{
+-  RefPtr<PromiseWorkerProxy> mPromiseProxy;
+-  nsresult mResult;
+-
+-public:
+-  ResolveClaimRunnable(WorkerPrivate* aWorkerPrivate,
+-                       PromiseWorkerProxy* aPromiseProxy,
+-                       nsresult aResult)
+-    : WorkerRunnable(aWorkerPrivate)
+-    , mPromiseProxy(aPromiseProxy)
+-    , mResult(aResult)
+-  {
+-    AssertIsOnMainThread();
+-  }
+-
+-  bool
+-  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+-  {
+-    MOZ_ASSERT(aWorkerPrivate);
+-    aWorkerPrivate->AssertIsOnWorkerThread();
+-
+-    RefPtr<Promise> promise = mPromiseProxy->WorkerPromise();
+-    MOZ_ASSERT(promise);
+-
+-    if (NS_SUCCEEDED(mResult)) {
+-      promise->MaybeResolveWithUndefined();
+-    } else {
+-      promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+-    }
+-
+-    mPromiseProxy->CleanUp();
+-    return true;
+-  }
+-};
+-
+-class ClaimRunnable final : public Runnable
+-{
+-  RefPtr<PromiseWorkerProxy> mPromiseProxy;
+-  nsCString mScope;
+-  uint64_t mServiceWorkerID;
+-
+-public:
+-  ClaimRunnable(PromiseWorkerProxy* aPromiseProxy, const nsCString& aScope)
+-    : mozilla::Runnable("ClaimRunnable")
+-    , mPromiseProxy(aPromiseProxy)
+-    , mScope(aScope)
+-    // Safe to call GetWorkerPrivate() since we are being called on the worker
+-    // thread via script (so no clean up has occured yet).
+-    , mServiceWorkerID(aPromiseProxy->GetWorkerPrivate()->ServiceWorkerID())
+-  {
+-    MOZ_ASSERT(aPromiseProxy);
+-  }
+-
+-  NS_IMETHOD
+-  Run() override
+-  {
+-    MutexAutoLock lock(mPromiseProxy->Lock());
+-    if (mPromiseProxy->CleanedUp()) {
+-      return NS_OK;
+-    }
+-
+-    WorkerPrivate* workerPrivate = mPromiseProxy->GetWorkerPrivate();
+-    MOZ_ASSERT(workerPrivate);
+-
+-    nsresult rv = NS_OK;
+-    RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+-    if (!swm) {
+-      // browser shutdown
+-      rv = NS_ERROR_FAILURE;
+-    } else {
+-      rv = swm->ClaimClients(workerPrivate->GetPrincipal(), mScope,
+-                             mServiceWorkerID);
+-    }
+-
+-    RefPtr<ResolveClaimRunnable> r =
+-      new ResolveClaimRunnable(workerPrivate, mPromiseProxy, rv);
+-
+-    r->Dispatch();
+-    return NS_OK;
+-  }
+-};
+-
+-class ResolveOpenWindowRunnable final : public WorkerRunnable
+-{
+-public:
+-  ResolveOpenWindowRunnable(PromiseWorkerProxy* aPromiseProxy,
+-                            UniquePtr<ServiceWorkerClientInfo>&& aClientInfo,
+-                            const nsresult aStatus)
+-  : WorkerRunnable(aPromiseProxy->GetWorkerPrivate())
+-  , mPromiseProxy(aPromiseProxy)
+-  , mClientInfo(Move(aClientInfo))
+-  , mStatus(aStatus)
+-  {
+-    AssertIsOnMainThread();
+-    MOZ_ASSERT(aPromiseProxy);
+-  }
+-
+-  bool
+-  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+-  {
+-    MOZ_ASSERT(aWorkerPrivate);
+-    aWorkerPrivate->AssertIsOnWorkerThread();
+-
+-    Promise* promise = mPromiseProxy->WorkerPromise();
+-    if (NS_WARN_IF(NS_FAILED(mStatus))) {
+-      promise->MaybeReject(mStatus);
+-    } else if (mClientInfo) {
+-      RefPtr<ServiceWorkerWindowClient> client =
+-        new ServiceWorkerWindowClient(promise->GetParentObject(),
+-                                      *mClientInfo);
+-      promise->MaybeResolve(client);
+-    } else {
+-      promise->MaybeResolve(JS::NullHandleValue);
+-    }
+-
+-    mPromiseProxy->CleanUp();
+-    return true;
+-  }
+-
+-private:
+-  RefPtr<PromiseWorkerProxy> mPromiseProxy;
+-  UniquePtr<ServiceWorkerClientInfo> mClientInfo;
+-  const nsresult mStatus;
+-};
+-
+-class WebProgressListener final : public nsIWebProgressListener,
+-                                  public nsSupportsWeakReference
+-{
+-public:
+-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+-  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(WebProgressListener, nsIWebProgressListener)
+-
+-  WebProgressListener(PromiseWorkerProxy* aPromiseProxy,
+-                      ServiceWorkerPrivate* aServiceWorkerPrivate,
+-                      nsPIDOMWindowOuter* aWindow,
+-                      nsIURI* aBaseURI)
+-  : mPromiseProxy(aPromiseProxy)
+-  , mServiceWorkerPrivate(aServiceWorkerPrivate)
+-  , mWindow(aWindow)
+-  , mBaseURI(aBaseURI)
+-  {
+-    MOZ_ASSERT(aPromiseProxy);
+-    MOZ_ASSERT(aServiceWorkerPrivate);
+-    MOZ_ASSERT(aWindow);
+-    MOZ_ASSERT(aWindow->IsOuterWindow());
+-    MOZ_ASSERT(aBaseURI);
+-    AssertIsOnMainThread();
+-
+-    mServiceWorkerPrivate->StoreISupports(static_cast<nsIWebProgressListener*>(this));
+-  }
+-
+-  NS_IMETHOD
+-  OnStateChange(nsIWebProgress* aWebProgress,
+-                nsIRequest* aRequest,
+-                uint32_t aStateFlags, nsresult aStatus) override
+-  {
+-    if (!(aStateFlags & STATE_IS_DOCUMENT) ||
+-         !(aStateFlags & (STATE_STOP | STATE_TRANSFERRING))) {
+-      return NS_OK;
+-    }
+-
+-    // Our caller keeps a strong reference, so it is safe to remove the listener
+-    // from ServiceWorkerPrivate.
+-    mServiceWorkerPrivate->RemoveISupports(static_cast<nsIWebProgressListener*>(this));
+-    aWebProgress->RemoveProgressListener(this);
+-
+-    MutexAutoLock lock(mPromiseProxy->Lock());
+-    if (mPromiseProxy->CleanedUp()) {
+-      return NS_OK;
+-    }
+-
+-    nsCOMPtr<nsIDocument> doc = mWindow->GetExtantDoc();
+-    UniquePtr<ServiceWorkerClientInfo> clientInfo;
+-    if (doc) {
+-      // Check same origin.
+-      nsCOMPtr<nsIScriptSecurityManager> securityManager =
+-        nsContentUtils::GetSecurityManager();
+-      nsresult rv = securityManager->CheckSameOriginURI(doc->GetOriginalURI(),
+-                                                        mBaseURI, false);
+-      if (NS_SUCCEEDED(rv)) {
+-        clientInfo.reset(new ServiceWorkerClientInfo(doc));
+-      }
+-    }
+-
+-    RefPtr<ResolveOpenWindowRunnable> r =
+-      new ResolveOpenWindowRunnable(mPromiseProxy,
+-                                    Move(clientInfo),
+-                                    NS_OK);
+-    r->Dispatch();
+-
+-    return NS_OK;
+-  }
+-
+-  NS_IMETHOD
+-  OnProgressChange(nsIWebProgress* aWebProgress,
+-                   nsIRequest* aRequest,
+-                   int32_t aCurSelfProgress,
+-                   int32_t aMaxSelfProgress,
+-                   int32_t aCurTotalProgress,
+-                   int32_t aMaxTotalProgress) override
+-  {
+-    MOZ_ASSERT(false, "Unexpected notification.");
+-    return NS_OK;
+-  }
+-
+-  NS_IMETHOD
+-  OnLocationChange(nsIWebProgress* aWebProgress,
+-                   nsIRequest* aRequest,
+-                   nsIURI* aLocation,
+-                   uint32_t aFlags) override
+-  {
+-    MOZ_ASSERT(false, "Unexpected notification.");
+-    return NS_OK;
+-  }
+-
+-  NS_IMETHOD
+-  OnStatusChange(nsIWebProgress* aWebProgress,
+-                 nsIRequest* aRequest,
+-                 nsresult aStatus, const char16_t* aMessage) override
+-  {
+-    MOZ_ASSERT(false, "Unexpected notification.");
+-    return NS_OK;
+-  }
+-
+-  NS_IMETHOD
+-  OnSecurityChange(nsIWebProgress* aWebProgress,
+-                   nsIRequest* aRequest,
+-                   uint32_t aState) override
+-  {
+-    MOZ_ASSERT(false, "Unexpected notification.");
+-    return NS_OK;
+-  }
+-
+-private:
+-  ~WebProgressListener()
+-  { }
+-
+-  RefPtr<PromiseWorkerProxy> mPromiseProxy;
+-  RefPtr<ServiceWorkerPrivate> mServiceWorkerPrivate;
+-  nsCOMPtr<nsPIDOMWindowOuter> mWindow;
+-  nsCOMPtr<nsIURI> mBaseURI;
+-};
+-
+-NS_IMPL_CYCLE_COLLECTING_ADDREF(WebProgressListener)
+-NS_IMPL_CYCLE_COLLECTING_RELEASE(WebProgressListener)
+-NS_IMPL_CYCLE_COLLECTION(WebProgressListener, mPromiseProxy,
+-                         mServiceWorkerPrivate, mWindow)
+-
+-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebProgressListener)
+-  NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener)
+-  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+-NS_INTERFACE_MAP_END
+-
+-class OpenWindowRunnable final : public Runnable
+-                               , public nsIObserver
+-                               , public nsSupportsWeakReference
+-{
+-  RefPtr<PromiseWorkerProxy> mPromiseProxy;
+-  nsString mUrl;
+-  nsString mScope;
+-
+-public:
+-  NS_DECL_ISUPPORTS_INHERITED
+-  // Note: |OpenWindowRunnable| cannot be cycle collected because it inherits
+-  // thread safe reference counting from |mozilla::Runnable|. On Fennec, we
+-  // might use |ServiceWorkerPrivate::StoreISupports| to keep this object alive
+-  // while waiting for an event from the observer service. As such, to avoid
+-  // creating a cycle that will leak, |OpenWindowRunnable| must not hold a strong
+-  // reference to |ServiceWorkerPrivate|.
+-
+-  OpenWindowRunnable(PromiseWorkerProxy* aPromiseProxy,
+-                     const nsAString& aUrl,
+-                     const nsAString& aScope)
+-    : mozilla::Runnable("OpenWindowRunnable")
+-    , mPromiseProxy(aPromiseProxy)
+-    , mUrl(aUrl)
+-    , mScope(aScope)
+-  {
+-    MOZ_ASSERT(aPromiseProxy);
+-    MOZ_ASSERT(aPromiseProxy->GetWorkerPrivate());
+-    aPromiseProxy->GetWorkerPrivate()->AssertIsOnWorkerThread();
+-  }
+-
+-  NS_IMETHOD
+-  Observe(nsISupports* aSubject, const char* aTopic, const char16_t* /* aData */) override
+-  {
+-    AssertIsOnMainThread();
+-
+-    nsCString topic(aTopic);
+-    if (!topic.EqualsLiteral("BrowserChrome:Ready")) {
+-      MOZ_ASSERT(false, "Unexpected topic.");
+-      return NS_ERROR_FAILURE;
+-    }
+-
+-    nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+-    NS_ENSURE_STATE(os);
+-    os->RemoveObserver(this, "BrowserChrome:Ready");
+-
+-    RefPtr<ServiceWorkerPrivate> swp = GetServiceWorkerPrivate();
+-    NS_ENSURE_STATE(swp);
+-
+-    MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
+-    swp->RemoveISupports(static_cast<nsIObserver*>(this));
+-
+-    return NS_OK;
+-  }
+-
+-  NS_IMETHOD
+-  Run() override
+-  {
+-    AssertIsOnMainThread();
+-
+-    MutexAutoLock lock(mPromiseProxy->Lock());
+-    if (mPromiseProxy->CleanedUp()) {
+-      return NS_OK;
+-    }
+-
+-#ifdef MOZ_WIDGET_ANDROID
+-    // This fires an intent that will start launching Fennec and foreground it,
+-    // if necessary.
+-    if (jni::IsFennec()) {
+-      java::GeckoApp::LaunchOrBringToFront();
+-    }
+-#endif
+-
+-    nsCOMPtr<nsPIDOMWindowOuter> window;
+-    nsresult rv = OpenWindow(getter_AddRefs(window));
+-    if (NS_SUCCEEDED(rv)) {
+-      MOZ_ASSERT(window);
+-
+-      rv = nsContentUtils::DispatchFocusChromeEvent(window);
+-      if (NS_WARN_IF(NS_FAILED(rv))) {
+-        return rv;
+-      }
+-
+-      WorkerPrivate* workerPrivate = mPromiseProxy->GetWorkerPrivate();
+-      MOZ_ASSERT(workerPrivate);
+-
+-      WorkerPrivate::LocationInfo& info = workerPrivate->GetLocationInfo();
+-      nsCOMPtr<nsIURI> baseURI;
+-      nsresult rv = NS_NewURI(getter_AddRefs(baseURI), info.mHref);
+-      if (NS_WARN_IF(NS_FAILED(rv))) {
+-        return NS_ERROR_FAILURE;
+-      }
+-
+-      nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
+-      nsCOMPtr<nsIWebProgress> webProgress = do_GetInterface(docShell);
+-
+-      if (!webProgress) {
+-        return NS_ERROR_FAILURE;
+-      }
+-
+-      RefPtr<ServiceWorkerPrivate> swp = GetServiceWorkerPrivate();
+-      NS_ENSURE_STATE(swp);
+-
+-      nsCOMPtr<nsIWebProgressListener> listener =
+-        new WebProgressListener(mPromiseProxy, swp, window, baseURI);
+-
+-      rv = webProgress->AddProgressListener(listener,
+-                                            nsIWebProgress::NOTIFY_STATE_DOCUMENT);
+-      MOZ_ASSERT(NS_SUCCEEDED(rv));
+-      return NS_OK;
+-    }
+-#ifdef MOZ_WIDGET_ANDROID
+-    else if (rv == NS_ERROR_NOT_AVAILABLE && jni::IsFennec()) {
+-      // We couldn't get a browser window, so Fennec must not be running.
+-      // Send an Intent to launch Fennec and wait for "BrowserChrome:Ready"
+-      // to try opening a window again.
+-      RefPtr<ServiceWorkerPrivate> swp = GetServiceWorkerPrivate();
+-      NS_ENSURE_STATE(swp);
+-
+-      nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+-      NS_ENSURE_STATE(os);
+-
+-      rv = os->AddObserver(this, "BrowserChrome:Ready", /* weakRef */ true);
+-      NS_ENSURE_SUCCESS(rv, rv);
+-
+-      swp->StoreISupports(static_cast<nsIObserver*>(this));
+-
+-      return NS_OK;
+-    }
+-#endif
+-
+-    RefPtr<ResolveOpenWindowRunnable> resolveRunnable =
+-      new ResolveOpenWindowRunnable(mPromiseProxy, nullptr, rv);
+-
+-    Unused << NS_WARN_IF(!resolveRunnable->Dispatch());
+-
+-    return NS_OK;
+-  }
+-
+-private:
+-  ~OpenWindowRunnable()
+-  { }
+-
+-  ServiceWorkerPrivate*
+-  GetServiceWorkerPrivate() const
+-  {
+-    AssertIsOnMainThread();
+-
+-    RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+-    if (!swm) {
+-      // browser shutdown
+-      return nullptr;
+-    }
+-
+-    WorkerPrivate* workerPrivate = mPromiseProxy->GetWorkerPrivate();
+-    MOZ_ASSERT(workerPrivate);
+-
+-    nsCOMPtr<nsIPrincipal> principal = workerPrivate->GetPrincipal();
+-    MOZ_DIAGNOSTIC_ASSERT(principal);
+-
+-    RefPtr<ServiceWorkerRegistrationInfo> registration =
+-      swm->GetRegistration(principal, NS_ConvertUTF16toUTF8(mScope));
+-    if (NS_WARN_IF(!registration)) {
+-      return nullptr;
+-    }
+-
+-    RefPtr<ServiceWorkerInfo> serviceWorkerInfo =
+-      registration->GetServiceWorkerInfoById(workerPrivate->ServiceWorkerID());
+-    if (NS_WARN_IF(!serviceWorkerInfo)) {
+-      return nullptr;
+-    }
+-
+-    return serviceWorkerInfo->WorkerPrivate();
+-  }
+-
+-  nsresult
+-  OpenWindow(nsPIDOMWindowOuter** aWindow)
+-  {
+-    MOZ_DIAGNOSTIC_ASSERT(aWindow);
+-    WorkerPrivate* workerPrivate = mPromiseProxy->GetWorkerPrivate();
+-
+-    nsCOMPtr<nsIPrincipal> triggeringPrincipal = workerPrivate->GetPrincipal();
+-    MOZ_DIAGNOSTIC_ASSERT(triggeringPrincipal);
+-
+-    // [[1. Let url be the result of parsing url with entry settings object's API
+-    //   base URL.]]
+-    nsCOMPtr<nsIURI> uri;
+-    WorkerPrivate::LocationInfo& info = workerPrivate->GetLocationInfo();
+-
+-    nsCOMPtr<nsIURI> baseURI;
+-    nsresult rv = NS_NewURI(getter_AddRefs(baseURI), info.mHref);
+-    if (NS_WARN_IF(NS_FAILED(rv))) {
+-      return NS_ERROR_TYPE_ERR;
+-    }
+-
+-    rv = NS_NewURI(getter_AddRefs(uri), mUrl, nullptr, baseURI);
+-    if (NS_WARN_IF(NS_FAILED(rv))) {
+-      return NS_ERROR_TYPE_ERR;
+-    }
+-
+-    // [[6.1 Open Window]]
+-    nsCOMPtr<nsIWindowMediator> wm = do_GetService(NS_WINDOWMEDIATOR_CONTRACTID,
+-                                                   &rv);
+-    if (NS_WARN_IF(NS_FAILED(rv))) {
+-      return rv;
+-    }
+-
+-    if (XRE_IsContentProcess()) {
+-
+-      // Let's create a sandbox in order to have a valid JSContext and correctly
+-      // propagate the SubjectPrincipal.
+-      AutoJSAPI jsapi;
+-      jsapi.Init();
+-
+-      JSContext* cx = jsapi.cx();
+-
+-      nsIXPConnect* xpc = nsContentUtils::XPConnect();
+-      MOZ_ASSERT(xpc, "This should never be null!");
+-
+-      JS::Rooted<JSObject*> sandbox(cx);
+-      rv = xpc->CreateSandbox(cx, triggeringPrincipal, sandbox.address());
+-      if (NS_WARN_IF(NS_FAILED(rv))) {
+-        return rv;
+-      }
+-
+-      JSAutoCompartment ac(cx, sandbox);
+-
+-      // ContentProcess
+-      nsCOMPtr<nsIWindowWatcher> wwatch =
+-        do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+-      if (NS_WARN_IF(NS_FAILED(rv))) {
+-        return rv;
+-      }
+-      nsCOMPtr<nsPIWindowWatcher> pwwatch(do_QueryInterface(wwatch));
+-      NS_ENSURE_STATE(pwwatch);
+-
+-      nsCString spec;
+-      rv = uri->GetSpec(spec);
+-      if (NS_WARN_IF(NS_FAILED(rv))) {
+-        return rv;
+-      }
+-
+-      nsCOMPtr<mozIDOMWindowProxy> newWindow;
+-      rv = pwwatch->OpenWindow2(nullptr,
+-                                spec.get(),
+-                                nullptr,
+-                                nullptr,
+-                                false, false, true, nullptr,
+-                                // Not a spammy popup; we got permission, we swear!
+-                                /* aIsPopupSpam = */ false,
+-                                // Don't force noopener.  We're not passing in an
+-                                // opener anyway, and we _do_ want the returned
+-                                // window.
+-                                /* aForceNoOpener = */ false,
+-                                /* aLoadInfp = */ nullptr,
+-                                getter_AddRefs(newWindow));
+-      if (NS_WARN_IF(NS_FAILED(rv))) {
+-        return rv;
+-      }
+-      nsCOMPtr<nsPIDOMWindowOuter> pwindow = nsPIDOMWindowOuter::From(newWindow);
+-      pwindow.forget(aWindow);
+-      MOZ_DIAGNOSTIC_ASSERT(*aWindow);
+-      return NS_OK;
+-    }
+-
+-    // Find the most recent browser window and open a new tab in it.
+-    nsCOMPtr<nsPIDOMWindowOuter> browserWindow =
+-      nsContentUtils::GetMostRecentNonPBWindow();
+-    if (!browserWindow) {
+-      // It is possible to be running without a browser window on Mac OS, so
+-      // we need to open a new chrome window.
+-      // TODO(catalinb): open new chrome window. Bug 1218080
+-      return NS_ERROR_NOT_AVAILABLE;
+-    }
+-
+-    nsCOMPtr<nsIDOMChromeWindow> chromeWin = do_QueryInterface(browserWindow);
+-    if (NS_WARN_IF(!chromeWin)) {
+-      return NS_ERROR_FAILURE;
+-    }
+-
+-    nsCOMPtr<nsIBrowserDOMWindow> bwin;
+-    chromeWin->GetBrowserDOMWindow(getter_AddRefs(bwin));
+-
+-    if (NS_WARN_IF(!bwin)) {
+-      return NS_ERROR_FAILURE;
+-    }
+-
+-    nsCOMPtr<mozIDOMWindowProxy> win;
+-    rv = bwin->OpenURI(uri, nullptr,
+-                       nsIBrowserDOMWindow::OPEN_DEFAULTWINDOW,
+-                       nsIBrowserDOMWindow::OPEN_NEW,
+-                       triggeringPrincipal,
+-                       getter_AddRefs(win));
+-    if (NS_WARN_IF(NS_FAILED(rv))) {
+-      return rv;
+-    }
+-    NS_ENSURE_STATE(win);
+-
+-    nsCOMPtr<nsPIDOMWindowOuter> pWin = nsPIDOMWindowOuter::From(win);
+-    pWin.forget(aWindow);
+-    MOZ_DIAGNOSTIC_ASSERT(*aWindow);
+-
+-    return NS_OK;
+-  }
+-};
+-
+-NS_IMPL_ADDREF_INHERITED(OpenWindowRunnable, Runnable)                                    \
+-NS_IMPL_RELEASE_INHERITED(OpenWindowRunnable, Runnable)
+-
+-NS_INTERFACE_MAP_BEGIN(OpenWindowRunnable)
+-NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+-NS_INTERFACE_MAP_ENTRY(nsIObserver)
+-NS_INTERFACE_MAP_ENTRY(nsIRunnable)
+-NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
+-NS_INTERFACE_MAP_END
+-
+-} // namespace
+-
+-already_AddRefed<Promise>
+-ServiceWorkerClients::Get(const nsAString& aClientId, ErrorResult& aRv)
+-{
+-  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+-  MOZ_ASSERT(workerPrivate);
+-  workerPrivate->AssertIsOnWorkerThread();
+-
+-  RefPtr<Promise> promise = Promise::Create(mWorkerScope, aRv);
+-  if (NS_WARN_IF(aRv.Failed())) {
+-    return nullptr;
+-  }
+-
+-  RefPtr<PromiseWorkerProxy> promiseProxy =
+-    PromiseWorkerProxy::Create(workerPrivate, promise);
+-  if (!promiseProxy) {
+-    promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+-    return promise.forget();
+-  }
+-
+-  RefPtr<GetRunnable> r =
+-    new GetRunnable(promiseProxy, aClientId);
+-  MOZ_ALWAYS_SUCCEEDS(workerPrivate->DispatchToMainThread(r.forget()));
+-  return promise.forget();
+-}
+-
+-already_AddRefed<Promise>
+-ServiceWorkerClients::MatchAll(const ClientQueryOptions& aOptions,
+-                               ErrorResult& aRv)
+-{
+-  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+-  MOZ_ASSERT(workerPrivate);
+-  workerPrivate->AssertIsOnWorkerThread();
+-
+-  nsString scope;
+-  mWorkerScope->GetScope(scope);
+-
+-  if (aOptions.mType != ClientType::Window) {
+-    aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+-    return nullptr;
+-  }
+-
+-  RefPtr<Promise> promise = Promise::Create(mWorkerScope, aRv);
+-  if (NS_WARN_IF(aRv.Failed())) {
+-    return nullptr;
+-  }
+-
+-  RefPtr<PromiseWorkerProxy> promiseProxy =
+-    PromiseWorkerProxy::Create(workerPrivate, promise);
+-  if (!promiseProxy) {
+-    promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+-    return promise.forget();
+-  }
+-
+-  RefPtr<MatchAllRunnable> r =
+-    new MatchAllRunnable(promiseProxy,
+-                         NS_ConvertUTF16toUTF8(scope),
+-                         workerPrivate->ServiceWorkerID(),
+-                         aOptions.mIncludeUncontrolled);
+-  MOZ_ALWAYS_SUCCEEDS(workerPrivate->DispatchToMainThread(r.forget()));
+-  return promise.forget();
+-}
+-
+-already_AddRefed<Promise>
+-ServiceWorkerClients::OpenWindow(const nsAString& aUrl,
+-                                 ErrorResult& aRv)
+-{
+-  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+-  MOZ_ASSERT(workerPrivate);
+-
+-  RefPtr<Promise> promise = Promise::Create(mWorkerScope, aRv);
+-  if (NS_WARN_IF(aRv.Failed())) {
+-    return nullptr;
+-  }
+-
+-  if (aUrl.EqualsLiteral("about:blank")) {
+-    promise->MaybeReject(NS_ERROR_TYPE_ERR);
+-    return promise.forget();
+-  }
+-
+-  // [[4. If this algorithm is not allowed to show a popup ..]]
+-  // In Gecko the service worker is allowed to show a popup only if the user
+-  // just clicked on a notification.
+-  if (!workerPrivate->GlobalScope()->WindowInteractionAllowed()) {
+-    promise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+-    return promise.forget();
+-  }
+-
+-  RefPtr<PromiseWorkerProxy> promiseProxy =
+-    PromiseWorkerProxy::Create(workerPrivate, promise);
+-
+-  if (!promiseProxy) {
+-    return nullptr;
+-  }
+-
+-  nsString scope;
+-  mWorkerScope->GetScope(scope);
+-
+-  RefPtr<OpenWindowRunnable> r = new OpenWindowRunnable(promiseProxy,
+-                                                        aUrl, scope);
+-  MOZ_ALWAYS_SUCCEEDS(workerPrivate->DispatchToMainThread(r.forget()));
+-
+-  return promise.forget();
+-}
+-
+-already_AddRefed<Promise>
+-ServiceWorkerClients::Claim(ErrorResult& aRv)
+-{
+-  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+-  MOZ_ASSERT(workerPrivate);
+-
+-  RefPtr<Promise> promise = Promise::Create(mWorkerScope, aRv);
+-  if (NS_WARN_IF(aRv.Failed())) {
+-    return nullptr;
+-  }
+-
+-  RefPtr<PromiseWorkerProxy> promiseProxy =
+-    PromiseWorkerProxy::Create(workerPrivate, promise);
+-  if (!promiseProxy) {
+-    promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+-    return promise.forget();
+-  }
+-
+-  nsString scope;
+-  mWorkerScope->GetScope(scope);
+-
+-  RefPtr<ClaimRunnable> runnable =
+-    new ClaimRunnable(promiseProxy, NS_ConvertUTF16toUTF8(scope));
+-
+-  MOZ_ALWAYS_SUCCEEDS(workerPrivate->DispatchToMainThread(runnable.forget()));
+-  return promise.forget();
+-}
+diff --git a/dom/workers/ServiceWorkerClients.h b/dom/workers/ServiceWorkerClients.h
+deleted file mode 100644
+--- a/dom/workers/ServiceWorkerClients.h
++++ /dev/null
+@@ -1,64 +0,0 @@
+-/* -*- 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/.
+- */
+-
+-#ifndef mozilla_dom_workers_serviceworkerclients_h
+-#define mozilla_dom_workers_serviceworkerclients_h
+-
+-#include "nsWrapperCache.h"
+-
+-#include "mozilla/dom/WorkerScope.h"
+-#include "mozilla/dom/BindingDeclarations.h"
+-#include "mozilla/dom/ClientsBinding.h"
+-#include "mozilla/ErrorResult.h"
+-
+-namespace mozilla {
+-namespace dom {
+-namespace workers {
+-
+-class ServiceWorkerClients final : public nsISupports,
+-                                   public nsWrapperCache
+-{
+-public:
+-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ServiceWorkerClients)
+-
+-  explicit ServiceWorkerClients(ServiceWorkerGlobalScope* aWorkerScope);
+-
+-  already_AddRefed<Promise>
+-  Get(const nsAString& aClientId, ErrorResult& aRv);
+-
+-  already_AddRefed<Promise>
+-  MatchAll(const ClientQueryOptions& aOptions, ErrorResult& aRv);
+-
+-  already_AddRefed<Promise>
+-  OpenWindow(const nsAString& aUrl, ErrorResult& aRv);
+-
+-  already_AddRefed<Promise>
+-  Claim(ErrorResult& aRv);
+-
+-  JSObject*
+-  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+-
+-  ServiceWorkerGlobalScope*
+-  GetParentObject() const
+-  {
+-    return mWorkerScope;
+-  }
+-
+-private:
+-  ~ServiceWorkerClients()
+-  {
+-  }
+-
+-  RefPtr<ServiceWorkerGlobalScope> mWorkerScope;
+-};
+-
+-} // namespace workers
+-} // namespace dom
+-} // namespace mozilla
+-
+-#endif // mozilla_dom_workers_serviceworkerclients_h
+diff --git a/dom/workers/ServiceWorkerEvents.cpp b/dom/workers/ServiceWorkerEvents.cpp
+--- a/dom/workers/ServiceWorkerEvents.cpp
++++ b/dom/workers/ServiceWorkerEvents.cpp
+@@ -19,23 +19,23 @@
+ #include "nsContentUtils.h"
+ #include "nsComponentManagerUtils.h"
+ #include "nsServiceManagerUtils.h"
+ #include "nsStreamUtils.h"
+ #include "nsNetCID.h"
+ #include "nsNetUtil.h"
+ #include "nsSerializationHelper.h"
+ #include "nsQueryObject.h"
+-#include "ServiceWorkerClient.h"
+ #include "ServiceWorkerManager.h"
+ 
+ #include "mozilla/ErrorResult.h"
+ #include "mozilla/LoadInfo.h"
+ #include "mozilla/Preferences.h"
+ #include "mozilla/dom/BodyUtil.h"
++#include "mozilla/dom/Client.h"
+ #include "mozilla/dom/FetchEventBinding.h"
+ #include "mozilla/dom/MessagePort.h"
+ #include "mozilla/dom/PromiseNativeHandler.h"
+ #include "mozilla/dom/PushEventBinding.h"
+ #include "mozilla/dom/PushMessageDataBinding.h"
+ #include "mozilla/dom/PushUtil.h"
+ #include "mozilla/dom/Request.h"
+ #include "mozilla/dom/TypedArray.h"
+diff --git a/dom/workers/ServiceWorkerEvents.h b/dom/workers/ServiceWorkerEvents.h
+--- a/dom/workers/ServiceWorkerEvents.h
++++ b/dom/workers/ServiceWorkerEvents.h
+@@ -20,16 +20,17 @@
+ #include "nsProxyRelease.h"
+ #include "nsContentUtils.h"
+ 
+ class nsIInterceptedChannel;
+ 
+ namespace mozilla {
+ namespace dom {
+ class Blob;
++class Client;
+ class MessagePort;
+ class Request;
+ class ResponseOrPromise;
+ 
+ struct PushEventInit;
+ } // namespace dom
+ } // namespace mozilla
+ 
+@@ -264,17 +265,17 @@ public:
+   }
+ };
+ 
+ class ExtendableMessageEvent final : public ExtendableEvent
+ {
+   JS::Heap<JS::Value> mData;
+   nsString mOrigin;
+   nsString mLastEventId;
+-  RefPtr<ServiceWorkerClient> mClient;
++  RefPtr<Client> mClient;
+   RefPtr<ServiceWorker> mServiceWorker;
+   RefPtr<MessagePort> mMessagePort;
+   nsTArray<RefPtr<MessagePort>> mPorts;
+ 
+ protected:
+   explicit ExtendableMessageEvent(EventTarget* aOwner);
+   ~ExtendableMessageEvent();
+ 
+diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp
+--- a/dom/workers/ServiceWorkerManager.cpp
++++ b/dom/workers/ServiceWorkerManager.cpp
+@@ -63,17 +63,16 @@
+ #include "nsGlobalWindow.h"
+ #include "nsNetUtil.h"
+ #include "nsProxyRelease.h"
+ #include "nsQueryObject.h"
+ #include "nsTArray.h"
+ 
+ #include "RuntimeService.h"
+ #include "ServiceWorker.h"
+-#include "ServiceWorkerClient.h"
+ #include "ServiceWorkerContainer.h"
+ #include "ServiceWorkerInfo.h"
+ #include "ServiceWorkerJobQueue.h"
+ #include "ServiceWorkerManagerChild.h"
+ #include "ServiceWorkerPrivate.h"
+ #include "ServiceWorkerRegisterJob.h"
+ #include "ServiceWorkerRegistrar.h"
+ #include "ServiceWorkerRegistration.h"
+@@ -3191,178 +3190,16 @@ FireControllerChangeOnDocument(nsIDocume
+   container->ControllerChanged(result);
+   if (result.Failed()) {
+     NS_WARNING("Failed to dispatch controllerchange event");
+   }
+ }
+ 
+ } // anonymous namespace
+ 
+-UniquePtr<ServiceWorkerClientInfo>
+-ServiceWorkerManager::GetClient(nsIPrincipal* aPrincipal,
+-                                const nsAString& aClientId,
+-                                ErrorResult& aRv)
+-{
+-  AssertIsOnMainThread();
+-  UniquePtr<ServiceWorkerClientInfo> clientInfo;
+-  nsCOMPtr<nsISupportsInterfacePointer> ifptr =
+-    do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID);
+-  if (NS_WARN_IF(!ifptr)) {
+-    return clientInfo;
+-  }
+-  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+-  if (NS_WARN_IF(!obs)) {
+-    return clientInfo;
+-  }
+-
+-  nsresult rv = obs->NotifyObservers(ifptr, "service-worker-get-client",
+-                                     PromiseFlatString(aClientId).get());
+-  if (NS_WARN_IF(NS_FAILED(rv))) {
+-    return clientInfo;
+-  }
+-
+-  nsCOMPtr<nsISupports> ptr;
+-  ifptr->GetData(getter_AddRefs(ptr));
+-  nsCOMPtr<nsIDocument> doc = do_QueryInterface(ptr);
+-  if (NS_WARN_IF(!doc || !doc->GetInnerWindow())) {
+-    return clientInfo;
+-  }
+-
+-  bool equals = false;
+-  aPrincipal->Equals(doc->NodePrincipal(), &equals);
+-  if (!equals) {
+-    return clientInfo;
+-  }
+-
+-  if (!IsFromAuthenticatedOrigin(doc)) {
+-    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+-    return clientInfo;
+-  }
+-
+-  // Don't let service worker see 3rd party iframes that are denied storage
+-  // access.  We don't want these to communicate.
+-  auto storageAccess =
+-    nsContentUtils::StorageAllowedForWindow(doc->GetInnerWindow());
+-  if (storageAccess != nsContentUtils::StorageAccess::eAllow) {
+-    nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
+-                                    NS_LITERAL_CSTRING("Service Workers"), doc,
+-                                    nsContentUtils::eDOM_PROPERTIES,
+-                                    "ServiceWorkerGetClientStorageError");
+-    return clientInfo;
+-  }
+-
+-  clientInfo.reset(new ServiceWorkerClientInfo(doc));
+-  return clientInfo;
+-}
+-
+-void
+-ServiceWorkerManager::GetAllClients(nsIPrincipal* aPrincipal,
+-                                    const nsCString& aScope,
+-                                    uint64_t aServiceWorkerID,
+-                                    bool aIncludeUncontrolled,
+-                                    nsTArray<ServiceWorkerClientInfo>& aDocuments)
+-{
+-  AssertIsOnMainThread();
+-  MOZ_ASSERT(aPrincipal);
+-
+-  RefPtr<ServiceWorkerRegistrationInfo> registration =
+-    GetRegistration(aPrincipal, aScope);
+-
+-  if (!registration) {
+-    // The registration was removed, leave the array empty.
+-    return;
+-  }
+-
+-  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+-  if (NS_WARN_IF(!obs)) {
+-    return;
+-  }
+-
+-  nsCOMPtr<nsISimpleEnumerator> enumerator;
+-  nsresult rv = obs->EnumerateObservers("service-worker-get-client",
+-                                        getter_AddRefs(enumerator));
+-  if (NS_WARN_IF(NS_FAILED(rv))) {
+-    return;
+-  }
+-
+-  // Get a list of Client documents out of the observer service
+-  AutoTArray<nsCOMPtr<nsIDocument>, 32> docList;
+-  bool loop = true;
+-  while (NS_SUCCEEDED(enumerator->HasMoreElements(&loop)) && loop) {
+-    nsCOMPtr<nsISupports> ptr;
+-    rv = enumerator->GetNext(getter_AddRefs(ptr));
+-    if (NS_WARN_IF(NS_FAILED(rv))) {
+-      continue;
+-    }
+-
+-    nsCOMPtr<nsIDocument> doc = do_QueryInterface(ptr);
+-    if (!doc || !doc->GetWindow() || !doc->GetInnerWindow()) {
+-      continue;
+-    }
+-
+-    bool equals = false;
+-    Unused << aPrincipal->Equals(doc->NodePrincipal(), &equals);
+-    if (!equals) {
+-      continue;
+-    }
+-
+-    // Treat http windows with devtools opened as secure if the correct devtools
+-    // setting is enabled.
+-    if (!doc->GetWindow()->GetServiceWorkersTestingEnabled() &&
+-        !Preferences::GetBool("dom.serviceWorkers.testing.enabled") &&
+-        !IsFromAuthenticatedOrigin(doc)) {
+-      continue;
+-    }
+-
+-    // Don't let service worker find 3rd party iframes that are denied storage
+-    // access.  We don't want these to communicate.
+-    auto storageAccess =
+-      nsContentUtils::StorageAllowedForWindow(doc->GetInnerWindow());
+-    if (storageAccess != nsContentUtils::StorageAccess::eAllow) {
+-      nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
+-                                      NS_LITERAL_CSTRING("Service Workers"),
+-                                      doc, nsContentUtils::eDOM_PROPERTIES,
+-                                      "ServiceWorkerGetClientStorageError");
+-      continue;
+-    }
+-
+-    // If we are only returning controlled Clients then skip any documents
+-    // that are for different registrations.  We also skip service workers
+-    // that don't match the ID of our calling service worker.  We should
+-    // only return Clients controlled by that precise service worker.
+-    if (!aIncludeUncontrolled) {
+-      ServiceWorkerRegistrationInfo* reg = mControlledDocuments.GetWeak(doc);
+-      if (!reg || reg->mScope != aScope || !reg->GetActive() ||
+-          reg->GetActive()->ID() != aServiceWorkerID) {
+-        continue;
+-      }
+-    }
+-
+-    if (!aIncludeUncontrolled && !mControlledDocuments.Contains(doc)) {
+-      continue;
+-    }
+-
+-    docList.AppendElement(doc.forget());
+-  }
+-
+-  // The observer service gives us the list in reverse creation order.
+-  // We need to maintain creation order, so reverse the list before
+-  // processing.
+-  docList.Reverse();
+-
+-  // Finally convert to the list of ServiceWorkerClientInfo objects.
+-  uint32_t ordinal = 0;
+-  for (uint32_t i = 0; i < docList.Length(); ++i) {
+-    aDocuments.AppendElement(ServiceWorkerClientInfo(docList[i], ordinal));
+-    ordinal += 1;
+-  }
+-
+-  aDocuments.Sort();
+-}
+-
+ void
+ ServiceWorkerManager::MaybeClaimClient(nsIDocument* aDocument,
+                                        ServiceWorkerRegistrationInfo* aWorkerRegistration)
+ {
+   MOZ_ASSERT(aWorkerRegistration);
+   MOZ_ASSERT(aWorkerRegistration->GetActive());
+ 
+   // Same origin check
+@@ -3386,56 +3223,16 @@ ServiceWorkerManager::MaybeClaimClient(n
+   if (controllingRegistration) {
+     StopControllingADocument(controllingRegistration);
+   }
+ 
+   StartControllingADocument(aWorkerRegistration, aDocument, NS_LITERAL_STRING(""));
+   FireControllerChangeOnDocument(aDocument);
+ }
+ 
+-nsresult
+-ServiceWorkerManager::ClaimClients(nsIPrincipal* aPrincipal,
+-                                   const nsCString& aScope, uint64_t aId)
+-{
+-  RefPtr<ServiceWorkerRegistrationInfo> registration =
+-    GetRegistration(aPrincipal, aScope);
+-
+-  if (!registration || !registration->GetActive() ||
+-      !(registration->GetActive()->ID() == aId)) {
+-    // The worker is not active.
+-    return NS_ERROR_DOM_INVALID_STATE_ERR;
+-  }
+-
+-  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+-  if (NS_WARN_IF(!obs)) {
+-    return NS_ERROR_FAILURE;
+-  }
+-
+-  nsCOMPtr<nsISimpleEnumerator> enumerator;
+-  nsresult rv = obs->EnumerateObservers("service-worker-get-client",
+-                                        getter_AddRefs(enumerator));
+-  if (NS_WARN_IF(NS_FAILED(rv))) {
+-    return rv;
+-  }
+-
+-  bool loop = true;
+-  while (NS_SUCCEEDED(enumerator->HasMoreElements(&loop)) && loop) {
+-    nsCOMPtr<nsISupports> ptr;
+-    rv = enumerator->GetNext(getter_AddRefs(ptr));
+-    if (NS_WARN_IF(NS_FAILED(rv))) {
+-      continue;
+-    }
+-
+-    nsCOMPtr<nsIDocument> doc = do_QueryInterface(ptr);
+-    MaybeClaimClient(doc, registration);
+-  }
+-
+-  return NS_OK;
+-}
+-
+ void
+ ServiceWorkerManager::SetSkipWaitingFlag(nsIPrincipal* aPrincipal,
+                                          const nsCString& aScope,
+                                          uint64_t aServiceWorkerID)
+ {
+   RefPtr<ServiceWorkerRegistrationInfo> registration =
+     GetRegistration(aPrincipal, aScope);
+   if (NS_WARN_IF(!registration)) {
+diff --git a/dom/workers/ServiceWorkerManager.h b/dom/workers/ServiceWorkerManager.h
+--- a/dom/workers/ServiceWorkerManager.h
++++ b/dom/workers/ServiceWorkerManager.h
+@@ -41,17 +41,16 @@ class OriginAttributes;
+ 
+ namespace dom {
+ 
+ class ServiceWorkerRegistrar;
+ class ServiceWorkerRegistrationListener;
+ 
+ namespace workers {
+ 
+-class ServiceWorkerClientInfo;
+ class ServiceWorkerInfo;
+ class ServiceWorkerJobQueue;
+ class ServiceWorkerManagerChild;
+ class ServiceWorkerPrivate;
+ 
+ class ServiceWorkerUpdateFinishCallback
+ {
+ protected:
+@@ -268,35 +267,20 @@ public:
+               const nsString& aMessage,
+               const nsString& aFilename,
+               const nsString& aLine,
+               uint32_t aLineNumber,
+               uint32_t aColumnNumber,
+               uint32_t aFlags,
+               JSExnType aExnType);
+ 
+-  UniquePtr<ServiceWorkerClientInfo>
+-  GetClient(nsIPrincipal* aPrincipal,
+-            const nsAString& aClientId,
+-            ErrorResult& aRv);
+-
+-  void
+-  GetAllClients(nsIPrincipal* aPrincipal,
+-                const nsCString& aScope,
+-                uint64_t aServiceWorkerID,
+-                bool aIncludeUncontrolled,
+-                nsTArray<ServiceWorkerClientInfo>& aDocuments);
+-
+   void
+   MaybeClaimClient(nsIDocument* aDocument,
+                    ServiceWorkerRegistrationInfo* aWorkerRegistration);
+ 
+-  nsresult
+-  ClaimClients(nsIPrincipal* aPrincipal, const nsCString& aScope, uint64_t aId);
+-
+   void
+   SetSkipWaitingFlag(nsIPrincipal* aPrincipal, const nsCString& aScope,
+                      uint64_t aServiceWorkerID);
+ 
+   static already_AddRefed<ServiceWorkerManager>
+   GetInstance();
+ 
+   void
+diff --git a/dom/workers/ServiceWorkerManagerService.h b/dom/workers/ServiceWorkerManagerService.h
+--- a/dom/workers/ServiceWorkerManagerService.h
++++ b/dom/workers/ServiceWorkerManagerService.h
+@@ -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/. */
+ 
+ #ifndef mozilla_dom_ServiceWorkerManagerService_h
+ #define mozilla_dom_ServiceWorkerManagerService_h
+ 
+ #include "nsISupportsImpl.h"
+ #include "nsHashKeys.h"
++#include "nsTArray.h"
+ #include "nsTHashtable.h"
+ 
+ namespace mozilla {
+ 
+ class OriginAttributes;
+ 
+ namespace ipc {
+ class PrincipalInfo;
+diff --git a/dom/workers/ServiceWorkerPrivate.cpp b/dom/workers/ServiceWorkerPrivate.cpp
+--- a/dom/workers/ServiceWorkerPrivate.cpp
++++ b/dom/workers/ServiceWorkerPrivate.cpp
+@@ -2,17 +2,16 @@
+ /* vim: set ts=8 sts=2 et sw=2 tw=80: */
+ /* This Source Code Form is subject to the terms of the Mozilla Public
+  * License, v. 2.0. If a copy of the MPL was not distributed with this
+  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+ 
+ #include "ServiceWorkerPrivate.h"
+ 
+ #include "ServiceWorkerManager.h"
+-#include "ServiceWorkerWindowClient.h"
+ #include "nsContentUtils.h"
+ #include "nsIHttpChannelInternal.h"
+ #include "nsIHttpHeaderVisitor.h"
+ #include "nsINamed.h"
+ #include "nsINetworkInterceptController.h"
+ #include "nsIPushErrorReporter.h"
+ #include "nsISupportsImpl.h"
+ #include "nsITimedChannel.h"
+@@ -20,30 +19,34 @@
+ #include "nsNetUtil.h"
+ #include "nsProxyRelease.h"
+ #include "nsQueryObject.h"
+ #include "nsStreamUtils.h"
+ #include "nsStringStream.h"
+ #include "WorkerRunnable.h"
+ #include "WorkerScope.h"
+ #include "mozilla/Assertions.h"
++#include "mozilla/dom/Client.h"
++#include "mozilla/dom/ClientIPCTypes.h"
+ #include "mozilla/dom/FetchUtil.h"
+ #include "mozilla/dom/IndexedDatabaseManager.h"
+ #include "mozilla/dom/InternalHeaders.h"
+ #include "mozilla/dom/NotificationEvent.h"
+ #include "mozilla/dom/PromiseNativeHandler.h"
+ #include "mozilla/dom/PushEventBinding.h"
+ #include "mozilla/dom/RequestBinding.h"
+ #include "mozilla/Unused.h"
+ 
+ using namespace mozilla;
+ using namespace mozilla::dom;
+ 
+ BEGIN_WORKERS_NAMESPACE
+ 
++using mozilla::ipc::PrincipalInfo;
++
+ NS_IMPL_CYCLE_COLLECTING_NATIVE_ADDREF(ServiceWorkerPrivate)
+ NS_IMPL_CYCLE_COLLECTING_NATIVE_RELEASE(ServiceWorkerPrivate)
+ NS_IMPL_CYCLE_COLLECTION(ServiceWorkerPrivate, mSupportsArray)
+ 
+ NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(ServiceWorkerPrivate, AddRef)
+ NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(ServiceWorkerPrivate, Release)
+ 
+ // Tracks the "dom.serviceWorkers.disable_open_click_delay" preference.  Modified
+@@ -478,32 +481,31 @@ public:
+       result.SuppressException();
+       return NS_ERROR_XPC_JS_THREW_EXCEPTION;
+     }
+ 
+     return NS_OK;
+   }
+ };
+ 
+-class SendMesssageEventRunnable final : public ExtendableEventWorkerRunnable
+-                                      , public StructuredCloneHolder
++class SendMessageEventRunnable final : public ExtendableEventWorkerRunnable
++                                     , public StructuredCloneHolder
+ {
+-  UniquePtr<ServiceWorkerClientInfo> mEventSource;
++  const ClientInfoAndState mClientInfoAndState;
+ 
+ public:
+-  SendMesssageEventRunnable(WorkerPrivate*  aWorkerPrivate,
+-                            KeepAliveToken* aKeepAliveToken,
+-                            UniquePtr<ServiceWorkerClientInfo>&& aEventSource)
++  SendMessageEventRunnable(WorkerPrivate*  aWorkerPrivate,
++                           KeepAliveToken* aKeepAliveToken,
++                           const ClientInfoAndState& aClientInfoAndState)
+     : ExtendableEventWorkerRunnable(aWorkerPrivate, aKeepAliveToken)
+     , StructuredCloneHolder(CloningSupported, TransferringSupported,
+                             StructuredCloneScope::SameProcessDifferentThread)
+-    , mEventSource(Move(aEventSource))
++    , mClientInfoAndState(aClientInfoAndState)
+   {
+     AssertIsOnMainThread();
+-    MOZ_ASSERT(mEventSource);
+   }
+ 
+   bool
+   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+   {
+     JS::Rooted<JS::Value> messageData(aCx);
+     nsCOMPtr<nsIGlobalObject> sgo = aWorkerPrivate->GlobalScope();
+     ErrorResult rv;
+@@ -512,26 +514,25 @@ public:
+       return true;
+     }
+ 
+     Sequence<OwningNonNull<MessagePort>> ports;
+     if (!TakeTransferredPortsAsSequence(ports)) {
+       return true;
+     }
+ 
+-    RefPtr<ServiceWorkerClient> client = new ServiceWorkerWindowClient(sgo,
+-                                                                       *mEventSource);
+     RootedDictionary<ExtendableMessageEventInit> init(aCx);
+ 
+     init.mBubbles = false;
+     init.mCancelable = false;
+ 
+     init.mData = messageData;
+     init.mPorts = ports;
+-    init.mSource.SetValue().SetAsClient() = client;
++    init.mSource.SetValue().SetAsClient() =
++      new Client(sgo, mClientInfoAndState);
+ 
+     RefPtr<EventTarget> target = aWorkerPrivate->GlobalScope();
+     RefPtr<ExtendableMessageEvent> extendableEvent =
+       ExtendableMessageEvent::Constructor(target, NS_LITERAL_STRING("message"),
+                                           init, rv);
+     if (NS_WARN_IF(rv.Failed())) {
+       rv.SuppressException();
+       return false;
+@@ -547,17 +548,17 @@ public:
+ };
+ 
+ } // anonymous namespace
+ 
+ nsresult
+ ServiceWorkerPrivate::SendMessageEvent(JSContext* aCx,
+                                        JS::Handle<JS::Value> aMessage,
+                                        const Sequence<JSObject*>& aTransferable,
+-                                       UniquePtr<ServiceWorkerClientInfo>&& aClientInfo)
++                                       const ClientInfoAndState& aClientInfoAndState)
+ {
+   AssertIsOnMainThread();
+ 
+   ErrorResult rv(SpawnWorkerIfNeeded(MessageEvent, nullptr));
+   if (NS_WARN_IF(rv.Failed())) {
+     return rv.StealNSResult();
+   }
+ 
+@@ -565,18 +566,18 @@ ServiceWorkerPrivate::SendMessageEvent(J
+ 
+   rv = nsContentUtils::CreateJSValueFromSequenceOfObject(aCx, aTransferable,
+                                                          &transferable);
+   if (NS_WARN_IF(rv.Failed())) {
+     return rv.StealNSResult();
+   }
+ 
+   RefPtr<KeepAliveToken> token = CreateEventKeepAliveToken();
+-  RefPtr<SendMesssageEventRunnable> runnable =
+-    new SendMesssageEventRunnable(mWorkerPrivate, token, Move(aClientInfo));
++  RefPtr<SendMessageEventRunnable> runnable =
++    new SendMessageEventRunnable(mWorkerPrivate, token, aClientInfoAndState);
+ 
+   runnable->Write(aCx, aMessage, transferable, JS::CloneDataPolicy(), rv);
+   if (NS_WARN_IF(rv.Failed())) {
+     return rv.StealNSResult();
+   }
+ 
+   if (!runnable->Dispatch()) {
+     return NS_ERROR_FAILURE;
+diff --git a/dom/workers/ServiceWorkerPrivate.h b/dom/workers/ServiceWorkerPrivate.h
+--- a/dom/workers/ServiceWorkerPrivate.h
++++ b/dom/workers/ServiceWorkerPrivate.h
+@@ -13,16 +13,19 @@
+ 
+ #define NOTIFICATION_CLICK_EVENT_NAME "notificationclick"
+ #define NOTIFICATION_CLOSE_EVENT_NAME "notificationclose"
+ 
+ class nsIInterceptedChannel;
+ 
+ namespace mozilla {
+ namespace dom {
++
++class ClientInfoAndState;
++
+ namespace workers {
+ 
+ class ServiceWorkerInfo;
+ class ServiceWorkerRegistrationInfo;
+ class KeepAliveToken;
+ 
+ class LifeCycleEventCallback : public Runnable
+ {
+@@ -80,17 +83,17 @@ protected:
+   NS_DECL_OWNINGTHREAD
+ 
+ public:
+   explicit ServiceWorkerPrivate(ServiceWorkerInfo* aInfo);
+ 
+   nsresult
+   SendMessageEvent(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+                    const Sequence<JSObject*>& aTransferable,
+-                   UniquePtr<ServiceWorkerClientInfo>&& aClientInfo);
++                   const ClientInfoAndState& aClientInfoAndState);
+ 
+   // This is used to validate the worker script and continue the installation
+   // process.
+   nsresult
+   CheckScriptEvaluation(LifeCycleEventCallback* aCallback);
+ 
+   nsresult
+   SendLifeCycleEvent(const nsAString& aEventType,
+diff --git a/dom/workers/ServiceWorkerWindowClient.cpp b/dom/workers/ServiceWorkerWindowClient.cpp
+deleted file mode 100644
+--- a/dom/workers/ServiceWorkerWindowClient.cpp
++++ /dev/null
+@@ -1,555 +0,0 @@
+-/* -*- 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 "ServiceWorkerWindowClient.h"
+-
+-#include "js/Value.h"
+-#include "mozilla/Mutex.h"
+-#include "mozilla/dom/ClientBinding.h"
+-#include "mozilla/dom/Promise.h"
+-#include "mozilla/dom/PromiseWorkerProxy.h"
+-#include "mozilla/UniquePtr.h"
+-#include "nsContentUtils.h"
+-#include "nsGlobalWindow.h"
+-#include "nsIDocShell.h"
+-#include "nsIDocShellLoadInfo.h"
+-#include "nsIDocument.h"
+-#include "nsIGlobalObject.h"
+-#include "nsIPrincipal.h"
+-#include "nsIScriptSecurityManager.h"
+-#include "nsIWebNavigation.h"
+-#include "nsIWebProgress.h"
+-#include "nsIWebProgressListener.h"
+-#include "nsString.h"
+-#include "nsWeakReference.h"
+-#include "ServiceWorker.h"
+-#include "ServiceWorkerInfo.h"
+-#include "ServiceWorkerManager.h"
+-#include "WorkerPrivate.h"
+-#include "WorkerScope.h"
+-
+-using namespace mozilla;
+-using namespace mozilla::dom;
+-using namespace mozilla::dom::workers;
+-
+-using mozilla::UniquePtr;
+-
+-JSObject*
+-ServiceWorkerWindowClient::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+-{
+-  return WindowClientBinding::Wrap(aCx, this, aGivenProto);
+-}
+-
+-namespace {
+-
+-class ResolveOrRejectPromiseRunnable final : public WorkerRunnable
+-{
+-  RefPtr<PromiseWorkerProxy> mPromiseProxy;
+-  UniquePtr<ServiceWorkerClientInfo> mClientInfo;
+-  nsresult mRv;
+-
+-public:
+-  // Passing a null clientInfo will resolve the promise with a null value.
+-  ResolveOrRejectPromiseRunnable(
+-    WorkerPrivate* aWorkerPrivate, PromiseWorkerProxy* aPromiseProxy,
+-    UniquePtr<ServiceWorkerClientInfo>&& aClientInfo)
+-    : WorkerRunnable(aWorkerPrivate)
+-    , mPromiseProxy(aPromiseProxy)
+-    , mClientInfo(Move(aClientInfo))
+-    , mRv(NS_OK)
+-  {
+-    AssertIsOnMainThread();
+-  }
+-
+-  // Reject the promise with passed nsresult.
+-  ResolveOrRejectPromiseRunnable(WorkerPrivate* aWorkerPrivate,
+-                                 PromiseWorkerProxy* aPromiseProxy,
+-                                 nsresult aRv)
+-    : WorkerRunnable(aWorkerPrivate)
+-    , mPromiseProxy(aPromiseProxy)
+-    , mClientInfo(nullptr)
+-    , mRv(aRv)
+-  {
+-    MOZ_ASSERT(NS_FAILED(aRv));
+-    AssertIsOnMainThread();
+-  }
+-
+-  bool
+-  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+-  {
+-    MOZ_ASSERT(aWorkerPrivate);
+-    aWorkerPrivate->AssertIsOnWorkerThread();
+-
+-    RefPtr<Promise> promise = mPromiseProxy->WorkerPromise();
+-    MOZ_ASSERT(promise);
+-
+-    if (NS_WARN_IF(NS_FAILED(mRv))) {
+-      promise->MaybeReject(mRv);
+-    } else if (mClientInfo) {
+-      RefPtr<ServiceWorkerWindowClient> client =
+-        new ServiceWorkerWindowClient(promise->GetParentObject(), *mClientInfo);
+-      promise->MaybeResolve(client);
+-    } else {
+-      promise->MaybeResolve(JS::NullHandleValue);
+-    }
+-
+-    // Release the reference on the worker thread.
+-    mPromiseProxy->CleanUp();
+-
+-    return true;
+-  }
+-};
+-
+-class ClientFocusRunnable final : public Runnable
+-{
+-  uint64_t mWindowId;
+-  RefPtr<PromiseWorkerProxy> mPromiseProxy;
+-
+-public:
+-  ClientFocusRunnable(uint64_t aWindowId, PromiseWorkerProxy* aPromiseProxy)
+-    : mozilla::Runnable("ClientFocusRunnable")
+-    , mWindowId(aWindowId)
+-    , mPromiseProxy(aPromiseProxy)
+-  {
+-    MOZ_ASSERT(mPromiseProxy);
+-  }
+-
+-  NS_IMETHOD
+-  Run() override
+-  {
+-    AssertIsOnMainThread();
+-    nsGlobalWindow* window = nsGlobalWindow::GetInnerWindowWithId(mWindowId);
+-    UniquePtr<ServiceWorkerClientInfo> clientInfo;
+-
+-    if (window) {
+-      nsCOMPtr<nsIDocument> doc = window->GetDocument();
+-      if (doc) {
+-        nsContentUtils::DispatchFocusChromeEvent(window->GetOuterWindow());
+-        clientInfo.reset(new ServiceWorkerClientInfo(doc));
+-      }
+-    }
+-
+-    DispatchResult(Move(clientInfo));
+-    return NS_OK;
+-  }
+-
+-private:
+-  void
+-  DispatchResult(UniquePtr<ServiceWorkerClientInfo>&& aClientInfo)
+-  {
+-    AssertIsOnMainThread();
+-    MutexAutoLock lock(mPromiseProxy->Lock());
+-    if (mPromiseProxy->CleanedUp()) {
+-      return;
+-    }
+-
+-    RefPtr<ResolveOrRejectPromiseRunnable> resolveRunnable;
+-    if (aClientInfo) {
+-      resolveRunnable = new ResolveOrRejectPromiseRunnable(
+-        mPromiseProxy->GetWorkerPrivate(), mPromiseProxy, Move(aClientInfo));
+-    } else {
+-      resolveRunnable = new ResolveOrRejectPromiseRunnable(
+-        mPromiseProxy->GetWorkerPrivate(), mPromiseProxy,
+-        NS_ERROR_DOM_INVALID_ACCESS_ERR);
+-    }
+-
+-    resolveRunnable->Dispatch();
+-  }
+-};
+-
+-} // namespace
+-
+-already_AddRefed<Promise>
+-ServiceWorkerWindowClient::Focus(ErrorResult& aRv) const
+-{
+-  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+-  MOZ_ASSERT(workerPrivate);
+-  workerPrivate->AssertIsOnWorkerThread();
+-
+-  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+-  MOZ_ASSERT(global);
+-
+-  RefPtr<Promise> promise = Promise::Create(global, aRv);
+-  if (NS_WARN_IF(aRv.Failed())) {
+-    return nullptr;
+-  }
+-
+-  if (workerPrivate->GlobalScope()->WindowInteractionAllowed()) {
+-    RefPtr<PromiseWorkerProxy> promiseProxy =
+-      PromiseWorkerProxy::Create(workerPrivate, promise);
+-    if (promiseProxy) {
+-      RefPtr<ClientFocusRunnable> r = new ClientFocusRunnable(mWindowId,
+-                                                              promiseProxy);
+-      MOZ_ALWAYS_SUCCEEDS(workerPrivate->DispatchToMainThread(r.forget()));
+-    } else {
+-      promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+-    }
+-
+-  } else {
+-    promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+-  }
+-
+-  return promise.forget();
+-}
+-
+-class WebProgressListener final : public nsIWebProgressListener,
+-                                  public nsSupportsWeakReference
+-{
+-public:
+-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+-  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(WebProgressListener,
+-                                           nsIWebProgressListener)
+-
+-  WebProgressListener(PromiseWorkerProxy* aPromiseProxy,
+-                      ServiceWorkerPrivate* aServiceWorkerPrivate,
+-                      nsPIDOMWindowOuter* aWindow, nsIURI* aBaseURI)
+-    : mPromiseProxy(aPromiseProxy)
+-    , mServiceWorkerPrivate(aServiceWorkerPrivate)
+-    , mWindow(aWindow)
+-    , mBaseURI(aBaseURI)
+-  {
+-    MOZ_ASSERT(aPromiseProxy);
+-    MOZ_ASSERT(aServiceWorkerPrivate);
+-    MOZ_ASSERT(aWindow);
+-    MOZ_ASSERT(aWindow->IsOuterWindow());
+-    MOZ_ASSERT(aBaseURI);
+-    AssertIsOnMainThread();
+-
+-    mServiceWorkerPrivate->StoreISupports(static_cast<nsIWebProgressListener*>(this));
+-  }
+-
+-  NS_IMETHOD
+-  OnStateChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+-                uint32_t aStateFlags, nsresult aStatus) override
+-  {
+-    if (!(aStateFlags & STATE_IS_DOCUMENT) ||
+-        !(aStateFlags & (STATE_STOP | STATE_TRANSFERRING))) {
+-      return NS_OK;
+-    }
+-
+-    // This is safe because our caller holds a strong ref.
+-    mServiceWorkerPrivate->RemoveISupports(static_cast<nsIWebProgressListener*>(this));
+-    aWebProgress->RemoveProgressListener(this);
+-
+-    WorkerPrivate* workerPrivate;
+-
+-    {
+-      MutexAutoLock lock(mPromiseProxy->Lock());
+-      if (mPromiseProxy->CleanedUp()) {
+-        return NS_OK;
+-      }
+-
+-      workerPrivate = mPromiseProxy->GetWorkerPrivate();
+-    }
+-
+-    nsCOMPtr<nsIDocument> doc = mWindow->GetExtantDoc();
+-
+-    RefPtr<ResolveOrRejectPromiseRunnable> resolveRunnable;
+-    UniquePtr<ServiceWorkerClientInfo> clientInfo;
+-    if (!doc) {
+-      resolveRunnable = new ResolveOrRejectPromiseRunnable(
+-        workerPrivate, mPromiseProxy, NS_ERROR_TYPE_ERR);
+-      resolveRunnable->Dispatch();
+-
+-      return NS_OK;
+-    }
+-
+-    // Check same origin.
+-    nsCOMPtr<nsIScriptSecurityManager> securityManager =
+-      nsContentUtils::GetSecurityManager();
+-    nsresult rv = securityManager->CheckSameOriginURI(doc->GetOriginalURI(),
+-                                                      mBaseURI, false);
+-
+-    if (NS_SUCCEEDED(rv)) {
+-      nsContentUtils::DispatchFocusChromeEvent(mWindow->GetOuterWindow());
+-      clientInfo.reset(new ServiceWorkerClientInfo(doc));
+-    }
+-
+-    resolveRunnable = new ResolveOrRejectPromiseRunnable(
+-      workerPrivate, mPromiseProxy, Move(clientInfo));
+-    resolveRunnable->Dispatch();
+-
+-    return NS_OK;
+-  }
+-
+-  NS_IMETHOD
+-  OnProgressChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+-                   int32_t aCurSelfProgress, int32_t aMaxSelfProgress,
+-                   int32_t aCurTotalProgress,
+-                   int32_t aMaxTotalProgress) override
+-  {
+-    MOZ_CRASH("Unexpected notification.");
+-    return NS_OK;
+-  }
+-
+-  NS_IMETHOD
+-  OnLocationChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+-                   nsIURI* aLocation, uint32_t aFlags) override
+-  {
+-    MOZ_CRASH("Unexpected notification.");
+-    return NS_OK;
+-  }
+-
+-  NS_IMETHOD
+-  OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+-                 nsresult aStatus, const char16_t* aMessage) override
+-  {
+-    MOZ_CRASH("Unexpected notification.");
+-    return NS_OK;
+-  }
+-
+-  NS_IMETHOD
+-  OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+-                   uint32_t aState) override
+-  {
+-    MOZ_CRASH("Unexpected notification.");
+-    return NS_OK;
+-  }
+-
+-private:
+-  ~WebProgressListener() {}
+-
+-  RefPtr<PromiseWorkerProxy> mPromiseProxy;
+-  RefPtr<ServiceWorkerPrivate> mServiceWorkerPrivate;
+-  nsCOMPtr<nsPIDOMWindowOuter> mWindow;
+-  nsCOMPtr<nsIURI> mBaseURI;
+-};
+-
+-NS_IMPL_CYCLE_COLLECTING_ADDREF(WebProgressListener)
+-NS_IMPL_CYCLE_COLLECTING_RELEASE(WebProgressListener)
+-NS_IMPL_CYCLE_COLLECTION(WebProgressListener, mPromiseProxy,
+-                         mServiceWorkerPrivate, mWindow)
+-
+-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebProgressListener)
+-  NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener)
+-  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+-NS_INTERFACE_MAP_END
+-
+-class ClientNavigateRunnable final : public Runnable
+-{
+-  uint64_t mWindowId;
+-  nsString mUrl;
+-  nsCString mBaseUrl;
+-  nsString mScope;
+-  RefPtr<PromiseWorkerProxy> mPromiseProxy;
+-  MOZ_INIT_OUTSIDE_CTOR WorkerPrivate* mWorkerPrivate;
+-
+-public:
+-  ClientNavigateRunnable(uint64_t aWindowId,
+-                         const nsAString& aUrl,
+-                         const nsAString& aScope,
+-                         PromiseWorkerProxy* aPromiseProxy)
+-    : mozilla::Runnable("ClientNavigateRunnable")
+-    , mWindowId(aWindowId)
+-    , mUrl(aUrl)
+-    , mScope(aScope)
+-    , mPromiseProxy(aPromiseProxy)
+-    , mWorkerPrivate(nullptr)
+-  {
+-    MOZ_ASSERT(aPromiseProxy);
+-    MOZ_ASSERT(aPromiseProxy->GetWorkerPrivate());
+-    aPromiseProxy->GetWorkerPrivate()->AssertIsOnWorkerThread();
+-  }
+-
+-  NS_IMETHOD
+-  Run() override
+-  {
+-    AssertIsOnMainThread();
+-
+-    nsCOMPtr<nsIPrincipal> principal;
+-
+-    {
+-      MutexAutoLock lock(mPromiseProxy->Lock());
+-      if (mPromiseProxy->CleanedUp()) {
+-        return NS_OK;
+-      }
+-
+-      mWorkerPrivate = mPromiseProxy->GetWorkerPrivate();
+-      WorkerPrivate::LocationInfo& info = mWorkerPrivate->GetLocationInfo();
+-      mBaseUrl = info.mHref;
+-      principal = mWorkerPrivate->GetPrincipal();
+-      MOZ_DIAGNOSTIC_ASSERT(principal);
+-    }
+-
+-    nsCOMPtr<nsIURI> baseUrl;
+-    nsCOMPtr<nsIURI> url;
+-    nsresult rv = ParseUrl(getter_AddRefs(baseUrl), getter_AddRefs(url));
+-
+-    if (NS_WARN_IF(NS_FAILED(rv))) {
+-      return RejectPromise(NS_ERROR_TYPE_ERR);
+-    }
+-
+-    nsGlobalWindow* window;
+-    rv = Navigate(url, principal, &window);
+-    if (NS_WARN_IF(NS_FAILED(rv))) {
+-      return RejectPromise(rv);
+-    }
+-
+-    nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
+-    nsCOMPtr<nsIWebProgress> webProgress = do_GetInterface(docShell);
+-    if (NS_WARN_IF(!webProgress)) {
+-      return NS_ERROR_FAILURE;
+-    }
+-
+-    RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+-    if (!swm) {
+-      return NS_ERROR_FAILURE;
+-    }
+-
+-    RefPtr<ServiceWorkerRegistrationInfo> registration =
+-      swm->GetRegistration(principal, NS_ConvertUTF16toUTF8(mScope));
+-    if (NS_WARN_IF(!registration)) {
+-      return NS_ERROR_FAILURE;
+-    }
+-    RefPtr<ServiceWorkerInfo> serviceWorkerInfo =
+-      registration->GetServiceWorkerInfoById(mWorkerPrivate->ServiceWorkerID());
+-    if (NS_WARN_IF(!serviceWorkerInfo)) {
+-      return NS_ERROR_FAILURE;
+-    }
+-
+-    nsCOMPtr<nsIWebProgressListener> listener =
+-      new WebProgressListener(mPromiseProxy, serviceWorkerInfo->WorkerPrivate(),
+-                              window->GetOuterWindow(), baseUrl);
+-
+-    rv = webProgress->AddProgressListener(
+-      listener, nsIWebProgress::NOTIFY_STATE_DOCUMENT);
+-
+-    if (NS_WARN_IF(NS_FAILED(rv))) {
+-      return RejectPromise(rv);
+-    }
+-
+-    return NS_OK;
+-  }
+-
+-private:
+-  nsresult
+-  RejectPromise(nsresult aRv)
+-  {
+-    MOZ_ASSERT(mWorkerPrivate);
+-    RefPtr<ResolveOrRejectPromiseRunnable> resolveRunnable =
+-      new ResolveOrRejectPromiseRunnable(mWorkerPrivate, mPromiseProxy, aRv);
+-
+-    resolveRunnable->Dispatch();
+-    return NS_OK;
+-  }
+-
+-  nsresult
+-  ResolvePromise(UniquePtr<ServiceWorkerClientInfo>&& aClientInfo)
+-  {
+-    MOZ_ASSERT(mWorkerPrivate);
+-    RefPtr<ResolveOrRejectPromiseRunnable> resolveRunnable =
+-      new ResolveOrRejectPromiseRunnable(mWorkerPrivate, mPromiseProxy,
+-                                         Move(aClientInfo));
+-
+-    resolveRunnable->Dispatch();
+-    return NS_OK;
+-  }
+-
+-  nsresult
+-  ParseUrl(nsIURI** aBaseUrl, nsIURI** aUrl)
+-  {
+-    MOZ_ASSERT(aBaseUrl);
+-    MOZ_ASSERT(aUrl);
+-    AssertIsOnMainThread();
+-
+-    nsCOMPtr<nsIURI> baseUrl;
+-    nsresult rv = NS_NewURI(getter_AddRefs(baseUrl), mBaseUrl);
+-    NS_ENSURE_SUCCESS(rv, rv);
+-
+-    nsCOMPtr<nsIURI> url;
+-    rv = NS_NewURI(getter_AddRefs(url), mUrl, nullptr, baseUrl);
+-    NS_ENSURE_SUCCESS(rv, rv);
+-
+-    baseUrl.forget(aBaseUrl);
+-    url.forget(aUrl);
+-
+-    return NS_OK;
+-  }
+-
+-  nsresult
+-  Navigate(nsIURI* aUrl, nsIPrincipal* aPrincipal, nsGlobalWindow** aWindow)
+-  {
+-    MOZ_ASSERT(aWindow);
+-
+-    nsGlobalWindow* window = nsGlobalWindow::GetInnerWindowWithId(mWindowId);
+-    if (NS_WARN_IF(!window)) {
+-      return NS_ERROR_TYPE_ERR;
+-    }
+-
+-    nsCOMPtr<nsIDocument> doc = window->GetDocument();
+-    if (NS_WARN_IF(!doc)) {
+-      return NS_ERROR_TYPE_ERR;
+-    }
+-
+-    if (NS_WARN_IF(!doc->IsActive())) {
+-      return NS_ERROR_TYPE_ERR;
+-    }
+-
+-    nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
+-    if (NS_WARN_IF(!docShell)) {
+-      return NS_ERROR_TYPE_ERR;
+-    }
+-
+-    nsCOMPtr<nsIDocShellLoadInfo> loadInfo;
+-    nsresult rv = docShell->CreateLoadInfo(getter_AddRefs(loadInfo));
+-    if (NS_WARN_IF(NS_FAILED(rv))) {
+-      return NS_ERROR_TYPE_ERR;
+-    }
+-
+-    loadInfo->SetTriggeringPrincipal(aPrincipal);
+-    loadInfo->SetReferrerPolicy(doc->GetReferrerPolicy());
+-    loadInfo->SetLoadType(nsIDocShellLoadInfo::loadStopContent);
+-    loadInfo->SetSourceDocShell(docShell);
+-    rv =
+-      docShell->LoadURI(aUrl, loadInfo, nsIWebNavigation::LOAD_FLAGS_NONE, true);
+-
+-    if (NS_WARN_IF(NS_FAILED(rv))) {
+-      return NS_ERROR_TYPE_ERR;
+-    }
+-
+-    *aWindow = window;
+-    return NS_OK;
+-  }
+-};
+-
+-already_AddRefed<Promise>
+-ServiceWorkerWindowClient::Navigate(const nsAString& aUrl, ErrorResult& aRv)
+-{
+-  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+-  MOZ_ASSERT(workerPrivate);
+-  workerPrivate->AssertIsOnWorkerThread();
+-
+-  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+-  MOZ_ASSERT(global);
+-
+-  RefPtr<Promise> promise = Promise::Create(global, aRv);
+-  if (NS_WARN_IF(aRv.Failed())) {
+-    return nullptr;
+-  }
+-
+-  if (aUrl.EqualsLiteral("about:blank")) {
+-    promise->MaybeReject(NS_ERROR_TYPE_ERR);
+-    return promise.forget();
+-  }
+-
+-  ServiceWorkerGlobalScope* globalScope =
+-    static_cast<ServiceWorkerGlobalScope*>(workerPrivate->GlobalScope());
+-  nsString scope;
+-  globalScope->GetScope(scope);
+-
+-  RefPtr<PromiseWorkerProxy> promiseProxy =
+-    PromiseWorkerProxy::Create(workerPrivate, promise);
+-  if (promiseProxy) {
+-    RefPtr<ClientNavigateRunnable> r =
+-      new ClientNavigateRunnable(mWindowId, aUrl, scope, promiseProxy);
+-    MOZ_ALWAYS_SUCCEEDS(workerPrivate->DispatchToMainThread(r.forget()));
+-  } else {
+-    promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+-  }
+-
+-  return promise.forget();
+-}
+diff --git a/dom/workers/ServiceWorkerWindowClient.h b/dom/workers/ServiceWorkerWindowClient.h
+deleted file mode 100644
+--- a/dom/workers/ServiceWorkerWindowClient.h
++++ /dev/null
+@@ -1,64 +0,0 @@
+-/* -*- 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/.
+- */
+-
+-#ifndef mozilla_dom_workers_serviceworkerwindowclient_h
+-#define mozilla_dom_workers_serviceworkerwindowclient_h
+-
+-#include "ServiceWorkerClient.h"
+-
+-namespace mozilla {
+-namespace dom {
+-
+-class Promise;
+-
+-namespace workers {
+-
+-class ServiceWorkerWindowClient final : public ServiceWorkerClient
+-{
+-public:
+-  ServiceWorkerWindowClient(nsISupports* aOwner,
+-                            const ServiceWorkerClientInfo& aClientInfo)
+-    : ServiceWorkerClient(aOwner, aClientInfo),
+-      mVisibilityState(aClientInfo.mVisibilityState),
+-      mFocused(aClientInfo.mFocused)
+-  {
+-  }
+-
+-  virtual JSObject*
+-  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+-
+-  mozilla::dom::VisibilityState
+-  VisibilityState() const
+-  {
+-    return mVisibilityState;
+-  }
+-
+-  bool
+-  Focused() const
+-  {
+-    return mFocused;
+-  }
+-
+-  already_AddRefed<Promise>
+-  Focus(ErrorResult& aRv) const;
+-
+-  already_AddRefed<Promise>
+-  Navigate(const nsAString& aUrl,  ErrorResult& aRv);
+-
+-private:
+-  ~ServiceWorkerWindowClient()
+-  { }
+-
+-  mozilla::dom::VisibilityState mVisibilityState;
+-  bool mFocused;
+-};
+-
+-} // namespace workers
+-} // namespace dom
+-} // namespace mozilla
+-
+-#endif // mozilla_dom_workers_serviceworkerwindowclient_h
+diff --git a/dom/workers/WorkerScope.cpp b/dom/workers/WorkerScope.cpp
+--- a/dom/workers/WorkerScope.cpp
++++ b/dom/workers/WorkerScope.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 "WorkerScope.h"
+ 
+ #include "jsapi.h"
+ #include "mozilla/EventListenerManager.h"
+ #include "mozilla/dom/BindingDeclarations.h"
++#include "mozilla/dom/Clients.h"
+ #include "mozilla/dom/Console.h"
+ #include "mozilla/dom/DedicatedWorkerGlobalScopeBinding.h"
+ #include "mozilla/dom/Fetch.h"
+ #include "mozilla/dom/FunctionBinding.h"
+ #include "mozilla/dom/IDBFactory.h"
+ #include "mozilla/dom/ImageBitmap.h"
+ #include "mozilla/dom/ImageBitmapBinding.h"
+ #include "mozilla/dom/Performance.h"
+@@ -39,17 +40,16 @@
+ #endif
+ 
+ #include "Crypto.h"
+ #include "Principal.h"
+ #include "RuntimeService.h"
+ #include "ScriptLoader.h"
+ #include "WorkerPrivate.h"
+ #include "WorkerRunnable.h"
+-#include "ServiceWorkerClients.h"
+ #include "ServiceWorkerManager.h"
+ #include "ServiceWorkerRegistration.h"
+ 
+ #ifdef XP_WIN
+ #undef PostMessage
+ #endif
+ 
+ extern already_AddRefed<nsIScriptTimeoutHandler>
+@@ -630,24 +630,25 @@ ServiceWorkerGlobalScope::WrapGlobalObje
+   JS::CompartmentOptions options;
+   mWorkerPrivate->CopyJSCompartmentOptions(options);
+ 
+   return ServiceWorkerGlobalScopeBinding::Wrap(aCx, this, this, options,
+                                                GetWorkerPrincipal(),
+                                                true, aReflector);
+ }
+ 
+-ServiceWorkerClients*
+-ServiceWorkerGlobalScope::Clients()
++already_AddRefed<Clients>
++ServiceWorkerGlobalScope::GetClients()
+ {
+   if (!mClients) {
+-    mClients = new ServiceWorkerClients(this);
++    mClients = new Clients(this);
+   }
+ 
+-  return mClients;
++  RefPtr<Clients> ref = mClients;
++  return ref.forget();
+ }
+ 
+ ServiceWorkerRegistration*
+ ServiceWorkerGlobalScope::Registration()
+ {
+   if (!mRegistration) {
+     mRegistration =
+       ServiceWorkerRegistration::CreateForWorker(mWorkerPrivate, mScope);
+diff --git a/dom/workers/WorkerScope.h b/dom/workers/WorkerScope.h
+--- a/dom/workers/WorkerScope.h
++++ b/dom/workers/WorkerScope.h
+@@ -14,16 +14,17 @@
+ #include "nsWeakReference.h"
+ #include "mozilla/dom/ImageBitmapSource.h"
+ 
+ namespace mozilla {
+ namespace dom {
+ 
+ class AnyCallback;
+ struct ChannelPixelLayout;
++class Clients;
+ class Console;
+ class Crypto;
+ class Function;
+ class IDBFactory;
+ enum class ImageBitmapFormat : uint8_t;
+ class Performance;
+ class Promise;
+ class RequestOrUSVString;
+@@ -35,17 +36,16 @@ enum class CallerType : uint32_t;
+ namespace cache {
+ 
+ class CacheStorage;
+ 
+ } // namespace cache
+ 
+ namespace workers {
+ 
+-class ServiceWorkerClients;
+ class WorkerPrivate;
+ 
+ } // namespace workers
+ 
+ class WorkerGlobalScope : public DOMEventTargetHelper,
+                           public nsIGlobalObject,
+                           public nsSupportsWeakReference
+ {
+@@ -277,17 +277,17 @@ public:
+   Close(JSContext* aCx);
+ 
+   IMPL_EVENT_HANDLER(connect)
+ };
+ 
+ class ServiceWorkerGlobalScope final : public WorkerGlobalScope
+ {
+   const nsString mScope;
+-  RefPtr<workers::ServiceWorkerClients> mClients;
++  RefPtr<Clients> mClients;
+   RefPtr<ServiceWorkerRegistration> mRegistration;
+ 
+   ~ServiceWorkerGlobalScope();
+ 
+ public:
+   NS_DECL_ISUPPORTS_INHERITED
+   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ServiceWorkerGlobalScope,
+                                            WorkerGlobalScope)
+@@ -301,18 +301,18 @@ public:
+                    JS::MutableHandle<JSObject*> aReflector) override;
+ 
+   void
+   GetScope(nsString& aScope) const
+   {
+     aScope = mScope;
+   }
+ 
+-  workers::ServiceWorkerClients*
+-  Clients();
++  already_AddRefed<Clients>
++  GetClients();
+ 
+   ServiceWorkerRegistration*
+   Registration();
+ 
+   already_AddRefed<Promise>
+   SkipWaiting(ErrorResult& aRv);
+ 
+   IMPL_EVENT_HANDLER(activate)
+diff --git a/dom/workers/moz.build b/dom/workers/moz.build
+--- a/dom/workers/moz.build
++++ b/dom/workers/moz.build
+@@ -32,19 +32,16 @@ EXPORTS.mozilla.dom.workers += [
+     'ServiceWorkerRegistrationInfo.h',
+     'WorkerDebuggerManager.h',
+     'Workers.h',
+ ]
+ 
+ # Stuff needed for the bindings, not really public though.
+ EXPORTS.mozilla.dom.workers.bindings += [
+     'ServiceWorker.h',
+-    'ServiceWorkerClient.h',
+-    'ServiceWorkerClients.h',
+-    'ServiceWorkerWindowClient.h',
+     'SharedWorker.h',
+     'WorkerHolder.h',
+     'WorkerHolderToken.h',
+ ]
+ 
+ XPIDL_MODULE = 'dom_workers'
+ 
+ XPIDL_SOURCES += [
+@@ -55,18 +52,16 @@ XPIDL_SOURCES += [
+ UNIFIED_SOURCES += [
+     'ChromeWorkerScope.cpp',
+     'FileReaderSync.cpp',
+     'Principal.cpp',
+     'RegisterBindings.cpp',
+     'RuntimeService.cpp',
+     'ScriptLoader.cpp',
+     'ServiceWorker.cpp',
+-    'ServiceWorkerClient.cpp',
+-    'ServiceWorkerClients.cpp',
+     'ServiceWorkerContainer.cpp',
+     'ServiceWorkerDescriptor.cpp',
+     'ServiceWorkerEvents.cpp',
+     'ServiceWorkerInfo.cpp',
+     'ServiceWorkerJob.cpp',
+     'ServiceWorkerJobQueue.cpp',
+     'ServiceWorkerManager.cpp',
+     'ServiceWorkerManagerChild.cpp',
+@@ -77,17 +72,16 @@ UNIFIED_SOURCES += [
+     'ServiceWorkerRegistrar.cpp',
+     'ServiceWorkerRegistration.cpp',
+     'ServiceWorkerRegistrationInfo.cpp',
+     'ServiceWorkerScriptCache.cpp',
+     'ServiceWorkerUnregisterJob.cpp',
+     'ServiceWorkerUpdateJob.cpp',
+     'ServiceWorkerUpdaterChild.cpp',
+     'ServiceWorkerUpdaterParent.cpp',
+-    'ServiceWorkerWindowClient.cpp',
+     'SharedWorker.cpp',
+     'WorkerDebuggerManager.cpp',
+     'WorkerHolder.cpp',
+     'WorkerHolderToken.cpp',
+     'WorkerLocation.cpp',
+     'WorkerNavigator.cpp',
+     'WorkerPrivate.cpp',
+     'WorkerRunnable.cpp',

+ 802 - 0
frg/work-js/mozilla-release/patches/1293277-6-59a1.patch

@@ -0,0 +1,802 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1513111488 18000
+# Node ID baa02d97591ea55e6b9ee1867061c3e9e0324021
+# Parent  67e07652637165e46c9d5ed312634d52a62bd303
+Bug 1293277 P6 Use the ClientInfo.Id() value for FetchEvent.clientId. r=baku
+
+diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp
+--- a/docshell/base/nsDocShell.cpp
++++ b/docshell/base/nsDocShell.cpp
+@@ -15156,28 +15156,23 @@ nsDocShell::ChannelIntercepted(nsIInterc
+   nsCOMPtr<nsIDocument> doc;
+ 
+   bool isSubresourceLoad = !nsContentUtils::IsNonSubresourceRequest(channel);
+   if (isSubresourceLoad) {
+     doc = GetDocument();
+     if (!doc) {
+       return NS_ERROR_NOT_AVAILABLE;
+     }
+-  } else {
+-    // For top-level navigations, save a document ID which will be passed to
+-    // the FetchEvent as the clientId later on.
+-    rv = nsIDocument::GenerateDocumentId(mInterceptedDocumentId);
+-    NS_ENSURE_SUCCESS(rv, rv);
+   }
+ 
+   bool isReload = mLoadType & LOAD_CMD_RELOAD;
+ 
+   ErrorResult error;
+-  swm->DispatchFetchEvent(mOriginAttributes, doc, mInterceptedDocumentId,
+-                          aChannel, isReload, isSubresourceLoad, error);
++  swm->DispatchFetchEvent(mOriginAttributes, doc, aChannel, isReload,
++                          isSubresourceLoad, error);
+   if (NS_WARN_IF(error.Failed())) {
+     return error.StealNSResult();
+   }
+ 
+   return NS_OK;
+ }
+ 
+ bool
+diff --git a/docshell/base/nsDocShell.h b/docshell/base/nsDocShell.h
+--- a/docshell/base/nsDocShell.h
++++ b/docshell/base/nsDocShell.h
+@@ -366,21 +366,16 @@ public:
+   const mozilla::OriginAttributes&
+   GetOriginAttributes()
+   {
+     return mOriginAttributes;
+   }
+ 
+   nsresult SetOriginAttributes(const mozilla::OriginAttributes& aAttrs);
+ 
+-  void GetInterceptedDocumentId(nsAString& aId)
+-  {
+-    aId = mInterceptedDocumentId;
+-  }
+-
+ private:
+   // An observed docshell wrapper is created when recording markers is enabled.
+   mozilla::UniquePtr<mozilla::ObservedDocShell> mObserved;
+ 
+   // It is necessary to allow adding a timeline marker wherever a docshell
+   // instance is available. This operation happens frequently and needs to
+   // be very fast, so instead of using a Map or having to search for some
+   // docshell-specific markers storage, a pointer to an `ObservedDocShell` is
+@@ -1142,18 +1137,16 @@ protected:
+ 
+   // This represents the state of private browsing in the docshell.
+   // Currently treated as a binary value: 1 - in private mode, 0 - not private mode
+   // On content docshells mPrivateBrowsingId == mOriginAttributes.mPrivateBrowsingId
+   // On chrome docshells this value will be set, but not have the corresponding
+   // origin attribute set.
+   uint32_t mPrivateBrowsingId;
+ 
+-  nsString mInterceptedDocumentId;
+-
+   // This represents the CSS display-mode we are currently using.
+   // It can be any of the following values from nsIDocShell.idl:
+   //
+   // DISPLAY_MODE_BROWSER = 0
+   // DISPLAY_MODE_MINIMAL_UI = 1
+   // DISPLAY_MODE_STANDALONE = 2
+   // DISPLAY_MODE_FULLSCREEN = 3
+   //
+diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp
+--- a/dom/base/nsDocument.cpp
++++ b/dom/base/nsDocument.cpp
+@@ -1707,17 +1707,16 @@ NS_INTERFACE_TABLE_HEAD(nsDocument)
+     NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIDOMDocumentXBL)
+     NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIScriptObjectPrincipal)
+     NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIDOMEventTarget)
+     NS_INTERFACE_TABLE_ENTRY(nsDocument, mozilla::dom::EventTarget)
+     NS_INTERFACE_TABLE_ENTRY(nsDocument, nsISupportsWeakReference)
+     NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIRadioGroupContainer)
+     NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIMutationObserver)
+     NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIApplicationCacheContainer)
+-    NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIObserver)
+     NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIDOMXPathEvaluator)
+   NS_INTERFACE_TABLE_END
+   NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsDocument)
+ NS_INTERFACE_MAP_END
+ 
+ 
+ NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDocument)
+ NS_IMETHODIMP_(MozExternalRefCountType)
+@@ -4915,38 +4914,16 @@ nsDocument::SetScriptGlobalObject(nsIScr
+         // of bfcache.  Clear our state to allow that to happen.  Only
+         // clear this flag if we are actually controlled, though, so pages
+         // that were force reloaded don't become controlled when they
+         // come out of bfcache.
+         mMaybeServiceWorkerControlled = false;
+       }
+       swm->MaybeStopControlling(this);
+     }
+-
+-    // Remove ourself from the list of clients.  We only register
+-    // content principal documents in this list.
+-    if (!nsContentUtils::IsSystemPrincipal(GetPrincipal()) &&
+-        !GetPrincipal()->GetIsNullPrincipal()) {
+-      nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+-      if (os) {
+-        os->RemoveObserver(this, "service-worker-get-client");
+-      }
+-    }
+-
+-  } else if (!mScriptGlobalObject && aScriptGlobalObject &&
+-             mDocumentContainer && GetChannel() &&
+-             !nsContentUtils::IsSystemPrincipal(GetPrincipal()) &&
+-             !GetPrincipal()->GetIsNullPrincipal()) {
+-    // This document is being activated.  Register it in the list of
+-    // clients.  We only do this for content principal documents
+-    // since we can never observe system or null principals.
+-    nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+-    if (os) {
+-      os->AddObserver(this, "service-worker-get-client", /* ownsWeak */ false);
+-    }
+   }
+ 
+   // BlockOnload() might be called before mScriptGlobalObject is set.
+   // We may need to add the blocker once mScriptGlobalObject is set.
+   bool needOnloadBlocker = !mScriptGlobalObject && aScriptGlobalObject;
+ 
+   mScriptGlobalObject = aScriptGlobalObject;
+ 
+@@ -5057,25 +5034,17 @@ nsDocument::SetScriptGlobalObject(nsIScr
+     // If we are shift-reloaded, don't associate with a ServiceWorker.
+     if (IsForceReloadType(loadType)) {
+       NS_WARNING("Page was shift reloaded, skipping ServiceWorker control");
+       return;
+     }
+ 
+     nsCOMPtr<nsIServiceWorkerManager> swm = mozilla::services::GetServiceWorkerManager();
+     if (swm) {
+-      // If this document is being resurrected from the bfcache, then we may
+-      // already have a document ID.  In that case reuse the same ID.  Otherwise
+-      // get our document ID from the docshell.
+-      nsString documentId(GetId());
+-      if (documentId.IsEmpty()) {
+-        static_cast<nsDocShell*>(docShell.get())->GetInterceptedDocumentId(documentId);
+-      }
+-
+-      swm->MaybeStartControlling(this, documentId);
++      swm->MaybeStartControlling(this);
+       mMaybeServiceWorkerControlled = true;
+     }
+   }
+ }
+ 
+ nsIScriptGlobalObject*
+ nsDocument::GetScriptHandlingObjectInternal() const
+ {
+@@ -12206,42 +12175,16 @@ nsIDocument::GetPointerLockElement()
+     do_QueryReferent(EventStateManager::sPointerLockedDoc);
+   if (pointerLockedDoc != this) {
+     return nullptr;
+   }
+ 
+   return pointerLockedElement;
+ }
+ 
+-nsresult
+-nsDocument::Observe(nsISupports *aSubject,
+-                    const char *aTopic,
+-                    const char16_t *aData)
+-{
+-  if (strcmp("service-worker-get-client", aTopic) == 0) {
+-    // No need to generate the ID if it doesn't exist here.  The ID being
+-    // requested must already be generated in order to passed in as
+-    // aSubject.
+-    nsString clientId = GetId();
+-    if (!clientId.IsEmpty() && clientId.Equals(aData)) {
+-      nsCOMPtr<nsISupportsInterfacePointer> ifptr = do_QueryInterface(aSubject);
+-      if (ifptr) {
+-#ifdef DEBUG
+-        nsCOMPtr<nsISupports> value;
+-        MOZ_ALWAYS_SUCCEEDS(ifptr->GetData(getter_AddRefs(value)));
+-        MOZ_ASSERT(!value);
+-#endif
+-        ifptr->SetData(static_cast<nsIDocument*>(this));
+-        ifptr->SetDataIID(&NS_GET_IID(nsIDocument));
+-      }
+-    }
+-  }
+-  return NS_OK;
+-}
+-
+ void
+ nsDocument::UpdateVisibilityState()
+ {
+   dom::VisibilityState oldState = mVisibilityState;
+   mVisibilityState = GetVisibilityState();
+   if (oldState != mVisibilityState) {
+     nsContentUtils::DispatchTrustedEvent(this, static_cast<nsIDocument*>(this),
+                                          NS_LITERAL_STRING("visibilitychange"),
+@@ -12848,59 +12791,16 @@ nsIDocument::CreateHTMLElement(nsIAtom* 
+   DebugOnly<nsresult> rv = NS_NewHTMLElement(getter_AddRefs(element),
+                                              nodeInfo.forget(),
+                                              mozilla::dom::NOT_FROM_PARSER);
+ 
+   MOZ_ASSERT(NS_SUCCEEDED(rv), "NS_NewHTMLElement should never fail");
+   return element.forget();
+ }
+ 
+-/* static */
+-nsresult
+-nsIDocument::GenerateDocumentId(nsAString& aId)
+-{
+-  nsID id;
+-  nsresult rv = nsContentUtils::GenerateUUIDInPlace(id);
+-  if (NS_WARN_IF(NS_FAILED(rv))) {
+-    return rv;
+-  }
+-
+-  // Build a string in {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} format
+-  char buffer[NSID_LENGTH];
+-  id.ToProvidedString(buffer);
+-  NS_ConvertASCIItoUTF16 uuid(buffer);
+-
+-  // Remove {} and the null terminator
+-  aId.Assign(Substring(uuid, 1, NSID_LENGTH - 3));
+-  return NS_OK;
+-}
+-
+-nsresult
+-nsIDocument::GetOrCreateId(nsAString& aId)
+-{
+-  if (mId.IsEmpty()) {
+-    nsresult rv = GenerateDocumentId(mId);
+-    if (NS_WARN_IF(NS_FAILED(rv))) {
+-      return rv;
+-    }
+-  }
+-
+-  aId = mId;
+-  return NS_OK;
+-}
+-
+-void
+-nsIDocument::SetId(const nsAString& aId)
+-{
+-  // The ID should only be set one time, but we may get the same value
+-  // more than once if the document is controlled coming out of bfcache.
+-  MOZ_ASSERT_IF(mId != aId, mId.IsEmpty());
+-  mId = aId;
+-}
+-
+ bool
+ MarkDocumentTreeToBeInSyncOperation(nsIDocument* aDoc, void* aData)
+ {
+   nsCOMArray<nsIDocument>* documents =
+     static_cast<nsCOMArray<nsIDocument>*>(aData);
+   if (aDoc) {
+     aDoc->SetIsInSyncOperation(true);
+     if (nsCOMPtr<nsPIDOMWindowInner> window = aDoc->GetInnerWindow()) {
+diff --git a/dom/base/nsDocument.h b/dom/base/nsDocument.h
+--- a/dom/base/nsDocument.h
++++ b/dom/base/nsDocument.h
+@@ -320,17 +320,16 @@ protected:
+ class nsDocument : public nsIDocument,
+                    public nsIDOMDocument,
+                    public nsIDOMDocumentXBL,
+                    public nsSupportsWeakReference,
+                    public nsIScriptObjectPrincipal,
+                    public nsIRadioGroupContainer,
+                    public nsIApplicationCacheContainer,
+                    public nsStubMutationObserver,
+-                   public nsIObserver,
+                    public nsIDOMXPathEvaluator
+ {
+   friend class nsIDocument;
+ 
+ public:
+   typedef mozilla::dom::Element Element;
+   typedef mozilla::net::ReferrerPolicy ReferrerPolicy;
+ 
+@@ -663,19 +662,16 @@ public:
+     GetExistingListenerManager() const override;
+ 
+   // nsIScriptObjectPrincipal
+   virtual nsIPrincipal* GetPrincipal() override;
+ 
+   // nsIApplicationCacheContainer
+   NS_DECL_NSIAPPLICATIONCACHECONTAINER
+ 
+-  // nsIObserver
+-  NS_DECL_NSIOBSERVER
+-
+   NS_DECL_NSIDOMXPATHEVALUATOR
+ 
+   virtual nsresult Init();
+ 
+   virtual already_AddRefed<Element> CreateElem(const nsAString& aName,
+                                                nsIAtom* aPrefix,
+                                                int32_t aNamespaceID,
+                                                const nsAString* aIs = nullptr) override;
+diff --git a/dom/base/nsIDocument.h b/dom/base/nsIDocument.h
+--- a/dom/base/nsIDocument.h
++++ b/dom/base/nsIDocument.h
+@@ -1049,20 +1049,16 @@ public:
+    * inserted anonymous content (in other words, the clone of the aElement
+    * that was passed to InsertAnonymousContent).
+    */
+   Element* GetAnonRootIfInAnonymousContentContainer(nsINode* aNode) const;
+   nsTArray<RefPtr<mozilla::dom::AnonymousContent>>& GetAnonymousContents() {
+     return mAnonymousContents;
+   }
+ 
+-  static nsresult GenerateDocumentId(nsAString& aId);
+-  nsresult GetOrCreateId(nsAString& aId);
+-  void SetId(const nsAString& aId);
+-
+   mozilla::TimeStamp GetPageUnloadingEventTimeStamp() const
+   {
+     if (!mParentDocument) {
+       return mPageUnloadingEventTimeStamp;
+     }
+ 
+     mozilla::TimeStamp parentTimeStamp(mParentDocument->GetPageUnloadingEventTimeStamp());
+     if (parentTimeStamp.IsNull()) {
+@@ -3273,21 +3269,16 @@ protected:
+ 
+   mozilla::dom::XPathEvaluator* XPathEvaluator();
+ 
+   void HandleRebuildUserFontSet() {
+     mPostedFlushUserFontSet = false;
+     FlushUserFontSet();
+   }
+ 
+-  const nsString& GetId() const
+-  {
+-    return mId;
+-  }
+-
+   // Update our frame request callback scheduling state, if needed.  This will
+   // schedule or unschedule them, if necessary, and update
+   // mFrameRequestCallbacksScheduled.  aOldShell should only be passed when
+   // mPresShell is becoming null; in that case it will be used to get hold of
+   // the relevant refresh driver.
+   void UpdateFrameRequestCallbackSchedulingState(nsIPresShell* aOldShell = nullptr);
+ 
+   // Helper for GetScrollingElement/IsScrollingElement.
+@@ -3631,17 +3622,16 @@ protected:
+   uint32_t mSandboxFlags;
+ 
+   nsCString mContentLanguage;
+ 
+   // The channel that got passed to nsDocument::StartDocumentLoad(), if any.
+   nsCOMPtr<nsIChannel> mChannel;
+ private:
+   nsCString mContentType;
+-  nsString mId;
+ protected:
+ 
+   // The document's security info
+   nsCOMPtr<nsISupports> mSecurityInfo;
+ 
+   // The channel that failed to load and resulted in an error page.
+   // This only applies to error pages. Might be null.
+   nsCOMPtr<nsIChannel> mFailedChannel;
+diff --git a/dom/interfaces/base/nsIServiceWorkerManager.idl b/dom/interfaces/base/nsIServiceWorkerManager.idl
+--- a/dom/interfaces/base/nsIServiceWorkerManager.idl
++++ b/dom/interfaces/base/nsIServiceWorkerManager.idl
+@@ -143,17 +143,17 @@ interface nsIServiceWorkerManager : nsIS
+                                                               in DOMString aScope);
+ 
+   /**
+    * Call this to request that document `aDoc` be controlled by a ServiceWorker
+    * if a registration exists for it's scope.
+    *
+    * This MUST only be called once per document!
+    */
+-  [notxpcom,nostdcall] void MaybeStartControlling(in nsIDocument aDoc, in DOMString aDocumentId);
++  [notxpcom,nostdcall] void MaybeStartControlling(in nsIDocument aDoc);
+ 
+   /**
+    * Documents that have called MaybeStartControlling() should call this when
+    * they are destroyed. This function may be called multiple times, and is
+    * idempotent.
+    */
+   [notxpcom,nostdcall] void MaybeStopControlling(in nsIDocument aDoc);
+ 
+diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp
+--- a/dom/workers/ServiceWorkerManager.cpp
++++ b/dom/workers/ServiceWorkerManager.cpp
+@@ -2296,26 +2296,25 @@ ServiceWorkerManager::MaybeRemoveRegistr
+     if (entry.Data()->mOrderedScopes.IsEmpty() &&
+         entry.Data()->mJobQueues.Count() == 0) {
+       entry.Remove();
+     }
+   }
+ }
+ 
+ void
+-ServiceWorkerManager::MaybeStartControlling(nsIDocument* aDoc,
+-                                            const nsAString& aDocumentId)
++ServiceWorkerManager::MaybeStartControlling(nsIDocument* aDoc)
+ {
+   AssertIsOnMainThread();
+   MOZ_ASSERT(aDoc);
+   RefPtr<ServiceWorkerRegistrationInfo> registration =
+     GetServiceWorkerRegistrationInfo(aDoc);
+   if (registration) {
+     MOZ_ASSERT(!mControlledDocuments.Contains(aDoc));
+-    StartControllingADocument(registration, aDoc, aDocumentId);
++    StartControllingADocument(registration, aDoc);
+   }
+ }
+ 
+ void
+ ServiceWorkerManager::MaybeStopControlling(nsIDocument* aDoc)
+ {
+   AssertIsOnMainThread();
+   MOZ_ASSERT(aDoc);
+@@ -2347,34 +2346,30 @@ ServiceWorkerManager::MaybeCheckNavigati
+   mControlledDocuments.Get(aDoc, getter_AddRefs(registration));
+   if (registration) {
+     registration->MaybeScheduleUpdate();
+   }
+ }
+ 
+ RefPtr<GenericPromise>
+ ServiceWorkerManager::StartControllingADocument(ServiceWorkerRegistrationInfo* aRegistration,
+-                                                nsIDocument* aDoc,
+-                                                const nsAString& aDocumentId)
++                                                nsIDocument* aDoc)
+ {
+   MOZ_ASSERT(aRegistration);
+   MOZ_ASSERT(aDoc);
+ 
+ #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+   auto storageAllowed = nsContentUtils::StorageAllowedForDocument(aDoc);
+   MOZ_DIAGNOSTIC_ASSERT(storageAllowed == nsContentUtils::StorageAccess::eAllow);
+ #endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ 
+   RefPtr<GenericPromise> ref = GenericPromise::CreateAndResolve(true, __func__);
+ 
+   aRegistration->StartControllingADocument();
+   mControlledDocuments.Put(aDoc, aRegistration);
+-  if (!aDocumentId.IsEmpty()) {
+-    aDoc->SetId(aDocumentId);
+-  }
+ 
+   // Mark the document's ClientSource as controlled using the ClientHandle
+   // interface.  While we could get at the ClientSource directly from the
+   // document here, our goal is to move ServiceWorkerManager to a separate
+   // process.  Using the ClientHandle supports this remote operation.
+   ServiceWorkerInfo* activeWorker = aRegistration->GetActive();
+   nsPIDOMWindowInner* innerWindow = aDoc->GetInnerWindow();
+   if (activeWorker && innerWindow) {
+@@ -2558,30 +2553,27 @@ ServiceWorkerManager::GetServiceWorkerFo
+ 
+ namespace {
+ 
+ class ContinueDispatchFetchEventRunnable : public Runnable
+ {
+   RefPtr<ServiceWorkerPrivate> mServiceWorkerPrivate;
+   nsCOMPtr<nsIInterceptedChannel> mChannel;
+   nsCOMPtr<nsILoadGroup> mLoadGroup;
+-  nsString mDocumentId;
+   bool mIsReload;
+ public:
+   ContinueDispatchFetchEventRunnable(
+     ServiceWorkerPrivate* aServiceWorkerPrivate,
+     nsIInterceptedChannel* aChannel,
+     nsILoadGroup* aLoadGroup,
+-    const nsAString& aDocumentId,
+     bool aIsReload)
+     : Runnable("dom::workers::ContinueDispatchFetchEventRunnable")
+     , mServiceWorkerPrivate(aServiceWorkerPrivate)
+     , mChannel(aChannel)
+     , mLoadGroup(aLoadGroup)
+-    , mDocumentId(aDocumentId)
+     , mIsReload(aIsReload)
+   {
+     MOZ_ASSERT(aServiceWorkerPrivate);
+     MOZ_ASSERT(aChannel);
+   }
+ 
+   void
+   HandleError()
+@@ -2612,70 +2604,72 @@ public:
+     // if that happens.
+     nsresult status;
+     rv = channel->GetStatus(&status);
+     if (NS_WARN_IF(NS_FAILED(rv) || NS_FAILED(status))) {
+       HandleError();
+       return NS_OK;
+     }
+ 
+-    rv = mServiceWorkerPrivate->SendFetchEvent(mChannel, mLoadGroup,
+-                                               mDocumentId, mIsReload);
++    nsString clientId;
++    nsCOMPtr<nsILoadInfo> loadInfo = channel->GetLoadInfo();
++    if (loadInfo) {
++      Maybe<ClientInfo> clientInfo = loadInfo->GetClientInfo();
++      if (clientInfo.isSome()) {
++        char buf[NSID_LENGTH];
++        clientInfo.ref().Id().ToProvidedString(buf);
++        CopyUTF8toUTF16(nsDependentCString(buf), clientId);
++      }
++    }
++
++    rv = mServiceWorkerPrivate->SendFetchEvent(mChannel, mLoadGroup, clientId,
++                                               mIsReload);
+     if (NS_WARN_IF(NS_FAILED(rv))) {
+       HandleError();
+     }
+ 
+     return NS_OK;
+   }
+ };
+ 
+ } // anonymous namespace
+ 
+ void
+ ServiceWorkerManager::DispatchFetchEvent(const OriginAttributes& aOriginAttributes,
+                                          nsIDocument* aDoc,
+-                                         const nsAString& aDocumentIdForTopLevelNavigation,
+                                          nsIInterceptedChannel* aChannel,
+                                          bool aIsReload,
+                                          bool aIsSubresourceLoad,
+                                          ErrorResult& aRv)
+ {
+   MOZ_ASSERT(aChannel);
+   AssertIsOnMainThread();
+ 
+   RefPtr<ServiceWorkerInfo> serviceWorker;
+   nsCOMPtr<nsILoadGroup> loadGroup;
+-  nsAutoString documentId;
+ 
+   if (aIsSubresourceLoad) {
+     MOZ_ASSERT(aDoc);
+ 
+     serviceWorker = GetActiveWorkerInfoForDocument(aDoc);
+     if (!serviceWorker) {
+       aRv.Throw(NS_ERROR_FAILURE);
+       return;
+     }
+ 
+     loadGroup = aDoc->GetDocumentLoadGroup();
+-    nsresult rv = aDoc->GetOrCreateId(documentId);
+-    if (NS_WARN_IF(NS_FAILED(rv))) {
+-      return;
+-    }
+   } else {
+     nsCOMPtr<nsIChannel> internalChannel;
+     aRv = aChannel->GetChannel(getter_AddRefs(internalChannel));
+     if (NS_WARN_IF(aRv.Failed())) {
+       return;
+     }
+ 
+     internalChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+ 
+-    // TODO: Use aDocumentIdForTopLevelNavigation for potentialClientId, pending
+-    // the spec change.
+-
+     nsCOMPtr<nsIURI> uri;
+     aRv = aChannel->GetSecureUpgradedChannelURI(getter_AddRefs(uri));
+     if (NS_WARN_IF(aRv.Failed())) {
+       return;
+     }
+ 
+     // non-subresource request means the URI contains the principal
+     nsCOMPtr<nsIPrincipal> principal =
+@@ -2745,18 +2739,17 @@ ServiceWorkerManager::DispatchFetchEvent
+   if (NS_WARN_IF(aRv.Failed())) {
+     return;
+   }
+ 
+   MOZ_DIAGNOSTIC_ASSERT(serviceWorker);
+ 
+   nsCOMPtr<nsIRunnable> continueRunnable =
+     new ContinueDispatchFetchEventRunnable(serviceWorker->WorkerPrivate(),
+-                                           aChannel, loadGroup,
+-                                           documentId, aIsReload);
++                                           aChannel, loadGroup, aIsReload);
+ 
+   // When this service worker was registered, we also sent down the permissions
+   // for the runnable. They should have arrived by now, but we still need to
+   // wait for them if they have not.
+   nsCOMPtr<nsIRunnable> permissionsRunnable = NS_NewRunnableFunction(
+     "dom::workers::ServiceWorkerManager::DispatchFetchEvent", [=]() {
+       nsCOMPtr<nsIPermissionManager> permMgr = services::GetPermissionManager();
+       MOZ_ALWAYS_SUCCEEDS(permMgr->WhenPermissionsAvailable(serviceWorker->Principal(),
+@@ -3219,17 +3212,17 @@ ServiceWorkerManager::MaybeClaimClient(n
+         aWorkerRegistration == controllingRegistration) {
+     return;
+   }
+ 
+   if (controllingRegistration) {
+     StopControllingADocument(controllingRegistration);
+   }
+ 
+-  StartControllingADocument(aWorkerRegistration, aDocument, NS_LITERAL_STRING(""));
++  StartControllingADocument(aWorkerRegistration, aDocument);
+   FireControllerChangeOnDocument(aDocument);
+ }
+ 
+ void
+ ServiceWorkerManager::SetSkipWaitingFlag(nsIPrincipal* aPrincipal,
+                                          const nsCString& aScope,
+                                          uint64_t aServiceWorkerID)
+ {
+diff --git a/dom/workers/ServiceWorkerManager.h b/dom/workers/ServiceWorkerManager.h
+--- a/dom/workers/ServiceWorkerManager.h
++++ b/dom/workers/ServiceWorkerManager.h
+@@ -145,17 +145,16 @@ public:
+   // reasons without having to worry about shutdown races with the worker.
+   bool
+   MayHaveActiveServiceWorkerInstance(ContentParent* aContent,
+                                      nsIPrincipal* aPrincipal);
+ 
+   void
+   DispatchFetchEvent(const OriginAttributes& aOriginAttributes,
+                      nsIDocument* aDoc,
+-                     const nsAString& aDocumentIdForTopLevelNavigation,
+                      nsIInterceptedChannel* aChannel,
+                      bool aIsReload,
+                      bool aIsSubresourceLoad,
+                      ErrorResult& aRv);
+ 
+   void
+   Update(nsIPrincipal* aPrincipal,
+          const nsACString& aScope,
+@@ -372,18 +371,17 @@ private:
+   InvalidateServiceWorkerRegistrationWorker(ServiceWorkerRegistrationInfo* aRegistration,
+                                             WhichServiceWorker aWhichOnes);
+ 
+   void
+   NotifyServiceWorkerRegistrationRemoved(ServiceWorkerRegistrationInfo* aRegistration);
+ 
+   RefPtr<GenericPromise>
+   StartControllingADocument(ServiceWorkerRegistrationInfo* aRegistration,
+-                            nsIDocument* aDoc,
+-                            const nsAString& aDocumentId);
++                            nsIDocument* aDoc);
+ 
+   void
+   StopControllingADocument(ServiceWorkerRegistrationInfo* aRegistration);
+ 
+   already_AddRefed<ServiceWorkerRegistrationInfo>
+   GetServiceWorkerRegistrationInfo(nsPIDOMWindowInner* aWindow);
+ 
+   already_AddRefed<ServiceWorkerRegistrationInfo>
+diff --git a/dom/workers/ServiceWorkerPrivate.cpp b/dom/workers/ServiceWorkerPrivate.cpp
+--- a/dom/workers/ServiceWorkerPrivate.cpp
++++ b/dom/workers/ServiceWorkerPrivate.cpp
+@@ -1344,24 +1344,24 @@ class FetchEventRunnable : public Extend
+ public:
+   FetchEventRunnable(WorkerPrivate* aWorkerPrivate,
+                      KeepAliveToken* aKeepAliveToken,
+                      nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
+                      // CSP checks might require the worker script spec
+                      // later on.
+                      const nsACString& aScriptSpec,
+                      nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration,
+-                     const nsAString& aDocumentId,
++                     const nsAString& aClientId,
+                      bool aIsReload,
+                      bool aMarkLaunchServiceWorkerEnd)
+     : ExtendableFunctionalEventWorkerRunnable(
+         aWorkerPrivate, aKeepAliveToken, aRegistration)
+     , mInterceptedChannel(aChannel)
+     , mScriptSpec(aScriptSpec)
+-    , mClientId(aDocumentId)
++    , mClientId(aClientId)
+     , mIsReload(aIsReload)
+     , mMarkLaunchServiceWorkerEnd(aMarkLaunchServiceWorkerEnd)
+     , mCacheMode(RequestCache::Default)
+     , mRequestMode(RequestMode::No_cors)
+     , mRequestRedirect(RequestRedirect::Follow)
+     // By default we set it to same-origin since normal HTTP fetches always
+     // send credentials to same-origin websites unless explicitly forbidden.
+     , mRequestCredentials(RequestCredentials::Same_origin)
+@@ -1613,17 +1613,21 @@ private:
+ 
+     MOZ_ASSERT_IF(internalReq->IsNavigationRequest(),
+                   request->Redirect() == RequestRedirect::Manual);
+ 
+     RootedDictionary<FetchEventInit> init(aCx);
+     init.mRequest = request;
+     init.mBubbles = false;
+     init.mCancelable = true;
+-    if (!mClientId.IsEmpty()) {
++    // Only expose the FetchEvent.clientId on subresource requests for now.
++    // Once we implement .resultingClientId and .targetClientId we can then
++    // start exposing .clientId on non-subresource requests as well.  See
++    // bug 1264177.
++    if (!mClientId.IsEmpty() && !internalReq->IsNavigationRequest()) {
+       init.mClientId = mClientId;
+     }
+     init.mIsReload = mIsReload;
+     RefPtr<FetchEvent> event =
+       FetchEvent::Constructor(globalObj, NS_LITERAL_STRING("fetch"), init, result);
+     if (NS_WARN_IF(result.Failed())) {
+       result.SuppressException();
+       return false;
+@@ -1659,18 +1663,17 @@ private:
+ 
+ NS_IMPL_ISUPPORTS_INHERITED(FetchEventRunnable, WorkerRunnable, nsIHttpHeaderVisitor)
+ 
+ } // anonymous namespace
+ 
+ nsresult
+ ServiceWorkerPrivate::SendFetchEvent(nsIInterceptedChannel* aChannel,
+                                      nsILoadGroup* aLoadGroup,
+-                                     const nsAString& aDocumentId,
+-                                     bool aIsReload)
++                                     const nsAString& aClientId, bool aIsReload)
+ {
+   AssertIsOnMainThread();
+ 
+   RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+   if (NS_WARN_IF(!mInfo || !swm)) {
+     return NS_ERROR_FAILURE;
+   }
+ 
+@@ -1737,17 +1740,17 @@ ServiceWorkerPrivate::SendFetchEvent(nsI
+       "ServiceWorkerRegistrationInfoProxy", registration, false));
+ 
+   RefPtr<KeepAliveToken> token = CreateEventKeepAliveToken();
+ 
+ 
+   RefPtr<FetchEventRunnable> r =
+     new FetchEventRunnable(mWorkerPrivate, token, handle,
+                            mInfo->ScriptSpec(), regInfo,
+-                           aDocumentId, aIsReload, newWorkerCreated);
++                           aClientId, aIsReload, newWorkerCreated);
+   rv = r->Init();
+   if (NS_WARN_IF(NS_FAILED(rv))) {
+     return rv;
+   }
+ 
+   if (mInfo->State() == ServiceWorkerState::Activating) {
+     mPendingFunctionalEvents.AppendElement(r.forget());
+     return NS_OK;
+@@ -1886,17 +1889,17 @@ ServiceWorkerPrivate::SpawnWorkerIfNeede
+   jsapi.Init();
+   ErrorResult error;
+   NS_ConvertUTF8toUTF16 scriptSpec(mInfo->ScriptSpec());
+ 
+   mWorkerPrivate = WorkerPrivate::Constructor(jsapi.cx(),
+                                               scriptSpec,
+                                               false, WorkerTypeService,
+                                               VoidString(),
+-                                              mInfo->Scope(),
++                                              EmptyCString(),
+                                               &info, error);
+   if (NS_WARN_IF(error.Failed())) {
+     return error.StealNSResult();
+   }
+ 
+   RenewKeepAliveToken(aWhy);
+ 
+   if (aNewWorkerCreated) {
+diff --git a/dom/workers/ServiceWorkerPrivate.h b/dom/workers/ServiceWorkerPrivate.h
+--- a/dom/workers/ServiceWorkerPrivate.h
++++ b/dom/workers/ServiceWorkerPrivate.h
+@@ -117,20 +117,18 @@ public:
+                         const nsAString& aBody,
+                         const nsAString& aTag,
+                         const nsAString& aIcon,
+                         const nsAString& aData,
+                         const nsAString& aBehavior,
+                         const nsAString& aScope);
+ 
+   nsresult
+-  SendFetchEvent(nsIInterceptedChannel* aChannel,
+-                 nsILoadGroup* aLoadGroup,
+-                 const nsAString& aDocumentId,
+-                 bool aIsReload);
++  SendFetchEvent(nsIInterceptedChannel* aChannel, nsILoadGroup* aLoadGroup,
++                 const nsAString& aClientId, bool aIsReload);
+ 
+   void
+   StoreISupports(nsISupports* aSupports);
+ 
+   void
+   RemoveISupports(nsISupports* aSupports);
+ 
+   // This will terminate the current running worker thread and drop the

+ 407 - 0
frg/work-js/mozilla-release/patches/1293277-7no8-59a1.patch

@@ -0,0 +1,407 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1513111488 18000
+# Node ID 3a2388a414a4792d0f0bf598df9f8d0b2b391ee6
+# Parent  d7fcad674dddc084a8d9aebe89ff983d0b88c0bd
+Bug 1293277 P7 Make ServiceWorkerManager use ClientHandle::Control() for claiming and ClientSource::SetController() fire the controller change event. r=baku
+
+diff --git a/dom/clients/manager/ClientSource.cpp b/dom/clients/manager/ClientSource.cpp
+--- a/dom/clients/manager/ClientSource.cpp
++++ b/dom/clients/manager/ClientSource.cpp
+@@ -356,16 +356,33 @@ ClientSource::SetController(const Servic
+   NS_ASSERT_OWNINGTHREAD(ClientSource);
+ 
+   if (mController.isSome() && mController.ref() == aServiceWorker) {
+     return;
+   }
+ 
+   mController.reset();
+   mController.emplace(aServiceWorker);
++
++  RefPtr<ServiceWorkerContainer> swc;
++  nsPIDOMWindowInner* window = GetInnerWindow();
++  if (window) {
++    RefPtr<Navigator> navigator =
++      static_cast<Navigator*>(window->GetNavigator());
++    if (navigator) {
++      swc = navigator->ServiceWorker();
++    }
++  }
++
++  // TODO: Also self.navigator.serviceWorker on workers when its exposed there
++
++  if (swc && nsContentUtils::IsSafeToRunScript()) {
++    IgnoredErrorResult ignored;
++    swc->ControllerChanged(ignored);
++  }
+ }
+ 
+ RefPtr<ClientOpPromise>
+ ClientSource::Control(const ClientControlledArgs& aArgs)
+ {
+   NS_ASSERT_OWNINGTHREAD(ClientSource);
+ 
+   SetController(ServiceWorkerDescriptor(aArgs.serviceWorker()));
+@@ -550,21 +567,49 @@ ClientSource::PostMessage(const ClientPo
+ 
+   ref = ClientOpPromise::CreateAndResolve(NS_OK, __func__);
+   return ref.forget();
+ }
+ 
+ RefPtr<ClientOpPromise>
+ ClientSource::Claim(const ClientClaimArgs& aArgs)
+ {
+-  SetController(ServiceWorkerDescriptor(aArgs.serviceWorker()));
++  RefPtr<ClientOpPromise> ref;
++
++  ServiceWorkerDescriptor swd(aArgs.serviceWorker());
+ 
+-  RefPtr<ClientOpPromise> ref =
+-    ClientOpPromise::CreateAndResolve(NS_OK, __func__);
++  // Today the ServiceWorkerManager maintains its own list of
++  // nsIDocument objects controlled by each service worker.  We
++  // need to try to update that data structure for now.  If we
++  // can't, however, then simply mark the Client as controlled.
++  // In the future this will be enough for the SWM as well since
++  // it will eventually hold ClientHandle objects instead of
++  // nsIDocuments.
++  nsPIDOMWindowInner* innerWindow = GetInnerWindow();
++  nsIDocument* doc = innerWindow ? innerWindow->GetExtantDoc() : nullptr;
++  RefPtr<ServiceWorkerManager> swm = doc ? ServiceWorkerManager::GetInstance()
++                                         : nullptr;
++  if (!swm || !doc) {
++    SetController(swd);
++    ref = ClientOpPromise::CreateAndResolve(NS_OK, __func__);
++    return ref.forget();
++  }
+ 
++  RefPtr<ClientOpPromise::Private> outerPromise =
++    new ClientOpPromise::Private(__func__);
++
++  RefPtr<GenericPromise> p = swm->MaybeClaimClient(doc, swd);
++  p->Then(mEventTarget, __func__,
++    [outerPromise] (bool aResult) {
++      outerPromise->Resolve(NS_OK, __func__);
++    }, [outerPromise] (nsresult aResult) {
++      outerPromise->Reject(aResult, __func__);
++    });
++
++  ref = outerPromise;
+   return ref.forget();
+ }
+ 
+ RefPtr<ClientOpPromise>
+ ClientSource::GetInfoAndState(const ClientGetInfoAndStateArgs& aArgs)
+ {
+   RefPtr<ClientOpPromise> ref;
+ 
+diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp
+--- a/dom/workers/ServiceWorkerManager.cpp
++++ b/dom/workers/ServiceWorkerManager.cpp
+@@ -2833,32 +2833,52 @@ ServiceWorkerManager::GetDocumentRegistr
+ NS_IMETHODIMP
+ ServiceWorkerManager::GetDocumentController(nsPIDOMWindowInner* aWindow,
+                                             nsISupports** aServiceWorker)
+ {
+   if (NS_WARN_IF(!aWindow)) {
+     return NS_ERROR_DOM_INVALID_STATE_ERR;
+   }
+ 
++  Maybe<ServiceWorkerDescriptor> controller = aWindow->GetController();
++  if (controller.isNothing()) {
++    return NS_ERROR_DOM_INVALID_STATE_ERR;
++  }
++
+   nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc();
+-  if (!doc) {
++  if (NS_WARN_IF(!doc)) {
+     return NS_ERROR_DOM_INVALID_STATE_ERR;
+   }
+ 
+-  RefPtr<ServiceWorkerRegistrationInfo> registration;
+-  nsresult rv = GetDocumentRegistration(doc, getter_AddRefs(registration));
++  nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
++  if (NS_WARN_IF(!principal)) {
++    return NS_ERROR_DOM_INVALID_STATE_ERR;
++  }
++
++  nsAutoCString scopeKey;
++  nsresult rv = PrincipalToScopeKey(principal, scopeKey);
+   if (NS_WARN_IF(NS_FAILED(rv))) {
+     return rv;
+   }
+ 
+-  MOZ_ASSERT(registration->GetActive());
+-  RefPtr<ServiceWorker> serviceWorker =
+-    registration->GetActive()->GetOrCreateInstance(aWindow);
+-
++  RefPtr<ServiceWorkerRegistrationInfo> registration =
++    GetRegistration(scopeKey, controller.ref().Scope());
++  if (NS_WARN_IF(!registration)) {
++    return NS_ERROR_DOM_INVALID_STATE_ERR;
++  }
++
++  RefPtr<ServiceWorkerInfo> active = registration->GetActive();
++  if (NS_WARN_IF(!active) ||
++      NS_WARN_IF(active->Descriptor().Id() != controller.ref().Id())) {
++    return NS_ERROR_DOM_INVALID_STATE_ERR;
++  }
++
++  RefPtr<ServiceWorker> serviceWorker = active->GetOrCreateInstance(aWindow);
+   serviceWorker.forget(aServiceWorker);
++
+   return NS_OK;
+ }
+ 
+ NS_IMETHODIMP
+ ServiceWorkerManager::GetInstalling(nsPIDOMWindowInner* aWindow,
+                                     const nsAString& aScope,
+                                     nsISupports** aServiceWorker)
+ {
+@@ -3153,77 +3173,75 @@ ServiceWorkerManager::UpdateInternal(nsI
+                                registration->GetUpdateViaCache());
+ 
+   RefPtr<UpdateJobCallback> cb = new UpdateJobCallback(aCallback);
+   job->AppendResultCallback(cb);
+ 
+   queue->ScheduleJob(job);
+ }
+ 
+-namespace {
+-
+-static void
+-FireControllerChangeOnDocument(nsIDocument* aDocument)
+-{
+-  AssertIsOnMainThread();
+-  MOZ_ASSERT(aDocument);
+-
+-  nsCOMPtr<nsPIDOMWindowInner> w = aDocument->GetInnerWindow();
+-  if (!w) {
+-    NS_WARNING("Failed to dispatch controllerchange event");
+-    return;
+-  }
+-
+-  auto* window = nsGlobalWindow::Cast(w.get());
+-  dom::Navigator* navigator = window->Navigator();
+-  if (!navigator) {
+-    return;
+-  }
+-
+-  RefPtr<ServiceWorkerContainer> container = navigator->ServiceWorker();
+-  ErrorResult result;
+-  container->ControllerChanged(result);
+-  if (result.Failed()) {
+-    NS_WARNING("Failed to dispatch controllerchange event");
+-  }
+-}
+-
+-} // anonymous namespace
+-
+-void
++already_AddRefed<GenericPromise>
+ ServiceWorkerManager::MaybeClaimClient(nsIDocument* aDocument,
+                                        ServiceWorkerRegistrationInfo* aWorkerRegistration)
+ {
+   MOZ_ASSERT(aWorkerRegistration);
+   MOZ_ASSERT(aWorkerRegistration->GetActive());
+ 
++  RefPtr<GenericPromise> ref;
++
+   // Same origin check
+   if (!aWorkerRegistration->mPrincipal->Equals(aDocument->NodePrincipal())) {
+-    return;
++    ref = GenericPromise::CreateAndReject(NS_ERROR_DOM_SECURITY_ERR, __func__);
++    return ref.forget();
+   }
+ 
+   // The registration that should be controlling the client
+   RefPtr<ServiceWorkerRegistrationInfo> matchingRegistration =
+     GetServiceWorkerRegistrationInfo(aDocument);
+ 
+   // The registration currently controlling the client
+   RefPtr<ServiceWorkerRegistrationInfo> controllingRegistration;
+   GetDocumentRegistration(aDocument, getter_AddRefs(controllingRegistration));
+ 
+   if (aWorkerRegistration != matchingRegistration ||
+-        aWorkerRegistration == controllingRegistration) {
+-    return;
++      aWorkerRegistration == controllingRegistration) {
++    ref = GenericPromise::CreateAndResolve(true, __func__);
++    return ref.forget();
+   }
+ 
+   if (controllingRegistration) {
+     StopControllingADocument(controllingRegistration);
+   }
+ 
+-  StartControllingADocument(aWorkerRegistration, aDocument);
+-  FireControllerChangeOnDocument(aDocument);
++  ref = StartControllingADocument(aWorkerRegistration, aDocument);
++  return ref.forget();
++}
++
++already_AddRefed<GenericPromise>
++ServiceWorkerManager::MaybeClaimClient(nsIDocument* aDoc,
++                                       const ServiceWorkerDescriptor& aServiceWorker)
++{
++  RefPtr<GenericPromise> ref;
++
++  nsCOMPtr<nsIPrincipal> principal =
++    PrincipalInfoToPrincipal(aServiceWorker.PrincipalInfo());
++  if (!principal) {
++    ref = GenericPromise::CreateAndResolve(false, __func__);
++    return ref.forget();
++  }
++
++  RefPtr<ServiceWorkerRegistrationInfo> registration =
++    GetRegistration(principal, aServiceWorker.Scope());
++  if (!registration) {
++    ref = GenericPromise::CreateAndResolve(false, __func__);
++    return ref.forget();
++  }
++
++  ref = MaybeClaimClient(aDoc, registration);
++  return ref.forget();
+ }
+ 
+ void
+ ServiceWorkerManager::SetSkipWaitingFlag(nsIPrincipal* aPrincipal,
+                                          const nsCString& aScope,
+                                          uint64_t aServiceWorkerID)
+ {
+   RefPtr<ServiceWorkerRegistrationInfo> registration =
+@@ -3242,38 +3260,52 @@ ServiceWorkerManager::SetSkipWaitingFlag
+   worker->SetSkipWaitingFlag();
+ 
+   if (worker->State() == ServiceWorkerState::Installed) {
+     registration->TryToActivateAsync();
+   }
+ }
+ 
+ void
+-ServiceWorkerManager::FireControllerChange(ServiceWorkerRegistrationInfo* aRegistration)
++ServiceWorkerManager::UpdateClientControllers(ServiceWorkerRegistrationInfo* aRegistration)
+ {
+   AssertIsOnMainThread();
+ 
+-  AutoTArray<nsCOMPtr<nsIDocument>, 16> documents;
++  RefPtr<ServiceWorkerInfo> activeWorker = aRegistration->GetActive();
++  MOZ_DIAGNOSTIC_ASSERT(activeWorker);
++
++  AutoTArray<nsCOMPtr<nsPIDOMWindowInner>, 16> innerWindows;
+   for (auto iter = mControlledDocuments.Iter(); !iter.Done(); iter.Next()) {
+     if (iter.UserData() != aRegistration) {
+       continue;
+     }
+ 
+     nsCOMPtr<nsIDocument> doc = do_QueryInterface(iter.Key());
+     if (NS_WARN_IF(!doc)) {
+       continue;
+     }
+ 
+-    documents.AppendElement(doc);
++    nsPIDOMWindowInner* innerWindow = doc->GetInnerWindow();
++    if (NS_WARN_IF(!innerWindow)) {
++      continue;
++    }
++
++    innerWindows.AppendElement(innerWindow);
+   }
+ 
+   // Fire event after iterating mControlledDocuments is done to prevent
+   // modification by reentering from the event handlers during iteration.
+-  for (auto& doc : documents) {
+-    FireControllerChangeOnDocument(doc);
++  for (auto& innerWindow : innerWindows) {
++    Maybe<ClientInfo> clientInfo = innerWindow->GetClientInfo();
++    if (clientInfo.isSome()) {
++      RefPtr<ClientHandle> clientHandle =
++        ClientManager::CreateHandle(clientInfo.ref(),
++                                    innerWindow->EventTargetFor(TaskCategory::Other));
++      clientHandle->Control(activeWorker->Descriptor());
++    }
+   }
+ }
+ 
+ already_AddRefed<ServiceWorkerRegistrationInfo>
+ ServiceWorkerManager::GetRegistration(nsIPrincipal* aPrincipal,
+                                       const nsACString& aScope) const
+ {
+   MOZ_ASSERT(aPrincipal);
+diff --git a/dom/workers/ServiceWorkerManager.h b/dom/workers/ServiceWorkerManager.h
+--- a/dom/workers/ServiceWorkerManager.h
++++ b/dom/workers/ServiceWorkerManager.h
+@@ -266,20 +266,24 @@ public:
+               const nsString& aMessage,
+               const nsString& aFilename,
+               const nsString& aLine,
+               uint32_t aLineNumber,
+               uint32_t aColumnNumber,
+               uint32_t aFlags,
+               JSExnType aExnType);
+ 
+-  void
++  already_AddRefed<GenericPromise>
+   MaybeClaimClient(nsIDocument* aDocument,
+                    ServiceWorkerRegistrationInfo* aWorkerRegistration);
+ 
++  already_AddRefed<GenericPromise>
++  MaybeClaimClient(nsIDocument* aDoc,
++                   const ServiceWorkerDescriptor& aServiceWorker);
++
+   void
+   SetSkipWaitingFlag(nsIPrincipal* aPrincipal, const nsCString& aScope,
+                      uint64_t aServiceWorkerID);
+ 
+   static already_AddRefed<ServiceWorkerManager>
+   GetInstance();
+ 
+   void
+@@ -418,17 +422,17 @@ private:
+   void
+   QueueFireEventOnServiceWorkerRegistrations(ServiceWorkerRegistrationInfo* aRegistration,
+                                              const nsAString& aName);
+ 
+   void
+   FireUpdateFoundOnServiceWorkerRegistrations(ServiceWorkerRegistrationInfo* aRegistration);
+ 
+   void
+-  FireControllerChange(ServiceWorkerRegistrationInfo* aRegistration);
++  UpdateClientControllers(ServiceWorkerRegistrationInfo* aRegistration);
+ 
+   void
+   StorePendingReadyPromise(nsPIDOMWindowInner* aWindow, nsIURI* aURI,
+                            Promise* aPromise);
+ 
+   bool
+   CheckReadyPromise(nsPIDOMWindowInner* aWindow, nsIURI* aURI,
+                     Promise* aPromise);
+diff --git a/dom/workers/ServiceWorkerRegistrationInfo.cpp b/dom/workers/ServiceWorkerRegistrationInfo.cpp
+--- a/dom/workers/ServiceWorkerRegistrationInfo.cpp
++++ b/dom/workers/ServiceWorkerRegistrationInfo.cpp
+@@ -269,23 +269,18 @@ ServiceWorkerRegistrationInfo::Activate(
+     return;
+   }
+ 
+   TransitionWaitingToActive();
+ 
+   // FIXME(nsm): Unlink appcache if there is one.
+ 
+   // "Queue a task to fire a simple event named controllerchange..."
+-  nsCOMPtr<nsIRunnable> controllerChangeRunnable =
+-    NewRunnableMethod<RefPtr<ServiceWorkerRegistrationInfo>>(
+-      "dom::workers::ServiceWorkerManager::FireControllerChange",
+-      swm,
+-      &ServiceWorkerManager::FireControllerChange,
+-      this);
+-  NS_DispatchToMainThread(controllerChangeRunnable);
++  MOZ_DIAGNOSTIC_ASSERT(mActiveWorker);
++  swm->UpdateClientControllers(this);
+ 
+   nsCOMPtr<nsIRunnable> failRunnable = NewRunnableMethod<bool>(
+     "dom::workers::ServiceWorkerRegistrationInfo::FinishActivate",
+     this,
+     &ServiceWorkerRegistrationInfo::FinishActivate,
+     false /* success */);
+ 
+   nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> handle(

+ 26 - 0
frg/work-js/mozilla-release/patches/1293277-9-59a1.patch

@@ -0,0 +1,26 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1513111488 18000
+# Node ID ec647e2200163d7e37f22c7c9fb471250beb5540
+# Parent  976c21cebdc08b6ec7e51e9e43917ca849dd2d60
+Bug 1293277 P9 Disable browser_service_workers_push_service.js on non-e10s due to races. r=jryans
+
+diff --git a/devtools/client/aboutdebugging/test/browser.ini b/devtools/client/aboutdebugging/test/browser.ini
+--- a/devtools/client/aboutdebugging/test/browser.ini
++++ b/devtools/client/aboutdebugging/test/browser.ini
+@@ -42,14 +42,15 @@ tags = webextensions
+ [browser_service_workers.js]
+ [browser_service_workers_fetch_flag.js]
+ skip-if = os == 'mac' # bug 1333759
+ [browser_service_workers_multi_content_process.js]
+ skip-if = !e10s # This test is only valid in e10s
+ [browser_service_workers_not_compatible.js]
+ [browser_service_workers_push.js]
+ [browser_service_workers_push_service.js]
++skip-if = !e10s # Bug 1424895
+ [browser_service_workers_start.js]
+ [browser_service_workers_status.js]
+ [browser_service_workers_timeout.js]
+ skip-if = true # Bug 1232931
+ [browser_service_workers_unregister.js]
+ [browser_tabs.js]

+ 2 - 2
frg/work-js/mozilla-release/patches/1340901-87a1.patch

@@ -3,7 +3,7 @@
 # Date 1611682034 0
 #      Tue Jan 26 17:27:14 2021 +0000
 # Node ID 2252ab087d8039acf6a5231155270dbef28205db
-# Parent  905c4bd465611986cdd62cdf44e3a5bcc6246200
+# Parent  ea4892894d8fa3d194e57d60573d6745c46a00f8
 Bug 1340901 - Update Snappy to version 1.1.8. r=dom-workers-and-storage-reviewers,asuth
 
 Add a static assertion in IndexedDB to detect future updates.
@@ -26,12 +26,12 @@ diff --git a/dom/indexedDB/ActorsParent.cpp b/dom/indexedDB/ActorsParent.cpp
 +
  using namespace mozilla::dom::quota;
  using namespace mozilla::ipc;
+ using mozilla::dom::quota::Client;
  
  namespace {
  
  class ConnectionPool;
  class Cursor;
- class Database;
 diff --git a/other-licenses/snappy/README b/other-licenses/snappy/README
 --- a/other-licenses/snappy/README
 +++ b/other-licenses/snappy/README

+ 4 - 4
frg/work-js/mozilla-release/patches/1393609-60a1.patch

@@ -2,7 +2,7 @@
 # User ewhite7 <ewhite7@myseneca.ca>
 # Date 1515206033 18000
 # Node ID 1684292955256a9392ed2ef153e3e51d10d706a5
-# Parent  c4b9b71f90dc0f06b83e39d732ae4f8adc7b1393
+# Parent  c694d3f0dff422c094bc05906303ed74e6bfd9a3
 Bug 1393609 - Add a test for line-height in console.log custom style; r=nchevobbe.
 
 MozReview-Commit-ID: BD2fjv6u4b2
@@ -166,13 +166,13 @@ diff --git a/devtools/client/webconsole/new-console-output/test/fixtures/stubs/c
    },
    "groupId": null,
    "exceptionDocURL": null,
-@@ -2538,18 +2538,18 @@ stubPackets.set(`console.log(%cfoobar)`,
-     "counter": null,
+@@ -2573,18 +2573,18 @@ stubPackets.set(`console.log(%cfoobar)`,
      "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
      "functionName": "triggerPacket",
      "groupName": "",
      "level": "log",
      "lineNumber": 2,
+     "prefix": "",
      "private": false,
      "styles": [
 -      "color:blue;font-size:1.3em;background:url('http://example.com/test');position:absolute;top:10px",
@@ -187,7 +187,7 @@ diff --git a/devtools/client/webconsole/new-console-output/test/fixtures/stubs/c
    }
  });
  
-@@ -2656,17 +2656,17 @@ stubPackets.set(`console.groupEnd(%cfoo%
+@@ -2695,17 +2695,17 @@ stubPackets.set(`console.groupEnd(%cfoo%
  stubPackets.set(`console.dir({C, M, Y, K})`, {
    "from": "server1.conn0.child1/consoleActor2",
    "type": "consoleAPICall",

+ 42 - 0
frg/work-js/mozilla-release/patches/1403866-58a1.patch

@@ -0,0 +1,42 @@
+# HG changeset patch
+# User Andrea Marchesini <amarchesini@mozilla.com>
+# Date 1506669658 -7200
+# Node ID c5e45bf14e9ab27b9cedac2b47d96bdb3725f3ca
+# Parent  de4e28c5c4a64a3aef78180b58b4bd7f27b8f05b
+Bug 1403866 - No AutoSafeJSContext in Console.cpp, r=bz
+
+diff --git a/dom/console/Console.cpp b/dom/console/Console.cpp
+--- a/dom/console/Console.cpp
++++ b/dom/console/Console.cpp
+@@ -397,25 +397,28 @@ protected:
+ 
+     WorkerPrivate* wp = mWorkerPrivate;
+     while (wp->GetParent()) {
+       wp = wp->GetParent();
+     }
+ 
+     MOZ_ASSERT(!wp->GetWindow());
+ 
+-    AutoSafeJSContext cx;
++    AutoJSAPI jsapi;
++    jsapi.Init();
++
++    JSContext* cx = jsapi.cx();
+ 
+     JS::Rooted<JSObject*> global(cx, mConsole->GetOrCreateSandbox(cx, wp->GetPrincipal()));
+     if (NS_WARN_IF(!global)) {
+       return;
+     }
+ 
+-    // The CreateSandbox call returns a proxy to the actual sandbox object. We
+-    // don't need a proxy here.
++    // The GetOrCreateSandbox call returns a proxy to the actual sandbox object.
++    // We don't need a proxy here.
+     global = js::UncheckedUnwrap(global);
+ 
+     JSAutoCompartment ac(cx, global);
+ 
+     RunConsole(cx, nullptr, nullptr);
+   }
+ 
+   void

+ 31 - 0
frg/work-js/mozilla-release/patches/1415056-1-59a1.patch

@@ -0,0 +1,31 @@
+# HG changeset patch
+# User Gijs Kruitbosch <gijskruitbosch@gmail.com>
+# Date 1512389710 0
+# Node ID 8f0234c0ffb70c97ce628b25d9cd3e0ae84deacf
+# Parent  745dc6d3cc2dbf2cb9e2b235b575f57de6aca566
+Bug 1415056 - re-enable animations in the palette in customize mode, r=jaws
+
+MozReview-Commit-ID: DXYBPCdXMBG
+
+diff --git a/browser/components/customizableui/CustomizeMode.jsm b/browser/components/customizableui/CustomizeMode.jsm
+--- a/browser/components/customizableui/CustomizeMode.jsm
++++ b/browser/components/customizableui/CustomizeMode.jsm
+@@ -1795,17 +1795,17 @@ CustomizeMode.prototype = {
+     }
+ 
+     // Do nothing if the widget is not allowed to move to the target area.
+     if (targetArea.id != kPaletteId &&
+         !CustomizableUI.canWidgetMoveToArea(draggedItemId, targetArea.id)) {
+       return;
+     }
+ 
+-    let targetAreaType = CustomizableUI.getAreaType(targetArea.id);
++    let targetAreaType = CustomizableUI.getPlaceForItem(targetArea);
+     let targetNode = this._getDragOverNode(aEvent, targetArea, targetAreaType, draggedItemId);
+ 
+     // We need to determine the place that the widget is being dropped in
+     // the target.
+     let dragOverItem, dragValue;
+     if (targetNode == targetArea.customizationTarget) {
+       // We'll assume if the user is dragging directly over the target, that
+       // they're attempting to append a child to that target.

+ 33 - 0
frg/work-js/mozilla-release/patches/1415056-2-59a1.patch

@@ -0,0 +1,33 @@
+# HG changeset patch
+# User Gijs Kruitbosch <gijskruitbosch@gmail.com>
+# Date 1512390699 0
+# Node ID 7ed8bedfc83cf65bb55097def9052d705b497294
+# Parent  d06ac6c5957ea9dcec4fb452208a5df8dc9961d4
+Bug 1415056 - reverse direction of horizontal shift in RTL when shifting palette items in customize mode, r=jaws
+
+This also fixes the vertical offset because we take the correct branch in the yDiff if block lower down.
+
+MozReview-Commit-ID: KbHkRx1575X
+
+diff --git a/browser/components/customizableui/DragPositionManager.jsm b/browser/components/customizableui/DragPositionManager.jsm
+--- a/browser/components/customizableui/DragPositionManager.jsm
++++ b/browser/components/customizableui/DragPositionManager.jsm
+@@ -275,17 +275,17 @@ AreaPositionManager.prototype = {
+       }
+     } else {
+       // We don't have a sibling whose position we can use. First, let's see
+       // if we're also the first item (which complicates things):
+       let firstNode = this._firstInRow(aNode);
+       if (aNode == firstNode) {
+         // Maybe we stored the horizontal distance between non-wide nodes,
+         // if not, we'll use the width of the incoming node as a proxy:
+-        xDiff = this._horizontalDistance || aSize.width;
++        xDiff = this._horizontalDistance || (this._dir == "ltr" ? 1 : -1) * aSize.width;
+       } else {
+         // If not, we should be able to get the distance to the previous node
+         // and use the inverse, unless there's no room for another node (ie we
+         // are the last node and there's no room for another one)
+         xDiff = this._moveNextBasedOnPrevious(aNode, nodeBounds, firstNode);
+       }
+     }
+ 

+ 393 - 0
frg/work-js/mozilla-release/patches/1418243-1no2-59a1.patch

@@ -0,0 +1,393 @@
+# HG changeset patch
+# User Chung-Sheng Fu <cfu@mozilla.com>
+# Date 1516136340 -7200
+# Node ID 25fd1d8d42b47c72493fdabcdb27c5a1708a01ad
+# Parent  547a545b73356f940b0e0bd5067c328066061d98
+Bug 1418243 - Fix SecurityPolicyViolationEvent.violatedDirective. r=ckerschb
+
+MozReview-Commit-ID: 8DQ7CI5exUL
+
+diff --git a/dom/security/nsCSPContext.cpp b/dom/security/nsCSPContext.cpp
+--- a/dom/security/nsCSPContext.cpp
++++ b/dom/security/nsCSPContext.cpp
+@@ -1,14 +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 <string>
++#include <unordered_set>
++
+ #include "nsCOMPtr.h"
+ #include "nsContentPolicyUtils.h"
+ #include "nsContentUtils.h"
+ #include "nsCSPContext.h"
+ #include "nsCSPParser.h"
+ #include "nsCSPService.h"
+ #include "nsError.h"
+ #include "nsIAsyncVerifyRedirectCallback.h"
+@@ -59,16 +62,39 @@ GetCspContextLog()
+   return gCspContextPRLog;
+ }
+ 
+ #define CSPCONTEXTLOG(args) MOZ_LOG(GetCspContextLog(), mozilla::LogLevel::Debug, args)
+ #define CSPCONTEXTLOGENABLED() MOZ_LOG_TEST(GetCspContextLog(), mozilla::LogLevel::Debug)
+ 
+ static const uint32_t CSP_CACHE_URI_CUTOFF_SIZE = 512;
+ 
++#ifdef DEBUG
++/**
++ * This function is only used for verification purposes within
++ * GatherSecurityPolicyViolationEventData.
++ */
++static bool
++ValidateDirectiveName(const nsAString& aDirective)
++{
++  static const auto directives = [] () {
++    std::unordered_set<std::string> directives;
++    constexpr size_t dirLen = sizeof(CSPStrDirectives) / sizeof(CSPStrDirectives[0]);
++    for (size_t i = 0; i < dirLen; ++i) {
++      directives.insert(CSPStrDirectives[i]);
++    }
++    return directives;
++  } ();
++
++  nsAutoString directive(aDirective);
++  auto itr = directives.find(NS_ConvertUTF16toUTF8(directive).get());
++  return itr != directives.end();
++}
++#endif // DEBUG
++
+ /**
+  * Creates a key for use in the ShouldLoad cache.
+  * Looks like: <uri>!<nsIContentPolicy::LOAD_TYPE>
+  */
+ nsresult
+ CreateCacheKey_Internal(nsIURI* aContentLocation,
+                         nsContentPolicyType aContentType,
+                         nsACString& outCacheKey)
+@@ -864,16 +890,18 @@ nsCSPContext::GatherSecurityPolicyViolat
+   uint32_t aViolatedPolicyIndex,
+   nsAString& aSourceFile,
+   nsAString& aScriptSample,
+   uint32_t aLineNum,
+   mozilla::dom::SecurityPolicyViolationEventInit& aViolationEventInit)
+ {
+   NS_ENSURE_ARG_MAX(aViolatedPolicyIndex, mPolicies.Length() - 1);
+ 
++  MOZ_ASSERT(ValidateDirectiveName(aViolatedDirective), "Invalid directive name");
++
+   nsresult rv;
+ 
+   // document-uri
+   nsAutoCString reportDocumentURI;
+   StripURIForReporting(mSelfURI, mSelfURI, reportDocumentURI);
+   aViolationEventInit.mDocumentURI = NS_ConvertUTF8toUTF16(reportDocumentURI);
+ 
+   // referrer
+@@ -895,21 +923,24 @@ nsCSPContext::GatherSecurityPolicyViolat
+     if (reportBlockedURI.IsEmpty()) {
+       // this can happen for frame-ancestors violation where the violating
+       // ancestor is cross-origin.
+       NS_WARNING("No blocked URI (null aBlockedContentSource) for CSP violation report.");
+     }
+     aViolationEventInit.mBlockedURI = NS_ConvertUTF8toUTF16(reportBlockedURI);
+   }
+ 
+-  // violated-directive
+-  aViolationEventInit.mViolatedDirective = aViolatedDirective;
++  // effective-directive
++  // The name of the policy directive that was violated.
++  aViolationEventInit.mEffectiveDirective = aViolatedDirective;
+ 
+-  // effective-directive
+-  aViolationEventInit.mEffectiveDirective = aViolatedDirective;
++  // violated-directive
++  // In CSP2, the policy directive that was violated, as it appears in the policy.
++  // In CSP3, the same as effective-directive.
++  aViolationEventInit.mViolatedDirective = aViolatedDirective;
+ 
+   // original-policy
+   nsAutoString originalPolicy;
+   rv = this->GetPolicyString(aViolatedPolicyIndex, originalPolicy);
+   NS_ENSURE_SUCCESS(rv, rv);
+   aViolationEventInit.mOriginalPolicy = originalPolicy;
+ 
+   // source-file
+@@ -1211,28 +1242,31 @@ class CSPReportSenderRunnable final : pu
+         mObserverSubject = do_QueryInterface(supportscstr);
+       }
+     }
+ 
+     NS_IMETHOD Run() override
+     {
+       MOZ_ASSERT(NS_IsMainThread());
+ 
++      nsresult rv;
++
+       // 0) prepare violation data
+       mozilla::dom::SecurityPolicyViolationEventInit init;
+-      mCSPContext->GatherSecurityPolicyViolationEventData(
++      rv = mCSPContext->GatherSecurityPolicyViolationEventData(
+         mBlockedContentSource, mOriginalURI,
+         mViolatedDirective, mViolatedPolicyIndex,
+         mSourceFile, mScriptSample, mLineNum,
+         init);
++      NS_ENSURE_SUCCESS(rv, rv);
+ 
+       // 1) notify observers
+       nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
+       NS_ASSERTION(observerService, "needs observer service");
+-      nsresult rv = observerService->NotifyObservers(mObserverSubject,
++      rv = observerService->NotifyObservers(mObserverSubject,
+                                                      CSP_VIOLATION_TOPIC,
+                                                      mViolatedDirective.get());
+       NS_ENSURE_SUCCESS(rv, rv);
+ 
+       // 2) send reports for the policy that was violated
+       mCSPContext->SendReports(init, mViolatedPolicyIndex);
+ 
+       // 3) log to console (one per policy violation)
+diff --git a/dom/security/nsCSPUtils.cpp b/dom/security/nsCSPUtils.cpp
+--- a/dom/security/nsCSPUtils.cpp
++++ b/dom/security/nsCSPUtils.cpp
+@@ -1241,16 +1241,22 @@ nsCSPDirective::visitSrcs(nsCSPSrcVisito
+   return true;
+ }
+ 
+ bool nsCSPDirective::equals(CSPDirective aDirective) const
+ {
+   return (mDirective == aDirective);
+ }
+ 
++void
++nsCSPDirective::getDirName(nsAString& outStr) const
++{
++  outStr.AppendASCII(CSP_CSPDirectiveToString(mDirective));
++}
++
+ /* =============== nsCSPChildSrcDirective ============= */
+ 
+ nsCSPChildSrcDirective::nsCSPChildSrcDirective(CSPDirective aDirective)
+   : nsCSPDirective(aDirective)
+   , mRestrictFrames(false)
+   , mRestrictWorkers(false)
+ {
+ }
+@@ -1326,16 +1332,23 @@ nsBlockAllMixedContentDirective::~nsBloc
+ 
+ void
+ nsBlockAllMixedContentDirective::toString(nsAString& outStr) const
+ {
+   outStr.AppendASCII(CSP_CSPDirectiveToString(
+     nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT));
+ }
+ 
++void
++nsBlockAllMixedContentDirective::getDirName(nsAString& outStr) const
++{
++  outStr.AppendASCII(CSP_CSPDirectiveToString(
++    nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT));
++}
++
+ /* =============== nsUpgradeInsecureDirective ============= */
+ 
+ nsUpgradeInsecureDirective::nsUpgradeInsecureDirective(CSPDirective aDirective)
+ : nsCSPDirective(aDirective)
+ {
+ }
+ 
+ nsUpgradeInsecureDirective::~nsUpgradeInsecureDirective()
+@@ -1344,16 +1357,23 @@ nsUpgradeInsecureDirective::~nsUpgradeIn
+ 
+ void
+ nsUpgradeInsecureDirective::toString(nsAString& outStr) const
+ {
+   outStr.AppendASCII(CSP_CSPDirectiveToString(
+     nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE));
+ }
+ 
++void
++nsUpgradeInsecureDirective::getDirName(nsAString& outStr) const
++{
++  outStr.AppendASCII(CSP_CSPDirectiveToString(
++    nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE));
++}
++
+ /* ===== nsRequireSRIForDirective ========================= */
+ 
+ nsRequireSRIForDirective::nsRequireSRIForDirective(CSPDirective aDirective)
+ : nsCSPDirective(aDirective)
+ {
+ }
+ 
+ nsRequireSRIForDirective::~nsRequireSRIForDirective()
+@@ -1395,16 +1415,23 @@ nsRequireSRIForDirective::restrictsConte
+ bool
+ nsRequireSRIForDirective::allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce,
+                                  bool aParserCreated) const
+ {
+   // can only disallow CSP_REQUIRE_SRI_FOR.
+   return (aKeyword != CSP_REQUIRE_SRI_FOR);
+ }
+ 
++void
++nsRequireSRIForDirective::getDirName(nsAString& outStr) const
++{
++  outStr.AppendASCII(CSP_CSPDirectiveToString(
++    nsIContentSecurityPolicy::REQUIRE_SRI_FOR));
++}
++
+ /* ===== nsCSPPolicy ========================= */
+ 
+ nsCSPPolicy::nsCSPPolicy()
+   : mUpgradeInsecDir(nullptr)
+   , mReportOnly(false)
+ {
+   CSPUTILSLOG(("nsCSPPolicy::nsCSPPolicy"));
+ }
+@@ -1448,32 +1475,32 @@ nsCSPPolicy::permits(CSPDirective aDir,
+   nsCSPDirective* defaultDir = nullptr;
+ 
+   // Try to find a relevant directive
+   // These directive arrays are short (1-5 elements), not worth using a hashtable.
+   for (uint32_t i = 0; i < mDirectives.Length(); i++) {
+     if (mDirectives[i]->equals(aDir)) {
+       if (!mDirectives[i]->permits(aUri, aNonce, aWasRedirected, mReportOnly,
+                                    mUpgradeInsecDir, aParserCreated)) {
+-        mDirectives[i]->toString(outViolatedDirective);
++        mDirectives[i]->getDirName(outViolatedDirective);
+         return false;
+       }
+       return true;
+     }
+     if (mDirectives[i]->isDefaultDirective()) {
+       defaultDir = mDirectives[i];
+     }
+   }
+ 
+   // If the above loop runs through, we haven't found a matching directive.
+   // Avoid relooping, just store the result of default-src while looping.
+   if (!aSpecific && defaultDir) {
+     if (!defaultDir->permits(aUri, aNonce, aWasRedirected, mReportOnly,
+                              mUpgradeInsecDir, aParserCreated)) {
+-      defaultDir->toString(outViolatedDirective);
++      defaultDir->getDirName(outViolatedDirective);
+       return false;
+     }
+     return true;
+   }
+ 
+   // Nothing restricts this, so we're allowing the load
+   // See bug 764937
+   return true;
+@@ -1590,27 +1617,27 @@ nsCSPPolicy::hasDirective(CSPDirective a
+  */
+ void
+ nsCSPPolicy::getDirectiveStringForContentType(nsContentPolicyType aContentType,
+                                               nsAString& outDirective) const
+ {
+   nsCSPDirective* defaultDir = nullptr;
+   for (uint32_t i = 0; i < mDirectives.Length(); i++) {
+     if (mDirectives[i]->restrictsContentType(aContentType)) {
+-      mDirectives[i]->toString(outDirective);
++      mDirectives[i]->getDirName(outDirective);
+       return;
+     }
+     if (mDirectives[i]->isDefaultDirective()) {
+       defaultDir = mDirectives[i];
+     }
+   }
+   // if we haven't found a matching directive yet,
+   // the contentType must be restricted by the default directive
+   if (defaultDir) {
+-    defaultDir->toString(outDirective);
++    defaultDir->getDirName(outDirective);
+     return;
+   }
+   NS_ASSERTION(false, "Can not query directive string for contentType!");
+   outDirective.AppendASCII("couldNotQueryViolatedDirective");
+ }
+ 
+ void
+ nsCSPPolicy::getDirectiveAsString(CSPDirective aDir, nsAString& outDirective) const
+diff --git a/dom/security/nsCSPUtils.h b/dom/security/nsCSPUtils.h
+--- a/dom/security/nsCSPUtils.h
++++ b/dom/security/nsCSPUtils.h
+@@ -466,16 +466,18 @@ class nsCSPDirective {
+      { return mDirective == nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE; }
+ 
+     virtual bool equals(CSPDirective aDirective) const;
+ 
+     void getReportURIs(nsTArray<nsString> &outReportURIs) const;
+ 
+     bool visitSrcs(nsCSPSrcVisitor* aVisitor) const;
+ 
++    virtual void getDirName(nsAString& outStr) const;
++
+   protected:
+     CSPDirective            mDirective;
+     nsTArray<nsCSPBaseSrc*> mSrcs;
+ };
+ 
+ /* =============== nsCSPChildSrcDirective ============= */
+ 
+ /*
+@@ -544,16 +546,18 @@ class nsBlockAllMixedContentDirective : 
+     bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce,
+                 bool aParserCreated) const override
+       { return false; }
+ 
+     void toString(nsAString& outStr) const override;
+ 
+     void addSrcs(const nsTArray<nsCSPBaseSrc*>& aSrcs) override
+       {  MOZ_ASSERT(false, "block-all-mixed-content does not hold any srcs"); }
++
++    void getDirName(nsAString& outStr) const override;
+ };
+ 
+ /* =============== nsUpgradeInsecureDirective === */
+ 
+ /*
+  * Upgrading insecure requests includes the following actors:
+  * (1) CSP:
+  *     The CSP implementation whitelists the http-request
+@@ -597,16 +601,18 @@ class nsUpgradeInsecureDirective : publi
+     bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce,
+                 bool aParserCreated) const override
+       { return false; }
+ 
+     void toString(nsAString& outStr) const override;
+ 
+     void addSrcs(const nsTArray<nsCSPBaseSrc*>& aSrcs) override
+       {  MOZ_ASSERT(false, "upgrade-insecure-requests does not hold any srcs"); }
++
++    void getDirName(nsAString& outStr) const override;
+ };
+ 
+ /* ===== nsRequireSRIForDirective ========================= */
+ 
+ class nsRequireSRIForDirective : public nsCSPDirective {
+   public:
+     explicit nsRequireSRIForDirective(CSPDirective aDirective);
+     ~nsRequireSRIForDirective();
+@@ -614,16 +620,17 @@ class nsRequireSRIForDirective : public 
+     void toString(nsAString& outStr) const override;
+ 
+     void addType(nsContentPolicyType aType)
+       { mTypes.AppendElement(aType); }
+     bool hasType(nsContentPolicyType aType) const;
+     bool restrictsContentType(nsContentPolicyType aType) const override;
+     bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce,
+                 bool aParserCreated) const override;
++    void getDirName(nsAString& outStr) const override;
+ 
+   private:
+     nsTArray<nsContentPolicyType> mTypes;
+ };
+ 
+ /* =============== nsCSPPolicy ================== */
+ 
+ class nsCSPPolicy {

+ 194 - 0
frg/work-js/mozilla-release/patches/1418243-3-59a1.patch

@@ -0,0 +1,194 @@
+# HG changeset patch
+# User Chung-Sheng Fu <cfu@mozilla.com>
+# Date 1516136400 -7200
+# Node ID a1752a8ab7a040da7807d33d5bda0c07721a33d2
+# Parent  222a9d613f1b2ad07faa123b5dc43b4799457349
+Bug 1418243 - Fix mochitest failures due to violationDirective change. r=ckerschb
+
+MozReview-Commit-ID: AphtAxYo6Hr
+
+diff --git a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_cspro.js b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_cspro.js
+--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_cspro.js
++++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_cspro.js
+@@ -16,21 +16,21 @@ See Bug 1010953.
+ 
+ "use strict";
+ 
+ const TEST_URI = "data:text/html;charset=utf8,Web Console CSP report only test";
+ const TEST_VIOLATION = "http://example.com/browser/devtools/client/webconsole/" +
+                        "new-console-output/test/mochitest/test-cspro.html";
+ const CSP_VIOLATION_MSG =
+   "Content Security Policy: The page\u2019s settings blocked the loading of a resource " +
+-  "at http://some.example.com/cspro.png (\u201cimg-src http://example.com\u201d).";
++  "at http://some.example.com/cspro.png (\u201cimg-src\u201d).";
+ const CSP_REPORT_MSG =
+   "Content Security Policy: The page\u2019s settings observed the loading of a " +
+   "resource at http://some.example.com/cspro.js " +
+-  "(\u201cscript-src http://example.com\u201d). A CSP report is being sent.";
++  "(\u201cscript-src\u201d). A CSP report is being sent.";
+ 
+ add_task(async function () {
+   let hud = await openNewTabAndConsole(TEST_URI);
+ 
+   let onCspViolationMessage = waitForMessage(hud, CSP_VIOLATION_MSG, ".message.error");
+   let onCspReportMessage = waitForMessage(hud, CSP_REPORT_MSG, ".message.error");
+ 
+   info("Load a page with CSP warnings.");
+diff --git a/devtools/client/webconsole/test/browser_webconsole_bug_1010953_cspro.js b/devtools/client/webconsole/test/browser_webconsole_bug_1010953_cspro.js
+--- a/devtools/client/webconsole/test/browser_webconsole_bug_1010953_cspro.js
++++ b/devtools/client/webconsole/test/browser_webconsole_bug_1010953_cspro.js
+@@ -18,21 +18,21 @@ CSP_REPORT_MSG are confirmed to be found
+ 
+ const TEST_URI = "data:text/html;charset=utf8,Web Console CSP report only " +
+                  "test (bug 1010953)";
+ const TEST_VIOLATION = "http://example.com/browser/devtools/client/" +
+                        "webconsole/test/test_bug_1010953_cspro.html";
+ const CSP_VIOLATION_MSG = "Content Security Policy: The page\u2019s settings " +
+                           "blocked the loading of a resource at " +
+                           "http://some.example.com/test.png " +
+-                          "(\u201cimg-src http://example.com\u201d).";
++                          "(\u201cimg-src\u201d).";
+ const CSP_REPORT_MSG = "Content Security Policy: The page\u2019s settings " +
+                        "observed the loading of a resource at " +
+                        "http://some.example.com/test_bug_1010953_cspro.js " +
+-                       "(\u201cscript-src http://example.com\u201d). A CSP report is " +
++                       "(\u201cscript-src\u201d). A CSP report is " +
+                        "being sent.";
+ 
+ add_task(function* () {
+   let { browser } = yield loadTab(TEST_URI);
+ 
+   let hud = yield openConsole();
+ 
+   hud.jsterm.clearOutput();
+diff --git a/devtools/client/webconsole/test/browser_webconsole_bug_1247459_violation.js b/devtools/client/webconsole/test/browser_webconsole_bug_1247459_violation.js
+--- a/devtools/client/webconsole/test/browser_webconsole_bug_1247459_violation.js
++++ b/devtools/client/webconsole/test/browser_webconsole_bug_1247459_violation.js
+@@ -8,18 +8,17 @@
+ 
+ "use strict";
+ 
+ const TEST_URI = "data:text/html;charset=utf8,Web Console CSP violation test";
+ const TEST_VIOLATION = "https://example.com/browser/devtools/client/" +
+                        "webconsole/test/test_bug_1247459_violation.html";
+ const CSP_VIOLATION_MSG = "Content Security Policy: The page\u2019s settings " +
+                           "blocked the loading of a resource at " +
+-                          "http://some.example.com/test.png (\u201cimg-src " +
+-                          "https://example.com\u201d).";
++                          "http://some.example.com/test.png (\u201cimg-src\u201d).";
+ 
+ add_task(function* () {
+   let { browser } = yield loadTab(TEST_URI);
+ 
+   let hud = yield openConsole();
+ 
+   hud.jsterm.clearOutput();
+ 
+diff --git a/devtools/client/webconsole/test/browser_webconsole_bug_770099_violation.js b/devtools/client/webconsole/test/browser_webconsole_bug_770099_violation.js
+--- a/devtools/client/webconsole/test/browser_webconsole_bug_770099_violation.js
++++ b/devtools/client/webconsole/test/browser_webconsole_bug_770099_violation.js
+@@ -7,18 +7,17 @@
+ 
+ "use strict";
+ 
+ const TEST_URI = "data:text/html;charset=utf8,Web Console CSP violation test";
+ const TEST_VIOLATION = "https://example.com/browser/devtools/client/" +
+                        "webconsole/test/test_bug_770099_violation.html";
+ const CSP_VIOLATION_MSG = "Content Security Policy: The page\u2019s settings " +
+                           "blocked the loading of a resource at " +
+-                          "http://some.example.com/test.png (\u201cdefault-src " +
+-                          "https://example.com\u201d).";
++                          "http://some.example.com/test.png (\u201cdefault-src\u201d).";
+ 
+ add_task(function* () {
+   let { browser } = yield loadTab(TEST_URI);
+ 
+   let hud = yield openConsole();
+ 
+   hud.jsterm.clearOutput();
+ 
+diff --git a/dom/security/test/csp/test_frame_ancestors_ro.html b/dom/security/test/csp/test_frame_ancestors_ro.html
+--- a/dom/security/test/csp/test_frame_ancestors_ro.html
++++ b/dom/security/test/csp/test_frame_ancestors_ro.html
+@@ -18,17 +18,17 @@ let testResults = {
+ 
+ function checkResults(reportObj) {
+   let cspReport = reportObj["csp-report"];
+   is(cspReport["document-uri"], docUri, "Incorrect document-uri");
+ 
+   // we can not test for the whole referrer since it includes platform specific information
+   is(cspReport["referrer"], document.location.toString(), "Incorrect referrer");
+   is(cspReport["blocked-uri"], document.location.toString(), "Incorrect blocked-uri");
+-  is(cspReport["violated-directive"], "frame-ancestors 'none'", "Incorrect violated-directive");
++  is(cspReport["violated-directive"], "frame-ancestors", "Incorrect violated-directive");
+   is(cspReport["original-policy"], "frame-ancestors 'none'; report-uri http://mochi.test:8888/foo.sjs", "Incorrect original-policy");
+   testResults.reportFired = true;
+ }
+ 
+ let chromeScriptUrl = SimpleTest.getTestFileURL("file_report_chromescript.js");
+ let script = SpecialPowers.loadChromeScript(chromeScriptUrl);
+ 
+ script.addMessageListener('opening-request-completed', function ml(msg) {
+diff --git a/dom/security/test/csp/test_report.html b/dom/security/test/csp/test_report.html
+--- a/dom/security/test/csp/test_report.html
++++ b/dom/security/test/csp/test_report.html
+@@ -45,17 +45,17 @@ window.checkResults = function(reportObj
+   is(cspReport["document-uri"], docUri, "Incorrect document-uri");
+ 
+   // we can not test for the whole referrer since it includes platform specific information
+   ok(cspReport["referrer"].startsWith("http://mochi.test:8888/tests/dom/security/test/csp/test_report.html"),
+      "Incorrect referrer");
+ 
+   is(cspReport["blocked-uri"], "self", "Incorrect blocked-uri");
+ 
+-  is(cspReport["violated-directive"], "default-src 'none'", "Incorrect violated-directive");
++  is(cspReport["violated-directive"], "default-src", "Incorrect violated-directive");
+ 
+   is(cspReport["original-policy"], "default-src 'none'; report-uri http://mochi.test:8888/foo.sjs",
+      "Incorrect original-policy");
+ 
+   is(cspReport["source-file"], docUri, "Incorrect source-file");
+ 
+   is(cspReport["script-sample"], "\n    var foo = \"propEscFoo\";\n    var bar...",
+      "Incorrect script-sample");
+diff --git a/dom/security/test/csp/test_report_for_import.html b/dom/security/test/csp/test_report_for_import.html
+--- a/dom/security/test/csp/test_report_for_import.html
++++ b/dom/security/test/csp/test_report_for_import.html
+@@ -45,17 +45,17 @@ function checkResults(reportStr) {
+     var reportObj = JSON.parse(reportStr);
+     var cspReport = reportObj["csp-report"];
+ 
+     is(cspReport["document-uri"], DOC_URI, "Incorrect document-uri");
+     is(cspReport["referrer"],
+        "http://mochi.test:8888/tests/dom/security/test/csp/test_report_for_import.html",
+        "Incorrect referrer");
+     is(cspReport["violated-directive"],
+-       "style-src http://mochi.test:8888",
++       "style-src",
+        "Incorrect violated-directive");
+     is(cspReport["original-policy"],
+        "style-src http://mochi.test:8888; report-uri " +
+        "http://mochi.test:8888/tests/dom/security/test/csp/file_report_for_import_server.sjs?report",
+        "Incorrect original-policy");
+     is(cspReport["blocked-uri"],
+        "http://example.com",
+        "Incorrect blocked-uri");
+diff --git a/dom/security/test/csp/test_security_policy_violation_event.html b/dom/security/test/csp/test_security_policy_violation_event.html
+--- a/dom/security/test/csp/test_security_policy_violation_event.html
++++ b/dom/security/test/csp/test_security_policy_violation_event.html
+@@ -6,14 +6,14 @@
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({
+   set: [
+     ["security.csp.enable_violation_events", true]
+   ]
+ });
+ document.addEventListener("securitypolicyviolation", (e) => {
+   SimpleTest.is(e.blockedURI, "http://mochi.test:8888/foo/bar.jpg", "blockedURI");
+-  SimpleTest.todo_is(e.violatedDirective, "img-src", "violatedDirective")
++  SimpleTest.is(e.violatedDirective, "img-src", "violatedDirective")
+   SimpleTest.is(e.originalPolicy, "img-src 'none'", "originalPolicy");
+   SimpleTest.finish();
+ });
+ </script>
+ <img src="http://mochi.test:8888/foo/bar.jpg">

+ 507 - 0
frg/work-js/mozilla-release/patches/1418376-59a1.patch

@@ -0,0 +1,507 @@
+# HG changeset patch
+# User Tom Tung <shes050117@gmail.com>
+# Date 1511918995 -28800
+# Node ID eae544da26b6ea225e3f402c1bd62085cf1c7ec1
+# Parent  cc14dcfc8219f59012368671e0614e2c13c8c838
+Bug 1418376: Report to the console if a service worker is blocked because the storage is not fully allow. r=bkelly
+
+diff --git a/dom/locales/en-US/chrome/dom/dom.properties b/dom/locales/en-US/chrome/dom/dom.properties
+--- a/dom/locales/en-US/chrome/dom/dom.properties
++++ b/dom/locales/en-US/chrome/dom/dom.properties
+@@ -221,16 +221,24 @@ InterceptedNonResponseWithURL=Failed to load ‘%1$S’. A ServiceWorker passed a promise to FetchEvent.respondWith() that resolved with non-Response value ‘%2$S’.
+ # LOCALIZATION NOTE: Do not translate "mozImageSmoothingEnabled", or "imageSmoothingEnabled"
+ PrefixedImageSmoothingEnabledWarning=Use of mozImageSmoothingEnabled is deprecated. Please use the unprefixed imageSmoothingEnabled property instead.
+ # LOCALIZATION NOTE: Do not translate "ServiceWorker", "Service-Worker-Allowed" or "HTTP". %1$S and %2$S are URLs.
+ ServiceWorkerScopePathMismatch=Failed to register a ServiceWorker: The path of the provided scope ‘%1$S’ is not under the max scope allowed ‘%2$S’. Adjust the scope, move the Service Worker script, or use the Service-Worker-Allowed HTTP header to allow the scope.
+ # LOCALIZATION NOTE: Do not translate "ServiceWorker". %1$S is a URL representing the scope of the ServiceWorker, %2$S is a stringified numeric HTTP status code like "404" and %3$S is a URL.
+ ServiceWorkerRegisterNetworkError=Failed to register/update a ServiceWorker for scope ‘%1$S’: Load failed with status %2$S for script ‘%3$S’.
+ # LOCALIZATION NOTE: Do not translate "ServiceWorker". %1$S is a URL representing the scope of the ServiceWorker, %2$S is a MIME Media Type like "text/plain" and %3$S is a URL.
+ ServiceWorkerRegisterMimeTypeError=Failed to register/update a ServiceWorker for scope ‘%1$S’: Bad Content-Type of ‘%2$S’ received for script ‘%3$S’.  Must be ‘text/javascript’, ‘application/x-javascript’, or ‘application/javascript’.
++# LOCALIZATION NOTE: Do not translate "ServiceWorker". %S is a URL representing the scope of the ServiceWorker.
++ServiceWorkerRegisterStorageError=Failed to register/update a ServiceWorker for scope ‘%S’: Storage access is restricted in this context due to user settings or private browsing mode.
++# LOCALIZATION NOTE: Do not translate "ServiceWorker".
++ServiceWorkerGetRegistrationStorageError=Failed to get service worker registration(s): Storage access is restricted in this context due to user settings or private browsing mode.
++# LOCALIZATION NOTE: Do not translate "ServiceWorker".
++ServiceWorkerGetClientStorageError=Failed to get service worker’s client(s): Storage access is restricted in this context due to user settings or private browsing mode.
++# LOCALIZATION NOTE: Do not translate "ServiceWorker". %S is a URL representing the scope of the ServiceWorker.
++ServiceWorkerPostMessageStorageError=The ServiceWorker for scope ‘%S’ fail to postMessage because storage access is restricted in this context due to user settings or private browsing mode.
+ # LOCALIZATION NOTE: Do not translate "ServiceWorker". %1$S is a URL representing the scope of the ServiceWorker.
+ ServiceWorkerGraceTimeoutTermination=Terminating ServiceWorker for scope ‘%1$S’ with pending waitUntil/respondWith promises because of grace timeout.
+ # LOCALIZATION NOTE (ServiceWorkerNoFetchHandler): Do not translate "Fetch".
+ ServiceWorkerNoFetchHandler=Fetch event handlers must be added during the worker script’s initial evaluation.
+ ExecCommandCutCopyDeniedNotInputDriven=document.execCommand(‘cut’/‘copy’) was denied because it was not called from inside a short running user-generated event handler.
+ ManifestShouldBeObject=Manifest should be an object.
+ ManifestScopeURLInvalid=The scope URL is invalid.
+ ManifestScopeNotSameOrigin=The scope URL must be same origin as document.
+diff --git a/dom/workers/ServiceWorker.cpp b/dom/workers/ServiceWorker.cpp
+--- a/dom/workers/ServiceWorker.cpp
++++ b/dom/workers/ServiceWorker.cpp
+@@ -90,16 +90,19 @@ ServiceWorker::PostMessage(JSContext* aC
+   if (!window || !window->GetExtantDoc()) {
+     NS_WARNING("Trying to call post message from an invalid dom object.");
+     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+     return;
+   }
+ 
+   auto storageAllowed = nsContentUtils::StorageAllowedForWindow(window);
+   if (storageAllowed != nsContentUtils::StorageAccess::eAllow) {
++    ServiceWorkerManager::LocalizeAndReportToAllClients(
++      mInfo->Scope(), "ServiceWorkerPostMessageStorageError",
++      nsTArray<nsString> { NS_ConvertUTF8toUTF16(mInfo->Scope()) });
+     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+     return;
+   }
+ 
+   UniquePtr<ServiceWorkerClientInfo> clientInfo(new ServiceWorkerClientInfo(window->GetExtantDoc()));
+   ServiceWorkerPrivate* workerPrivate = mInfo->WorkerPrivate();
+   aRv = workerPrivate->SendMessageEvent(aCx, aMessage, aTransferable, Move(clientInfo));
+ }
+diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp
+--- a/dom/workers/ServiceWorkerManager.cpp
++++ b/dom/workers/ServiceWorkerManager.cpp
+@@ -725,26 +725,33 @@ ServiceWorkerManager::Register(mozIDOMWi
+   AssertIsOnMainThread();
+ 
+   if (NS_WARN_IF(!aWindow)) {
+     return NS_ERROR_DOM_INVALID_STATE_ERR;
+   }
+ 
+   auto* window = nsPIDOMWindowInner::From(aWindow);
+ 
++  nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
++  MOZ_ASSERT(doc);
++
+   // Don't allow a service worker to be registered if storage is restricted
+   // for the window.
+   auto storageAllowed = nsContentUtils::StorageAllowedForWindow(window);
+   if (storageAllowed != nsContentUtils::StorageAccess::eAllow) {
++    NS_ConvertUTF8toUTF16 reportScope(aScopeURI->GetSpecOrDefault());
++    const char16_t* param[] = { reportScope.get() };
++    nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
++                                    NS_LITERAL_CSTRING("Service Workers"), doc,
++                                    nsContentUtils::eDOM_PROPERTIES,
++                                    "ServiceWorkerRegisterStorageError", param,
++                                    1);
+     return NS_ERROR_DOM_SECURITY_ERR;
+   }
+ 
+-  nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
+-  MOZ_ASSERT(doc);
+-
+   // Don't allow service workers to register when the *document* is chrome.
+   if (NS_WARN_IF(nsContentUtils::IsSystemPrincipal(doc->NodePrincipal()))) {
+     return NS_ERROR_DOM_SECURITY_ERR;
+   }
+ 
+   nsCOMPtr<nsPIDOMWindowOuter> outerWindow = window->GetOuterWindow();
+   bool serviceWorkersTestingEnabled =
+     outerWindow->GetServiceWorkersTestingEnabled();
+@@ -973,16 +980,21 @@ ServiceWorkerManager::GetRegistrations(m
+   auto* window = nsPIDOMWindowInner::From(aWindow);
+ 
+   // Don't allow a service worker to access service worker registrations
+   // from a window with storage disabled.  If these windows can access
+   // the registration it increases the chance they can bypass the storage
+   // block via postMessage(), etc.
+   auto storageAllowed = nsContentUtils::StorageAllowedForWindow(window);
+   if (storageAllowed != nsContentUtils::StorageAccess::eAllow) {
++    nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
++    nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
++                                    NS_LITERAL_CSTRING("Service Workers"), doc,
++                                    nsContentUtils::eDOM_PROPERTIES,
++                                    "ServiceWorkerGetRegistrationStorageError");
+     return NS_ERROR_DOM_SECURITY_ERR;
+   }
+ 
+   // Don't allow service workers to register when the *document* is chrome for
+   // now.
+   MOZ_ASSERT(!nsContentUtils::IsSystemPrincipal(window->GetExtantDoc()->NodePrincipal()));
+ 
+   nsCOMPtr<nsIGlobalObject> sgo = do_QueryInterface(window);
+@@ -1091,16 +1103,21 @@ ServiceWorkerManager::GetRegistration(mo
+   auto* window = nsPIDOMWindowInner::From(aWindow);
+ 
+   // Don't allow a service worker to access service worker registrations
+   // from a window with storage disabled.  If these windows can access
+   // the registration it increases the chance they can bypass the storage
+   // block via postMessage(), etc.
+   auto storageAllowed = nsContentUtils::StorageAllowedForWindow(window);
+   if (storageAllowed != nsContentUtils::StorageAccess::eAllow) {
++    nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
++    nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
++                                    NS_LITERAL_CSTRING("Service Workers"), doc,
++                                    nsContentUtils::eDOM_PROPERTIES,
++                                    "ServiceWorkerGetRegistrationStorageError");
+     return NS_ERROR_DOM_SECURITY_ERR;
+   }
+ 
+   // Don't allow service workers to register when the *document* is chrome for
+   // now.
+   MOZ_ASSERT(!nsContentUtils::IsSystemPrincipal(window->GetExtantDoc()->NodePrincipal()));
+ 
+   nsCOMPtr<nsIGlobalObject> sgo = do_QueryInterface(window);
+@@ -3183,16 +3200,17 @@ FireControllerChangeOnDocument(nsIDocume
+ 
+ } // anonymous namespace
+ 
+ UniquePtr<ServiceWorkerClientInfo>
+ ServiceWorkerManager::GetClient(nsIPrincipal* aPrincipal,
+                                 const nsAString& aClientId,
+                                 ErrorResult& aRv)
+ {
++  AssertIsOnMainThread();
+   UniquePtr<ServiceWorkerClientInfo> clientInfo;
+   nsCOMPtr<nsISupportsInterfacePointer> ifptr =
+     do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID);
+   if (NS_WARN_IF(!ifptr)) {
+     return clientInfo;
+   }
+   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+   if (NS_WARN_IF(!obs)) {
+@@ -3223,30 +3241,35 @@ ServiceWorkerManager::GetClient(nsIPrinc
+     return clientInfo;
+   }
+ 
+   // Don't let service worker see 3rd party iframes that are denied storage
+   // access.  We don't want these to communicate.
+   auto storageAccess =
+     nsContentUtils::StorageAllowedForWindow(doc->GetInnerWindow());
+   if (storageAccess != nsContentUtils::StorageAccess::eAllow) {
++    nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
++                                    NS_LITERAL_CSTRING("Service Workers"), doc,
++                                    nsContentUtils::eDOM_PROPERTIES,
++                                    "ServiceWorkerGetClientStorageError");
+     return clientInfo;
+   }
+ 
+   clientInfo.reset(new ServiceWorkerClientInfo(doc));
+   return clientInfo;
+ }
+ 
+ void
+ ServiceWorkerManager::GetAllClients(nsIPrincipal* aPrincipal,
+                                     const nsCString& aScope,
+                                     uint64_t aServiceWorkerID,
+                                     bool aIncludeUncontrolled,
+                                     nsTArray<ServiceWorkerClientInfo>& aDocuments)
+ {
++  AssertIsOnMainThread();
+   MOZ_ASSERT(aPrincipal);
+ 
+   RefPtr<ServiceWorkerRegistrationInfo> registration =
+     GetRegistration(aPrincipal, aScope);
+ 
+   if (!registration) {
+     // The registration was removed, leave the array empty.
+     return;
+@@ -3293,16 +3316,20 @@ ServiceWorkerManager::GetAllClients(nsIP
+       continue;
+     }
+ 
+     // Don't let service worker find 3rd party iframes that are denied storage
+     // access.  We don't want these to communicate.
+     auto storageAccess =
+       nsContentUtils::StorageAllowedForWindow(doc->GetInnerWindow());
+     if (storageAccess != nsContentUtils::StorageAccess::eAllow) {
++      nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
++                                      NS_LITERAL_CSTRING("Service Workers"),
++                                      doc, nsContentUtils::eDOM_PROPERTIES,
++                                      "ServiceWorkerGetClientStorageError");
+       continue;
+     }
+ 
+     // If we are only returning controlled Clients then skip any documents
+     // that are for different registrations.  We also skip service workers
+     // that don't match the ID of our calling service worker.  We should
+     // only return Clients controlled by that precise service worker.
+     if (!aIncludeUncontrolled) {
+diff --git a/dom/workers/test/serviceworkers/error_reporting_helpers.js b/dom/workers/test/serviceworkers/error_reporting_helpers.js
+--- a/dom/workers/test/serviceworkers/error_reporting_helpers.js
++++ b/dom/workers/test/serviceworkers/error_reporting_helpers.js
+@@ -32,19 +32,23 @@ let localizer =
+  * @return {Object} Promise/handle to pass to wait_for_expected_message.
+  */
+ function expect_console_message(/* msgId, args, ... */) {
+   let expectations = [];
+   // process repeated paired arguments of: msgId, args
+   for (let i = 0; i < arguments.length; i += 2) {
+     let msgId = arguments[i];
+     let args = arguments[i + 1];
+-    expectations.push({
+-      errorMessage: localizer.formatStringFromName(msgId, args, args.length)
+-    });
++    if (args.length === 0) {
++      expectations.push({errorMessage: localizer.GetStringFromName(msgId)});
++    } else {
++      expectations.push({
++        errorMessage: localizer.formatStringFromName(msgId, args, args.length)
++      });
++    }
+   }
+   return new Promise(resolve => {
+     SimpleTest.monitorConsole(resolve, expectations);
+   });
+ }
+ let expect_console_messages = expect_console_message;
+ 
+ /**
+diff --git a/dom/workers/test/serviceworkers/mochitest.ini b/dom/workers/test/serviceworkers/mochitest.ini
+--- a/dom/workers/test/serviceworkers/mochitest.ini
++++ b/dom/workers/test/serviceworkers/mochitest.ini
+@@ -217,16 +217,17 @@ support-files =
+   async_waituntil_worker.js
+   lazy_worker.js
+   nofetch_handler_worker.js
+   service_worker.js
+   service_worker_client.html
+   utils.js
+   bug1290951_worker_main.sjs
+   bug1290951_worker_imported.sjs
++  sw_storage_not_allow.js
+ 
+ [test_bug1151916.html]
+ [test_bug1240436.html]
+ [test_bug1408734.html]
+ [test_claim.html]
+ [test_claim_oninstall.html]
+ [test_controller.html]
+ [test_cookie_fetch.html]
+diff --git a/dom/workers/test/serviceworkers/sw_storage_not_allow.js b/dom/workers/test/serviceworkers/sw_storage_not_allow.js
+new file mode 100644
+--- /dev/null
++++ b/dom/workers/test/serviceworkers/sw_storage_not_allow.js
+@@ -0,0 +1,21 @@
++let clientId;
++addEventListener('fetch', function(event) {
++  if (event.request.url.includes('getClients')) {
++    // Excepted to fail since the storage access is not allowed.
++    self.clients.matchAll();
++  } else if (event.request.url.includes('getClient-stage1')) {
++    self.clients.matchAll().then(function(clients) {
++      clientId = clients[0].id;
++    });
++  } else if (event.request.url.includes('getClient-stage2')) {
++    // Excepted to fail since the storage access is not allowed.
++    self.clients.get(clientId);
++  }
++});
++
++addEventListener('message', function(event) {
++  if (event.data === 'claim') {
++    event.waitUntil(clients.claim());
++  }
++});
++
+diff --git a/dom/workers/test/serviceworkers/test_error_reporting.html b/dom/workers/test/serviceworkers/test_error_reporting.html
+--- a/dom/workers/test/serviceworkers/test_error_reporting.html
++++ b/dom/workers/test/serviceworkers/test_error_reporting.html
+@@ -1,15 +1,16 @@
+ <!DOCTYPE HTML>
+ <html>
+ <head>
+   <title>Test Error Reporting of Service Worker Failures</title>
+   <script src="/tests/SimpleTest/SimpleTest.js"></script>
+   <script src="/tests/SimpleTest/SpawnTask.js"></script>
+   <script src="error_reporting_helpers.js"></script>
++  <script src="utils.js"></script>
+   <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+   <meta http-equiv="Content-type" content="text/html;charset=UTF-8">
+ </head>
+ <body>
+ 
+ <script type="text/javascript">
+ "use strict";
+ 
+@@ -65,12 +66,191 @@ add_task(async function register_bad_mim
+   // consume the expected rejection so it doesn't get thrown at us.
+   await navigator.serviceWorker.register("sw_bad_mime_type.js", { scope: "bad_mime_type/" })
+     .then(
+       () => { ok(false, "should have rejected"); },
+       (e) => { ok(e.name === "SecurityError", "bad MIME type failed as expected"); });
+ 
+   await wait_for_expected_message(expectedMessage);
+ });
++
++async function notAllowStorageAccess() {
++  return SpecialPowers.pushPrefEnv({"set": [
++    ["network.cookie.lifetimePolicy",
++     SpecialPowers.Ci.nsICookieService.ACCEPT_SESSION],
++  ]});
++}
++
++async function allowStorageAccess() {
++  return SpecialPowers.pushPrefEnv({"set": [
++    ["network.cookie.lifetimePolicy",
++     SpecialPowers.Ci.nsICookieService.ACCEPT_NORMALLY],
++  ]});
++}
++
++/**
++ * Ensure an error is logged when the storage is not allowed and the content
++ * script is trying to register a service worker.
++ */
++add_task(async function register_storage_error() {
++  let expectedMessage = expect_console_message(
++    "ServiceWorkerRegisterStorageError",
++    [make_absolute_url("storage_not_allow/")]);
++
++  await notAllowStorageAccess();
++
++  // consume the expected rejection so it doesn't get thrown at us.
++  await navigator.serviceWorker.register("sw_storage_not_allow.js",
++                                         { scope: "storage_not_allow/" })
++    .then(
++      () => { ok(false, "should have rejected"); },
++      (e) => { ok(e.name === "SecurityError",
++                  "storage access failed as expected."); });
++
++  await wait_for_expected_message(expectedMessage);
++
++  await allowStorageAccess();
++});
++
++/**
++ * Ensure an error is logged when the storage is not allowed and the content
++ * script is trying to get the service worker registration.
++ */
++add_task(async function get_registration_storage_error() {
++  let expectedMessage =
++    expect_console_message("ServiceWorkerGetRegistrationStorageError", []);
++
++  await notAllowStorageAccess();
++
++  // consume the expected rejection so it doesn't get thrown at us.
++  await navigator.serviceWorker.getRegistration()
++    .then(
++      () => { ok(false, "should have rejected"); },
++      (e) => { ok(e.name === "SecurityError",
++                  "storage access failed as expected."); });
++
++  await wait_for_expected_message(expectedMessage);
++
++  await allowStorageAccess();
++});
++
++/**
++ * Ensure an error is logged when the storage is not allowed and the content
++ * script is trying to get the service worker registrations.
++ */
++add_task(async function get_registrations_storage_error() {
++  let expectedMessage =
++    expect_console_message("ServiceWorkerGetRegistrationStorageError", []);
++
++  await notAllowStorageAccess();
++
++  // consume the expected rejection so it doesn't get thrown at us.
++  await navigator.serviceWorker.getRegistrations()
++    .then(
++      () => { ok(false, "should have rejected"); },
++      (e) => { ok(e.name === "SecurityError",
++                  "storage access failed as expected."); });
++
++  await wait_for_expected_message(expectedMessage);
++
++  await allowStorageAccess();
++});
++
++/**
++ * Ensure an error is logged when the storage is not allowed and the content
++ * script is trying to post a message to the service worker.
++ */
++add_task(async function postMessage_storage_error() {
++  let expectedMessage = expect_console_message(
++    "ServiceWorkerPostMessageStorageError",
++    [make_absolute_url("storage_not_allow/")]);
++
++  let registration;
++  // consume the expected rejection so it doesn't get thrown at us.
++  await navigator.serviceWorker.register("sw_storage_not_allow.js",
++                                         { scope: "storage_not_allow/" })
++    .then(reg => { registration = reg; })
++    .then(() => notAllowStorageAccess())
++    .then(() => registration.installing ||
++                registration.waiting ||
++                registration.active)
++    .then(worker =>  worker.postMessage('ha'))
++    .then(
++      () => { ok(false, "should have rejected"); },
++      (e) => { ok(e.name === "SecurityError",
++                  "storage access failed as expected."); });
++
++  await wait_for_expected_message(expectedMessage);
++
++  await registration.unregister();
++  await allowStorageAccess();
++});
++
++/**
++ * Ensure an error is logged when the storage is not allowed and the service
++ * worker is trying to get its client.
++ */
++add_task(async function get_client_storage_error() {
++  let expectedMessage =
++    expect_console_message("ServiceWorkerGetClientStorageError", []);
++
++  await SpecialPowers.pushPrefEnv({"set": [
++    // Make the test pass the IsOriginPotentiallyTrustworthy.
++    ["dom.securecontext.whitelist", "mochi.test"]
++  ]});
++
++  let registration, sw;
++  // consume the expected rejection so it doesn't get thrown at us.
++  await navigator.serviceWorker.register("sw_storage_not_allow.js",
++                                         { scope: "test_error_reporting.html" })
++    .then(reg => { registration = reg; })
++    .then(() => registration.installing ||
++                registration.waiting ||
++                registration.active)
++    .then(worker => { sw = worker; })
++    .then(() => waitForState(sw, 'activated'))
++    .then(() => sw.postMessage('claim'))
++    .then(() => waitForControlled(window))
++    // Get the client's ID in the stage 1
++    .then(() => fetch("getClient-stage1"))
++    .then(() => notAllowStorageAccess())
++    // Trigger the clients.get() in the stage 2
++    .then(() => fetch("getClient-stage2"))
++    .catch(e => ok(false, "fail due to:" + e));
++
++  await wait_for_expected_message(expectedMessage);
++
++  await registration.unregister();
++  await allowStorageAccess();
++});
++
++/**
++ * Ensure an error is logged when the storage is not allowed and the service
++ * worker is trying to get its clients.
++ */
++add_task(async function get_clients_storage_error() {
++  let expectedMessage =
++    expect_console_message("ServiceWorkerGetClientStorageError", []);
++
++  let registration, sw;
++  // consume the expected rejection so it doesn't get thrown at us.
++  await navigator.serviceWorker.register("sw_storage_not_allow.js",
++                                         { scope: "test_error_reporting.html" })
++    .then(reg => { registration = reg; })
++    .then(() => registration.installing ||
++                registration.waiting ||
++                registration.active)
++    .then(worker => { sw = worker; })
++    .then(() => waitForState(sw, 'activated'))
++    .then(() => sw.postMessage('claim'))
++    .then(() => waitForControlled(window))
++    .then(() => notAllowStorageAccess())
++    .then(() => fetch("getClients"))
++    .catch(e => ok(false, "fail due to:" + e));
++
++  await wait_for_expected_message(expectedMessage);
++
++  await registration.unregister();
++  await allowStorageAccess();
++});
+ </script>
+-
+ </body>
+ </html>

+ 341 - 0
frg/work-js/mozilla-release/patches/1419536-1-59a1.patch

@@ -0,0 +1,341 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1511295184 18000
+# Node ID 0f356a5c4dea9aa3cc0f806f74338beb484b9f12
+# Parent  9abed7c0efb306f68ac6ae01dd81dcf5a7751819
+Bug 1419536 P1 Add a helper to handle Client objects properly on nsIChannels. r=baku
+
+diff --git a/dom/clients/manager/ClientChannelHelper.cpp b/dom/clients/manager/ClientChannelHelper.cpp
+new file mode 100644
+--- /dev/null
++++ b/dom/clients/manager/ClientChannelHelper.cpp
+@@ -0,0 +1,256 @@
++/* -*- 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 "ClientChannelHelper.h"
++
++#include "ClientManager.h"
++#include "ClientSource.h"
++#include "MainThreadUtils.h"
++#include "mozilla/dom/ServiceWorkerDescriptor.h"
++#include "mozilla/ipc/BackgroundUtils.h"
++#include "nsContentUtils.h"
++#include "nsIAsyncVerifyRedirectCallback.h"
++#include "nsIChannel.h"
++#include "nsIChannelEventSink.h"
++#include "nsIDocShell.h"
++#include "nsIInterfaceRequestor.h"
++#include "nsIInterfaceRequestorUtils.h"
++
++namespace mozilla {
++namespace dom {
++
++using mozilla::ipc::PrincipalInfoToPrincipal;
++
++namespace {
++
++class ClientChannelHelper final : public nsIInterfaceRequestor
++                                , public nsIChannelEventSink
++{
++  nsCOMPtr<nsIInterfaceRequestor> mOuter;
++  nsCOMPtr<nsISerialEventTarget> mEventTarget;
++
++  ~ClientChannelHelper() = default;
++
++  NS_IMETHOD
++  GetInterface(const nsIID& aIID, void** aResultOut) override
++  {
++    if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
++      *aResultOut = static_cast<nsIChannelEventSink*>(this);
++      NS_ADDREF_THIS();
++      return NS_OK;
++    }
++
++    if (mOuter) {
++      return mOuter->GetInterface(aIID, aResultOut);
++    }
++
++    return NS_ERROR_NO_INTERFACE;
++  }
++
++  NS_IMETHOD
++  AsyncOnChannelRedirect(nsIChannel* aOldChannel,
++                         nsIChannel* aNewChannel,
++                         uint32_t aFlags,
++                         nsIAsyncVerifyRedirectCallback *aCallback) override
++  {
++    MOZ_ASSERT(NS_IsMainThread());
++
++    nsCOMPtr<nsILoadInfo> oldLoadInfo;
++    nsresult rv = aOldChannel->GetLoadInfo(getter_AddRefs(oldLoadInfo));
++    NS_ENSURE_SUCCESS(rv, rv);
++
++    nsCOMPtr<nsILoadInfo> newLoadInfo;
++    rv = aNewChannel->GetLoadInfo(getter_AddRefs(newLoadInfo));
++    NS_ENSURE_SUCCESS(rv, rv);
++
++    rv = nsContentUtils::CheckSameOrigin(aOldChannel, aNewChannel);
++    if (NS_WARN_IF(NS_FAILED(rv) && rv != NS_ERROR_DOM_BAD_URI)) {
++      return rv;
++    }
++
++    UniquePtr<ClientSource> reservedClient = oldLoadInfo->TakeReservedClientSource();
++
++    // If its a same-origin redirect we just move our reserved client to the
++    // new channel.
++    if (NS_SUCCEEDED(rv)) {
++      if (reservedClient) {
++        newLoadInfo->GiveReservedClientSource(Move(reservedClient));
++      }
++
++      // It seems sometimes necko passes two channels with the same LoadInfo.
++      // We only need to move the reserved/initial ClientInfo over if we
++      // actually have a different LoadInfo.
++      else if (oldLoadInfo != newLoadInfo) {
++        const Maybe<ClientInfo>& reservedClientInfo =
++          oldLoadInfo->GetReservedClientInfo();
++
++        const Maybe<ClientInfo>& initialClientInfo =
++          oldLoadInfo->GetInitialClientInfo();
++
++        MOZ_DIAGNOSTIC_ASSERT(reservedClientInfo.isNothing() ||
++                              initialClientInfo.isNothing());
++
++        if (reservedClientInfo.isSome()) {
++          newLoadInfo->SetReservedClientInfo(reservedClientInfo.ref());
++        }
++
++        if (initialClientInfo.isSome()) {
++          newLoadInfo->SetInitialClientInfo(initialClientInfo.ref());
++        }
++      }
++
++      // Make sure we keep the service worker controller on same-origin
++      // internal redirects.
++      if (oldLoadInfo != newLoadInfo &&
++          aFlags & nsIChannelEventSink::REDIRECT_INTERNAL) {
++        const Maybe<ServiceWorkerDescriptor>& controller = oldLoadInfo->GetController();
++        if (controller.isSome()) {
++          newLoadInfo->SetController(controller.ref());
++        }
++      }
++    }
++
++    // If it's a cross-origin redirect then we discard the old reserved client
++    // and create a new one.
++    else {
++      // If CheckSameOrigin() worked, then the security manager must exist.
++      nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
++      MOZ_DIAGNOSTIC_ASSERT(ssm);
++
++      nsCOMPtr<nsIPrincipal> principal;
++      rv = ssm->GetChannelResultPrincipal(aNewChannel, getter_AddRefs(principal));
++      NS_ENSURE_SUCCESS(rv, rv);
++
++      // Create the new ClientSource.  This should only happen for window
++      // Clients since support cross-origin redirects are blocked by the
++      // same-origin security policy.
++      reservedClient.reset();
++      reservedClient = ClientManager::CreateSource(ClientType::Window,
++                                                   mEventTarget, principal);
++
++      newLoadInfo->GiveReservedClientSource(Move(reservedClient));
++    }
++
++    nsCOMPtr<nsIChannelEventSink> outerSink = do_GetInterface(mOuter);
++    if (outerSink) {
++      return outerSink->AsyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags,
++                                               aCallback);
++    }
++
++    aCallback->OnRedirectVerifyCallback(NS_OK);
++    return NS_OK;
++  }
++
++public:
++  ClientChannelHelper(nsIInterfaceRequestor* aOuter,
++                      nsISerialEventTarget* aEventTarget)
++    : mOuter(aOuter)
++    , mEventTarget(aEventTarget)
++  {
++  }
++
++  NS_DECL_ISUPPORTS
++};
++
++NS_IMPL_ISUPPORTS(ClientChannelHelper, nsIInterfaceRequestor,
++                                       nsIChannelEventSink);
++
++} // anonymous namespace
++
++nsresult
++AddClientChannelHelper(nsIChannel* aChannel,
++                       Maybe<ClientInfo>&& aReservedClientInfo,
++                       Maybe<ClientInfo>&& aInitialClientInfo,
++                       nsISerialEventTarget* aEventTarget)
++{
++  MOZ_ASSERT(NS_IsMainThread());
++
++  Maybe<ClientInfo> initialClientInfo(Move(aInitialClientInfo));
++  Maybe<ClientInfo> reservedClientInfo(Move(aReservedClientInfo));
++  MOZ_DIAGNOSTIC_ASSERT(reservedClientInfo.isNothing() ||
++                        initialClientInfo.isNothing());
++
++  nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
++  NS_ENSURE_TRUE(loadInfo, NS_ERROR_FAILURE);
++
++  nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
++  NS_ENSURE_TRUE(ssm, NS_ERROR_FAILURE);
++
++  nsCOMPtr<nsIPrincipal> channelPrincipal;
++  nsresult rv = ssm->GetChannelResultPrincipal(aChannel, getter_AddRefs(channelPrincipal));
++  NS_ENSURE_SUCCESS(rv, rv);
++
++  // Only allow the initial ClientInfo to be set if the current channel
++  // principal matches.
++  if (initialClientInfo.isSome()) {
++    nsCOMPtr<nsIPrincipal> initialPrincipal =
++      PrincipalInfoToPrincipal(initialClientInfo.ref().PrincipalInfo(), nullptr);
++
++    bool equals = false;
++    rv = initialPrincipal ? initialPrincipal->Equals(channelPrincipal, &equals)
++                          : NS_ERROR_FAILURE;
++    if (NS_FAILED(rv) || !equals) {
++      initialClientInfo.reset();
++    }
++  }
++
++  // Only allow the reserved ClientInfo to be set if the current channel
++  // principal matches.
++  if (reservedClientInfo.isSome()) {
++    nsCOMPtr<nsIPrincipal> reservedPrincipal =
++      PrincipalInfoToPrincipal(reservedClientInfo.ref().PrincipalInfo(), nullptr);
++
++    bool equals = false;
++    rv = reservedPrincipal ? reservedPrincipal->Equals(channelPrincipal, &equals)
++                           : NS_ERROR_FAILURE;
++    if (NS_FAILED(rv) || !equals) {
++      reservedClientInfo.reset();
++    }
++  }
++
++  nsCOMPtr<nsIInterfaceRequestor> outerCallbacks;
++  rv = aChannel->GetNotificationCallbacks(getter_AddRefs(outerCallbacks));
++  NS_ENSURE_SUCCESS(rv, rv);
++
++  UniquePtr<ClientSource> reservedClient;
++  if (initialClientInfo.isNothing() && reservedClientInfo.isNothing()) {
++    // Wait to reserve the client until we are reasonably sure this method
++    // will succeed.  We should only follow this path for window clients.
++    // Workers should always provide a reserved ClientInfo since their
++    // ClientSource object is owned by a different thread.
++    reservedClient = ClientManager::CreateSource(ClientType::Window,
++                                                 aEventTarget,
++                                                 channelPrincipal);
++    NS_ENSURE_TRUE(reservedClient, NS_ERROR_FAILURE);
++  }
++
++  RefPtr<ClientChannelHelper> helper =
++    new ClientChannelHelper(outerCallbacks, aEventTarget);
++
++  // Only set the callbacks helper if we are able to reserve the client
++  // successfully.
++  rv = aChannel->SetNotificationCallbacks(helper);
++  NS_ENSURE_SUCCESS(rv, rv);
++
++  // Finally preserve the various client values on the nsILoadInfo once the
++  // redirect helper has been added to the channel.
++  if (reservedClient) {
++    loadInfo->GiveReservedClientSource(Move(reservedClient));
++  }
++
++  if (initialClientInfo.isSome()) {
++    loadInfo->SetInitialClientInfo(initialClientInfo.ref());
++  }
++
++  if (reservedClientInfo.isSome()) {
++    loadInfo->SetReservedClientInfo(reservedClientInfo.ref());
++  }
++
++  return NS_OK;
++}
++
++} // namespace dom
++} // namespace mozilla
+diff --git a/dom/clients/manager/ClientChannelHelper.h b/dom/clients/manager/ClientChannelHelper.h
+new file mode 100644
+--- /dev/null
++++ b/dom/clients/manager/ClientChannelHelper.h
+@@ -0,0 +1,34 @@
++/* -*- 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/. */
++#ifndef _mozilla_dom_ClientChannelHelper_h
++#define _mozilla_dom_ClientChannelHelper_h
++
++#include "mozilla/Maybe.h"
++#include "nsError.h"
++
++class nsIChannel;
++class nsISerialEventTarget;
++
++namespace mozilla {
++namespace dom {
++
++class ClientInfo;
++
++// Attach a redirect listener to the given nsIChannel that will manage
++// the various client values on the channel's LoadInfo.  This will
++// properly handle creating a new ClientSource on cross-origin redirect
++// and propagate the current reserved/initial client on same-origin
++// redirect.
++nsresult
++AddClientChannelHelper(nsIChannel* aChannel,
++                       Maybe<ClientInfo>&& aReservedClientInfo,
++                       Maybe<ClientInfo>&& aInitialClientInfo,
++                       nsISerialEventTarget* aEventTarget);
++
++} // namespace dom
++} // namespace mozilla
++
++#endif // _mozilla_dom_ClientChannelHelper_h
+diff --git a/dom/clients/manager/moz.build b/dom/clients/manager/moz.build
+--- a/dom/clients/manager/moz.build
++++ b/dom/clients/manager/moz.build
+@@ -1,28 +1,30 @@
+ # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+ # vim: set filetype=python:
+ # This Source Code Form is subject to the terms of the Mozilla Public
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
+ # file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ 
+ EXPORTS.mozilla.dom += [
++  'ClientChannelHelper.h',
+   'ClientHandle.h',
+   'ClientInfo.h',
+   'ClientIPCUtils.h',
+   'ClientManager.h',
+   'ClientManagerActors.h',
+   'ClientOpenWindowOpActors.h',
+   'ClientOpPromise.h',
+   'ClientSource.h',
+   'ClientState.h',
+   'ClientThing.h',
+ ]
+ 
+ UNIFIED_SOURCES += [
++  'ClientChannelHelper.cpp',
+   'ClientHandle.cpp',
+   'ClientHandleChild.cpp',
+   'ClientHandleOpChild.cpp',
+   'ClientHandleOpParent.cpp',
+   'ClientHandleParent.cpp',
+   'ClientInfo.cpp',
+   'ClientManager.cpp',
+   'ClientManagerActors.cpp',

+ 342 - 0
frg/work-js/mozilla-release/patches/1419536-2-59a1.patch

@@ -0,0 +1,342 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1511295184 18000
+# Node ID 061c5b3ee4d4a7874f4496835cdf1b7c29875a67
+# Parent  849aecdb450be335dc81495166b909df67b3464c
+Bug 1419536 P2 Create a ClientSource for the WorkerPrivate and mark it execution ready after the script loads. r=baku
+
+diff --git a/dom/webidl/Clients.webidl b/dom/webidl/Clients.webidl
+--- a/dom/webidl/Clients.webidl
++++ b/dom/webidl/Clients.webidl
+@@ -26,11 +26,13 @@ dictionary ClientQueryOptions {
+   boolean includeUncontrolled = false;
+   ClientType type = "window";
+ };
+ 
+ enum ClientType {
+   "window",
+   "worker",
+   "sharedworker",
++  // https://github.com/w3c/ServiceWorker/issues/1036
++  "serviceworker",
+   "all"
+ };
+ 
+diff --git a/dom/workers/ScriptLoader.cpp b/dom/workers/ScriptLoader.cpp
+--- a/dom/workers/ScriptLoader.cpp
++++ b/dom/workers/ScriptLoader.cpp
+@@ -45,16 +45,18 @@
+ #include "mozilla/LoadContext.h"
+ #include "mozilla/Maybe.h"
+ #include "mozilla/ipc/BackgroundUtils.h"
+ #include "mozilla/dom/CacheBinding.h"
+ #include "mozilla/dom/cache/CacheTypes.h"
+ #include "mozilla/dom/cache/Cache.h"
+ #include "mozilla/dom/cache/CacheStorage.h"
+ #include "mozilla/dom/ChannelInfo.h"
++#include "mozilla/dom/ClientChannelHelper.h"
++#include "mozilla/dom/ClientInfo.h"
+ #include "mozilla/dom/Exceptions.h"
+ #include "mozilla/dom/InternalResponse.h"
+ #include "mozilla/dom/nsCSPService.h"
+ #include "mozilla/dom/nsCSPUtils.h"
+ #include "mozilla/dom/Promise.h"
+ #include "mozilla/dom/PromiseNativeHandler.h"
+ #include "mozilla/dom/Response.h"
+ #include "mozilla/dom/ScriptLoader.h"
+@@ -254,16 +256,17 @@ struct ScriptLoadInfo
+   // resolution.
+   RefPtr<Promise> mCachePromise;
+ 
+   // The reader stream the cache entry should be filled from, for those cases
+   // when we're going to have an mCachePromise.
+   nsCOMPtr<nsIInputStream> mCacheReadStream;
+ 
+   nsCOMPtr<nsIChannel> mChannel;
++  Maybe<ClientInfo> mReservedClientInfo;
+   char16_t* mScriptTextBuf;
+   size_t mScriptTextLength;
+ 
+   nsresult mLoadResult;
+   bool mLoadingFinished;
+   bool mExecutionScheduled;
+   bool mExecutionResult;
+ 
+@@ -984,16 +987,27 @@ private:
+     // We don't care about progress so just use the simple stream loader for
+     // OnStreamComplete notification only.
+     nsCOMPtr<nsIStreamLoader> loader;
+     rv = NS_NewStreamLoader(getter_AddRefs(loader), listener);
+     if (NS_WARN_IF(NS_FAILED(rv))) {
+       return rv;
+     }
+ 
++    if (IsMainWorkerScript()) {
++      MOZ_DIAGNOSTIC_ASSERT(loadInfo.mReservedClientInfo.isSome());
++      rv = AddClientChannelHelper(channel,
++                                  Move(loadInfo.mReservedClientInfo),
++                                  Maybe<ClientInfo>(),
++                                  mWorkerPrivate->HybridEventTarget());
++      if (NS_WARN_IF(NS_FAILED(rv))) {
++        return rv;
++      }
++    }
++
+     if (loadInfo.mCacheStatus != ScriptLoadInfo::ToBeCached) {
+       rv = channel->AsyncOpen2(loader);
+       if (NS_WARN_IF(NS_FAILED(rv))) {
+         return rv;
+       }
+     } else {
+       nsCOMPtr<nsIOutputStream> writer;
+ 
+@@ -1964,16 +1978,22 @@ ScriptExecutorRunnable::WorkerRun(JSCont
+                                     loadInfo.mLoadResult, loadInfo.mURL);
+       // Top level scripts only!
+       if (mIsWorkerScript) {
+         aWorkerPrivate->MaybeDispatchLoadFailedRunnable();
+       }
+       return true;
+     }
+ 
++    // If this is a top level script that succeeded, then mark the
++    // Client execution ready.
++    if (mIsWorkerScript) {
++      aWorkerPrivate->ExecutionReady();
++    }
++
+     NS_ConvertUTF16toUTF8 filename(loadInfo.mURL);
+ 
+     JS::CompileOptions options(aCx);
+     options.setFileAndLine(filename.get(), 1)
+            .setNoScriptRval(true);
+ 
+     MOZ_ASSERT(loadInfo.mMutedErrorFlag.isSome());
+     options.setMutedErrors(loadInfo.mMutedErrorFlag.valueOr(true));
+@@ -2258,16 +2278,20 @@ LoadMainScript(WorkerPrivate* aWorkerPri
+                ErrorResult& aRv)
+ {
+   nsTArray<ScriptLoadInfo> loadInfos;
+ 
+   ScriptLoadInfo* info = loadInfos.AppendElement();
+   info->mURL = aScriptURL;
+   info->mLoadFlags = aWorkerPrivate->GetLoadFlags();
+ 
++  // We are loading the main script, so the worker's Client must be
++  // reserved.
++  info->mReservedClientInfo.emplace(aWorkerPrivate->GetClientInfo());
++
+   LoadAllScripts(aWorkerPrivate, loadInfos, true, aWorkerScriptType, aRv);
+ }
+ 
+ void
+ Load(WorkerPrivate* aWorkerPrivate,
+      const nsTArray<nsString>& aScriptURLs, WorkerScriptType aWorkerScriptType,
+      ErrorResult& aRv)
+ {
+diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp
+--- a/dom/workers/WorkerPrivate.cpp
++++ b/dom/workers/WorkerPrivate.cpp
+@@ -42,16 +42,18 @@
+ #include "mozilla/Assertions.h"
+ #include "mozilla/Attributes.h"
+ #include "mozilla/ContentEvents.h"
+ #include "mozilla/EventDispatcher.h"
+ #include "mozilla/Likely.h"
+ #include "mozilla/LoadContext.h"
+ #include "mozilla/Move.h"
+ #include "mozilla/dom/BindingUtils.h"
++#include "mozilla/dom/ClientManager.h"
++#include "mozilla/dom/ClientSource.h"
+ #include "mozilla/dom/Console.h"
+ #include "mozilla/dom/DocGroup.h"
+ #include "mozilla/dom/ErrorEvent.h"
+ #include "mozilla/dom/ErrorEventBinding.h"
+ #include "mozilla/dom/Exceptions.h"
+ #include "mozilla/dom/ExtendableMessageEventBinding.h"
+ #include "mozilla/dom/FunctionBinding.h"
+ #include "mozilla/dom/IndexedDatabaseManager.h"
+@@ -593,16 +595,18 @@ private:
+   // run we have not yet done our load so don't know things like our final
+   // principal and whatnot.
+ 
+   virtual bool
+   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+   {
+     aWorkerPrivate->AssertIsOnWorkerThread();
+ 
++    aWorkerPrivate->EnsureClientSource();
++
+     ErrorResult rv;
+     scriptloader::LoadMainScript(aWorkerPrivate, mScriptURL, WorkerScript, rv);
+     rv.WouldReportJSException();
+     // Explicitly ignore NS_BINDING_ABORTED on rv.  Or more precisely, still
+     // return false and don't SetWorkerScriptExecutedSuccessfully() in that
+     // case, but don't throw anything on aCx.  The idea is to not dispatch error
+     // events if our load is canceled with that error code.
+     if (rv.ErrorCodeIs(NS_BINDING_ABORTED)) {
+@@ -663,16 +667,18 @@ private:
+ 
+     WorkerDebuggerGlobalScope* globalScope =
+       aWorkerPrivate->CreateDebuggerGlobalScope(aCx);
+     if (!globalScope) {
+       NS_WARNING("Failed to make global!");
+       return false;
+     }
+ 
++    aWorkerPrivate->EnsureClientSource();
++
+     JS::Rooted<JSObject*> global(aCx, globalScope->GetWrapper());
+ 
+     ErrorResult rv;
+     JSAutoCompartment ac(aCx, global);
+     scriptloader::LoadMainScript(aWorkerPrivate, mScriptURL,
+                                  DebuggerScript, rv);
+     rv.WouldReportJSException();
+     // Explicitly ignore NS_BINDING_ABORTED on rv.  Or more precisely, still
+@@ -5123,16 +5129,18 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
+       }
+ 
+       // If we're supposed to die then we should exit the loop.
+       if (currentStatus == Killing) {
+         // Flush uncaught rejections immediately, without
+         // waiting for a next tick.
+         PromiseDebugging::FlushUncaughtRejections();
+ 
++        mClientSource = nullptr;
++
+         ShutdownGCTimers();
+ 
+         DisableMemoryReporter();
+ 
+         {
+           MutexAutoLock lock(mMutex);
+ 
+           mStatus = Dead;
+@@ -5285,16 +5293,60 @@ WorkerPrivate::ControlEventTarget()
+ 
+ nsISerialEventTarget*
+ WorkerPrivate::HybridEventTarget()
+ {
+   return mWorkerHybridEventTarget;
+ }
+ 
+ void
++WorkerPrivate::EnsureClientSource()
++{
++  AssertIsOnWorkerThread();
++
++  if (mClientSource) {
++    return;
++  }
++
++  ClientType type;
++  switch(Type()) {
++    case WorkerTypeDedicated:
++      type = ClientType::Worker;
++      break;
++    case WorkerTypeShared:
++      type = ClientType::Sharedworker;
++      break;
++    case WorkerTypeService:
++      type = ClientType::Serviceworker;
++      break;
++    default:
++      MOZ_CRASH("unknown worker type!");
++  }
++
++  mClientSource = ClientManager::CreateSource(type, mWorkerHybridEventTarget,
++                                              GetPrincipalInfo());
++}
++
++const ClientInfo&
++WorkerPrivate::GetClientInfo() const
++{
++  AssertIsOnWorkerThread();
++  MOZ_DIAGNOSTIC_ASSERT(mClientSource);
++  return mClientSource->Info();
++}
++
++void
++WorkerPrivate::ExecutionReady()
++{
++  AssertIsOnWorkerThread();
++  MOZ_DIAGNOSTIC_ASSERT(mClientSource);
++  mClientSource->WorkerExecutionReady(this);
++}
++
++void
+ WorkerPrivate::InitializeGCTimers()
+ {
+   AssertIsOnWorkerThread();
+ 
+   // We need a timer for GC. The basic plan is to run a non-shrinking GC
+   // periodically (PERIODIC_GC_TIMER_DELAY_SEC) while the worker is running.
+   // Once the worker goes idle we set a short (IDLE_GC_TIMER_DELAY_SEC) timer to
+   // run a shrinking GC. If the worker receives more messages then the short
+diff --git a/dom/workers/WorkerPrivate.h b/dom/workers/WorkerPrivate.h
+--- a/dom/workers/WorkerPrivate.h
++++ b/dom/workers/WorkerPrivate.h
+@@ -58,16 +58,18 @@ template<class T> class nsMainThreadPtrH
+ 
+ namespace JS {
+ struct RuntimeStats;
+ } // namespace JS
+ 
+ namespace mozilla {
+ class ThrottledEventQueue;
+ namespace dom {
++class ClientInfo;
++class ClientSource;
+ class Function;
+ class MessagePort;
+ class MessagePortIdentifier;
+ class PromiseNativeHandler;
+ class StructuredCloneHolder;
+ class WorkerDebuggerGlobalScope;
+ class WorkerGlobalScope;
+ struct WorkerOptions;
+@@ -1055,16 +1057,17 @@ class WorkerPrivate : public WorkerPriva
+   // fired on the main thread if the worker script fails to load
+   nsCOMPtr<nsIRunnable> mLoadFailedRunnable;
+ 
+   JS::UniqueChars mDefaultLocale; // nulled during worker JSContext init
+   TimeStamp mKillTime;
+   uint32_t mErrorHandlerRecursionCount;
+   uint32_t mNextTimeoutId;
+   Status mStatus;
++  UniquePtr<ClientSource> mClientSource;
+   bool mFrozen;
+   bool mTimerRunning;
+   bool mRunningExpiredTimeouts;
+   bool mPendingEventQueueClearing;
+   bool mCancelAllPendingRunnables;
+   bool mPeriodicGCTimerRunning;
+   bool mIdleGCTimerRunning;
+   bool mWorkerScriptExecutedSuccessfully;
+@@ -1489,16 +1492,25 @@ public:
+   // Get an event target that will attempt to dispatch a normal WorkerRunnable,
+   // but if that fails will then fall back to a control runnable.
+   nsISerialEventTarget*
+   HybridEventTarget();
+ 
+   void
+   DumpCrashInformation(nsACString& aString);
+ 
++  void
++  EnsureClientSource();
++
++  const ClientInfo&
++  GetClientInfo() const;
++
++  void
++  ExecutionReady();
++
+ private:
+   WorkerPrivate(WorkerPrivate* aParent,
+                 const nsAString& aScriptURL, bool aIsChromeWorker,
+                 WorkerType aWorkerType, const nsAString& aWorkerName,
+                 const nsACString& aServiceWorkerScope,
+                 WorkerLoadInfo& aLoadInfo);
+ 
+   bool

+ 752 - 0
frg/work-js/mozilla-release/patches/1419536-3-59a1.patch

@@ -0,0 +1,752 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1511295185 18000
+# Node ID 3e2118a29baf7cb034e4c6d59e57eb67c8f40c37
+# Parent  df29a83db0ca5308cd5a5dc3d628ecb2511f6025
+Bug 1419536 P3 Handle creating and activating the ClientSource in nsDocShell and nsGlobalWindow. r=baku r=freesamael
+
+diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp
+--- a/docshell/base/nsDocShell.cpp
++++ b/docshell/base/nsDocShell.cpp
+@@ -8,16 +8,21 @@
+ 
+ #include <algorithm>
+ 
+ #include "mozilla/ArrayUtils.h"
+ #include "mozilla/Attributes.h"
+ #include "mozilla/AutoRestore.h"
+ #include "mozilla/BasePrincipal.h"
+ #include "mozilla/Casting.h"
++#include "mozilla/dom/ClientChannelHelper.h"
++#include "mozilla/dom/ClientHandle.h"
++#include "mozilla/dom/ClientInfo.h"
++#include "mozilla/dom/ClientManager.h"
++#include "mozilla/dom/ClientSource.h"
+ #include "mozilla/dom/ContentChild.h"
+ #include "mozilla/dom/Element.h"
+ #include "mozilla/dom/HTMLAnchorElement.h"
+ #include "mozilla/dom/TabChild.h"
+ #include "mozilla/dom/ProfileTimelineMarkerBinding.h"
+ #include "mozilla/dom/ScreenOrientation.h"
+ #include "mozilla/dom/ToJSValue.h"
+ #include "mozilla/dom/PermissionMessageUtils.h"
+@@ -3368,16 +3373,90 @@ nsDocShell::GetParent(nsIDocShellTreeIte
+ already_AddRefed<nsDocShell>
+ nsDocShell::GetParentDocshell()
+ {
+   nsCOMPtr<nsIDocShell> docshell = do_QueryInterface(GetAsSupports(mParent));
+   return docshell.forget().downcast<nsDocShell>();
+ }
+ 
+ void
++nsDocShell::MaybeCreateInitialClientSource(nsIPrincipal* aPrincipal)
++{
++  // If there is an existing document then there is no need to create
++  // a client for a future initial about:blank document.
++  if (mScriptGlobal && mScriptGlobal->GetExtantDoc()) {
++    MOZ_DIAGNOSTIC_ASSERT(
++      mScriptGlobal->GetCurrentInnerWindowInternal()->GetClientInfo().isSome());
++    MOZ_DIAGNOSTIC_ASSERT(!mInitialClientSource);
++    return;
++  }
++
++  // Don't recreate the initial client source.  We call this multiple times
++  // when DoChannelLoad() is called before CreateAboutBlankContentViewer.
++  if (mInitialClientSource) {
++    return;
++  }
++
++  // Don't pre-allocate the client when we are sandboxed.  The inherited
++  // principal does not take sandboxing into account.
++  // TODO: Refactor sandboxing principal code out so we can use it here.
++  if (!aPrincipal && (mSandboxFlags & SANDBOXED_ORIGIN)) {
++    return;
++  }
++
++  nsIPrincipal* principal = aPrincipal ? aPrincipal
++                                       : GetInheritedPrincipal(false);
++
++  // Sometimes there is no principal available when we are called from
++  // CreateAboutBlankContentViewer.  For example, sometimes the principal
++  // is only extracted from the load context after the document is created
++  // in nsDocument::ResetToURI().  Ideally we would do something similar
++  // here, but for now lets just avoid the issue by not preallocating the
++  // client.
++  if (!principal) {
++    return;
++  }
++
++  nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow();
++  if (!win) {
++    return;
++  }
++
++  mInitialClientSource =
++    ClientManager::CreateSource(ClientType::Window,
++                                win->EventTargetFor(TaskCategory::Other),
++                                principal);
++
++  // Mark the initial client as execution ready, but owned by the docshell.
++  // If the client is actually used this will cause ClientSource to force
++  // the creation of the initial about:blank by calling nsDocShell::GetDocument().
++  mInitialClientSource->DocShellExecutionReady(this);
++}
++
++Maybe<ClientInfo>
++nsDocShell::GetInitialClientInfo() const
++{
++  if (mInitialClientSource) {
++    Maybe<ClientInfo> result;
++    result.emplace(mInitialClientSource->Info());
++    return Move(result);
++  }
++
++  nsGlobalWindow* innerWindow =
++    mScriptGlobal ? mScriptGlobal->GetCurrentInnerWindowInternal() : nullptr;
++  nsIDocument* doc = innerWindow ? innerWindow->GetExtantDoc() : nullptr;
++
++  if (!doc || !doc->IsInitialDocument()) {
++    return Maybe<ClientInfo>();
++  }
++
++  return innerWindow->GetClientInfo();
++}
++
++void
+ nsDocShell::RecomputeCanExecuteScripts()
+ {
+   bool old = mCanExecuteScripts;
+   RefPtr<nsDocShell> parent = GetParentDocshell();
+ 
+   // If we have no tree owner, that means that we've been detached from the
+   // docshell tree (this is distinct from having no parent dochshell, which
+   // is the case for root docshells). It would be nice to simply disallow
+@@ -5876,16 +5955,19 @@ nsDocShell::Destroy()
+       const char* msg = mItemType == typeContent ?
+         NS_WEBNAVIGATION_DESTROY : NS_CHROME_WEBNAVIGATION_DESTROY;
+       serv->NotifyObservers(GetAsSupports(this), msg, nullptr);
+     }
+   }
+ 
+   mIsBeingDestroyed = true;
+ 
++  // Brak the cycle with the initial client, if present.
++  mInitialClientSource.reset();
++
+   // Make sure we don't record profile timeline markers anymore
+   SetRecordProfileTimelineMarkers(false);
+ 
+   // Remove our pref observers
+   if (mObserveErrorPages) {
+     mObserveErrorPages = false;
+   }
+ 
+@@ -7687,16 +7769,20 @@ nsDocShell::EndPageLoad(nsIWebProgress* 
+         internalLoadGroup->OnEndPageLoad(aChannel);
+       }
+     }
+   }
+ 
+   // Timing is picked up by the window, we don't need it anymore
+   mTiming = nullptr;
+ 
++  // Make sure to discard the initial client if we never created the initial
++  // about:blank document.
++  mInitialClientSource.reset();
++
+   // clean up reload state for meta charset
+   if (eCharsetReloadRequested == mCharsetReloadState) {
+     mCharsetReloadState = eCharsetReloadStopOrigional;
+   } else {
+     mCharsetReloadState = eCharsetReloadInit;
+   }
+ 
+   // Save a pointer to the currently-loading history entry.
+@@ -8183,16 +8269,19 @@ nsDocShell::CreateAboutBlankContentViewe
+       if (aPrincipal) {
+         principal = NullPrincipal::CreateWithInheritedAttributes(aPrincipal);
+       } else {
+         principal = NullPrincipal::CreateWithInheritedAttributes(this);
+       }
+     } else {
+       principal = aPrincipal;
+     }
++
++    MaybeCreateInitialClientSource(principal);
++
+     // generate (about:blank) document to load
+     blankDoc = nsContentDLF::CreateBlankDocument(mLoadGroup, principal, this);
+     if (blankDoc) {
+       // Hack: set the base URI manually, since this document never
+       // got Reset() with a channel.
+       blankDoc->SetBaseURI(aBaseURI);
+ 
+       // Copy our sandbox flags to the document. These are immutable
+@@ -11633,16 +11722,34 @@ nsDocShell::DoChannelLoad(nsIChannel* aC
+ 
+   uint32_t openFlags = 0;
+   if (mLoadType == LOAD_LINK) {
+     openFlags |= nsIURILoader::IS_CONTENT_PREFERRED;
+   }
+   if (!mAllowContentRetargeting) {
+     openFlags |= nsIURILoader::DONT_RETARGET;
+   }
++
++  nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow();
++  NS_ENSURE_TRUE(win, NS_ERROR_FAILURE);
++
++  MaybeCreateInitialClientSource();
++
++  // Since we are loading a document we need to make sure the proper reserved
++  // and initial client data is stored on the nsILoadInfo.  The
++  // ClientChannelHelper does this and ensures that it is propagated properly
++  // on redirects.  We pass no reserved client here so that the helper will
++  // create the reserved ClientSource if necessary.
++  Maybe<ClientInfo> noReservedClient;
++  rv = AddClientChannelHelper(aChannel,
++                              Move(noReservedClient),
++                              GetInitialClientInfo(),
++                              win->EventTargetFor(TaskCategory::Other));
++  NS_ENSURE_SUCCESS(rv, rv);
++
+   rv = aURILoader->OpenURI(aChannel, openFlags, this);
+   NS_ENSURE_SUCCESS(rv, rv);
+ 
+   // We're about to load a new page and it may take time before necko
+   // gives back any data, so main thread might have a chance to process a
+   // collector slice
+   nsJSContext::MaybeRunNextCollectorSlice(this, JS::gcreason::DOCSHELL);
+ 
+@@ -15043,16 +15150,22 @@ nsDocShell::InFrameSwap()
+     if (shell->mInFrameSwap) {
+       return true;
+     }
+     shell = shell->GetParentDocshell();
+   } while (shell);
+   return false;
+ }
+ 
++UniquePtr<ClientSource>
++nsDocShell::TakeInitialClientSource()
++{
++  return Move(mInitialClientSource);
++}
++
+ NS_IMETHODIMP
+ nsDocShell::IssueWarning(uint32_t aWarning, bool aAsError)
+ {
+   if (mContentViewer) {
+     nsCOMPtr<nsIDocument> doc = mContentViewer->GetDocument();
+     if (doc) {
+       doc->WarnOnceAbout(nsIDocument::DeprecatedOperations(aWarning), aAsError);
+     }
+diff --git a/docshell/base/nsDocShell.h b/docshell/base/nsDocShell.h
+--- a/docshell/base/nsDocShell.h
++++ b/docshell/base/nsDocShell.h
+@@ -14,16 +14,17 @@
+ #include "nsIBaseWindow.h"
+ #include "nsINetworkInterceptController.h"
+ #include "nsIScrollable.h"
+ #include "nsITextScroll.h"
+ #include "nsIContentViewerContainer.h"
+ #include "nsIDOMStorageManager.h"
+ #include "nsDocLoader.h"
+ #include "mozilla/BasePrincipal.h"
++#include "mozilla/Maybe.h"
+ #include "mozilla/Move.h"
+ #include "mozilla/UniquePtr.h"
+ #include "mozilla/WeakPtr.h"
+ #include "mozilla/TimeStamp.h"
+ #include "GeckoProfiler.h"
+ #include "mozilla/dom/ProfileTimelineMarkerBinding.h"
+ #include "mozilla/LinkedList.h"
+ #include "jsapi.h"
+@@ -63,16 +64,18 @@
+ #include "Units.h"
+ #include "nsIDeprecationWarner.h"
+ 
+ namespace mozilla {
+ class Encoding;
+ class HTMLEditor;
+ enum class TaskCategory;
+ namespace dom {
++class ClientInfo;
++class ClientSource;
+ class EventTarget;
+ typedef uint32_t ScreenOrientationInternal;
+ } // namespace dom
+ } // namespace mozilla
+ 
+ class nsDocShell;
+ class nsDOMNavigationTiming;
+ class nsGlobalWindow;
+@@ -822,16 +825,36 @@ protected:
+ 
+   nsIChannel* GetCurrentDocChannel();
+ 
+   bool ShouldBlockLoadingForBackButton();
+ 
+   // Convenience method for getting our parent docshell. Can return null
+   already_AddRefed<nsDocShell> GetParentDocshell();
+ 
++  // Possibly create a ClientSource object to represent an initial about:blank
++  // window that has not been allocated yet.  Normally we try not to create
++  // this about:blank window until something calls GetDocument().  We still need
++  // the ClientSource to exist for this conceptual window, though.
++  //
++  // The ClientSource is created with the given principal if specified.  If
++  // the principal is not provided we will attempt to inherit it when we
++  // are sure it will match what the real about:blank window principal
++  // would have been.  There are some corner cases where we cannot easily
++  // determine the correct principal and will not create the ClientSource.
++  // In these cases the initial about:blank will appear to not exist until
++  // its real document and window are created.
++  void MaybeCreateInitialClientSource(nsIPrincipal* aPrincipal = nullptr);
++
++  // Return the ClientInfo for the initial about:blank window, if it exists
++  // or we have speculatively created a ClientSource in
++  // MaybeCreateInitialClientSource().  This can return a ClientInfo object
++  // even if GetExtantDoc() returns nullptr.
++  mozilla::Maybe<mozilla::dom::ClientInfo> GetInitialClientInfo() const;
++
+ protected:
+   nsresult GetCurScrollPos(int32_t aScrollOrientation, int32_t* aCurPos);
+   nsresult SetCurScrollPosEx(int32_t aCurHorizontalPos,
+                              int32_t aCurVerticalPos);
+ 
+   // Override the parent setter from nsDocLoader
+   virtual nsresult SetDocLoaderParent(nsDocLoader* aLoader) override;
+ 
+@@ -1145,16 +1168,18 @@ private:
+   nsCOMPtr<nsIPrincipal> mParentCharsetPrincipal;
+   nsTObserverArray<nsWeakPtr> mPrivacyObservers;
+   nsTObserverArray<nsWeakPtr> mReflowObservers;
+   nsTObserverArray<nsWeakPtr> mScrollObservers;
+   nsCString mOriginalUriString;
+   nsWeakPtr mOpener;
+   mozilla::OriginAttributes mOriginAttributes;
+ 
++  mozilla::UniquePtr<mozilla::dom::ClientSource> mInitialClientSource;
++
+   // A depth count of how many times NotifyRunToCompletionStart
+   // has been called without a matching NotifyRunToCompletionStop.
+   uint32_t mJSRunToCompletionDepth;
+ 
+   // Whether or not touch events are overridden. Possible values are defined
+   // as constants in the nsIDocShell.idl file.
+   uint32_t mTouchEventsOverride;
+ 
+diff --git a/docshell/base/nsIDocShell.idl b/docshell/base/nsIDocShell.idl
+--- a/docshell/base/nsIDocShell.idl
++++ b/docshell/base/nsIDocShell.idl
+@@ -7,34 +7,39 @@
+ #include "domstubs.idl"
+ #include "nsIDocShellTreeItem.idl"
+ #include "nsIRequest.idl"
+ 
+ %{ C++
+ #include "js/TypeDecls.h"
+ #include "mozilla/Maybe.h"
+ #include "mozilla/NotNull.h"
++#include "mozilla/UniquePtr.h"
+ #include "nsCOMPtr.h"
+ #include "nsIURI.h"
+ class nsPresContext;
+ class nsIPresShell;
+ namespace mozilla {
+ class Encoding;
+ class HTMLEditor;
++namespace dom {
++class ClientSource;
++} // namespace dom
+ }
+ %}
+ 
+ /**
+  * The nsIDocShell interface.
+  */
+ 
+ [ptr] native nsPresContext(nsPresContext);
+ [ptr] native nsIPresShell(nsIPresShell);
+ [ref] native MaybeURI(mozilla::Maybe<nsCOMPtr<nsIURI>>);
+ [ref] native Encoding(const mozilla::Encoding*);
++      native UniqueClientSource(mozilla::UniquePtr<mozilla::dom::ClientSource>);
+ 
+ interface nsIURI;
+ interface nsIChannel;
+ interface nsIContentViewer;
+ interface nsIDOMEventTarget;
+ interface nsIDocShellLoadInfo;
+ interface nsIEditor;
+ interface nsIEditingSession;
+@@ -1155,16 +1160,27 @@ interface nsIDocShell : nsIDocShellTreeI
+    */
+   attribute boolean useTrackingProtection;
+ 
+  /**
+   * Fire a dummy location change event asynchronously.
+   */
+   [noscript] void dispatchLocationChangeEvent();
+ 
++  /**
++   * Take ownership of the ClientSource representing an initial about:blank
++   * document that was never needed.  As an optimization we avoid creating
++   * this document if no code calls GetDocument(), but we still need a
++   * ClientSource object to represent the about:blank window.  This may return
++   * nullptr; for example if the docshell has created a real window and document
++   * already.
++   */
++  [noscript, nostdcall, notxpcom]
++  UniqueClientSource TakeInitialClientSource();
++
+ %{C++
+   /**
+    * These methods call nsDocShell::GetHTMLEditorInternal() and
+    * nsDocShell::SetHTMLEditorInternal() with static_cast.
+    */
+   mozilla::HTMLEditor* GetHTMLEditor();
+   nsresult SetHTMLEditor(mozilla::HTMLEditor* aHTMLEditor);
+ %}
+diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp
+--- a/dom/base/nsGlobalWindow.cpp
++++ b/dom/base/nsGlobalWindow.cpp
+@@ -12,16 +12,18 @@
+ 
+ // Local Includes
+ #include "Navigator.h"
+ #include "nsContentSecurityManager.h"
+ #include "nsScreen.h"
+ #include "nsHistory.h"
+ #include "nsDOMNavigationTiming.h"
+ #include "nsIDOMStorageManager.h"
++#include "mozilla/dom/ClientManager.h"
++#include "mozilla/dom/ClientSource.h"
+ #include "mozilla/dom/LocalStorage.h"
+ #include "mozilla/dom/Storage.h"
+ #include "mozilla/dom/IdleRequest.h"
+ #include "mozilla/dom/Performance.h"
+ #include "mozilla/dom/StorageEvent.h"
+ #include "mozilla/dom/StorageEventBinding.h"
+ #include "mozilla/dom/StorageNotifierService.h"
+ #include "mozilla/dom/StorageUtils.h"
+@@ -2129,16 +2131,19 @@ nsGlobalWindow::FreeInnerObjects()
+   DisableGamepadUpdates();
+   mHasGamepad = false;
+   mGamepads.Clear();
+   DisableVRUpdates();
+   mHasVREvents = false;
+   mHasVRDisplayActivateEvents = false;
+   mVRDisplays.Clear();
+ 
++  // This breaks a cycle between the window and the ClientSource object.
++  mClientSource.reset();
++
+   if (mTabChild) {
+     while (mBeforeUnloadListenerCount-- > 0) {
+       mTabChild->BeforeUnloadRemoved();
+     }
+   }
+ }
+ 
+ //*****************************************************************************
+@@ -2404,16 +2409,18 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
+   tmp->UnlinkHostObjectURIs();
+ 
+   NS_IMPL_CYCLE_COLLECTION_UNLINK(mIdleRequestExecutor)
+ 
+   // Here the IdleRequest list would've been unlinked, but we rely on
+   // that IdleRequest objects have been traced and will remove
+   // themselves while unlinking.
+ 
++  tmp->mClientSource.reset();
++
+   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingPromises)
+ 
+   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+ 
+ #ifdef DEBUG
+ void
+ nsGlobalWindow::RiskyUnlink()
+@@ -3239,24 +3246,32 @@ nsGlobalWindow::SetNewDocument(nsIDocume
+         newInnerWindow->mDoc = aDocument;
+ 
+         // The storage objects contain the URL of the window. We have to
+         // recreate them when the innerWindow is reused.
+         newInnerWindow->mLocalStorage = nullptr;
+         newInnerWindow->mSessionStorage = nullptr;
+ 
+         newInnerWindow->ClearDocumentDependentSlots(cx);
++
++        // When replacing an initial about:blank document we call
++        // ExecutionReady again to update the client creation URL.
++        rv = newInnerWindow->ExecutionReady();
++        NS_ENSURE_SUCCESS(rv, rv);
+       }
+     } else {
+       newInnerWindow->InnerSetNewDocument(cx, aDocument);
+ 
+       // Initialize DOM classes etc on the inner window.
+       JS::Rooted<JSObject*> obj(cx, newInnerGlobal);
+       rv = kungFuDeathGrip->InitClasses(obj);
+       NS_ENSURE_SUCCESS(rv, rv);
++
++      rv = newInnerWindow->ExecutionReady();
++      NS_ENSURE_SUCCESS(rv, rv);
+     }
+ 
+     // If the document comes from a JAR, check if the channel was determined
+     // to be unsafe. If so, permanently disable script on the compartment by
+     // calling Block() and throwing away the key.
+     nsCOMPtr<nsIJARChannel> jarChannel = do_QueryInterface(aDocument->GetChannel());
+     if (jarChannel && jarChannel->GetIsUnsafe()) {
+       xpc::Scriptability::Get(newInnerGlobal).Block();
+@@ -3420,16 +3435,78 @@ nsGlobalWindow::InnerSetNewDocument(JSCo
+ 
+   Telemetry::Accumulate(Telemetry::INNERWINDOWS_WITH_MUTATION_LISTENERS,
+                         mMutationBits ? 1 : 0);
+ 
+   // Clear our mutation bitfield.
+   mMutationBits = 0;
+ }
+ 
++nsresult
++nsGlobalWindow::EnsureClientSource()
++{
++  MOZ_DIAGNOSTIC_ASSERT(mDoc);
++
++  nsCOMPtr<nsIChannel> channel = mDoc->GetChannel();
++  nsCOMPtr<nsILoadInfo> loadInfo = channel ? channel->GetLoadInfo() : nullptr;
++
++  // Try to get the reserved client from the LoadInfo.  A Client is
++  // reserved at the start of the channel load if there is not an
++  // initial about:blank document that will be reused.  It is also
++  // created if the channel load encounters a cross-origin redirect.
++  if (loadInfo) {
++    UniquePtr<ClientSource> reservedClient = loadInfo->TakeReservedClientSource();
++    if (reservedClient) {
++      mClientSource.reset();
++      mClientSource = Move(reservedClient);
++    }
++  }
++
++  // We don't have a LoadInfo reserved client, but maybe we should
++  // be inheriting an initial one from the docshell.  This means
++  // that the docshell started the channel load before creating the
++  // initial about:blank document.  This is an optimization, though,
++  // and it created an initial Client as a placeholder for the document.
++  // In this case we want to inherit this placeholder Client here.
++  if (!mClientSource) {
++    nsIDocShell* docshell = GetDocShell();
++    if (docshell) {
++      mClientSource = docshell->TakeInitialClientSource();
++    }
++  }
++ 
++  // If we don't have a reserved client or an initial client, then create
++  // one now.  This can happen in certain cases where we avoid preallocating
++  // the client in the docshell.  This mainly occurs in situations where
++  // the principal is not clearly inherited from the parent; e.g. sandboxed
++  // iframes, window.open(), etc.
++  if (!mClientSource) {
++    mClientSource = ClientManager::CreateSource(ClientType::Window,
++                                                EventTargetFor(TaskCategory::Other),
++                                                mDoc->NodePrincipal());
++    if (NS_WARN_IF(!mClientSource)) {
++      return NS_ERROR_FAILURE;
++    }
++  }
++
++  return NS_OK;
++}
++
++nsresult
++nsGlobalWindow::ExecutionReady()
++{
++  nsresult rv = EnsureClientSource();
++  NS_ENSURE_SUCCESS(rv, rv);
++
++  rv = mClientSource->WindowExecutionReady(AsInner());
++  NS_ENSURE_SUCCESS(rv, rv);
++
++  return NS_OK;
++}
++
+ void
+ nsGlobalWindow::SetDocShell(nsIDocShell* aDocShell)
+ {
+   NS_ASSERTION(IsOuterWindow(), "Uh, SetDocShell() called on inner window!");
+   MOZ_ASSERT(aDocShell);
+ 
+   if (aDocShell == mDocShell) {
+     return;
+@@ -4283,16 +4360,22 @@ nsPIDOMWindowInner::Thaw()
+ }
+ 
+ void
+ nsPIDOMWindowInner::SyncStateFromParentWindow()
+ {
+   nsGlobalWindow::Cast(this)->SyncStateFromParentWindow();
+ }
+ 
++Maybe<ClientInfo>
++nsPIDOMWindowInner::GetClientInfo() const
++{
++  return Move(nsGlobalWindow::Cast(this)->GetClientInfo());
++}
++
+ void
+ nsGlobalWindow::UpdateTopInnerWindow()
+ {
+   if (!IsInnerWindow() || AsInner()->IsTopInnerWindow()) {
+     return;
+   }
+ 
+   AsInner()->UpdateWebSocketCount(-(int32_t)mNumOfOpenWebSockets);
+@@ -12459,16 +12542,27 @@ nsGlobalWindow::CallOnChildren(Method aM
+     if (!mDoc || !frame || mDoc != frame->OwnerDoc() || !inner) {
+       continue;
+     }
+ 
+     (inner->*aMethod)();
+   }
+ }
+ 
++Maybe<ClientInfo>
++nsGlobalWindow::GetClientInfo() const
++{
++  MOZ_ASSERT(NS_IsMainThread());
++  Maybe<ClientInfo> clientInfo;
++  if (mClientSource) {
++    clientInfo.emplace(mClientSource->Info());
++  }
++  return Move(clientInfo);
++}
++
+ nsresult
+ nsGlobalWindow::FireDelayedDOMEvents()
+ {
+   FORWARD_TO_INNER(FireDelayedDOMEvents, (), NS_ERROR_UNEXPECTED);
+ 
+   if (mApplicationCache) {
+     static_cast<nsDOMOfflineResourceList*>(mApplicationCache.get())->FirePendingEvents();
+   }
+diff --git a/dom/base/nsGlobalWindow.h b/dom/base/nsGlobalWindow.h
+--- a/dom/base/nsGlobalWindow.h
++++ b/dom/base/nsGlobalWindow.h
+@@ -106,16 +106,17 @@ class IdleRequestExecutor;
+ 
+ namespace mozilla {
+ class AbstractThread;
+ class DOMEventTargetHelper;
+ class ThrottledEventQueue;
+ namespace dom {
+ class BarProp;
+ struct ChannelPixelLayout;
++class ClientSource;
+ class Console;
+ class Crypto;
+ class CustomElementRegistry;
+ class DocGroup;
+ class External;
+ class Function;
+ class Gamepad;
+ enum class ImageBitmapFormat : uint8_t;
+@@ -397,16 +398,18 @@ public:
+   void Suspend();
+   void Resume();
+   virtual bool IsSuspended() const override;
+   void Freeze();
+   void Thaw();
+   virtual bool IsFrozen() const override;
+   void SyncStateFromParentWindow();
+ 
++  mozilla::Maybe<mozilla::dom::ClientInfo> GetClientInfo() const;
++
+   virtual nsresult FireDelayedDOMEvents() override;
+ 
+   // Outer windows only.
+   virtual bool WouldReuseInnerWindow(nsIDocument* aNewDocument) override;
+ 
+   virtual void SetDocShell(nsIDocShell* aDocShell) override;
+   virtual void DetachFromDocShell() override;
+   virtual nsresult SetNewDocument(nsIDocument *aDocument,
+@@ -1462,16 +1465,19 @@ protected:
+ 
+   void FreeInnerObjects();
+   nsGlobalWindow *CallerInnerWindow();
+ 
+   // Only to be called on an inner window.
+   // aDocument must not be null.
+   void InnerSetNewDocument(JSContext* aCx, nsIDocument* aDocument);
+ 
++  nsresult EnsureClientSource();
++  nsresult ExecutionReady();
++
+   // Inner windows only.
+   nsresult DefineArgumentsProperty(nsIArray *aArguments);
+ 
+   // Get the parent, returns null if this is a toplevel window
+   nsPIDOMWindowOuter* GetParentInternal();
+ 
+ public:
+   // popup tracking
+@@ -2032,16 +2038,18 @@ protected:
+   // When non-zero, the document should receive a vrdisplayactivate event
+   // after loading.  The value is the ID of the VRDisplay that content should
+   // begin presentation on.
+   uint32_t mAutoActivateVRDisplayID; // Outer windows only
+   int64_t mBeforeUnloadListenerCount; // Inner windows only
+ 
+   RefPtr<mozilla::dom::IntlUtils> mIntlUtils;
+ 
++  mozilla::UniquePtr<mozilla::dom::ClientSource> mClientSource;
++
+   nsTArray<RefPtr<mozilla::dom::Promise>> mPendingPromises; // Inner windows only
+ 
+   friend class nsDOMScriptableHelper;
+   friend class nsDOMWindowUtils;
+   friend class mozilla::dom::PostMessageEvent;
+   friend class DesktopNotification;
+   friend class mozilla::dom::TimeoutManager;
+   friend class IdleRequestExecutor;
+diff --git a/dom/base/nsPIDOMWindow.h b/dom/base/nsPIDOMWindow.h
+--- a/dom/base/nsPIDOMWindow.h
++++ b/dom/base/nsPIDOMWindow.h
+@@ -40,16 +40,17 @@ class nsPIWindowRoot;
+ class nsXBLPrototypeHandler;
+ 
+ typedef uint32_t SuspendTypes;
+ 
+ namespace mozilla {
+ class ThrottledEventQueue;
+ namespace dom {
+ class AudioContext;
++class ClientInfo;
+ class DocGroup;
+ class TabGroup;
+ class Element;
+ class Performance;
+ class ServiceWorkerRegistration;
+ class Timeout;
+ class TimeoutManager;
+ class CustomElementRegistry;
+@@ -930,16 +931,18 @@ public:
+ 
+   // Increase/Decrease the number of open WebSockets.
+   void UpdateWebSocketCount(int32_t aDelta);
+ 
+   // Return true if there are any open WebSockets that could block
+   // timeout-throttling.
+   bool HasOpenWebSockets() const;
+ 
++  mozilla::Maybe<mozilla::dom::ClientInfo> GetClientInfo() const;
++
+ protected:
+   void CreatePerformanceObjectIfNeeded();
+ };
+ 
+ NS_DEFINE_STATIC_IID_ACCESSOR(nsPIDOMWindowInner, NS_PIDOMWINDOWINNER_IID)
+ 
+ // NB: It's very very important that these two classes have identical vtables
+ // and memory layout!

+ 441 - 0
frg/work-js/mozilla-release/patches/1419536-4-59a1.patch

@@ -0,0 +1,441 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1511295185 18000
+# Node ID 61d93d8a46679c63f56b0ad4719834e87a7a7f72
+# Parent  e91820ef7d982cd096558634056b19ab53b6c57c
+Bug 1419536 P4 Allow ClientSource objects to be frozen while in bfcache. r=baku
+
+diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp
+--- a/dom/base/nsGlobalWindow.cpp
++++ b/dom/base/nsGlobalWindow.cpp
+@@ -3440,56 +3440,69 @@ nsGlobalWindow::InnerSetNewDocument(JSCo
+   mMutationBits = 0;
+ }
+ 
+ nsresult
+ nsGlobalWindow::EnsureClientSource()
+ {
+   MOZ_DIAGNOSTIC_ASSERT(mDoc);
+ 
++  bool newClientSource = false;
++
+   nsCOMPtr<nsIChannel> channel = mDoc->GetChannel();
+   nsCOMPtr<nsILoadInfo> loadInfo = channel ? channel->GetLoadInfo() : nullptr;
+ 
+   // Try to get the reserved client from the LoadInfo.  A Client is
+   // reserved at the start of the channel load if there is not an
+   // initial about:blank document that will be reused.  It is also
+   // created if the channel load encounters a cross-origin redirect.
+   if (loadInfo) {
+     UniquePtr<ClientSource> reservedClient = loadInfo->TakeReservedClientSource();
+     if (reservedClient) {
+       mClientSource.reset();
+       mClientSource = Move(reservedClient);
++      newClientSource = true;
+     }
+   }
+ 
+   // We don't have a LoadInfo reserved client, but maybe we should
+   // be inheriting an initial one from the docshell.  This means
+   // that the docshell started the channel load before creating the
+   // initial about:blank document.  This is an optimization, though,
+   // and it created an initial Client as a placeholder for the document.
+   // In this case we want to inherit this placeholder Client here.
+   if (!mClientSource) {
+     nsIDocShell* docshell = GetDocShell();
+     if (docshell) {
+       mClientSource = docshell->TakeInitialClientSource();
++      if (mClientSource) {
++        newClientSource = true;
++      }
+     }
+   }
+  
+   // If we don't have a reserved client or an initial client, then create
+   // one now.  This can happen in certain cases where we avoid preallocating
+   // the client in the docshell.  This mainly occurs in situations where
+   // the principal is not clearly inherited from the parent; e.g. sandboxed
+   // iframes, window.open(), etc.
+   if (!mClientSource) {
+     mClientSource = ClientManager::CreateSource(ClientType::Window,
+                                                 EventTargetFor(TaskCategory::Other),
+                                                 mDoc->NodePrincipal());
+     if (NS_WARN_IF(!mClientSource)) {
+       return NS_ERROR_FAILURE;
+     }
++    newClientSource = true;
++  }
++
++  // Its possible that we got a client just after being frozen in
++  // the bfcache.  In that case freeze the client immediately.
++  if (newClientSource && IsFrozen()) {
++    mClientSource->Freeze();
+   }
+ 
+   return NS_OK;
+ }
+ 
+ nsresult
+ nsGlobalWindow::ExecutionReady()
+ {
+@@ -12405,16 +12418,19 @@ nsGlobalWindow::FreezeInternal()
+   MOZ_ASSERT(mSuspendDepth >= mFreezeDepth);
+   if (mFreezeDepth != 1) {
+     return;
+   }
+ 
+   mozilla::dom::workers::FreezeWorkersForWindow(AsInner());
+ 
+   mTimeoutManager->Freeze();
++  if (mClientSource) {
++    mClientSource->Freeze();
++  }
+ 
+   NotifyDOMWindowFrozen(this);
+ }
+ 
+ void
+ nsGlobalWindow::Thaw()
+ {
+   MOZ_ASSERT(NS_IsMainThread());
+@@ -12434,16 +12450,19 @@ nsGlobalWindow::ThawInternal()
+ 
+   MOZ_ASSERT(mFreezeDepth != 0);
+   mFreezeDepth -= 1;
+   MOZ_ASSERT(mSuspendDepth >= mFreezeDepth);
+   if (mFreezeDepth != 0) {
+     return;
+   }
+ 
++  if (mClientSource) {
++    mClientSource->Thaw();
++  }
+   mTimeoutManager->Thaw();
+ 
+   mozilla::dom::workers::ThawWorkersForWindow(AsInner());
+ 
+   NotifyDOMWindowThawed(this);
+ }
+ 
+ bool
+diff --git a/dom/clients/manager/ClientManagerService.cpp b/dom/clients/manager/ClientManagerService.cpp
+--- a/dom/clients/manager/ClientManagerService.cpp
++++ b/dom/clients/manager/ClientManagerService.cpp
+@@ -121,17 +121,18 @@ ClientManagerService::FindSource(const n
+   AssertIsOnBackgroundThread();
+ 
+   auto entry = mSourceTable.Lookup(aID);
+   if (!entry) {
+     return nullptr;
+   }
+ 
+   ClientSourceParent* source = entry.Data();
+-  if (!MatchPrincipalInfo(source->Info().PrincipalInfo(), aPrincipalInfo)) {
++  if (source->IsFrozen() ||
++      !MatchPrincipalInfo(source->Info().PrincipalInfo(), aPrincipalInfo)) {
+     return nullptr;
+   }
+ 
+   return source;
+ }
+ 
+ } // namespace dom
+ } // namespace mozilla
+diff --git a/dom/clients/manager/ClientSource.cpp b/dom/clients/manager/ClientSource.cpp
+--- a/dom/clients/manager/ClientSource.cpp
++++ b/dom/clients/manager/ClientSource.cpp
+@@ -230,16 +230,32 @@ ClientSource::DocShellExecutionReady(nsI
+ 
+   ClientSourceExecutionReadyArgs args(NS_LITERAL_CSTRING("about:blank"),
+                                       frameType);
+   ExecutionReady(args);
+ 
+   return NS_OK;
+ }
+ 
++void
++ClientSource::Freeze()
++{
++  MaybeExecute([](PClientSourceChild* aActor) {
++    aActor->SendFreeze();
++  });
++}
++
++void
++ClientSource::Thaw()
++{
++  MaybeExecute([](PClientSourceChild* aActor) {
++    aActor->SendThaw();
++  });
++}
++
+ const ClientInfo&
+ ClientSource::Info() const
+ {
+   return mClientInfo;
+ }
+ 
+ nsISerialEventTarget*
+ ClientSource::EventTarget() const
+diff --git a/dom/clients/manager/ClientSource.h b/dom/clients/manager/ClientSource.h
+--- a/dom/clients/manager/ClientSource.h
++++ b/dom/clients/manager/ClientSource.h
+@@ -77,16 +77,22 @@ public:
+   WorkerExecutionReady(mozilla::dom::workers::WorkerPrivate* aWorkerPrivate);
+ 
+   nsresult
+   WindowExecutionReady(nsPIDOMWindowInner* aInnerWindow);
+ 
+   nsresult
+   DocShellExecutionReady(nsIDocShell* aDocShell);
+ 
++  void
++  Freeze();
++
++  void
++  Thaw();
++
+   const ClientInfo&
+   Info() const;
+ 
+   nsISerialEventTarget*
+   EventTarget() const;
+ };
+ 
+ } // namespace dom
+diff --git a/dom/clients/manager/ClientSourceParent.cpp b/dom/clients/manager/ClientSourceParent.cpp
+--- a/dom/clients/manager/ClientSourceParent.cpp
++++ b/dom/clients/manager/ClientSourceParent.cpp
+@@ -106,16 +106,40 @@ ClientSourceParent::RecvExecutionReady(c
+ 
+   for (ClientHandleParent* handle : mHandleList) {
+     Unused << handle->SendExecutionReady(mClientInfo.ToIPC());
+   }
+ 
+   return IPC_OK();
+ };
+ 
++IPCResult
++ClientSourceParent::RecvFreeze()
++{
++  MOZ_DIAGNOSTIC_ASSERT(!mFrozen);
++  mFrozen = true;
++
++  // Frozen clients should not be observable.  Act as if the client has
++  // been destroyed.
++  nsTArray<ClientHandleParent*> handleList(mHandleList);
++  for (ClientHandleParent* handle : handleList) {
++    Unused << ClientHandleParent::Send__delete__(handle);
++  }
++
++  return IPC_OK();
++}
++
++IPCResult
++ClientSourceParent::RecvThaw()
++{
++  MOZ_DIAGNOSTIC_ASSERT(mFrozen);
++  mFrozen = false;
++  return IPC_OK();
++}
++
+ void
+ ClientSourceParent::ActorDestroy(ActorDestroyReason aReason)
+ {
+   DebugOnly<bool> removed = mService->RemoveSource(this);
+   MOZ_ASSERT(removed);
+ 
+   nsTArray<ClientHandleParent*> handleList(mHandleList);
+   for (ClientHandleParent* handle : handleList) {
+@@ -139,16 +163,17 @@ ClientSourceParent::DeallocPClientSource
+   delete aActor;
+   return true;
+ }
+ 
+ ClientSourceParent::ClientSourceParent(const ClientSourceConstructorArgs& aArgs)
+   : mClientInfo(aArgs.id(), aArgs.type(), aArgs.principalInfo(), aArgs.creationTime())
+   , mService(ClientManagerService::GetOrCreateInstance())
+   , mExecutionReady(false)
++  , mFrozen(false)
+ {
+ }
+ 
+ ClientSourceParent::~ClientSourceParent()
+ {
+   MOZ_DIAGNOSTIC_ASSERT(mHandleList.IsEmpty());
+ }
+ 
+@@ -173,20 +198,27 @@ ClientSourceParent::Init()
+ }
+ 
+ const ClientInfo&
+ ClientSourceParent::Info() const
+ {
+   return mClientInfo;
+ }
+ 
++bool
++ClientSourceParent::IsFrozen() const
++{
++  return mFrozen;
++}
++
+ void
+ ClientSourceParent::AttachHandle(ClientHandleParent* aClientHandle)
+ {
+   MOZ_DIAGNOSTIC_ASSERT(aClientHandle);
++  MOZ_DIAGNOSTIC_ASSERT(!mFrozen);
+   MOZ_ASSERT(!mHandleList.Contains(aClientHandle));
+   mHandleList.AppendElement(aClientHandle);
+ }
+ 
+ void
+ ClientSourceParent::DetachHandle(ClientHandleParent* aClientHandle)
+ {
+   MOZ_DIAGNOSTIC_ASSERT(aClientHandle);
+diff --git a/dom/clients/manager/ClientSourceParent.h b/dom/clients/manager/ClientSourceParent.h
+--- a/dom/clients/manager/ClientSourceParent.h
++++ b/dom/clients/manager/ClientSourceParent.h
+@@ -16,27 +16,34 @@ class ClientHandleParent;
+ class ClientManagerService;
+ 
+ class ClientSourceParent final : public PClientSourceParent
+ {
+   ClientInfo mClientInfo;
+   RefPtr<ClientManagerService> mService;
+   nsTArray<ClientHandleParent*> mHandleList;
+   bool mExecutionReady;
++  bool mFrozen;
+ 
+   void
+   KillInvalidChild();
+ 
+   // PClientSourceParent
+   mozilla::ipc::IPCResult
+   RecvTeardown() override;
+ 
+   mozilla::ipc::IPCResult
+   RecvExecutionReady(const ClientSourceExecutionReadyArgs& aArgs) override;
+ 
++  mozilla::ipc::IPCResult
++  RecvFreeze() override;
++
++  mozilla::ipc::IPCResult
++  RecvThaw() override;
++
+   void
+   ActorDestroy(ActorDestroyReason aReason) override;
+ 
+   PClientSourceOpParent*
+   AllocPClientSourceOpParent(const ClientOpConstructorArgs& aArgs) override;
+ 
+   bool
+   DeallocPClientSourceOpParent(PClientSourceOpParent* aActor) override;
+@@ -46,16 +53,19 @@ public:
+   ~ClientSourceParent();
+ 
+   void
+   Init();
+ 
+   const ClientInfo&
+   Info() const;
+ 
++  bool
++  IsFrozen() const;
++
+   void
+   AttachHandle(ClientHandleParent* aClientSource);
+ 
+   void
+   DetachHandle(ClientHandleParent* aClientSource);
+ };
+ 
+ } // namespace dom
+diff --git a/dom/clients/manager/PClientSource.ipdl b/dom/clients/manager/PClientSource.ipdl
+--- a/dom/clients/manager/PClientSource.ipdl
++++ b/dom/clients/manager/PClientSource.ipdl
+@@ -17,16 +17,18 @@ sync protocol PClientSource
+ {
+   manager PClientManager;
+ 
+   manages PClientSourceOp;
+ 
+ parent:
+   async Teardown();
+   async ExecutionReady(ClientSourceExecutionReadyArgs aArgs);
++  async Freeze();
++  async Thaw();
+ 
+ child:
+   async PClientSourceOp(ClientOpConstructorArgs aArgs);
+ 
+   async __delete__();
+ };
+ 
+ } // namespace dom
+diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp
+--- a/dom/workers/WorkerPrivate.cpp
++++ b/dom/workers/WorkerPrivate.cpp
+@@ -5318,16 +5318,19 @@ WorkerPrivate::EnsureClientSource()
+       type = ClientType::Serviceworker;
+       break;
+     default:
+       MOZ_CRASH("unknown worker type!");
+   }
+ 
+   mClientSource = ClientManager::CreateSource(type, mWorkerHybridEventTarget,
+                                               GetPrincipalInfo());
++  if (mFrozen) {
++    mClientSource->Freeze();
++  }
+ }
+ 
+ const ClientInfo&
+ WorkerPrivate::GetClientInfo() const
+ {
+   AssertIsOnWorkerThread();
+   MOZ_DIAGNOSTIC_ASSERT(mClientSource);
+   return mClientSource->Info();
+@@ -5675,16 +5678,20 @@ WorkerPrivate::ClearDebuggerEventQueue()
+ 
+ bool
+ WorkerPrivate::FreezeInternal()
+ {
+   AssertIsOnWorkerThread();
+ 
+   NS_ASSERTION(!mFrozen, "Already frozen!");
+ 
++  if (mClientSource) {
++    mClientSource->Freeze();
++  }
++
+   mFrozen = true;
+ 
+   for (uint32_t index = 0; index < mChildWorkers.Length(); index++) {
+     mChildWorkers[index]->Freeze(nullptr);
+   }
+ 
+   return true;
+ }
+@@ -5696,16 +5703,21 @@ WorkerPrivate::ThawInternal()
+ 
+   NS_ASSERTION(mFrozen, "Not yet frozen!");
+ 
+   for (uint32_t index = 0; index < mChildWorkers.Length(); index++) {
+     mChildWorkers[index]->Thaw(nullptr);
+   }
+ 
+   mFrozen = false;
++
++  if (mClientSource) {
++    mClientSource->Thaw();
++  }
++
+   return true;
+ }
+ 
+ void
+ WorkerPrivate::TraverseTimeouts(nsCycleCollectionTraversalCallback& cb)
+ {
+   for (uint32_t i = 0; i < mTimeouts.Length(); ++i) {
+     TimeoutInfo* tmp = mTimeouts[i];

+ 36 - 0
frg/work-js/mozilla-release/patches/1419536-5-59a1.patch

@@ -0,0 +1,36 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1511295185 18000
+# Node ID dbbca05aa64a14580d3ab23950e148019a097218
+# Parent  6babb535fca120de522cf786de787d05038e0c5b
+Bug 1419536 P5 Set ClientInfo on the LoadInfo for document owned network requests. r=baku
+
+diff --git a/netwerk/base/LoadInfo.cpp b/netwerk/base/LoadInfo.cpp
+--- a/netwerk/base/LoadInfo.cpp
++++ b/netwerk/base/LoadInfo.cpp
+@@ -104,16 +104,25 @@ LoadInfo::LoadInfo(nsIPrincipal* aLoadin
+   // if the load is sandboxed, we can not also inherit the principal
+   if (mSecurityFlags & nsILoadInfo::SEC_SANDBOXED) {
+     mForceInheritPrincipalDropped =
+       (mSecurityFlags & nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL);
+     mSecurityFlags &= ~nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
+   }
+ 
+   if (aLoadingContext) {
++    // Ensure that all network requests for a window client have the ClientInfo
++    // properly set.
++    // TODO: The ClientInfo is not set properly for worker initiated requests yet.
++    nsCOMPtr<nsPIDOMWindowInner> contextInner =
++      aLoadingContext->OwnerDoc()->GetInnerWindow();
++    if (contextInner) {
++      mClientInfo = contextInner->GetClientInfo();
++    }
++
+     nsCOMPtr<nsPIDOMWindowOuter> contextOuter = aLoadingContext->OwnerDoc()->GetWindow();
+     if (contextOuter) {
+       ComputeIsThirdPartyContext(contextOuter);
+       mOuterWindowID = contextOuter->WindowID();
+       nsCOMPtr<nsPIDOMWindowOuter> parent = contextOuter->GetScriptableParent();
+       mParentOuterWindowID = parent ? parent->WindowID() : mOuterWindowID;
+       mTopOuterWindowID = FindTopOuterWindowID(contextOuter);
+     }

+ 126 - 0
frg/work-js/mozilla-release/patches/1420221-59a1.patch

@@ -0,0 +1,126 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1511812097 18000
+# Node ID afefcc7ecca06203a0ea46f4f6949ab509911c93
+# Parent  c4ab0ad57f756a32aa6717c492bb2f3e27c3ecb7
+Bug 1420221 Make workers handle the case when the ClientSource cannot be created during shutdown. r=baku
+
+diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp
+--- a/dom/workers/WorkerPrivate.cpp
++++ b/dom/workers/WorkerPrivate.cpp
+@@ -595,17 +595,19 @@ private:
+   // run we have not yet done our load so don't know things like our final
+   // principal and whatnot.
+ 
+   virtual bool
+   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+   {
+     aWorkerPrivate->AssertIsOnWorkerThread();
+ 
+-    aWorkerPrivate->EnsureClientSource();
++    if (NS_WARN_IF(!aWorkerPrivate->EnsureClientSource())) {
++      return false;
++    }
+ 
+     ErrorResult rv;
+     scriptloader::LoadMainScript(aWorkerPrivate, mScriptURL, WorkerScript, rv);
+     rv.WouldReportJSException();
+     // Explicitly ignore NS_BINDING_ABORTED on rv.  Or more precisely, still
+     // return false and don't SetWorkerScriptExecutedSuccessfully() in that
+     // case, but don't throw anything on aCx.  The idea is to not dispatch error
+     // events if our load is canceled with that error code.
+@@ -667,17 +669,19 @@ private:
+ 
+     WorkerDebuggerGlobalScope* globalScope =
+       aWorkerPrivate->CreateDebuggerGlobalScope(aCx);
+     if (!globalScope) {
+       NS_WARNING("Failed to make global!");
+       return false;
+     }
+ 
+-    aWorkerPrivate->EnsureClientSource();
++    if (NS_WARN_IF(!aWorkerPrivate->EnsureClientSource())) {
++      return false;
++    }
+ 
+     JS::Rooted<JSObject*> global(aCx, globalScope->GetWrapper());
+ 
+     ErrorResult rv;
+     JSAutoCompartment ac(aCx, global);
+     scriptloader::LoadMainScript(aWorkerPrivate, mScriptURL,
+                                  DebuggerScript, rv);
+     rv.WouldReportJSException();
+@@ -5292,23 +5296,23 @@ WorkerPrivate::ControlEventTarget()
+ }
+ 
+ nsISerialEventTarget*
+ WorkerPrivate::HybridEventTarget()
+ {
+   return mWorkerHybridEventTarget;
+ }
+ 
+-void
++bool
+ WorkerPrivate::EnsureClientSource()
+ {
+   AssertIsOnWorkerThread();
+ 
+   if (mClientSource) {
+-    return;
++    return true;
+   }
+ 
+   ClientType type;
+   switch(Type()) {
+     case WorkerTypeDedicated:
+       type = ClientType::Worker;
+       break;
+     case WorkerTypeShared:
+@@ -5318,19 +5322,25 @@ WorkerPrivate::EnsureClientSource()
+       type = ClientType::Serviceworker;
+       break;
+     default:
+       MOZ_CRASH("unknown worker type!");
+   }
+ 
+   mClientSource = ClientManager::CreateSource(type, mWorkerHybridEventTarget,
+                                               GetPrincipalInfo());
++  if (!mClientSource) {
++    return false;
++  }
++
+   if (mFrozen) {
+     mClientSource->Freeze();
+   }
++
++  return true;
+ }
+ 
+ const ClientInfo&
+ WorkerPrivate::GetClientInfo() const
+ {
+   AssertIsOnWorkerThread();
+   MOZ_DIAGNOSTIC_ASSERT(mClientSource);
+   return mClientSource->Info();
+diff --git a/dom/workers/WorkerPrivate.h b/dom/workers/WorkerPrivate.h
+--- a/dom/workers/WorkerPrivate.h
++++ b/dom/workers/WorkerPrivate.h
+@@ -1492,17 +1492,17 @@ public:
+   // Get an event target that will attempt to dispatch a normal WorkerRunnable,
+   // but if that fails will then fall back to a control runnable.
+   nsISerialEventTarget*
+   HybridEventTarget();
+ 
+   void
+   DumpCrashInformation(nsACString& aString);
+ 
+-  void
++  bool
+   EnsureClientSource();
+ 
+   const ClientInfo&
+   GetClientInfo() const;
+ 
+   void
+   ExecutionReady();
+ 

+ 5 - 5
frg/work-js/mozilla-release/patches/1420594-1-59a1.patch

@@ -2,7 +2,7 @@
 # User Ben Kelly <ben@wanderview.com>
 # Date 1512399092 18000
 # Node ID 7ba9a363b3d2ae4bb0460fafc7279196754211b0
-# Parent  91afe90c9fb99829041f8fb64609a956791c4d9c
+# Parent  c1842f75058956c2af320f3fb5cdcf6a2a1c0ef9
 Bug 1420594 P1 Make ClientManagerService track active ClientManagerParent actors. r=baku
 
 diff --git a/dom/clients/manager/ClientManagerActors.cpp b/dom/clients/manager/ClientManagerActors.cpp
@@ -111,9 +111,9 @@ diff --git a/dom/clients/manager/ClientManagerService.cpp b/dom/clients/manager/
  // static
  already_AddRefed<ClientManagerService>
  ClientManagerService::GetOrCreateInstance()
-@@ -128,10 +129,28 @@ ClientManagerService::FindSource(const n
-   ClientSourceParent* source = entry.Data();
-   if (!MatchPrincipalInfo(source->Info().PrincipalInfo(), aPrincipalInfo)) {
+@@ -129,10 +130,28 @@ ClientManagerService::FindSource(const n
+   if (source->IsFrozen() ||
+       !MatchPrincipalInfo(source->Info().PrincipalInfo(), aPrincipalInfo)) {
      return nullptr;
    }
  
@@ -199,7 +199,7 @@ diff --git a/dom/clients/manager/ClientManagerService.h b/dom/clients/manager/Cl
 diff --git a/ipc/glue/BackgroundParentImpl.cpp b/ipc/glue/BackgroundParentImpl.cpp
 --- a/ipc/glue/BackgroundParentImpl.cpp
 +++ b/ipc/glue/BackgroundParentImpl.cpp
-@@ -976,16 +976,23 @@ BackgroundParentImpl::AllocPClientManage
+@@ -975,16 +975,23 @@ BackgroundParentImpl::AllocPClientManage
  }
  
  bool

+ 149 - 0
frg/work-js/mozilla-release/patches/1420743-1-59a1.patch

@@ -0,0 +1,149 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1511889040 18000
+# Node ID 2319e3c47580aadcecdb404cc490752f75da744c
+# Parent  de34b5d2a9760c73a12615abf8a34991146bde7b
+Bug 1420743 P1 Do a better job of clearing docshell's mInitialClientSource at the end of page load. r=baku
+
+diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp
+--- a/docshell/base/nsDocShell.cpp
++++ b/docshell/base/nsDocShell.cpp
+@@ -7734,16 +7734,21 @@ nsDocShell::OnSecurityChange(nsIWebProgr
+ nsresult
+ nsDocShell::EndPageLoad(nsIWebProgress* aProgress,
+                         nsIChannel* aChannel, nsresult aStatus)
+ {
+   if (!aChannel) {
+     return NS_ERROR_NULL_POINTER;
+   }
+ 
++  // Make sure to discard the initial client if we never created the initial
++  // about:blank document.  Do this before possibly returning from the method
++  // due to an error.
++  mInitialClientSource.reset();
++
+   nsCOMPtr<nsIConsoleReportCollector> reporter = do_QueryInterface(aChannel);
+   if (reporter) {
+     nsCOMPtr<nsILoadGroup> loadGroup;
+     aChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+     if (loadGroup) {
+       reporter->FlushConsoleReports(loadGroup);
+     } else {
+       reporter->FlushConsoleReports(GetDocument());
+@@ -7769,20 +7774,16 @@ nsDocShell::EndPageLoad(nsIWebProgress* 
+         internalLoadGroup->OnEndPageLoad(aChannel);
+       }
+     }
+   }
+ 
+   // Timing is picked up by the window, we don't need it anymore
+   mTiming = nullptr;
+ 
+-  // Make sure to discard the initial client if we never created the initial
+-  // about:blank document.
+-  mInitialClientSource.reset();
+-
+   // clean up reload state for meta charset
+   if (eCharsetReloadRequested == mCharsetReloadState) {
+     mCharsetReloadState = eCharsetReloadStopOrigional;
+   } else {
+     mCharsetReloadState = eCharsetReloadInit;
+   }
+ 
+   // Save a pointer to the currently-loading history entry.
+@@ -11723,16 +11724,21 @@ nsDocShell::DoChannelLoad(nsIChannel* aC
+   uint32_t openFlags = 0;
+   if (mLoadType == LOAD_LINK) {
+     openFlags |= nsIURILoader::IS_CONTENT_PREFERRED;
+   }
+   if (!mAllowContentRetargeting) {
+     openFlags |= nsIURILoader::DONT_RETARGET;
+   }
+ 
++  // If anything fails here, make sure to clear our initial ClientSource.
++  auto cleanupInitialClient = MakeScopeExit([&] {
++    mInitialClientSource.reset();
++  });
++
+   nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow();
+   NS_ENSURE_TRUE(win, NS_ERROR_FAILURE);
+ 
+   MaybeCreateInitialClientSource();
+ 
+   // Since we are loading a document we need to make sure the proper reserved
+   // and initial client data is stored on the nsILoadInfo.  The
+   // ClientChannelHelper does this and ensures that it is propagated properly
+@@ -11748,16 +11754,19 @@ nsDocShell::DoChannelLoad(nsIChannel* aC
+   rv = aURILoader->OpenURI(aChannel, openFlags, this);
+   NS_ENSURE_SUCCESS(rv, rv);
+ 
+   // We're about to load a new page and it may take time before necko
+   // gives back any data, so main thread might have a chance to process a
+   // collector slice
+   nsJSContext::MaybeRunNextCollectorSlice(this, JS::gcreason::DOCSHELL);
+ 
++  // Success.  Keep the initial ClientSource if it exists.
++  cleanupInitialClient.release();
++
+   return NS_OK;
+ }
+ 
+ nsresult
+ nsDocShell::ScrollToAnchor(bool aCurHasRef, bool aNewHasRef,
+                            nsACString& aNewHash, uint32_t aLoadType)
+ {
+   if (!mCurrentURI) {
+diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp
+--- a/dom/base/nsGlobalWindow.cpp
++++ b/dom/base/nsGlobalWindow.cpp
+@@ -3445,16 +3445,24 @@ nsGlobalWindow::EnsureClientSource()
+ {
+   MOZ_DIAGNOSTIC_ASSERT(mDoc);
+ 
+   bool newClientSource = false;
+ 
+   nsCOMPtr<nsIChannel> channel = mDoc->GetChannel();
+   nsCOMPtr<nsILoadInfo> loadInfo = channel ? channel->GetLoadInfo() : nullptr;
+ 
++  // Take the initial client source from the docshell immediately.  Even if we
++  // don't end up using it here we should consume it.
++  UniquePtr<ClientSource> initialClientSource;
++  nsIDocShell* docshell = GetDocShell();
++  if (docshell) {
++    initialClientSource = docshell->TakeInitialClientSource();
++  }
++
+   // Try to get the reserved client from the LoadInfo.  A Client is
+   // reserved at the start of the channel load if there is not an
+   // initial about:blank document that will be reused.  It is also
+   // created if the channel load encounters a cross-origin redirect.
+   if (loadInfo) {
+     UniquePtr<ClientSource> reservedClient = loadInfo->TakeReservedClientSource();
+     if (reservedClient) {
+       mClientSource.reset();
+@@ -3465,22 +3473,19 @@ nsGlobalWindow::EnsureClientSource()
+ 
+   // We don't have a LoadInfo reserved client, but maybe we should
+   // be inheriting an initial one from the docshell.  This means
+   // that the docshell started the channel load before creating the
+   // initial about:blank document.  This is an optimization, though,
+   // and it created an initial Client as a placeholder for the document.
+   // In this case we want to inherit this placeholder Client here.
+   if (!mClientSource) {
+-    nsIDocShell* docshell = GetDocShell();
+-    if (docshell) {
+-      mClientSource = docshell->TakeInitialClientSource();
+-      if (mClientSource) {
+-        newClientSource = true;
+-      }
++    mClientSource = Move(initialClientSource);
++    if (mClientSource) {
++      newClientSource = true;
+     }
+   }
+  
+   // If we don't have a reserved client or an initial client, then create
+   // one now.  This can happen in certain cases where we avoid preallocating
+   // the client in the docshell.  This mainly occurs in situations where
+   // the principal is not clearly inherited from the parent; e.g. sandboxed
+   // iframes, window.open(), etc.

+ 30 - 0
frg/work-js/mozilla-release/patches/1420743-2-59a1.patch

@@ -0,0 +1,30 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1511889040 18000
+# Node ID e7b3e385157a43ad065f36ccf3d07327dfc170a3
+# Parent  b6a4d2d3483112e98dbb62c27334e00552dc8ac9
+Bug 1420743 P2 Check the inner window for an existing document instead of the outer window in nsDocShell::EnsureInitialClientSource(). r=baku
+
+diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp
+--- a/docshell/base/nsDocShell.cpp
++++ b/docshell/base/nsDocShell.cpp
+@@ -3377,17 +3377,18 @@ nsDocShell::GetParentDocshell()
+   return docshell.forget().downcast<nsDocShell>();
+ }
+ 
+ void
+ nsDocShell::MaybeCreateInitialClientSource(nsIPrincipal* aPrincipal)
+ {
+   // If there is an existing document then there is no need to create
+   // a client for a future initial about:blank document.
+-  if (mScriptGlobal && mScriptGlobal->GetExtantDoc()) {
++  if (mScriptGlobal && mScriptGlobal->GetCurrentInnerWindowInternal() &&
++      mScriptGlobal->GetCurrentInnerWindowInternal()->GetExtantDoc()) {
+     MOZ_DIAGNOSTIC_ASSERT(
+       mScriptGlobal->GetCurrentInnerWindowInternal()->GetClientInfo().isSome());
+     MOZ_DIAGNOSTIC_ASSERT(!mInitialClientSource);
+     return;
+   }
+ 
+   // Don't recreate the initial client source.  We call this multiple times
+   // when DoChannelLoad() is called before CreateAboutBlankContentViewer.

+ 30 - 0
frg/work-js/mozilla-release/patches/1422584-59a1.patch

@@ -0,0 +1,30 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1512492311 18000
+# Node ID e18c1ff30e404e6376b00d57102d773c51de77de
+# Parent  bfb0d78aeebe22cfbabf068a469c2990d42681b1
+Bug 1422584 Handle nullptr ClientSource in nsDocShell::MaybeCreateInitialClientSource(). r=baku
+
+diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp
+--- a/docshell/base/nsDocShell.cpp
++++ b/docshell/base/nsDocShell.cpp
+@@ -3421,16 +3421,19 @@ nsDocShell::MaybeCreateInitialClientSour
+   if (!win) {
+     return;
+   }
+ 
+   mInitialClientSource =
+     ClientManager::CreateSource(ClientType::Window,
+                                 win->EventTargetFor(TaskCategory::Other),
+                                 principal);
++  if (NS_WARN_IF(!mInitialClientSource)) {
++    return;
++  }
+ 
+   // Mark the initial client as execution ready, but owned by the docshell.
+   // If the client is actually used this will cause ClientSource to force
+   // the creation of the initial about:blank by calling nsDocShell::GetDocument().
+   mInitialClientSource->DocShellExecutionReady(this);
+ }
+ 
+ Maybe<ClientInfo>

+ 17 - 20
frg/work-js/mozilla-release/patches/1422983-59a1.patch

@@ -2,14 +2,13 @@
 # User Ben Kelly <ben@wanderview.com>
 # Date 1512443613 18000
 # Node ID 46463dab3129e5c56a96d34f1c275d14149d32bd
-# Parent  7eb30f8e43cb910c8a79fca55fd0bd7281b3dc60
+# Parent  af9c1a9a51da51a3034c5166a6ffa86afd16ba02
 Bug 1422983 Cycle collect the ClientSource object when owned by an nsGlobalWindowInner or nsDocShell object. r=mccr8
 
 diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp
 --- a/docshell/base/nsDocShell.cpp
 +++ b/docshell/base/nsDocShell.cpp
-@@ -950,17 +950,18 @@ nsDocShell::DestroyChildren()
-   }
+@@ -948,16 +948,17 @@ nsDocShell::DestroyChildren()
  
    nsDocLoader::DestroyChildren();
  }
@@ -17,9 +16,9 @@ diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp
  NS_IMPL_CYCLE_COLLECTION_INHERITED(nsDocShell,
                                     nsDocLoader,
                                     mSessionStorageManager,
--                                   mScriptGlobal)
-+                                   mScriptGlobal,
-+                                   mInitialClientSource)
+                                    mScriptGlobal,
++                                   mInitialClientSource,
+                                    mChromeEventHandler)
  
  NS_IMPL_ADDREF_INHERITED(nsDocShell, nsDocLoader)
  NS_IMPL_RELEASE_INHERITED(nsDocShell, nsDocLoader)
@@ -27,11 +26,10 @@ diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp
  NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDocShell)
    NS_INTERFACE_MAP_ENTRY(nsIDocShell)
    NS_INTERFACE_MAP_ENTRY(nsIDocShellTreeItem)
-   NS_INTERFACE_MAP_ENTRY(nsIWebNavigation)
-diff --git a/dom/base/nsGlobalWindowInner.cpp b/dom/base/nsGlobalWindowInner.cpp
---- a/dom/base/nsGlobalWindowInner.cpp
-+++ b/dom/base/nsGlobalWindowInner.cpp
-@@ -1479,16 +1479,18 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
+diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp
+--- a/dom/base/nsGlobalWindow.cpp
++++ b/dom/base/nsGlobalWindow.cpp
+@@ -2290,16 +2290,18 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
  
    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleRequestExecutor)
    for (IdleRequest* request : tmp->mIdleRequestCallbacks) {
@@ -50,7 +48,7 @@ diff --git a/dom/base/nsGlobalWindowInner.cpp b/dom/base/nsGlobalWindowInner.cpp
    // Traverse stuff from nsPIDOMWindow
    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChromeEventHandler)
    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParentTarget)
-@@ -1590,17 +1592,17 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
+@@ -2409,17 +2411,17 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
    tmp->UnlinkHostObjectURIs();
  
    NS_IMPL_CYCLE_COLLECTION_UNLINK(mIdleRequestExecutor)
@@ -62,13 +60,13 @@ diff --git a/dom/base/nsGlobalWindowInner.cpp b/dom/base/nsGlobalWindowInner.cpp
 -  tmp->mClientSource.reset();
 +  NS_IMPL_CYCLE_COLLECTION_UNLINK(mClientSource)
  
-   if (tmp->IsChromeWindow()) {
-     if (tmp->mChromeFields.mMessageManager) {
-       static_cast<nsFrameMessageManager*>(
-         tmp->mChromeFields.mMessageManager.get())->Disconnect();
-       NS_IMPL_CYCLE_COLLECTION_UNLINK(mChromeFields.mMessageManager)
-     }
-     tmp->DisconnectAndClearGroupMessageManagers();
+   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingPromises)
+ 
+   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+ 
+ #ifdef DEBUG
+ void
 diff --git a/dom/clients/manager/ClientSource.cpp b/dom/clients/manager/ClientSource.cpp
 --- a/dom/clients/manager/ClientSource.cpp
 +++ b/dom/clients/manager/ClientSource.cpp
@@ -139,4 +137,3 @@ diff --git a/dom/clients/manager/ClientSource.h b/dom/clients/manager/ClientSour
  } // namespace mozilla
  
  #endif // _mozilla_dom_ClientSource_h
-

+ 703 - 0
frg/work-js/mozilla-release/patches/1423328-59a1.patch

@@ -0,0 +1,703 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1512503650 18000
+# Node ID c0c7f12df75a4cd3c15f904606a017af8bcb9ba7
+# Parent  70e780d3f9bd1791f08002d891c143fde7cc9740
+Bug 1423328 Allow a caller to set a ClientSource controlled by a service worker using a ClientHandle. r=baku
+
+diff --git a/dom/clients/manager/ClientHandle.cpp b/dom/clients/manager/ClientHandle.cpp
+--- a/dom/clients/manager/ClientHandle.cpp
++++ b/dom/clients/manager/ClientHandle.cpp
+@@ -5,16 +5,17 @@
+  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+ 
+ #include "ClientHandle.h"
+ 
+ #include "ClientHandleChild.h"
+ #include "ClientHandleOpChild.h"
+ #include "ClientManager.h"
+ #include "mozilla/dom/PClientManagerChild.h"
++#include "mozilla/dom/ServiceWorkerDescriptor.h"
+ 
+ namespace mozilla {
+ namespace dom {
+ 
+ ClientHandle::~ClientHandle()
+ {
+   Shutdown();
+ }
+@@ -96,10 +97,30 @@ ClientHandle::ExecutionReady(const Clien
+ }
+ 
+ const ClientInfo&
+ ClientHandle::Info() const
+ {
+   return mClientInfo;
+ }
+ 
++RefPtr<GenericPromise>
++ClientHandle::Control(const ServiceWorkerDescriptor& aServiceWorker)
++{
++  RefPtr<GenericPromise::Private> outerPromise =
++    new GenericPromise::Private(__func__);
++
++  RefPtr<ClientOpPromise> innerPromise =
++    StartOp(ClientControlledArgs(aServiceWorker.ToIPC()));
++
++  innerPromise->Then(mSerialEventTarget, __func__,
++    [outerPromise](const ClientOpResult& aResult) {
++      outerPromise->Resolve(true, __func__);
++    },
++    [outerPromise](const ClientOpResult& aResult) {
++      outerPromise->Reject(aResult.get_nsresult(), __func__);
++    });
++
++  return outerPromise.forget();
++}
++
+ } // namespace dom
+ } // namespace mozilla
+diff --git a/dom/clients/manager/ClientHandle.h b/dom/clients/manager/ClientHandle.h
+--- a/dom/clients/manager/ClientHandle.h
++++ b/dom/clients/manager/ClientHandle.h
+@@ -4,29 +4,31 @@
+  * License, v. 2.0. If a copy of the MPL was not distributed with this
+  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+ #ifndef _mozilla_dom_ClientHandle_h
+ #define _mozilla_dom_ClientHandle_h
+ 
+ #include "mozilla/dom/ClientInfo.h"
+ #include "mozilla/dom/ClientOpPromise.h"
+ #include "mozilla/dom/ClientThing.h"
++#include "mozilla/MozPromise.h"
+ 
+ #ifdef XP_WIN
+ #undef PostMessage
+ #endif
+ 
+ namespace mozilla {
+ 
+ namespace dom {
+ 
+ class ClientManager;
+ class ClientHandleChild;
+ class ClientOpConstructorArgs;
+ class PClientManagerChild;
++class ServiceWorkerDescriptor;
+ 
+ // The ClientHandle allows code to take a simple ClientInfo struct and
+ // convert it into a live actor-backed object attached to a particular
+ // ClientSource somewhere in the browser.  If the ClientSource is
+ // destroyed then the ClientHandle will simply begin to reject operations.
+ // We do not currently provide a way to be notified when the ClientSource
+ // is destroyed, but this could be added in the future.
+ class ClientHandle final : public ClientThing<ClientHandleChild>
+@@ -57,15 +59,21 @@ class ClientHandle final : public Client
+ 
+   void
+   Activate(PClientManagerChild* aActor);
+ 
+ public:
+   const ClientInfo&
+   Info() const;
+ 
++  // Mark the ClientSource attached to this handle as controlled by the
++  // given service worker.  The promise will resolve true if the ClientSource
++  // is successfully marked or reject if the operation could not be completed.
++  RefPtr<GenericPromise>
++  Control(const ServiceWorkerDescriptor& aServiceWorker);
++
+   NS_INLINE_DECL_REFCOUNTING(ClientHandle);
+ };
+ 
+ } // namespace dom
+ } // namespace mozilla
+ 
+ #endif // _mozilla_dom_ClientHandle_h
+diff --git a/dom/clients/manager/ClientHandleOpParent.cpp b/dom/clients/manager/ClientHandleOpParent.cpp
+--- a/dom/clients/manager/ClientHandleOpParent.cpp
++++ b/dom/clients/manager/ClientHandleOpParent.cpp
+@@ -1,23 +1,54 @@
+ /* -*- 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 "ClientHandleOpParent.h"
+ 
++#include "ClientHandleParent.h"
++#include "ClientSourceParent.h"
++
+ namespace mozilla {
+ namespace dom {
+ 
++ClientSourceParent*
++ClientHandleOpParent::GetSource() const
++{
++  auto handle = static_cast<ClientHandleParent*>(Manager());
++  return handle->GetSource();
++}
++
+ void
+ ClientHandleOpParent::ActorDestroy(ActorDestroyReason aReason)
+ {
++  mPromiseRequestHolder.DisconnectIfExists();
+ }
+ 
+ void
+ ClientHandleOpParent::Init(const ClientOpConstructorArgs& aArgs)
+ {
++  ClientSourceParent* source = GetSource();
++  if (!source) {
++    Unused << PClientHandleOpParent::Send__delete__(this, NS_ERROR_DOM_ABORT_ERR);
++    return;
++  }
++
++  RefPtr<ClientOpPromise> p = source->StartOp(aArgs);
++
++  // Capturing 'this' is safe here because we disconnect the promise in
++  // ActorDestroy() which ensures neither lambda is called if the actor
++  // is destroyed before the source operation completes.
++  p->Then(GetCurrentThreadSerialEventTarget(), __func__,
++    [this] (const ClientOpResult& aResult) {
++      mPromiseRequestHolder.Complete();
++      Unused << PClientHandleOpParent::Send__delete__(this, aResult);
++    },
++    [this] (nsresult aRv) {
++      mPromiseRequestHolder.Complete();
++      Unused << PClientHandleOpParent::Send__delete__(this, aRv);
++    })->Track(mPromiseRequestHolder);
+ }
+ 
+ } // namespace dom
+ } // namespace mozilla
+diff --git a/dom/clients/manager/ClientHandleOpParent.h b/dom/clients/manager/ClientHandleOpParent.h
+--- a/dom/clients/manager/ClientHandleOpParent.h
++++ b/dom/clients/manager/ClientHandleOpParent.h
+@@ -1,23 +1,31 @@
+ /* -*- 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/. */
+ #ifndef _mozilla_dom_ClientHandleOpParent_h
+ #define _mozilla_dom_ClientHandleOpParent_h
+ 
++#include "ClientOpPromise.h"
+ #include "mozilla/dom/PClientHandleOpParent.h"
+ 
+ namespace mozilla {
+ namespace dom {
+ 
++class ClientSourceParent;
++
+ class ClientHandleOpParent final : public PClientHandleOpParent
+ {
++  MozPromiseRequestHolder<ClientOpPromise> mPromiseRequestHolder;
++
++  ClientSourceParent*
++  GetSource() const;
++
+   // PClientHandleOpParent interface
+   void
+   ActorDestroy(ActorDestroyReason aReason) override;
+ 
+ public:
+   ClientHandleOpParent() = default;
+   ~ClientHandleOpParent() = default;
+ 
+diff --git a/dom/clients/manager/ClientIPCTypes.ipdlh b/dom/clients/manager/ClientIPCTypes.ipdlh
+--- a/dom/clients/manager/ClientIPCTypes.ipdlh
++++ b/dom/clients/manager/ClientIPCTypes.ipdlh
+@@ -51,22 +51,28 @@ union IPCClientState
+ };
+ 
+ struct ClientSourceExecutionReadyArgs
+ {
+   nsCString url;
+   FrameType frameType;
+ };
+ 
++struct ClientControlledArgs
++{
++  IPCServiceWorkerDescriptor serviceWorker;
++};
++
+ struct ClientOpenWindowArgs
+ {
+ };
+ 
+-struct ClientOpConstructorArgs
++union ClientOpConstructorArgs
+ {
++  ClientControlledArgs;
+ };
+ 
+ struct ClientNavigateOpConstructorArgs
+ {
+ };
+ 
+ union ClientOpResult
+ {
+diff --git a/dom/clients/manager/ClientSource.cpp b/dom/clients/manager/ClientSource.cpp
+--- a/dom/clients/manager/ClientSource.cpp
++++ b/dom/clients/manager/ClientSource.cpp
+@@ -252,16 +252,47 @@ ClientSource::Thaw()
+ }
+ 
+ const ClientInfo&
+ ClientSource::Info() const
+ {
+   return mClientInfo;
+ }
+ 
++void
++ClientSource::SetController(const ServiceWorkerDescriptor& aServiceWorker)
++{
++  NS_ASSERT_OWNINGTHREAD(ClientSource);
++
++  if (mController.isSome() && mController.ref() == aServiceWorker) {
++    return;
++  }
++
++  mController.reset();
++  mController.emplace(aServiceWorker);
++}
++
++RefPtr<ClientOpPromise>
++ClientSource::Control(const ClientControlledArgs& aArgs)
++{
++  NS_ASSERT_OWNINGTHREAD(ClientSource);
++
++  SetController(ServiceWorkerDescriptor(aArgs.serviceWorker()));
++
++  RefPtr<ClientOpPromise> ref =
++    ClientOpPromise::CreateAndResolve(NS_OK, __func__);
++  return ref.forget();
++}
++
++const Maybe<ServiceWorkerDescriptor>&
++ClientSource::GetController() const
++{
++  return mController;
++}
++
+ nsISerialEventTarget*
+ ClientSource::EventTarget() const
+ {
+   return mEventTarget;
+ }
+ 
+ void
+ ClientSource::Traverse(nsCycleCollectionTraversalCallback& aCallback,
+diff --git a/dom/clients/manager/ClientSource.h b/dom/clients/manager/ClientSource.h
+--- a/dom/clients/manager/ClientSource.h
++++ b/dom/clients/manager/ClientSource.h
+@@ -2,24 +2,29 @@
+ /* vim: set ts=8 sts=2 et sw=2 tw=80: */
+ /* This Source Code Form is subject to the terms of the Mozilla Public
+  * License, v. 2.0. If a copy of the MPL was not distributed with this
+  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+ #ifndef _mozilla_dom_ClientSource_h
+ #define _mozilla_dom_ClientSource_h
+ 
+ #include "mozilla/dom/ClientInfo.h"
++#include "mozilla/dom/ClientOpPromise.h"
+ #include "mozilla/dom/ClientThing.h"
++#include "mozilla/dom/ServiceWorkerDescriptor.h"
++#include "mozilla/Variant.h"
+ 
+ class nsIDocShell;
++class nsISerialEventTarget;
+ class nsPIDOMWindowInner;
+ 
+ namespace mozilla {
+ namespace dom {
+ 
++class ClientControlledArgs;
+ class ClientManager;
+ class ClientSourceChild;
+ class ClientSourceConstructorArgs;
+ class ClientSourceExecutionReadyArgs;
+ class PClientManagerChild;
+ 
+ namespace workers {
+ class WorkerPrivate;
+@@ -41,16 +46,17 @@ class ClientSource final : public Client
+   nsCOMPtr<nsISerialEventTarget> mEventTarget;
+ 
+   Variant<Nothing,
+           RefPtr<nsPIDOMWindowInner>,
+           nsCOMPtr<nsIDocShell>,
+           mozilla::dom::workers::WorkerPrivate*> mOwner;
+ 
+   ClientInfo mClientInfo;
++  Maybe<ServiceWorkerDescriptor> mController;
+ 
+   void
+   Shutdown();
+ 
+   void
+   ExecutionReady(const ClientSourceExecutionReadyArgs& aArgs);
+ 
+   mozilla::dom::workers::WorkerPrivate*
+@@ -86,16 +92,37 @@ public:
+   Freeze();
+ 
+   void
+   Thaw();
+ 
+   const ClientInfo&
+   Info() const;
+ 
++  // Synchronously mark the ClientSource as controlled by the given service
++  // worker.  This can happen as a result of a remote operation or directly
++  // by local code.  For example, if a client's initial network load is
++  // intercepted by a controlling service worker then this should be called
++  // immediately.
++  //
++  // Note, there is no way to clear the controlling service worker because
++  // the specification does not allow that operation.
++  void
++  SetController(const ServiceWorkerDescriptor& aServiceWorker);
++
++  // Mark the ClientSource as controlled using the remote operation arguments.
++  // This will in turn call SetController().
++  RefPtr<ClientOpPromise>
++  Control(const ClientControlledArgs& aArgs);
++
++  // Get the ClientSource's current controlling service worker, if one has
++  // been set.
++  const Maybe<ServiceWorkerDescriptor>&
++  GetController() const;
++
+   nsISerialEventTarget*
+   EventTarget() const;
+ 
+   void
+   Traverse(nsCycleCollectionTraversalCallback& aCallback,
+            const char* aName,
+            uint32_t aFlags);
+ };
+diff --git a/dom/clients/manager/ClientSourceChild.cpp b/dom/clients/manager/ClientSourceChild.cpp
+--- a/dom/clients/manager/ClientSourceChild.cpp
++++ b/dom/clients/manager/ClientSourceChild.cpp
+@@ -1,18 +1,18 @@
+ /* -*- 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 "ClientSourceChild.h"
+ 
++#include "ClientSource.h"
+ #include "ClientSourceOpChild.h"
+-#include "ClientThing.h"
+ #include "mozilla/dom/ClientIPCTypes.h"
+ #include "mozilla/Unused.h"
+ 
+ namespace mozilla {
+ namespace dom {
+ 
+ using mozilla::ipc::IPCResult;
+ 
+@@ -56,27 +56,33 @@ ClientSourceChild::ClientSourceChild(con
+ {
+ }
+ 
+ void
+ ClientSourceChild::SetOwner(ClientThing<ClientSourceChild>* aThing)
+ {
+   MOZ_DIAGNOSTIC_ASSERT(aThing);
+   MOZ_DIAGNOSTIC_ASSERT(!mSource);
+-  mSource = aThing;
++  mSource = static_cast<ClientSource*>(aThing);
+ }
+ 
+ void
+ ClientSourceChild::RevokeOwner(ClientThing<ClientSourceChild>* aThing)
+ {
+   MOZ_DIAGNOSTIC_ASSERT(mSource);
+-  MOZ_DIAGNOSTIC_ASSERT(mSource == aThing);
++  MOZ_DIAGNOSTIC_ASSERT(mSource == static_cast<ClientSource*>(aThing));
+   mSource = nullptr;
+ }
+ 
++ClientSource*
++ClientSourceChild::GetSource() const
++{
++  return mSource;
++}
++
+ void
+ ClientSourceChild::MaybeStartTeardown()
+ {
+   if (mTeardownStarted) {
+     return;
+   }
+   mTeardownStarted = true;
+   Unused << SendTeardown();
+diff --git a/dom/clients/manager/ClientSourceChild.h b/dom/clients/manager/ClientSourceChild.h
+--- a/dom/clients/manager/ClientSourceChild.h
++++ b/dom/clients/manager/ClientSourceChild.h
+@@ -6,22 +6,23 @@
+ #ifndef _mozilla_dom_ClientSourceChild_h
+ #define _mozilla_dom_ClientSourceChild_h
+ 
+ #include "mozilla/dom/PClientSourceChild.h"
+ 
+ namespace mozilla {
+ namespace dom {
+ 
++class ClientSource;
+ class ClientSourceConstructorArgs;
+ template <typename ActorType> class ClientThing;
+ 
+ class ClientSourceChild final : public PClientSourceChild
+ {
+-  ClientThing<ClientSourceChild>* mSource;
++  ClientSource* mSource;
+   bool mTeardownStarted;
+ 
+   // PClientSourceChild interface
+   void
+   ActorDestroy(ActorDestroyReason aReason) override;
+ 
+   PClientSourceOpChild*
+   AllocPClientSourceOpChild(const ClientOpConstructorArgs& aArgs) override;
+@@ -37,16 +38,19 @@ public:
+   explicit ClientSourceChild(const ClientSourceConstructorArgs& aArgs);
+ 
+   void
+   SetOwner(ClientThing<ClientSourceChild>* aThing);
+ 
+   void
+   RevokeOwner(ClientThing<ClientSourceChild>* aThing);
+ 
++  ClientSource*
++  GetSource() const;
++
+   void
+   MaybeStartTeardown();
+ };
+ 
+ } // namespace dom
+ } // namespace mozilla
+ 
+ #endif // _mozilla_dom_ClientSourceChild_h
+diff --git a/dom/clients/manager/ClientSourceOpChild.cpp b/dom/clients/manager/ClientSourceOpChild.cpp
+--- a/dom/clients/manager/ClientSourceOpChild.cpp
++++ b/dom/clients/manager/ClientSourceOpChild.cpp
+@@ -1,23 +1,91 @@
+ /* -*- 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 "ClientSourceOpChild.h"
+ 
++#include "ClientSource.h"
++#include "ClientSourceChild.h"
++#include "mozilla/Unused.h"
++
+ namespace mozilla {
+ namespace dom {
+ 
++ClientSource*
++ClientSourceOpChild::GetSource() const
++{
++  auto actor = static_cast<ClientSourceChild*>(Manager());
++  return actor->GetSource();
++}
++
++template<typename Method, typename Args>
++void
++ClientSourceOpChild::DoSourceOp(Method aMethod, const Args& aArgs)
++{
++  RefPtr<ClientOpPromise> promise;
++  nsCOMPtr<nsISerialEventTarget> target;
++
++  // Some ClientSource operations can cause the ClientSource to be destroyed.
++  // This means we should reference the ClientSource pointer for the minimum
++  // possible to start the operation.  Use an extra block scope here to help
++  // enforce this and prevent accidental usage later in the method.
++  {
++    ClientSource* source = GetSource();
++    if (!source) {
++      Unused << PClientSourceOpChild::Send__delete__(this, NS_ERROR_DOM_ABORT_ERR);
++      return;
++    }
++
++    target = source->EventTarget();
++
++    // This may cause the ClientSource object to be destroyed.  Do not
++    // use the source variable after this call.
++    promise = (source->*aMethod)(aArgs);
++  }
++
++  // The ClientSource methods are required to always return a promise.  If
++  // they encounter an error they should just immediately resolve or reject
++  // the promise as appropriate.
++  MOZ_DIAGNOSTIC_ASSERT(promise);
++
++  // Capture 'this' is safe here because we disconnect the promise
++  // ActorDestroy() which ensures nethier lambda is called if the
++  // actor is destroyed before the source operation completes.
++  promise->Then(target, __func__,
++    [this, aArgs] (const mozilla::dom::ClientOpResult& aResult) {
++      mPromiseRequestHolder.Complete();
++      Unused << PClientSourceOpChild::Send__delete__(this, aResult);
++    },
++    [this] (nsresult aRv) {
++      mPromiseRequestHolder.Complete();
++      Unused << PClientSourceOpChild::Send__delete__(this, aRv);
++    })->Track(mPromiseRequestHolder);
++}
++
+ void
+ ClientSourceOpChild::ActorDestroy(ActorDestroyReason aReason)
+ {
++  mPromiseRequestHolder.DisconnectIfExists();
+ }
+ 
+ void
+ ClientSourceOpChild::Init(const ClientOpConstructorArgs& aArgs)
+ {
++  switch (aArgs.type()) {
++    case ClientOpConstructorArgs::TClientControlledArgs:
++    {
++      DoSourceOp(&ClientSource::Control, aArgs.get_ClientControlledArgs());
++      break;
++    }
++    default:
++    {
++      MOZ_ASSERT_UNREACHABLE("unknown client operation!");
++      break;
++    }
++  }
+ }
+ 
+ } // namespace dom
+ } // namespace mozilla
+diff --git a/dom/clients/manager/ClientSourceOpChild.h b/dom/clients/manager/ClientSourceOpChild.h
+--- a/dom/clients/manager/ClientSourceOpChild.h
++++ b/dom/clients/manager/ClientSourceOpChild.h
+@@ -7,18 +7,29 @@
+ #define _mozilla_dom_ClientSourceOpChild_h
+ 
+ #include "mozilla/dom/PClientSourceOpChild.h"
+ #include "ClientOpPromise.h"
+ 
+ namespace mozilla {
+ namespace dom {
+ 
++class ClientSource;
++
+ class ClientSourceOpChild final : public PClientSourceOpChild
+ {
++  MozPromiseRequestHolder<ClientOpPromise> mPromiseRequestHolder;
++
++  ClientSource*
++  GetSource() const;
++
++  template <typename Method, typename Args>
++  void
++  DoSourceOp(Method aMethod, const Args& aArgs);
++
+   // PClientSourceOpChild interface
+   void
+   ActorDestroy(ActorDestroyReason aReason) override;
+ 
+ public:
+   ClientSourceOpChild() = default;
+   ~ClientSourceOpChild() = default;
+ 
+diff --git a/dom/clients/manager/ClientSourceParent.cpp b/dom/clients/manager/ClientSourceParent.cpp
+--- a/dom/clients/manager/ClientSourceParent.cpp
++++ b/dom/clients/manager/ClientSourceParent.cpp
+@@ -221,10 +221,30 @@ ClientSourceParent::AttachHandle(ClientH
+ void
+ ClientSourceParent::DetachHandle(ClientHandleParent* aClientHandle)
+ {
+   MOZ_DIAGNOSTIC_ASSERT(aClientHandle);
+   MOZ_ASSERT(mHandleList.Contains(aClientHandle));
+   mHandleList.RemoveElement(aClientHandle);
+ }
+ 
++RefPtr<ClientOpPromise>
++ClientSourceParent::StartOp(const ClientOpConstructorArgs& aArgs)
++{
++  RefPtr<ClientOpPromise::Private> promise =
++    new ClientOpPromise::Private(__func__);
++
++  // If we are being controlled, remember that data before propagating
++  // on to the ClientSource.
++  if (aArgs.type() == ClientOpConstructorArgs::TClientControlledArgs) {
++    mController.reset();
++    mController.emplace(aArgs.get_ClientControlledArgs().serviceWorker());
++  }
++
++  // Constructor failure will reject the promise via ActorDestroy().
++  ClientSourceOpParent* actor = new ClientSourceOpParent(aArgs, promise);
++  Unused << SendPClientSourceOpConstructor(actor, aArgs);
++
++  return promise.forget();
++}
++
+ } // namespace dom
+ } // namespace mozilla
+diff --git a/dom/clients/manager/ClientSourceParent.h b/dom/clients/manager/ClientSourceParent.h
+--- a/dom/clients/manager/ClientSourceParent.h
++++ b/dom/clients/manager/ClientSourceParent.h
+@@ -2,27 +2,30 @@
+ /* vim: set ts=8 sts=2 et sw=2 tw=80: */
+ /* This Source Code Form is subject to the terms of the Mozilla Public
+  * License, v. 2.0. If a copy of the MPL was not distributed with this
+  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+ #ifndef _mozilla_dom_ClientSourceParent_h
+ #define _mozilla_dom_ClientSourceParent_h
+ 
+ #include "ClientInfo.h"
++#include "ClientOpPromise.h"
+ #include "mozilla/dom/PClientSourceParent.h"
++#include "mozilla/dom/ServiceWorkerDescriptor.h"
+ 
+ namespace mozilla {
+ namespace dom {
+ 
+ class ClientHandleParent;
+ class ClientManagerService;
+ 
+ class ClientSourceParent final : public PClientSourceParent
+ {
+   ClientInfo mClientInfo;
++  Maybe<ServiceWorkerDescriptor> mController;
+   RefPtr<ClientManagerService> mService;
+   nsTArray<ClientHandleParent*> mHandleList;
+   bool mExecutionReady;
+   bool mFrozen;
+ 
+   void
+   KillInvalidChild();
+ 
+@@ -61,14 +64,17 @@ public:
+   bool
+   IsFrozen() const;
+ 
+   void
+   AttachHandle(ClientHandleParent* aClientSource);
+ 
+   void
+   DetachHandle(ClientHandleParent* aClientSource);
++
++  RefPtr<ClientOpPromise>
++  StartOp(const ClientOpConstructorArgs& aArgs);
+ };
+ 
+ } // namespace dom
+ } // namespace mozilla
+ 
+ #endif // _mozilla_dom_ClientSourceParent_h

+ 729 - 0
frg/work-js/mozilla-release/patches/1423412-1-59a1.patch

@@ -0,0 +1,729 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1512524722 18000
+# Node ID 699fd0f2b04666c7b043f0350cf56f0bc07189ac
+# Parent  b87e12c54cdea798e6705b885650bfcb5c542d33
+Bug 1423412 P1 Actually mark window/worker ClientSource objects controlled when loaded with a controlling service worker. r=baku r=jld
+
+diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp
+--- a/docshell/base/nsDocShell.cpp
++++ b/docshell/base/nsDocShell.cpp
+@@ -3429,16 +3429,42 @@ nsDocShell::MaybeCreateInitialClientSour
+   if (NS_WARN_IF(!mInitialClientSource)) {
+     return;
+   }
+ 
+   // Mark the initial client as execution ready, but owned by the docshell.
+   // If the client is actually used this will cause ClientSource to force
+   // the creation of the initial about:blank by calling nsDocShell::GetDocument().
+   mInitialClientSource->DocShellExecutionReady(this);
++
++  // Next, check to see if the parent is controlled.
++  nsCOMPtr<nsIDocShell> parent = GetParentDocshell();
++  nsPIDOMWindowOuter* parentOuter = parent ? parent->GetWindow() : nullptr;
++  nsPIDOMWindowInner* parentInner =
++    parentOuter ? parentOuter->GetCurrentInnerWindow() : nullptr;
++  if (!parentInner) {
++    return;
++  }
++
++  Maybe<ServiceWorkerDescriptor> controller(parentInner->GetController());
++  if (controller.isNothing()) {
++    return;
++  }
++
++  // If the parent is controlled then propagate that controller to the
++  // initial about:blank client as well.  This will set the controller
++  // in the ClientManagerService in the parent.
++  RefPtr<ClientHandle> handle =
++    ClientManager::CreateHandle(mInitialClientSource->Info(),
++                                parentInner->EventTargetFor(TaskCategory::Other));
++  handle->Control(controller.ref());
++
++  // Also mark the ClientSource as controlled directly in case script
++  // immediately accesses navigator.serviceWorker.controller.
++  mInitialClientSource->SetController(controller.ref());
+ }
+ 
+ Maybe<ClientInfo>
+ nsDocShell::GetInitialClientInfo() const
+ {
+   if (mInitialClientSource) {
+     Maybe<ClientInfo> result;
+     result.emplace(mInitialClientSource->Info());
+diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp
+--- a/dom/base/nsGlobalWindow.cpp
++++ b/dom/base/nsGlobalWindow.cpp
+@@ -3486,26 +3486,44 @@ nsGlobalWindow::EnsureClientSource()
+     }
+   }
+  
+   // If we don't have a reserved client or an initial client, then create
+   // one now.  This can happen in certain cases where we avoid preallocating
+   // the client in the docshell.  This mainly occurs in situations where
+   // the principal is not clearly inherited from the parent; e.g. sandboxed
+   // iframes, window.open(), etc.
++  // TODO: We may not be marking initial about:blank documents created
++  //       this way as controlled by a service worker properly.  The
++  //       controller should be coming from the same place as the inheritted
++  //       principal.  We do this in docshell, but as mentioned we aren't
++  //       smart enough to handle all cases yet.  For example, a
++  //       window.open() with new URL should inherit the controller from
++  //       the opener, but we probably don't handle that yet.
+   if (!mClientSource) {
+     mClientSource = ClientManager::CreateSource(ClientType::Window,
+                                                 EventTargetFor(TaskCategory::Other),
+                                                 mDoc->NodePrincipal());
+     if (NS_WARN_IF(!mClientSource)) {
+       return NS_ERROR_FAILURE;
+     }
+     newClientSource = true;
+   }
+ 
++  // The load may have started controlling the Client as well.  If
++  // so, mark it as controlled immediately here.  The actor may
++  // or may not have been notified by the parent side about being
++  // controlled yet.
++  if (loadInfo) {
++    const Maybe<ServiceWorkerDescriptor> controller = loadInfo->GetController();
++    if (controller.isSome()) {
++      mClientSource->SetController(controller.ref());
++    }
++  }
++
+   // Its possible that we got a client just after being frozen in
+   // the bfcache.  In that case freeze the client immediately.
+   if (newClientSource && IsFrozen()) {
+     mClientSource->Freeze();
+   }
+ 
+   return NS_OK;
+ }
+@@ -4386,16 +4404,22 @@ nsPIDOMWindowInner::SyncStateFromParentW
+ }
+ 
+ Maybe<ClientInfo>
+ nsPIDOMWindowInner::GetClientInfo() const
+ {
+   return Move(nsGlobalWindow::Cast(this)->GetClientInfo());
+ }
+ 
++Maybe<ServiceWorkerDescriptor>
++nsPIDOMWindowInner::GetController() const
++{
++  return Move(nsGlobalWindow::Cast(this)->GetController());
++}
++
+ void
+ nsGlobalWindow::UpdateTopInnerWindow()
+ {
+   if (!IsInnerWindow() || AsInner()->IsTopInnerWindow()) {
+     return;
+   }
+ 
+   AsInner()->UpdateWebSocketCount(-(int32_t)mNumOfOpenWebSockets);
+@@ -12579,16 +12603,27 @@ nsGlobalWindow::GetClientInfo() const
+   MOZ_ASSERT(NS_IsMainThread());
+   Maybe<ClientInfo> clientInfo;
+   if (mClientSource) {
+     clientInfo.emplace(mClientSource->Info());
+   }
+   return Move(clientInfo);
+ }
+ 
++Maybe<ServiceWorkerDescriptor>
++nsGlobalWindow::GetController() const
++{
++  MOZ_ASSERT(NS_IsMainThread());
++  Maybe<ServiceWorkerDescriptor> controller;
++  if (mClientSource) {
++    controller = mClientSource->GetController();
++  }
++  return Move(controller);
++}
++
+ nsresult
+ nsGlobalWindow::FireDelayedDOMEvents()
+ {
+   FORWARD_TO_INNER(FireDelayedDOMEvents, (), NS_ERROR_UNEXPECTED);
+ 
+   if (mApplicationCache) {
+     static_cast<nsDOMOfflineResourceList*>(mApplicationCache.get())->FirePendingEvents();
+   }
+diff --git a/dom/base/nsGlobalWindow.h b/dom/base/nsGlobalWindow.h
+--- a/dom/base/nsGlobalWindow.h
++++ b/dom/base/nsGlobalWindow.h
+@@ -399,16 +399,17 @@ public:
+   void Resume();
+   virtual bool IsSuspended() const override;
+   void Freeze();
+   void Thaw();
+   virtual bool IsFrozen() const override;
+   void SyncStateFromParentWindow();
+ 
+   mozilla::Maybe<mozilla::dom::ClientInfo> GetClientInfo() const;
++  mozilla::Maybe<mozilla::dom::ServiceWorkerDescriptor> GetController() const;
+ 
+   virtual nsresult FireDelayedDOMEvents() override;
+ 
+   // Outer windows only.
+   virtual bool WouldReuseInnerWindow(nsIDocument* aNewDocument) override;
+ 
+   virtual void SetDocShell(nsIDocShell* aDocShell) override;
+   virtual void DetachFromDocShell() override;
+diff --git a/dom/base/nsPIDOMWindow.h b/dom/base/nsPIDOMWindow.h
+--- a/dom/base/nsPIDOMWindow.h
++++ b/dom/base/nsPIDOMWindow.h
+@@ -45,16 +45,17 @@ namespace mozilla {
+ class ThrottledEventQueue;
+ namespace dom {
+ class AudioContext;
+ class ClientInfo;
+ class DocGroup;
+ class TabGroup;
+ class Element;
+ class Performance;
++class ServiceWorkerDescriptor;
+ class ServiceWorkerRegistration;
+ class Timeout;
+ class TimeoutManager;
+ class CustomElementRegistry;
+ enum class CallerType : uint32_t;
+ } // namespace dom
+ } // namespace mozilla
+ 
+@@ -932,16 +933,17 @@ public:
+   // Increase/Decrease the number of open WebSockets.
+   void UpdateWebSocketCount(int32_t aDelta);
+ 
+   // Return true if there are any open WebSockets that could block
+   // timeout-throttling.
+   bool HasOpenWebSockets() const;
+ 
+   mozilla::Maybe<mozilla::dom::ClientInfo> GetClientInfo() const;
++  mozilla::Maybe<mozilla::dom::ServiceWorkerDescriptor> GetController() const;
+ 
+ protected:
+   void CreatePerformanceObjectIfNeeded();
+ };
+ 
+ NS_DEFINE_STATIC_IID_ACCESSOR(nsPIDOMWindowInner, NS_PIDOMWINDOWINNER_IID)
+ 
+ // NB: It's very very important that these two classes have identical vtables
+diff --git a/dom/clients/manager/ClientManager.h b/dom/clients/manager/ClientManager.h
+--- a/dom/clients/manager/ClientManager.h
++++ b/dom/clients/manager/ClientManager.h
+@@ -32,16 +32,17 @@ class WorkerPrivate;
+ // The ClientManager provides a per-thread singleton interface workering
+ // with the client subsystem.  It allows globals to create ClientSource
+ // objects.  It allows other parts of the system to attach to this globals
+ // by creating ClientHandle objects.  The ClientManager also provides
+ // methods for querying the list of clients active in the system.
+ class ClientManager final : public ClientThing<ClientManagerChild>
+ {
+   friend class ClientManagerChild;
++  friend class ClientSource;
+ 
+   ClientManager();
+   ~ClientManager();
+ 
+   // Utility method to trigger a shutdown of the ClientManager.  This
+   // is called in various error conditions or when the last reference
+   // is dropped.
+   void
+diff --git a/dom/clients/manager/ClientSource.cpp b/dom/clients/manager/ClientSource.cpp
+--- a/dom/clients/manager/ClientSource.cpp
++++ b/dom/clients/manager/ClientSource.cpp
+@@ -253,16 +253,27 @@ ClientSource::Thaw()
+ 
+ const ClientInfo&
+ ClientSource::Info() const
+ {
+   return mClientInfo;
+ }
+ 
+ void
++ClientSource::WorkerSyncPing(WorkerPrivate* aWorkerPrivate)
++{
++  NS_ASSERT_OWNINGTHREAD(ClientSource);
++  MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate);
++  MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate == mManager->GetWorkerPrivate());
++  aWorkerPrivate->AssertIsOnWorkerThread();
++  MOZ_DIAGNOSTIC_ASSERT(GetActor());
++  GetActor()->SendWorkerSyncPing();
++}
++
++void
+ ClientSource::SetController(const ServiceWorkerDescriptor& aServiceWorker)
+ {
+   NS_ASSERT_OWNINGTHREAD(ClientSource);
+ 
+   if (mController.isSome() && mController.ref() == aServiceWorker) {
+     return;
+   }
+ 
+diff --git a/dom/clients/manager/ClientSource.h b/dom/clients/manager/ClientSource.h
+--- a/dom/clients/manager/ClientSource.h
++++ b/dom/clients/manager/ClientSource.h
+@@ -92,16 +92,23 @@ public:
+   Freeze();
+ 
+   void
+   Thaw();
+ 
+   const ClientInfo&
+   Info() const;
+ 
++  // Trigger a synchronous IPC ping to the parent process to confirm that
++  // the ClientSource actor has been created.  This should only be used
++  // by the WorkerPrivate startup code to deal with a ClientHandle::Control()
++  // call racing on the main thread.  Do not call this in other circumstances!
++  void
++  WorkerSyncPing(mozilla::dom::workers::WorkerPrivate* aWorkerPrivate);
++
+   // Synchronously mark the ClientSource as controlled by the given service
+   // worker.  This can happen as a result of a remote operation or directly
+   // by local code.  For example, if a client's initial network load is
+   // intercepted by a controlling service worker then this should be called
+   // immediately.
+   //
+   // Note, there is no way to clear the controlling service worker because
+   // the specification does not allow that operation.
+diff --git a/dom/clients/manager/ClientSourceParent.cpp b/dom/clients/manager/ClientSourceParent.cpp
+--- a/dom/clients/manager/ClientSourceParent.cpp
++++ b/dom/clients/manager/ClientSourceParent.cpp
+@@ -15,16 +15,17 @@
+ #include "mozilla/dom/PClientManagerParent.h"
+ #include "mozilla/ipc/BackgroundParent.h"
+ #include "mozilla/SystemGroup.h"
+ #include "mozilla/Unused.h"
+ 
+ namespace mozilla {
+ namespace dom {
+ 
++using mozilla::ipc::AssertIsOnBackgroundThread;
+ using mozilla::ipc::BackgroundParent;
+ using mozilla::ipc::IPCResult;
+ using mozilla::ipc::PrincipalInfo;
+ 
+ namespace {
+ 
+ // It would be nice to use a lambda instead of this class, but we cannot
+ // move capture in lambdas yet and ContentParent cannot be AddRef'd off
+@@ -77,16 +78,25 @@ ClientSourceParent::KillInvalidChild()
+   // typically mean someone sent us bogus data over the IPC link.  We can't
+   // trust that process any more.  We have to do this on the main thread, so
+   // there is a small window of time before we kill the process.  This is why
+   // we start the actor destruction immediately above.
+   nsCOMPtr<nsIRunnable> r = new KillContentParentRunnable(Move(process));
+   MOZ_ALWAYS_SUCCEEDS(SystemGroup::Dispatch(TaskCategory::Other, r.forget()));
+ }
+ 
++mozilla::ipc::IPCResult
++ClientSourceParent::RecvWorkerSyncPing()
++{
++  AssertIsOnBackgroundThread();
++  // Do nothing here.  This is purely a sync message allowing the child to
++  // confirm that the actor has been created on the parent process.
++  return IPC_OK();
++}
++
+ IPCResult
+ ClientSourceParent::RecvTeardown()
+ {
+   Unused << Send__delete__(this);
+   return IPC_OK();
+ }
+ 
+ IPCResult
+diff --git a/dom/clients/manager/ClientSourceParent.h b/dom/clients/manager/ClientSourceParent.h
+--- a/dom/clients/manager/ClientSourceParent.h
++++ b/dom/clients/manager/ClientSourceParent.h
+@@ -26,16 +26,19 @@ class ClientSourceParent final : public 
+   bool mExecutionReady;
+   bool mFrozen;
+ 
+   void
+   KillInvalidChild();
+ 
+   // PClientSourceParent
+   mozilla::ipc::IPCResult
++  RecvWorkerSyncPing() override;
++
++  mozilla::ipc::IPCResult
+   RecvTeardown() override;
+ 
+   mozilla::ipc::IPCResult
+   RecvExecutionReady(const ClientSourceExecutionReadyArgs& aArgs) override;
+ 
+   mozilla::ipc::IPCResult
+   RecvFreeze() override;
+ 
+diff --git a/dom/clients/manager/PClientSource.ipdl b/dom/clients/manager/PClientSource.ipdl
+--- a/dom/clients/manager/PClientSource.ipdl
++++ b/dom/clients/manager/PClientSource.ipdl
+@@ -15,16 +15,17 @@ namespace dom {
+ 
+ sync protocol PClientSource
+ {
+   manager PClientManager;
+ 
+   manages PClientSourceOp;
+ 
+ parent:
++  sync WorkerSyncPing();
+   async Teardown();
+   async ExecutionReady(ClientSourceExecutionReadyArgs aArgs);
+   async Freeze();
+   async Thaw();
+ 
+ child:
+   async PClientSourceOp(ClientOpConstructorArgs aArgs);
+ 
+diff --git a/dom/workers/ScriptLoader.cpp b/dom/workers/ScriptLoader.cpp
+--- a/dom/workers/ScriptLoader.cpp
++++ b/dom/workers/ScriptLoader.cpp
+@@ -565,16 +565,17 @@ class ScriptLoaderRunnable final : publi
+   friend class CachePromiseHandler;
+   friend class CacheScriptLoader;
+   friend class LoaderListener;
+ 
+   WorkerPrivate* mWorkerPrivate;
+   nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
+   nsTArray<ScriptLoadInfo> mLoadInfos;
+   RefPtr<CacheCreator> mCacheCreator;
++  Maybe<ServiceWorkerDescriptor> mController;
+   bool mIsMainScript;
+   WorkerScriptType mWorkerScriptType;
+   bool mCanceled;
+   bool mCanceledMainThread;
+   ErrorResult& mRv;
+ 
+ public:
+   NS_DECL_THREADSAFE_ISUPPORTS
+@@ -1213,16 +1214,20 @@ private:
+ 
+       mWorkerPrivate->SetReferrerPolicyFromHeaderValue(tRPHeaderCValue);
+ 
+       WorkerPrivate* parent = mWorkerPrivate->GetParent();
+       if (parent) {
+         // XHR Params Allowed
+         mWorkerPrivate->SetXHRParamsAllowed(parent->XHRParamsAllowed());
+       }
++
++      if (chanLoadInfo) {
++        mController = chanLoadInfo->GetController();
++      }
+     }
+ 
+     return NS_OK;
+   }
+ 
+   void
+   DataReceivedFromCache(uint32_t aIndex, const uint8_t* aString,
+                         uint32_t aStringLen,
+@@ -1979,18 +1984,21 @@ ScriptExecutorRunnable::WorkerRun(JSCont
+       // Top level scripts only!
+       if (mIsWorkerScript) {
+         aWorkerPrivate->MaybeDispatchLoadFailedRunnable();
+       }
+       return true;
+     }
+ 
+     // If this is a top level script that succeeded, then mark the
+-    // Client execution ready.
++    // Client execution ready and possible controlled by a service worker.
+     if (mIsWorkerScript) {
++      if (mScriptLoader.mController.isSome()) {
++        aWorkerPrivate->Control(mScriptLoader.mController.ref());
++      }
+       aWorkerPrivate->ExecutionReady();
+     }
+ 
+     NS_ConvertUTF16toUTF8 filename(loadInfo.mURL);
+ 
+     JS::CompileOptions options(aCx);
+     options.setFileAndLine(filename.get(), 1)
+            .setNoScriptRval(true);
+diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp
+--- a/dom/workers/ServiceWorkerManager.cpp
++++ b/dom/workers/ServiceWorkerManager.cpp
+@@ -32,16 +32,18 @@
+ 
+ #include "mozilla/BasePrincipal.h"
+ #include "mozilla/ClearOnShutdown.h"
+ #include "mozilla/ErrorNames.h"
+ #include "mozilla/LoadContext.h"
+ #include "mozilla/SystemGroup.h"
+ #include "mozilla/Telemetry.h"
+ #include "mozilla/dom/BindingUtils.h"
++#include "mozilla/dom/ClientHandle.h"
++#include "mozilla/dom/ClientManager.h"
+ #include "mozilla/dom/ContentParent.h"
+ #include "mozilla/dom/ErrorEvent.h"
+ #include "mozilla/dom/Headers.h"
+ #include "mozilla/dom/InternalHeaders.h"
+ #include "mozilla/dom/Navigator.h"
+ #include "mozilla/dom/NotificationEvent.h"
+ #include "mozilla/dom/PromiseNativeHandler.h"
+ #include "mozilla/dom/PromiseWindowProxy.h"
+@@ -2327,35 +2329,57 @@ ServiceWorkerManager::MaybeCheckNavigati
+   //    algorithm.
+   RefPtr<ServiceWorkerRegistrationInfo> registration;
+   mControlledDocuments.Get(aDoc, getter_AddRefs(registration));
+   if (registration) {
+     registration->MaybeScheduleUpdate();
+   }
+ }
+ 
+-void
++RefPtr<GenericPromise>
+ ServiceWorkerManager::StartControllingADocument(ServiceWorkerRegistrationInfo* aRegistration,
+                                                 nsIDocument* aDoc,
+                                                 const nsAString& aDocumentId)
+ {
+   MOZ_ASSERT(aRegistration);
+   MOZ_ASSERT(aDoc);
+ 
+ #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+   auto storageAllowed = nsContentUtils::StorageAllowedForDocument(aDoc);
+   MOZ_DIAGNOSTIC_ASSERT(storageAllowed == nsContentUtils::StorageAccess::eAllow);
+ #endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ 
++  RefPtr<GenericPromise> ref = GenericPromise::CreateAndResolve(true, __func__);
++
+   aRegistration->StartControllingADocument();
+   mControlledDocuments.Put(aDoc, aRegistration);
+   if (!aDocumentId.IsEmpty()) {
+     aDoc->SetId(aDocumentId);
+   }
++
++  // Mark the document's ClientSource as controlled using the ClientHandle
++  // interface.  While we could get at the ClientSource directly from the
++  // document here, our goal is to move ServiceWorkerManager to a separate
++  // process.  Using the ClientHandle supports this remote operation.
++  ServiceWorkerInfo* activeWorker = aRegistration->GetActive();
++  nsPIDOMWindowInner* innerWindow = aDoc->GetInnerWindow();
++  if (activeWorker && innerWindow) {
++    Maybe<ClientInfo> clientInfo = innerWindow->GetClientInfo();
++    if (clientInfo.isSome()) {
++      RefPtr<ClientHandle> clientHandle =
++        ClientManager::CreateHandle(clientInfo.ref(),
++                                    SystemGroup::EventTargetFor(TaskCategory::Other));
++      if (clientHandle) {
++        ref = Move(clientHandle->Control(activeWorker->Descriptor()));
++      }
++    }
++  }
++
+   Telemetry::Accumulate(Telemetry::SERVICE_WORKER_CONTROLLED_DOCUMENTS, 1);
++  return Move(ref);
+ }
+ 
+ void
+ ServiceWorkerManager::StopControllingADocument(ServiceWorkerRegistrationInfo* aRegistration)
+ {
+   aRegistration->StopControllingADocument();
+   if (aRegistration->IsControllingDocuments() || !aRegistration->IsIdle()) {
+     return;
+@@ -2656,16 +2680,58 @@ ServiceWorkerManager::DispatchFetchEvent
+     // before we get to this point.  Therefore we must handle a nullptr
+     // active worker here.
+     serviceWorker = registration->GetActive();
+     if (!serviceWorker) {
+       aRv.Throw(NS_ERROR_FAILURE);
+       return;
+     }
+ 
++    // If there is a reserved client it should be marked as controlled before
++    // the FetchEvent is dispatched.
++    nsCOMPtr<nsILoadInfo> loadInfo = internalChannel->GetLoadInfo();
++    if (loadInfo) {
++      Maybe<ClientInfo> clientInfo = loadInfo->GetReservedClientInfo();
++
++      // Also override the initial about:blank controller since the real
++      // network load may be intercepted by a different service worker.  If
++      // the intial about:blank has a controller here its simply been
++      // inherited from its parent.
++      if (clientInfo.isNothing()) {
++        clientInfo = loadInfo->GetInitialClientInfo();
++
++        // TODO: We need to handle the case where the initial about:blank is
++        //       controlled, but the final document load is not.  Right now
++        //       the spec does not really say what to do.  There currently
++        //       is no way for the controller to be cleared from a client in
++        //       the spec or our implementation.  We may want to force a
++        //       new inner window to be created instead of reusing the
++        //       initial about:blank global.  See bug 1419620 and the spec
++        //       issue here: https://github.com/w3c/ServiceWorker/issues/1232
++      }
++
++      if (clientInfo.isSome()) {
++        // First, attempt to mark the reserved client controlled directly.  This
++        // will update the controlled status in the ClientManagerService in the
++        // parent.  It will also eventually propagate back to the ClientSource.
++        RefPtr<ClientHandle> clientHandle =
++          ClientManager::CreateHandle(clientInfo.ref(),
++                                      SystemGroup::EventTargetFor(TaskCategory::Other));
++        if (clientHandle) {
++          clientHandle->Control(serviceWorker->Descriptor());
++        }
++      }
++
++      // But we also note the reserved state on the LoadInfo.  This allows the
++      // ClientSource to be updated immediately after the nsIChannel starts.
++      // This is necessary to have the correct controller in place for immediate
++      // follow-on requests.
++      loadInfo->SetController(serviceWorker->Descriptor());
++    }
++
+     AddNavigationInterception(serviceWorker->Scope(), aChannel);
+   }
+ 
+   if (NS_WARN_IF(aRv.Failed())) {
+     return;
+   }
+ 
+   MOZ_DIAGNOSTIC_ASSERT(serviceWorker);
+diff --git a/dom/workers/ServiceWorkerManager.h b/dom/workers/ServiceWorkerManager.h
+--- a/dom/workers/ServiceWorkerManager.h
++++ b/dom/workers/ServiceWorkerManager.h
+@@ -10,16 +10,17 @@
+ #include "nsIServiceWorkerManager.h"
+ #include "nsCOMPtr.h"
+ 
+ #include "ipc/IPCMessageUtils.h"
+ #include "mozilla/Attributes.h"
+ #include "mozilla/AutoRestore.h"
+ #include "mozilla/ConsoleReportCollector.h"
+ #include "mozilla/LinkedList.h"
++#include "mozilla/MozPromise.h"
+ #include "mozilla/Preferences.h"
+ #include "mozilla/TypedEnumBits.h"
+ #include "mozilla/UniquePtr.h"
+ #include "mozilla/WeakPtr.h"
+ #include "mozilla/dom/BindingUtils.h"
+ #include "mozilla/dom/Promise.h"
+ #include "mozilla/dom/ServiceWorkerCommon.h"
+ #include "mozilla/dom/ServiceWorkerRegistrar.h"
+@@ -385,17 +386,17 @@ private:
+                                             WhichServiceWorker aWhichOne);
+   void
+   InvalidateServiceWorkerRegistrationWorker(ServiceWorkerRegistrationInfo* aRegistration,
+                                             WhichServiceWorker aWhichOnes);
+ 
+   void
+   NotifyServiceWorkerRegistrationRemoved(ServiceWorkerRegistrationInfo* aRegistration);
+ 
+-  void
++  RefPtr<GenericPromise>
+   StartControllingADocument(ServiceWorkerRegistrationInfo* aRegistration,
+                             nsIDocument* aDoc,
+                             const nsAString& aDocumentId);
+ 
+   void
+   StopControllingADocument(ServiceWorkerRegistrationInfo* aRegistration);
+ 
+   already_AddRefed<ServiceWorkerRegistrationInfo>
+diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp
+--- a/dom/workers/WorkerPrivate.cpp
++++ b/dom/workers/WorkerPrivate.cpp
+@@ -5330,28 +5330,61 @@ WorkerPrivate::EnsureClientSource()
+   if (!mClientSource) {
+     return false;
+   }
+ 
+   if (mFrozen) {
+     mClientSource->Freeze();
+   }
+ 
++  // Shortly after the client is reserved we will try loading the main script
++  // for the worker.  This may get intercepted by the ServiceWorkerManager
++  // which will then try to create a ClientHandle.  Its actually possible for
++  // the main thread to create this ClientHandle before our IPC message creating
++  // the ClientSource completes.  To avoid this race we synchronously ping our
++  // parent Client actor here.  This ensure the worker ClientSource is created
++  // in the parent before the main thread might try reaching it with a
++  // ClientHandle.
++  //
++  // An alternative solution would have been to handle the out-of-order operations
++  // on the parent side.  We could have created a small window where we allow
++  // ClientHandle objects to exist without a ClientSource.  We would then time
++  // out these handles if they stayed orphaned for too long.  This approach would
++  // be much more complex, but also avoid this extra bit of latency when starting
++  // workers.
++  //
++  // Note, we only have to do this for workers that can be controlled by a
++  // service worker.  So avoid the sync overhead here if we are starting a
++  // service worker or a chrome worker.
++  if (Type() != WorkerTypeService && !IsChromeWorker()) {
++    mClientSource->WorkerSyncPing(this);
++  }
++
+   return true;
+ }
+ 
+ const ClientInfo&
+ WorkerPrivate::GetClientInfo() const
+ {
+   AssertIsOnWorkerThread();
+   MOZ_DIAGNOSTIC_ASSERT(mClientSource);
+   return mClientSource->Info();
+ }
+ 
+ void
++WorkerPrivate::Control(const ServiceWorkerDescriptor& aServiceWorker)
++{
++  AssertIsOnWorkerThread();
++  MOZ_DIAGNOSTIC_ASSERT(mClientSource);
++  MOZ_DIAGNOSTIC_ASSERT(!IsChromeWorker());
++  MOZ_DIAGNOSTIC_ASSERT(Type() != WorkerTypeService);
++  mClientSource->SetController(aServiceWorker);
++}
++
++void
+ WorkerPrivate::ExecutionReady()
+ {
+   AssertIsOnWorkerThread();
+   MOZ_DIAGNOSTIC_ASSERT(mClientSource);
+   mClientSource->WorkerExecutionReady(this);
+ }
+ 
+ void
+diff --git a/dom/workers/WorkerPrivate.h b/dom/workers/WorkerPrivate.h
+--- a/dom/workers/WorkerPrivate.h
++++ b/dom/workers/WorkerPrivate.h
+@@ -1499,16 +1499,19 @@ public:
+ 
+   bool
+   EnsureClientSource();
+ 
+   const ClientInfo&
+   GetClientInfo() const;
+ 
+   void
++  Control(const ServiceWorkerDescriptor& aServiceWorker);
++
++  void
+   ExecutionReady();
+ 
+ private:
+   WorkerPrivate(WorkerPrivate* aParent,
+                 const nsAString& aScriptURL, bool aIsChromeWorker,
+                 WorkerType aWorkerType, const nsAString& aWorkerName,
+                 const nsACString& aServiceWorkerScope,
+                 WorkerLoadInfo& aLoadInfo);
+diff --git a/ipc/ipdl/sync-messages.ini b/ipc/ipdl/sync-messages.ini
+--- a/ipc/ipdl/sync-messages.ini
++++ b/ipc/ipdl/sync-messages.ini
+@@ -1010,8 +1010,10 @@ description =
+ [PHandlerService::ExistsForProtocol]
+ description = bug 1382323
+ [PHandlerService::Exists]
+ description =
+ [PHandlerService::GetTypeFromExtension]
+ description =
+ [PLayerTransaction::ShutdownSync]
+ description = bug 1363126
++[PClientSource::WorkerSyncPing]
++description = Synchronous ping allowing worker thread to confirm actor is created. Necessary to avoid racing with ClientHandle actors on main thread.

+ 139 - 0
frg/work-js/mozilla-release/patches/1423412-2-59a1.patch

@@ -0,0 +1,139 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1512524723 18000
+# Node ID 09942818c67041082051e9bd25aee3f7899546ba
+# Parent  f92cf67d2d9d065c89425e5c3952271dc6e57a49
+Bug 1423412 P2 Copy the service worker controller across redirects by default and clear it explicitly for non-subresource redirects. r=baku
+
+diff --git a/dom/clients/manager/ClientChannelHelper.cpp b/dom/clients/manager/ClientChannelHelper.cpp
+--- a/dom/clients/manager/ClientChannelHelper.cpp
++++ b/dom/clients/manager/ClientChannelHelper.cpp
+@@ -96,26 +96,16 @@ class ClientChannelHelper final : public
+         if (reservedClientInfo.isSome()) {
+           newLoadInfo->SetReservedClientInfo(reservedClientInfo.ref());
+         }
+ 
+         if (initialClientInfo.isSome()) {
+           newLoadInfo->SetInitialClientInfo(initialClientInfo.ref());
+         }
+       }
+-
+-      // Make sure we keep the service worker controller on same-origin
+-      // internal redirects.
+-      if (oldLoadInfo != newLoadInfo &&
+-          aFlags & nsIChannelEventSink::REDIRECT_INTERNAL) {
+-        const Maybe<ServiceWorkerDescriptor>& controller = oldLoadInfo->GetController();
+-        if (controller.isSome()) {
+-          newLoadInfo->SetController(controller.ref());
+-        }
+-      }
+     }
+ 
+     // If it's a cross-origin redirect then we discard the old reserved client
+     // and create a new one.
+     else {
+       // If CheckSameOrigin() worked, then the security manager must exist.
+       nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+       MOZ_DIAGNOSTIC_ASSERT(ssm);
+@@ -129,16 +119,30 @@ class ClientChannelHelper final : public
+       // same-origin security policy.
+       reservedClient.reset();
+       reservedClient = ClientManager::CreateSource(ClientType::Window,
+                                                    mEventTarget, principal);
+ 
+       newLoadInfo->GiveReservedClientSource(Move(reservedClient));
+     }
+ 
++    // Normally we keep the controller across channel redirects, but we must
++    // clear it when a non-subresource load redirects.  Only do this for real
++    // redirects, however.
++    //
++    // There is an open spec question about what to do in this case for
++    // worker script redirects.  For now we clear the controller as that
++    // seems most sane. See:
++    //
++    //  https://github.com/w3c/ServiceWorker/issues/1239
++    //
++    if (!(aFlags & nsIChannelEventSink::REDIRECT_INTERNAL)) {
++      newLoadInfo->ClearController();
++    }
++
+     nsCOMPtr<nsIChannelEventSink> outerSink = do_GetInterface(mOuter);
+     if (outerSink) {
+       return outerSink->AsyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags,
+                                                aCallback);
+     }
+ 
+     aCallback->OnRedirectVerifyCallback(NS_OK);
+     return NS_OK;
+diff --git a/netwerk/base/LoadInfo.cpp b/netwerk/base/LoadInfo.cpp
+--- a/netwerk/base/LoadInfo.cpp
++++ b/netwerk/base/LoadInfo.cpp
+@@ -305,17 +305,17 @@ LoadInfo::LoadInfo(const LoadInfo& rhs)
+   , mTriggeringPrincipal(rhs.mTriggeringPrincipal)
+   , mPrincipalToInherit(rhs.mPrincipalToInherit)
+   , mSandboxedLoadingPrincipal(rhs.mSandboxedLoadingPrincipal)
+   , mResultPrincipalURI(rhs.mResultPrincipalURI)
+   , mClientInfo(rhs.mClientInfo)
+   // mReservedClientSource must be handled specially during redirect
+   // mReservedClientInfo must be handled specially during redirect
+   // mInitialClientInfo must be handled specially during redirect
+-  // mController must be handled specially during redirect
++  , mController(rhs.mController)
+   , mLoadingContext(rhs.mLoadingContext)
+   , mContextForTopLevelLoad(rhs.mContextForTopLevelLoad)
+   , mSecurityFlags(rhs.mSecurityFlags)
+   , mInternalContentPolicyType(rhs.mInternalContentPolicyType)
+   , mTainting(rhs.mTainting)
+   , mUpgradeInsecureRequests(rhs.mUpgradeInsecureRequests)
+   , mVerifySignedContent(rhs.mVerifySignedContent)
+   , mEnforceSRI(rhs.mEnforceSRI)
+@@ -1167,16 +1167,22 @@ LoadInfo::GetInitialClientInfo()
+ }
+ 
+ void
+ LoadInfo::SetController(const ServiceWorkerDescriptor& aServiceWorker)
+ {
+   mController.emplace(aServiceWorker);
+ }
+ 
++void
++LoadInfo::ClearController()
++{
++  mController.reset();
++}
++
+ const Maybe<ServiceWorkerDescriptor>&
+ LoadInfo::GetController()
+ {
+   return mController;
+ }
+ 
+ } // namespace net
+ } // namespace mozilla
+diff --git a/netwerk/base/nsILoadInfo.idl b/netwerk/base/nsILoadInfo.idl
+--- a/netwerk/base/nsILoadInfo.idl
++++ b/netwerk/base/nsILoadInfo.idl
+@@ -911,14 +911,22 @@ interface nsILoadInfo : nsISupports
+    * the first service worker interception occurs.  For subresource requests
+    * it may be set by the source client if its already controlled by a
+    * service worker.
+    */
+   [noscript, nostdcall, notxpcom]
+   void SetController(in const_ServiceWorkerDescriptorRef aServiceWorker);
+ 
+   /**
++   * Clear the service worker controller for this channel.  This should only
++   * be used for window navigation redirects.  By default we want to keep
++   * the controller in all other cases.
++   */
++  [noscript, nostdcall, notxpcom]
++  void ClearController();
++
++  /**
+    * Get the service worker controlling this network request, if one has
+    * been set.
+    */
+   [noscript, nostdcall, notxpcom]
+   const_MaybeServiceWorkerDescriptorRef GetController();
+ };

+ 49 - 0
frg/work-js/mozilla-release/patches/1423412-3-59a1.patch

@@ -0,0 +1,49 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1512524723 18000
+# Node ID 9a5aac891c5f144811b17168353e55641a31e10c
+# Parent  34d52c89ddddd09746d9c100ef8ce1976a690754
+Bug 1423412 P3 Handle the case where an iframe starts with an inherited controller and then ends up uncontrolled. r=baku
+
+diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp
+--- a/dom/base/nsGlobalWindow.cpp
++++ b/dom/base/nsGlobalWindow.cpp
+@@ -3512,16 +3512,38 @@ nsGlobalWindow::EnsureClientSource()
+   // so, mark it as controlled immediately here.  The actor may
+   // or may not have been notified by the parent side about being
+   // controlled yet.
+   if (loadInfo) {
+     const Maybe<ServiceWorkerDescriptor> controller = loadInfo->GetController();
+     if (controller.isSome()) {
+       mClientSource->SetController(controller.ref());
+     }
++
++    // We also have to handle the case where te initial about:blank is
++    // controlled due to inheritting the service worker from its parent,
++    // but the actual nsIChannel load is not covered by any service worker.
++    // In this case we want the final page to be uncontrolled.  There is
++    // an open spec issue about how exactly this should be handled, but for
++    // now we just force creation of a new ClientSource to clear the
++    // controller.
++    //
++    //  https://github.com/w3c/ServiceWorker/issues/1232
++    //
++    else if (mClientSource->GetController().isSome()) {
++      mClientSource.reset();
++      mClientSource =
++        ClientManager::CreateSource(ClientType::Window,
++                                    EventTargetFor(TaskCategory::Other),
++                                    mDoc->NodePrincipal());
++      if (NS_WARN_IF(!mClientSource)) {
++        return NS_ERROR_FAILURE;
++      }
++      newClientSource = true;
++    }
+   }
+ 
+   // Its possible that we got a client just after being frozen in
+   // the bfcache.  In that case freeze the client immediately.
+   if (newClientSource && IsFrozen()) {
+     mClientSource->Freeze();
+   }
+ 

+ 573 - 0
frg/work-js/mozilla-release/patches/1423412-4no5-59a1.patch

@@ -0,0 +1,573 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1512524723 18000
+# Node ID bd99f4732647f01441e5ca93d11b27e98592838b
+# Parent  b81d8e28ed76da935c7469b58bf9c4a6f1749dca
+Bug 1423412 P4 Add a ClientManager::GetInfoAndState() that retrieves the ClientInfo and ClientState for a given ID. r=baku
+
+diff --git a/dom/clients/manager/ClientIPCTypes.ipdlh b/dom/clients/manager/ClientIPCTypes.ipdlh
+--- a/dom/clients/manager/ClientIPCTypes.ipdlh
++++ b/dom/clients/manager/ClientIPCTypes.ipdlh
+@@ -45,39 +45,53 @@ struct IPCClientWorkerState
+ };
+ 
+ union IPCClientState
+ {
+   IPCClientWindowState;
+   IPCClientWorkerState;
+ };
+ 
++struct ClientInfoAndState
++{
++  IPCClientInfo info;
++  IPCClientState state;
++};
++
+ struct ClientSourceExecutionReadyArgs
+ {
+   nsCString url;
+   FrameType frameType;
+ };
+ 
+ struct ClientControlledArgs
+ {
+   IPCServiceWorkerDescriptor serviceWorker;
+ };
+ 
++struct ClientGetInfoAndStateArgs
++{
++  nsID id;
++  PrincipalInfo principalInfo;
++};
++
+ struct ClientOpenWindowArgs
+ {
+ };
+ 
+ union ClientOpConstructorArgs
+ {
+   ClientControlledArgs;
++  ClientGetInfoAndStateArgs;
+ };
+ 
+ struct ClientNavigateOpConstructorArgs
+ {
+ };
+ 
+ union ClientOpResult
+ {
+   nsresult;
++  ClientInfoAndState;
+ };
+ 
+ } // namespace dom
+ } // namespace mozilla
+diff --git a/dom/clients/manager/ClientManager.cpp b/dom/clients/manager/ClientManager.cpp
+--- a/dom/clients/manager/ClientManager.cpp
++++ b/dom/clients/manager/ClientManager.cpp
+@@ -240,10 +240,19 @@ ClientManager::CreateSource(ClientType a
+ already_AddRefed<ClientHandle>
+ ClientManager::CreateHandle(const ClientInfo& aClientInfo,
+                             nsISerialEventTarget* aSerialEventTarget)
+ {
+   RefPtr<ClientManager> mgr = GetOrCreateForCurrentThread();
+   return mgr->CreateHandleInternal(aClientInfo, aSerialEventTarget);
+ }
+ 
++// static
++RefPtr<ClientOpPromise>
++ClientManager::GetInfoAndState(const ClientGetInfoAndStateArgs& aArgs,
++                               nsISerialEventTarget* aSerialEventTarget)
++{
++  RefPtr<ClientManager> mgr = GetOrCreateForCurrentThread();
++  return mgr->StartOp(aArgs, aSerialEventTarget);
++}
++
+ } // namespace dom
+ } // namespace mozilla
+diff --git a/dom/clients/manager/ClientManager.h b/dom/clients/manager/ClientManager.h
+--- a/dom/clients/manager/ClientManager.h
++++ b/dom/clients/manager/ClientManager.h
+@@ -13,16 +13,17 @@ class nsIPrincipal;
+ 
+ namespace mozilla {
+ namespace ipc {
+ class PBackgroundChild;
+ class PrincipalInfo;
+ } // namespace ipc
+ namespace dom {
+ 
++class ClientGetInfoAndStateArgs;
+ class ClientHandle;
+ class ClientInfo;
+ class ClientManagerChild;
+ class ClientOpConstructorArgs;
+ class ClientSource;
+ enum class ClientType : uint8_t;
+ 
+ namespace workers {
+@@ -88,15 +89,19 @@ public:
+   static UniquePtr<ClientSource>
+   CreateSource(ClientType aType, nsISerialEventTarget* aEventTarget,
+                const mozilla::ipc::PrincipalInfo& aPrincipal);
+ 
+   static already_AddRefed<ClientHandle>
+   CreateHandle(const ClientInfo& aClientInfo,
+                nsISerialEventTarget* aSerialEventTarget);
+ 
++  static RefPtr<ClientOpPromise>
++  GetInfoAndState(const ClientGetInfoAndStateArgs& aArgs,
++                  nsISerialEventTarget* aSerialEventTarget);
++
+   NS_INLINE_DECL_REFCOUNTING(mozilla::dom::ClientManager)
+ };
+ 
+ } // namespace dom
+ } // namespace mozilla
+ 
+ #endif // _mozilla_dom_ClientManager_h
+diff --git a/dom/clients/manager/ClientManagerOpParent.cpp b/dom/clients/manager/ClientManagerOpParent.cpp
+--- a/dom/clients/manager/ClientManagerOpParent.cpp
++++ b/dom/clients/manager/ClientManagerOpParent.cpp
+@@ -6,26 +6,61 @@
+ 
+ #include "ClientManagerOpParent.h"
+ 
+ #include "ClientManagerService.h"
+ 
+ namespace mozilla {
+ namespace dom {
+ 
++template <typename Method, typename... Args>
++void
++ClientManagerOpParent::DoServiceOp(Method aMethod, Args&&... aArgs)
++{
++  // Note, we need perfect forarding of the template type in order
++  // to allow already_AddRefed<> to be passed as an arg.
++  RefPtr<ClientOpPromise> p = (mService->*aMethod)(Forward<Args>(aArgs)...);
++
++  // Capturing `this` is safe here because we disconnect the promise in
++  // ActorDestroy() which ensures neither lambda is called if the actor
++  // is destroyed before the source operation completes.
++  p->Then(GetCurrentThreadSerialEventTarget(), __func__,
++    [this] (const mozilla::dom::ClientOpResult& aResult) {
++      mPromiseRequestHolder.Complete();
++      Unused << PClientManagerOpParent::Send__delete__(this, aResult);
++    }, [this] (nsresult aRv) {
++      mPromiseRequestHolder.Complete();
++      Unused << PClientManagerOpParent::Send__delete__(this, aRv);
++    })->Track(mPromiseRequestHolder);
++}
++
+ void
+ ClientManagerOpParent::ActorDestroy(ActorDestroyReason aReason)
+ {
++  mPromiseRequestHolder.DisconnectIfExists();
+ }
+ 
+ ClientManagerOpParent::ClientManagerOpParent(ClientManagerService* aService)
+   : mService(aService)
+ {
+   MOZ_DIAGNOSTIC_ASSERT(mService);
+ }
+ 
+ void
+ ClientManagerOpParent::Init(const ClientOpConstructorArgs& aArgs)
+ {
++  switch (aArgs.type()) {
++    case ClientOpConstructorArgs::TClientGetInfoAndStateArgs:
++    {
++      DoServiceOp(&ClientManagerService::GetInfoAndState,
++                  aArgs.get_ClientGetInfoAndStateArgs());
++      break;
++    }
++    default:
++    {
++      MOZ_ASSERT_UNREACHABLE("Unknown Client operation!");
++      break;
++    }
++  }
+ }
+ 
+ } // namespace dom
+ } // namespace mozilla
+diff --git a/dom/clients/manager/ClientManagerOpParent.h b/dom/clients/manager/ClientManagerOpParent.h
+--- a/dom/clients/manager/ClientManagerOpParent.h
++++ b/dom/clients/manager/ClientManagerOpParent.h
+@@ -2,25 +2,31 @@
+ /* vim: set ts=8 sts=2 et sw=2 tw=80: */
+ /* This Source Code Form is subject to the terms of the Mozilla Public
+  * License, v. 2.0. If a copy of the MPL was not distributed with this
+  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+ #ifndef _mozilla_dom_ClientManagerOpParent_h
+ #define _mozilla_dom_ClientManagerOpParent_h
+ 
+ #include "mozilla/dom/PClientManagerOpParent.h"
++#include "ClientOpPromise.h"
+ 
+ namespace mozilla {
+ namespace dom {
+ 
+ class ClientManagerService;
+ 
+ class ClientManagerOpParent final : public PClientManagerOpParent
+ {
+   RefPtr<ClientManagerService> mService;
++  MozPromiseRequestHolder<ClientOpPromise> mPromiseRequestHolder;
++
++  template <typename Method, typename... Args>
++  void
++  DoServiceOp(Method aMethod, Args&&... aArgs);
+ 
+   // PClientManagerOpParent interface
+   void
+   ActorDestroy(ActorDestroyReason aReason) override;
+ 
+ public:
+   explicit ClientManagerOpParent(ClientManagerService* aService);
+   ~ClientManagerOpParent() = default;
+diff --git a/dom/clients/manager/ClientManagerService.cpp b/dom/clients/manager/ClientManagerService.cpp
+--- a/dom/clients/manager/ClientManagerService.cpp
++++ b/dom/clients/manager/ClientManagerService.cpp
+@@ -285,10 +285,24 @@ void
+ ClientManagerService::RemoveManager(ClientManagerParent* aManager)
+ {
+   AssertIsOnBackgroundThread();
+   MOZ_DIAGNOSTIC_ASSERT(aManager);
+   DebugOnly<bool> removed = mManagerList.RemoveElement(aManager);
+   MOZ_ASSERT(removed);
+ }
+ 
++RefPtr<ClientOpPromise>
++ClientManagerService::GetInfoAndState(const ClientGetInfoAndStateArgs& aArgs)
++{
++  RefPtr<ClientOpPromise> ref;
++
++  ClientSourceParent* source = FindSource(aArgs.id(), aArgs.principalInfo());
++  if (!source || !source->ExecutionReady()) {
++    ref = ClientOpPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
++    return ref.forget();
++  }
++
++  return source->StartOp(aArgs);
++}
++
+ } // namespace dom
+ } // namespace mozilla
+diff --git a/dom/clients/manager/ClientManagerService.h b/dom/clients/manager/ClientManagerService.h
+--- a/dom/clients/manager/ClientManagerService.h
++++ b/dom/clients/manager/ClientManagerService.h
+@@ -1,18 +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/. */
+ #ifndef _mozilla_dom_ClientManagerService_h
+ #define _mozilla_dom_ClientManagerService_h
+ 
+-#include "mozilla/ipc/PBackgroundSharedTypes.h"
+-#include "mozilla/MozPromise.h"
++#include "ClientOpPromise.h"
+ #include "nsDataHashtable.h"
+ 
+ namespace mozilla {
+ 
+ namespace dom {
+ 
+ class ClientManagerParent;
+ class ClientSourceParent;
+@@ -55,15 +54,18 @@ public:
+              const mozilla::ipc::PrincipalInfo& aPrincipalInfo);
+ 
+   void
+   AddManager(ClientManagerParent* aManager);
+ 
+   void
+   RemoveManager(ClientManagerParent* aManager);
+ 
++  RefPtr<ClientOpPromise>
++  GetInfoAndState(const ClientGetInfoAndStateArgs& aArgs);
++
+   NS_INLINE_DECL_REFCOUNTING(mozilla::dom::ClientManagerService)
+ };
+ 
+ } // namespace dom
+ } // namespace mozilla
+ 
+ #endif // _mozilla_dom_ClientManagerService_h
+diff --git a/dom/clients/manager/ClientSource.cpp b/dom/clients/manager/ClientSource.cpp
+--- a/dom/clients/manager/ClientSource.cpp
++++ b/dom/clients/manager/ClientSource.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 "ClientSource.h"
+ 
+ #include "ClientManager.h"
+ #include "ClientManagerChild.h"
+ #include "ClientSourceChild.h"
++#include "ClientState.h"
+ #include "ClientValidation.h"
+ #include "mozilla/dom/ClientIPCTypes.h"
+ #include "mozilla/dom/WorkerPrivate.h"
+ #include "nsIDocShell.h"
+ #include "nsPIDOMWindow.h"
+ 
+ namespace mozilla {
+ namespace dom {
+@@ -49,16 +50,47 @@ ClientSource::ExecutionReady(const Clien
+ 
+   mClientInfo.SetURL(aArgs.url());
+   mClientInfo.SetFrameType(aArgs.frameType());
+   MaybeExecute([aArgs](PClientSourceChild* aActor) {
+     aActor->SendExecutionReady(aArgs);
+   });
+ }
+ 
++nsresult
++ClientSource::SnapshotWindowState(ClientState* aStateOut)
++{
++  MOZ_ASSERT(NS_IsMainThread());
++
++  nsPIDOMWindowInner* window = GetInnerWindow();
++  if (!window || !window->IsCurrentInnerWindow() ||
++      !window->HasActiveDocument()) {
++    *aStateOut = ClientState(ClientWindowState(VisibilityState::Hidden,
++                                               TimeStamp(), false));
++    return NS_OK;
++  }
++
++  nsIDocument* doc = window->GetExtantDoc();
++  if (NS_WARN_IF(!doc)) {
++    return NS_ERROR_UNEXPECTED;
++  }
++
++  ErrorResult rv;
++  bool focused = doc->HasFocus(rv);
++  if (NS_WARN_IF(rv.Failed())) {
++    rv.SuppressException();
++    return rv.StealNSResult();
++  }
++
++  *aStateOut = ClientState(ClientWindowState(doc->VisibilityState(),
++                                             doc->LastFocusTime(), focused));
++
++  return NS_OK;
++}
++
+ WorkerPrivate*
+ ClientSource::GetWorkerPrivate() const
+ {
+   NS_ASSERT_OWNINGTHREAD(ClientSource);
+   if (!mOwner.is<WorkerPrivate*>()) {
+     return nullptr;
+   }
+   return mOwner.as<WorkerPrivate*>();
+@@ -69,16 +101,28 @@ ClientSource::GetDocShell() const
+ {
+   NS_ASSERT_OWNINGTHREAD(ClientSource);
+   if (!mOwner.is<nsCOMPtr<nsIDocShell>>()) {
+     return nullptr;
+   }
+   return mOwner.as<nsCOMPtr<nsIDocShell>>();
+ }
+ 
++void
++ClientSource::MaybeCreateInitialDocument()
++{
++  nsIDocShell* docshell = GetDocShell();
++  if (docshell) {
++    // Force the create of the initial document if it does not exist yet.
++    Unused << docshell->GetDocument();
++
++    MOZ_DIAGNOSTIC_ASSERT(GetInnerWindow());
++  }
++}
++
+ ClientSource::ClientSource(ClientManager* aManager,
+                            nsISerialEventTarget* aEventTarget,
+                            const ClientSourceConstructorArgs& aArgs)
+   : mManager(aManager)
+   , mEventTarget(aEventTarget)
+   , mOwner(AsVariant(Nothing()))
+   , mClientInfo(aArgs.id(), aArgs.type(), aArgs.principalInfo(), aArgs.creationTime())
+ {
+@@ -294,16 +338,52 @@ ClientSource::Control(const ClientContro
+ }
+ 
+ const Maybe<ServiceWorkerDescriptor>&
+ ClientSource::GetController() const
+ {
+   return mController;
+ }
+ 
++RefPtr<ClientOpPromise>
++ClientSource::GetInfoAndState(const ClientGetInfoAndStateArgs& aArgs)
++{
++  RefPtr<ClientOpPromise> ref;
++
++  ClientState state;
++  nsresult rv = SnapshotState(&state);
++  if (NS_FAILED(rv)) {
++    ref = ClientOpPromise::CreateAndReject(rv, __func__);
++    return ref.forget();
++  }
++
++  ref = ClientOpPromise::CreateAndResolve(ClientInfoAndState(mClientInfo.ToIPC(),
++                                                             state.ToIPC()), __func__);
++  return ref.forget();
++}
++
++nsresult
++ClientSource::SnapshotState(ClientState* aStateOut)
++{
++  NS_ASSERT_OWNINGTHREAD(ClientSource);
++  MOZ_DIAGNOSTIC_ASSERT(aStateOut);
++
++  if (mClientInfo.Type() == ClientType::Window) {
++    MaybeCreateInitialDocument();
++    nsresult rv = SnapshotWindowState(aStateOut);
++    if (NS_FAILED(rv)) {
++      return rv;
++    }
++    return NS_OK;
++  }
++
++  *aStateOut = ClientState(ClientWorkerState());
++  return NS_OK;
++}
++
+ nsISerialEventTarget*
+ ClientSource::EventTarget() const
+ {
+   return mEventTarget;
+ }
+ 
+ void
+ ClientSource::Traverse(nsCycleCollectionTraversalCallback& aCallback,
+diff --git a/dom/clients/manager/ClientSource.h b/dom/clients/manager/ClientSource.h
+--- a/dom/clients/manager/ClientSource.h
++++ b/dom/clients/manager/ClientSource.h
+@@ -60,16 +60,22 @@ class ClientSource final : public Client
+   ExecutionReady(const ClientSourceExecutionReadyArgs& aArgs);
+ 
+   mozilla::dom::workers::WorkerPrivate*
+   GetWorkerPrivate() const;
+ 
+   nsIDocShell*
+   GetDocShell() const;
+ 
++  void
++  MaybeCreateInitialDocument();
++
++  nsresult
++  SnapshotWindowState(ClientState* aStateOut);
++
+   // Private methods called by ClientManager
+   ClientSource(ClientManager* aManager,
+                nsISerialEventTarget* aEventTarget,
+                const ClientSourceConstructorArgs& aArgs);
+ 
+   void
+   Activate(PClientManagerChild* aActor);
+ 
+@@ -120,16 +126,22 @@ public:
+   RefPtr<ClientOpPromise>
+   Control(const ClientControlledArgs& aArgs);
+ 
+   // Get the ClientSource's current controlling service worker, if one has
+   // been set.
+   const Maybe<ServiceWorkerDescriptor>&
+   GetController() const;
+ 
++  RefPtr<ClientOpPromise>
++  GetInfoAndState(const ClientGetInfoAndStateArgs& aArgs);
++
++  nsresult
++  SnapshotState(ClientState* aStateOut);
++
+   nsISerialEventTarget*
+   EventTarget() const;
+ 
+   void
+   Traverse(nsCycleCollectionTraversalCallback& aCallback,
+            const char* aName,
+            uint32_t aFlags);
+ };
+diff --git a/dom/clients/manager/ClientSourceOpChild.cpp b/dom/clients/manager/ClientSourceOpChild.cpp
+--- a/dom/clients/manager/ClientSourceOpChild.cpp
++++ b/dom/clients/manager/ClientSourceOpChild.cpp
+@@ -74,16 +74,22 @@ void
+ ClientSourceOpChild::Init(const ClientOpConstructorArgs& aArgs)
+ {
+   switch (aArgs.type()) {
+     case ClientOpConstructorArgs::TClientControlledArgs:
+     {
+       DoSourceOp(&ClientSource::Control, aArgs.get_ClientControlledArgs());
+       break;
+     }
++    case ClientOpConstructorArgs::TClientGetInfoAndStateArgs:
++    {
++      DoSourceOp(&ClientSource::GetInfoAndState,
++                 aArgs.get_ClientGetInfoAndStateArgs());
++      break;
++    }
+     default:
+     {
+       MOZ_ASSERT_UNREACHABLE("unknown client operation!");
+       break;
+     }
+   }
+ }
+ 
+diff --git a/dom/clients/manager/ClientSourceParent.cpp b/dom/clients/manager/ClientSourceParent.cpp
+--- a/dom/clients/manager/ClientSourceParent.cpp
++++ b/dom/clients/manager/ClientSourceParent.cpp
+@@ -214,16 +214,22 @@ ClientSourceParent::Info() const
+ }
+ 
+ bool
+ ClientSourceParent::IsFrozen() const
+ {
+   return mFrozen;
+ }
+ 
++bool
++ClientSourceParent::ExecutionReady() const
++{
++  return mExecutionReady;
++}
++
+ void
+ ClientSourceParent::AttachHandle(ClientHandleParent* aClientHandle)
+ {
+   MOZ_DIAGNOSTIC_ASSERT(aClientHandle);
+   MOZ_DIAGNOSTIC_ASSERT(!mFrozen);
+   MOZ_ASSERT(!mHandleList.Contains(aClientHandle));
+   mHandleList.AppendElement(aClientHandle);
+ }
+diff --git a/dom/clients/manager/ClientSourceParent.h b/dom/clients/manager/ClientSourceParent.h
+--- a/dom/clients/manager/ClientSourceParent.h
++++ b/dom/clients/manager/ClientSourceParent.h
+@@ -62,16 +62,19 @@ public:
+   Init();
+ 
+   const ClientInfo&
+   Info() const;
+ 
+   bool
+   IsFrozen() const;
+ 
++  bool
++  ExecutionReady() const;
++
+   void
+   AttachHandle(ClientHandleParent* aClientSource);
+ 
+   void
+   DetachHandle(ClientHandleParent* aClientSource);
+ 
+   RefPtr<ClientOpPromise>
+   StartOp(const ClientOpConstructorArgs& aArgs);

+ 188 - 0
frg/work-js/mozilla-release/patches/1423913-1-59a1.patch

@@ -0,0 +1,188 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1512751926 18000
+# Node ID 79b9a988f0fd3d33be9d308b7f8aa58ccf1bf216
+# Parent  9ddb3f1c51b8184e3497f163d9bd09232969cd02
+Bug 1423913 P1 Make ClientManager::CreateSource() and CreateHandle() infallible.  Errors result in a detached object instead of nullptr.  r=baku
+
+diff --git a/dom/clients/manager/ClientManager.cpp b/dom/clients/manager/ClientManager.cpp
+--- a/dom/clients/manager/ClientManager.cpp
++++ b/dom/clients/manager/ClientManager.cpp
+@@ -98,46 +98,58 @@ ClientManager::Shutdown()
+ 
+ UniquePtr<ClientSource>
+ ClientManager::CreateSourceInternal(ClientType aType,
+                                     nsISerialEventTarget* aEventTarget,
+                                     const PrincipalInfo& aPrincipal)
+ {
+   NS_ASSERT_OWNINGTHREAD(ClientManager);
+ 
+-  if (IsShutdown()) {
+-    return nullptr;
+-  }
+-
+   nsID id;
+   nsresult rv = nsContentUtils::GenerateUUIDInPlace(id);
++  MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+   if (NS_WARN_IF(NS_FAILED(rv))) {
+-    return nullptr;
++    // If we can't even get a UUID, at least make sure not to use a garbage
++    // value.  Instead return a shutdown ClientSource with a zero'd id.
++    // This should be exceptionally rare, if it happens at all.
++    id.Clear();
++    ClientSourceConstructorArgs args(id, aType, aPrincipal, TimeStamp::Now());
++    UniquePtr<ClientSource> source(new ClientSource(this, aEventTarget, args));
++    source->Shutdown();
++    return Move(source);
+   }
+ 
+   ClientSourceConstructorArgs args(id, aType, aPrincipal, TimeStamp::Now());
+   UniquePtr<ClientSource> source(new ClientSource(this, aEventTarget, args));
++
++  if (IsShutdown()) {
++    source->Shutdown();
++    return Move(source);
++  }
++
+   source->Activate(GetActor());
+ 
+   return Move(source);
+ }
+ 
+ already_AddRefed<ClientHandle>
+ ClientManager::CreateHandleInternal(const ClientInfo& aClientInfo,
+                                     nsISerialEventTarget* aSerialEventTarget)
+ {
+   NS_ASSERT_OWNINGTHREAD(ClientManager);
+   MOZ_DIAGNOSTIC_ASSERT(aSerialEventTarget);
+ 
++  RefPtr<ClientHandle> handle = new ClientHandle(this, aSerialEventTarget,
++                                                 aClientInfo);
++
+   if (IsShutdown()) {
+-    return nullptr;
++    handle->Shutdown();
++    return handle.forget();
+   }
+ 
+-  RefPtr<ClientHandle> handle = new ClientHandle(this, aSerialEventTarget,
+-                                                 aClientInfo);
+   handle->Activate(GetActor());
+ 
+   return handle.forget();
+ }
+ 
+ already_AddRefed<ClientOpPromise>
+ ClientManager::StartOp(const ClientOpConstructorArgs& aArgs,
+                        nsISerialEventTarget* aSerialEventTarget)
+@@ -215,17 +227,17 @@ ClientManager::CreateSource(ClientType a
+                             nsIPrincipal* aPrincipal)
+ {
+   MOZ_ASSERT(NS_IsMainThread());
+   MOZ_ASSERT(aPrincipal);
+ 
+   PrincipalInfo principalInfo;
+   nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &principalInfo);
+   if (NS_WARN_IF(NS_FAILED(rv))) {
+-    return nullptr;
++    MOZ_CRASH("ClientManager::CreateSource() cannot serialize bad principal");
+   }
+ 
+   RefPtr<ClientManager> mgr = GetOrCreateForCurrentThread();
+   return mgr->CreateSourceInternal(aType, aEventTarget, principalInfo);
+ }
+ 
+ // static
+ UniquePtr<ClientSource>
+diff --git a/dom/clients/manager/ClientSource.cpp b/dom/clients/manager/ClientSource.cpp
+--- a/dom/clients/manager/ClientSource.cpp
++++ b/dom/clients/manager/ClientSource.cpp
+@@ -177,16 +177,20 @@ ClientSource::GetInnerWindow() const
+ }
+ 
+ void
+ ClientSource::WorkerExecutionReady(WorkerPrivate* aWorkerPrivate)
+ {
+   MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate);
+   aWorkerPrivate->AssertIsOnWorkerThread();
+ 
++  if (IsShutdown()) {
++    return;
++  }
++
+   // Its safe to store the WorkerPrivate* here because the ClientSource
+   // is explicitly destroyed by WorkerPrivate before exiting its run loop.
+   MOZ_DIAGNOSTIC_ASSERT(mOwner.is<Nothing>());
+   mOwner = AsVariant(aWorkerPrivate);
+ 
+   ClientSourceExecutionReadyArgs args(
+     aWorkerPrivate->GetLocationInfo().mHref,
+     FrameType::None);
+@@ -197,16 +201,20 @@ ClientSource::WorkerExecutionReady(Worke
+ nsresult
+ ClientSource::WindowExecutionReady(nsPIDOMWindowInner* aInnerWindow)
+ {
+   MOZ_ASSERT(NS_IsMainThread());
+   MOZ_DIAGNOSTIC_ASSERT(aInnerWindow);
+   MOZ_DIAGNOSTIC_ASSERT(aInnerWindow->IsCurrentInnerWindow());
+   MOZ_DIAGNOSTIC_ASSERT(aInnerWindow->HasActiveDocument());
+ 
++  if (IsShutdown()) {
++    return NS_OK;
++  }
++
+   nsIDocument* doc = aInnerWindow->GetExtantDoc();
+   if (NS_WARN_IF(!doc)) {
+     return NS_ERROR_UNEXPECTED;
+   }
+ 
+   // Don't use nsAutoCString here since IPC requires a full nsCString anyway.
+   nsCString spec;
+ 
+@@ -248,16 +256,20 @@ ClientSource::WindowExecutionReady(nsPID
+ }
+ 
+ nsresult
+ ClientSource::DocShellExecutionReady(nsIDocShell* aDocShell)
+ {
+   MOZ_ASSERT(NS_IsMainThread());
+   MOZ_DIAGNOSTIC_ASSERT(aDocShell);
+ 
++  if (IsShutdown()) {
++    return NS_OK;
++  }
++
+   nsPIDOMWindowOuter* outer = aDocShell->GetWindow();
+   if (NS_WARN_IF(!outer)) {
+     return NS_ERROR_UNEXPECTED;
+   }
+ 
+   // TODO: dedupe this with WindowExecutionReady
+   FrameType frameType = FrameType::Top_level;
+   if (!outer->IsTopLevelWindow()) {
+@@ -301,19 +313,25 @@ ClientSource::Info() const
+   return mClientInfo;
+ }
+ 
+ void
+ ClientSource::WorkerSyncPing(WorkerPrivate* aWorkerPrivate)
+ {
+   NS_ASSERT_OWNINGTHREAD(ClientSource);
+   MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate);
++
++  if (IsShutdown()) {
++    return;
++  }
++
+   MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate == mManager->GetWorkerPrivate());
+   aWorkerPrivate->AssertIsOnWorkerThread();
+   MOZ_DIAGNOSTIC_ASSERT(GetActor());
++
+   GetActor()->SendWorkerSyncPing();
+ }
+ 
+ void
+ ClientSource::SetController(const ServiceWorkerDescriptor& aServiceWorker)
+ {
+   NS_ASSERT_OWNINGTHREAD(ClientSource);
+ 

+ 185 - 0
frg/work-js/mozilla-release/patches/1423913-2-59a1.patch

@@ -0,0 +1,185 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1512751927 18000
+# Node ID f6b07d14b67bfaeafe0d23a5a743a1465957d831
+# Parent  cbbb560087b9e1a83cfba180ed5bf34a5b6f0af8
+Bug 1423913 P2 Make callers expect infallble CreateSource() and CreateHandle(). r=baku
+
+diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp
+--- a/docshell/base/nsDocShell.cpp
++++ b/docshell/base/nsDocShell.cpp
+@@ -3421,19 +3421,17 @@ nsDocShell::MaybeCreateInitialClientSour
+   if (!win) {
+     return;
+   }
+ 
+   mInitialClientSource =
+     ClientManager::CreateSource(ClientType::Window,
+                                 win->EventTargetFor(TaskCategory::Other),
+                                 principal);
+-  if (NS_WARN_IF(!mInitialClientSource)) {
+-    return;
+-  }
++  MOZ_DIAGNOSTIC_ASSERT(mInitialClientSource);
+ 
+   // Mark the initial client as execution ready, but owned by the docshell.
+   // If the client is actually used this will cause ClientSource to force
+   // the creation of the initial about:blank by calling nsDocShell::GetDocument().
+   mInitialClientSource->DocShellExecutionReady(this);
+ 
+   // Next, check to see if the parent is controlled.
+   nsCOMPtr<nsIDocShell> parent = GetParentDocshell();
+diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp
+--- a/dom/base/nsGlobalWindow.cpp
++++ b/dom/base/nsGlobalWindow.cpp
+@@ -3497,19 +3497,17 @@ nsGlobalWindow::EnsureClientSource()
+   //       principal.  We do this in docshell, but as mentioned we aren't
+   //       smart enough to handle all cases yet.  For example, a
+   //       window.open() with new URL should inherit the controller from
+   //       the opener, but we probably don't handle that yet.
+   if (!mClientSource) {
+     mClientSource = ClientManager::CreateSource(ClientType::Window,
+                                                 EventTargetFor(TaskCategory::Other),
+                                                 mDoc->NodePrincipal());
+-    if (NS_WARN_IF(!mClientSource)) {
+-      return NS_ERROR_FAILURE;
+-    }
++    MOZ_DIAGNOSTIC_ASSERT(mClientSource);
+     newClientSource = true;
+   }
+ 
+   // The load may have started controlling the Client as well.  If
+   // so, mark it as controlled immediately here.  The actor may
+   // or may not have been notified by the parent side about being
+   // controlled yet.
+   if (loadInfo) {
+@@ -3529,19 +3527,17 @@ nsGlobalWindow::EnsureClientSource()
+     //  https://github.com/w3c/ServiceWorker/issues/1232
+     //
+     else if (mClientSource->GetController().isSome()) {
+       mClientSource.reset();
+       mClientSource =
+         ClientManager::CreateSource(ClientType::Window,
+                                     EventTargetFor(TaskCategory::Other),
+                                     mDoc->NodePrincipal());
+-      if (NS_WARN_IF(!mClientSource)) {
+-        return NS_ERROR_FAILURE;
+-      }
++      MOZ_DIAGNOSTIC_ASSERT(mClientSource);
+       newClientSource = true;
+     }
+   }
+ 
+   // Its possible that we got a client just after being frozen in
+   // the bfcache.  In that case freeze the client immediately.
+   if (newClientSource && IsFrozen()) {
+     mClientSource->Freeze();
+diff --git a/dom/clients/manager/ClientChannelHelper.cpp b/dom/clients/manager/ClientChannelHelper.cpp
+--- a/dom/clients/manager/ClientChannelHelper.cpp
++++ b/dom/clients/manager/ClientChannelHelper.cpp
+@@ -115,16 +115,17 @@ class ClientChannelHelper final : public
+       NS_ENSURE_SUCCESS(rv, rv);
+ 
+       // Create the new ClientSource.  This should only happen for window
+       // Clients since support cross-origin redirects are blocked by the
+       // same-origin security policy.
+       reservedClient.reset();
+       reservedClient = ClientManager::CreateSource(ClientType::Window,
+                                                    mEventTarget, principal);
++      MOZ_DIAGNOSTIC_ASSERT(reservedClient);
+ 
+       newLoadInfo->GiveReservedClientSource(Move(reservedClient));
+     }
+ 
+     // Normally we keep the controller across channel redirects, but we must
+     // clear it when a non-subresource load redirects.  Only do this for real
+     // redirects, however.
+     //
+@@ -223,17 +224,17 @@ AddClientChannelHelper(nsIChannel* aChan
+   if (initialClientInfo.isNothing() && reservedClientInfo.isNothing()) {
+     // Wait to reserve the client until we are reasonably sure this method
+     // will succeed.  We should only follow this path for window clients.
+     // Workers should always provide a reserved ClientInfo since their
+     // ClientSource object is owned by a different thread.
+     reservedClient = ClientManager::CreateSource(ClientType::Window,
+                                                  aEventTarget,
+                                                  channelPrincipal);
+-    NS_ENSURE_TRUE(reservedClient, NS_ERROR_FAILURE);
++    MOZ_DIAGNOSTIC_ASSERT(reservedClient);
+   }
+ 
+   RefPtr<ClientChannelHelper> helper =
+     new ClientChannelHelper(outerCallbacks, aEventTarget);
+ 
+   // Only set the callbacks helper if we are able to reserve the client
+   // successfully.
+   rv = aChannel->SetNotificationCallbacks(helper);
+diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp
+--- a/dom/workers/ServiceWorkerManager.cpp
++++ b/dom/workers/ServiceWorkerManager.cpp
+@@ -2362,19 +2362,17 @@ ServiceWorkerManager::StartControllingAD
+   ServiceWorkerInfo* activeWorker = aRegistration->GetActive();
+   nsPIDOMWindowInner* innerWindow = aDoc->GetInnerWindow();
+   if (activeWorker && innerWindow) {
+     Maybe<ClientInfo> clientInfo = innerWindow->GetClientInfo();
+     if (clientInfo.isSome()) {
+       RefPtr<ClientHandle> clientHandle =
+         ClientManager::CreateHandle(clientInfo.ref(),
+                                     SystemGroup::EventTargetFor(TaskCategory::Other));
+-      if (clientHandle) {
+-        ref = Move(clientHandle->Control(activeWorker->Descriptor()));
+-      }
++      ref = Move(clientHandle->Control(activeWorker->Descriptor()));
+     }
+   }
+ 
+   Telemetry::Accumulate(Telemetry::SERVICE_WORKER_CONTROLLED_DOCUMENTS, 1);
+   return Move(ref);
+ }
+ 
+ void
+@@ -2710,19 +2708,17 @@ ServiceWorkerManager::DispatchFetchEvent
+ 
+       if (clientInfo.isSome()) {
+         // First, attempt to mark the reserved client controlled directly.  This
+         // will update the controlled status in the ClientManagerService in the
+         // parent.  It will also eventually propagate back to the ClientSource.
+         RefPtr<ClientHandle> clientHandle =
+           ClientManager::CreateHandle(clientInfo.ref(),
+                                       SystemGroup::EventTargetFor(TaskCategory::Other));
+-        if (clientHandle) {
+-          clientHandle->Control(serviceWorker->Descriptor());
+-        }
++        clientHandle->Control(serviceWorker->Descriptor());
+       }
+ 
+       // But we also note the reserved state on the LoadInfo.  This allows the
+       // ClientSource to be updated immediately after the nsIChannel starts.
+       // This is necessary to have the correct controller in place for immediate
+       // follow-on requests.
+       loadInfo->SetController(serviceWorker->Descriptor());
+     }
+diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp
+--- a/dom/workers/WorkerPrivate.cpp
++++ b/dom/workers/WorkerPrivate.cpp
+@@ -5322,19 +5322,17 @@ WorkerPrivate::EnsureClientSource()
+       type = ClientType::Serviceworker;
+       break;
+     default:
+       MOZ_CRASH("unknown worker type!");
+   }
+ 
+   mClientSource = ClientManager::CreateSource(type, mWorkerHybridEventTarget,
+                                               GetPrincipalInfo());
+-  if (!mClientSource) {
+-    return false;
+-  }
++  MOZ_DIAGNOSTIC_ASSERT(mClientSource);
+ 
+   if (mFrozen) {
+     mClientSource->Freeze();
+   }
+ 
+   // Shortly after the client is reserved we will try loading the main script
+   // for the worker.  This may get intercepted by the ServiceWorkerManager
+   // which will then try to create a ClientHandle.  Its actually possible for

+ 98 - 0
frg/work-js/mozilla-release/patches/1423913-3-59a1.patch

@@ -0,0 +1,98 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1512751927 18000
+# Node ID c71de7f98d08b1772f7dc54d56828c34f86ef16e
+# Parent  89619781ee4b54e7694e003dd9fae8c959313df2
+Bug 1423913 P3 Don't register more shutdown handle if we create more than one ClientManagerService instance. r=baku
+
+diff --git a/dom/clients/manager/ClientManagerService.cpp b/dom/clients/manager/ClientManagerService.cpp
+--- a/dom/clients/manager/ClientManagerService.cpp
++++ b/dom/clients/manager/ClientManagerService.cpp
+@@ -19,16 +19,17 @@ namespace dom {
+ 
+ using mozilla::ipc::AssertIsOnBackgroundThread;
+ using mozilla::ipc::ContentPrincipalInfo;
+ using mozilla::ipc::PrincipalInfo;
+ 
+ namespace {
+ 
+ ClientManagerService* sClientManagerServiceInstance = nullptr;
++bool sClientManagerServiceShutdownRegistered = false;
+ 
+ bool
+ MatchPrincipalInfo(const PrincipalInfo& aLeft, const PrincipalInfo& aRight)
+ {
+   if (aLeft.type() != aRight.type()) {
+     return false;
+   }
+ 
+@@ -141,45 +142,56 @@ OnShutdown()
+ 
+ } // anonymous namespace
+ 
+ ClientManagerService::ClientManagerService()
+   : mShutdown(false)
+ {
+   AssertIsOnBackgroundThread();
+ 
+-  // While the ClientManagerService will be gracefully terminated as windows
+-  // and workers are naturally killed, this can cause us to do extra work
+-  // relatively late in the shutdown process.  To avoid this we eagerly begin
+-  // shutdown at the first sign it has begun.  Since we handle normal shutdown
+-  // gracefully we don't really need to block anything here.  We just begin
+-  // destroying our IPC actors immediately.
+-  OnShutdown()->Then(GetCurrentThreadSerialEventTarget(), __func__,
+-    [] () {
+-      RefPtr<ClientManagerService> svc = ClientManagerService::GetInstance();
+-      if (svc) {
+-        svc->Shutdown();
+-      }
+-    });
++  // Only register one shutdown handler at a time.  If a previous service
++  // instance did this, but shutdown has not come, then we can avoid
++  // doing it again.
++  if (!sClientManagerServiceShutdownRegistered) {
++    sClientManagerServiceShutdownRegistered = true;
++
++    // While the ClientManagerService will be gracefully terminated as windows
++    // and workers are naturally killed, this can cause us to do extra work
++    // relatively late in the shutdown process.  To avoid this we eagerly begin
++    // shutdown at the first sign it has begun.  Since we handle normal shutdown
++    // gracefully we don't really need to block anything here.  We just begin
++    // destroying our IPC actors immediately.
++    OnShutdown()->Then(GetCurrentThreadSerialEventTarget(), __func__,
++      [] () {
++        // Look up the latest service instance, if it exists.  This may
++        // be different from the instance that registered the shutdown
++        // handler.
++        RefPtr<ClientManagerService> svc = ClientManagerService::GetInstance();
++        if (svc) {
++          svc->Shutdown();
++        }
++      });
++  }
+ }
+ 
+ ClientManagerService::~ClientManagerService()
+ {
+   AssertIsOnBackgroundThread();
+   MOZ_DIAGNOSTIC_ASSERT(mSourceTable.Count() == 0);
+   MOZ_DIAGNOSTIC_ASSERT(mManagerList.IsEmpty());
+ 
+   MOZ_DIAGNOSTIC_ASSERT(sClientManagerServiceInstance == this);
+   sClientManagerServiceInstance = nullptr;
+ }
+ 
+ void
+ ClientManagerService::Shutdown()
+ {
+   AssertIsOnBackgroundThread();
++  MOZ_DIAGNOSTIC_ASSERT(sClientManagerServiceShutdownRegistered);
+ 
+   // If many ClientManagerService are created and destroyed quickly we can
+   // in theory get more than one shutdown listener calling us.
+   if (mShutdown) {
+     return;
+   }
+   mShutdown = true;
+ 

+ 400 - 0
frg/work-js/mozilla-release/patches/1424338-1-59a1.patch

@@ -0,0 +1,400 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1512762402 18000
+# Node ID 74a13c7c99270e99af3b4da27a887979d5d710ee
+# Parent  3a4d695eeb5d1f139dc5c2a4d5028831fda126fb
+Bug 1424338 P1 Implement ClientManager::MatchAll() to support clients.matchAll() WebAPI. r=baku
+
+diff --git a/dom/clients/manager/ClientIPCTypes.ipdlh b/dom/clients/manager/ClientIPCTypes.ipdlh
+--- a/dom/clients/manager/ClientIPCTypes.ipdlh
++++ b/dom/clients/manager/ClientIPCTypes.ipdlh
+@@ -62,36 +62,56 @@ struct ClientSourceExecutionReadyArgs
+   FrameType frameType;
+ };
+ 
+ struct ClientControlledArgs
+ {
+   IPCServiceWorkerDescriptor serviceWorker;
+ };
+ 
++union ClientEndPoint
++{
++  IPCClientInfo;
++  IPCServiceWorkerDescriptor;
++};
++
++struct ClientMatchAllArgs
++{
++  ClientEndPoint endpoint;
++  ClientType type;
++  bool includeUncontrolled;
++};
++
+ struct ClientGetInfoAndStateArgs
+ {
+   nsID id;
+   PrincipalInfo principalInfo;
+ };
+ 
+ struct ClientOpenWindowArgs
+ {
+ };
+ 
+ union ClientOpConstructorArgs
+ {
+   ClientControlledArgs;
++  ClientMatchAllArgs;
+   ClientGetInfoAndStateArgs;
+ };
+ 
++struct ClientList
++{
++  ClientInfoAndState[] values;
++};
++
+ struct ClientNavigateOpConstructorArgs
+ {
+ };
+ 
+ union ClientOpResult
+ {
+   nsresult;
+   ClientInfoAndState;
++  ClientList;
+ };
+ 
+ } // namespace dom
+ } // namespace mozilla
+diff --git a/dom/clients/manager/ClientManager.cpp b/dom/clients/manager/ClientManager.cpp
+--- a/dom/clients/manager/ClientManager.cpp
++++ b/dom/clients/manager/ClientManager.cpp
+@@ -254,16 +254,25 @@ ClientManager::CreateHandle(const Client
+                             nsISerialEventTarget* aSerialEventTarget)
+ {
+   RefPtr<ClientManager> mgr = GetOrCreateForCurrentThread();
+   return mgr->CreateHandleInternal(aClientInfo, aSerialEventTarget);
+ }
+ 
+ // static
+ RefPtr<ClientOpPromise>
++ClientManager::MatchAll(const ClientMatchAllArgs& aArgs,
++                        nsISerialEventTarget* aSerialEventTarget)
++{
++  RefPtr<ClientManager> mgr = GetOrCreateForCurrentThread();
++  return mgr->StartOp(aArgs, aSerialEventTarget);
++}
++
++// static
++RefPtr<ClientOpPromise>
+ ClientManager::GetInfoAndState(const ClientGetInfoAndStateArgs& aArgs,
+                                nsISerialEventTarget* aSerialEventTarget)
+ {
+   RefPtr<ClientManager> mgr = GetOrCreateForCurrentThread();
+   return mgr->StartOp(aArgs, aSerialEventTarget);
+ }
+ 
+ } // namespace dom
+diff --git a/dom/clients/manager/ClientManager.h b/dom/clients/manager/ClientManager.h
+--- a/dom/clients/manager/ClientManager.h
++++ b/dom/clients/manager/ClientManager.h
+@@ -17,16 +17,17 @@ class PBackgroundChild;
+ class PrincipalInfo;
+ } // namespace ipc
+ namespace dom {
+ 
+ class ClientGetInfoAndStateArgs;
+ class ClientHandle;
+ class ClientInfo;
+ class ClientManagerChild;
++class ClientMatchAllArgs;
+ class ClientOpConstructorArgs;
+ class ClientSource;
+ enum class ClientType : uint8_t;
+ 
+ namespace workers {
+ class WorkerPrivate;
+ } // workers namespace
+ 
+@@ -90,16 +91,19 @@ public:
+   CreateSource(ClientType aType, nsISerialEventTarget* aEventTarget,
+                const mozilla::ipc::PrincipalInfo& aPrincipal);
+ 
+   static already_AddRefed<ClientHandle>
+   CreateHandle(const ClientInfo& aClientInfo,
+                nsISerialEventTarget* aSerialEventTarget);
+ 
+   static RefPtr<ClientOpPromise>
++  MatchAll(const ClientMatchAllArgs& aArgs, nsISerialEventTarget* aTarget);
++
++  static RefPtr<ClientOpPromise>
+   GetInfoAndState(const ClientGetInfoAndStateArgs& aArgs,
+                   nsISerialEventTarget* aSerialEventTarget);
+ 
+   NS_INLINE_DECL_REFCOUNTING(mozilla::dom::ClientManager)
+ };
+ 
+ } // namespace dom
+ } // namespace mozilla
+diff --git a/dom/clients/manager/ClientManagerOpParent.cpp b/dom/clients/manager/ClientManagerOpParent.cpp
+--- a/dom/clients/manager/ClientManagerOpParent.cpp
++++ b/dom/clients/manager/ClientManagerOpParent.cpp
+@@ -43,16 +43,22 @@ ClientManagerOpParent::ClientManagerOpPa
+ {
+   MOZ_DIAGNOSTIC_ASSERT(mService);
+ }
+ 
+ void
+ ClientManagerOpParent::Init(const ClientOpConstructorArgs& aArgs)
+ {
+   switch (aArgs.type()) {
++    case ClientOpConstructorArgs::TClientMatchAllArgs:
++    {
++      DoServiceOp(&ClientManagerService::MatchAll,
++                  aArgs.get_ClientMatchAllArgs());
++      break;
++    }
+     case ClientOpConstructorArgs::TClientGetInfoAndStateArgs:
+     {
+       DoServiceOp(&ClientManagerService::GetInfoAndState,
+                   aArgs.get_ClientGetInfoAndStateArgs());
+       break;
+     }
+     default:
+     {
+diff --git a/dom/clients/manager/ClientManagerService.cpp b/dom/clients/manager/ClientManagerService.cpp
+--- a/dom/clients/manager/ClientManagerService.cpp
++++ b/dom/clients/manager/ClientManagerService.cpp
+@@ -297,16 +297,161 @@ void
+ ClientManagerService::RemoveManager(ClientManagerParent* aManager)
+ {
+   AssertIsOnBackgroundThread();
+   MOZ_DIAGNOSTIC_ASSERT(aManager);
+   DebugOnly<bool> removed = mManagerList.RemoveElement(aManager);
+   MOZ_ASSERT(removed);
+ }
+ 
++namespace
++{
++
++class PromiseListHolder final
++{
++  RefPtr<ClientOpPromise::Private> mResultPromise;
++  nsTArray<RefPtr<ClientOpPromise>> mPromiseList;
++  nsTArray<ClientInfoAndState> mResultList;
++  uint32_t mOutstandingPromiseCount;
++
++  void
++  ProcessSuccess(const ClientInfoAndState& aResult)
++  {
++    mResultList.AppendElement(aResult);
++    ProcessCompletion();
++  }
++
++  void
++  ProcessCompletion()
++  {
++    MOZ_DIAGNOSTIC_ASSERT(mOutstandingPromiseCount > 0);
++    mOutstandingPromiseCount -= 1;
++    MaybeFinish();
++  }
++
++  ~PromiseListHolder() = default;
++public:
++  PromiseListHolder()
++    : mResultPromise(new ClientOpPromise::Private(__func__))
++    , mOutstandingPromiseCount(0)
++  {
++  }
++
++  already_AddRefed<ClientOpPromise>
++  GetResultPromise()
++  {
++    RefPtr<PromiseListHolder> kungFuDeathGrip = this;
++    mResultPromise->Then(
++      GetCurrentThreadSerialEventTarget(), __func__,
++      [kungFuDeathGrip] (const ClientOpResult& aResult) { },
++      [kungFuDeathGrip] (nsresult aResult) { });
++
++    RefPtr<ClientOpPromise> ref = mResultPromise;
++    return ref.forget();
++  }
++
++  void
++  AddPromise(RefPtr<ClientOpPromise>&& aPromise)
++  {
++    mPromiseList.AppendElement(Move(aPromise));
++    MOZ_DIAGNOSTIC_ASSERT(mPromiseList.LastElement());
++    mOutstandingPromiseCount += 1;
++
++    RefPtr<PromiseListHolder> self(this);
++    mPromiseList.LastElement()->Then(
++      GetCurrentThreadSerialEventTarget(), __func__,
++      [self] (const ClientOpResult& aResult) {
++        // TODO: This is pretty clunky.  Try to figure out a better
++        //       wait for MatchAll() and Claim() to share this code
++        //       even though they expect different return values.
++        if (aResult.type() == ClientOpResult::TClientInfoAndState) {
++          self->ProcessSuccess(aResult.get_ClientInfoAndState());
++        } else {
++          self->ProcessCompletion();
++        }
++      }, [self] (nsresult aResult) {
++        self->ProcessCompletion();
++      });
++  }
++
++  void
++  MaybeFinish()
++  {
++    if (!mOutstandingPromiseCount) {
++      mResultPromise->Resolve(mResultList, __func__);
++    }
++  }
++
++  NS_INLINE_DECL_REFCOUNTING(PromiseListHolder)
++};
++
++} // anonymous namespace
++
++RefPtr<ClientOpPromise>
++ClientManagerService::MatchAll(const ClientMatchAllArgs& aArgs)
++{
++  AssertIsOnBackgroundThread();
++
++  const ClientEndPoint& endpoint = aArgs.endpoint();
++
++  const PrincipalInfo& principalInfo =
++    endpoint.type() == ClientEndPoint::TIPCClientInfo
++      ? endpoint.get_IPCClientInfo().principalInfo()
++      : endpoint.get_IPCServiceWorkerDescriptor().principalInfo();
++
++  RefPtr<PromiseListHolder> promiseList = new PromiseListHolder();
++
++  for (auto iter = mSourceTable.Iter(); !iter.Done(); iter.Next()) {
++    ClientSourceParent* source = iter.UserData();
++    MOZ_DIAGNOSTIC_ASSERT(source);
++
++    if (source->IsFrozen() || !source->ExecutionReady()) {
++      continue;
++    }
++
++    if (aArgs.type() != ClientType::All &&
++        source->Info().Type() != aArgs.type()) {
++      continue;
++    }
++
++    if (!MatchPrincipalInfo(source->Info().PrincipalInfo(), principalInfo)) {
++      continue;
++    }
++
++    if (!aArgs.includeUncontrolled()) {
++      if (endpoint.type() != ClientEndPoint::TIPCServiceWorkerDescriptor) {
++        continue;
++      }
++
++      const Maybe<ServiceWorkerDescriptor>& controller =
++        source->GetController();
++      if (controller.isNothing()) {
++        continue;
++      }
++
++      const IPCServiceWorkerDescriptor& serviceWorker =
++        endpoint.get_IPCServiceWorkerDescriptor();
++
++      if(controller.ref().Id() != serviceWorker.id() ||
++         controller.ref().Scope() != serviceWorker.scope()) {
++        continue;
++      }
++    }
++
++    promiseList->AddPromise(
++      source->StartOp(Move(ClientGetInfoAndStateArgs(source->Info().Id(),
++                                                     source->Info().PrincipalInfo()))));
++  }
++
++  // Maybe finish the promise now in case we didn't find any matching clients.
++  promiseList->MaybeFinish();
++
++  return promiseList->GetResultPromise();
++}
++
+ RefPtr<ClientOpPromise>
+ ClientManagerService::GetInfoAndState(const ClientGetInfoAndStateArgs& aArgs)
+ {
+   RefPtr<ClientOpPromise> ref;
+ 
+   ClientSourceParent* source = FindSource(aArgs.id(), aArgs.principalInfo());
+   if (!source || !source->ExecutionReady()) {
+     ref = ClientOpPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+diff --git a/dom/clients/manager/ClientManagerService.h b/dom/clients/manager/ClientManagerService.h
+--- a/dom/clients/manager/ClientManagerService.h
++++ b/dom/clients/manager/ClientManagerService.h
+@@ -55,16 +55,19 @@ public:
+ 
+   void
+   AddManager(ClientManagerParent* aManager);
+ 
+   void
+   RemoveManager(ClientManagerParent* aManager);
+ 
+   RefPtr<ClientOpPromise>
++  MatchAll(const ClientMatchAllArgs& aArgs);
++
++  RefPtr<ClientOpPromise>
+   GetInfoAndState(const ClientGetInfoAndStateArgs& aArgs);
+ 
+   NS_INLINE_DECL_REFCOUNTING(mozilla::dom::ClientManagerService)
+ };
+ 
+ } // namespace dom
+ } // namespace mozilla
+ 
+diff --git a/dom/clients/manager/ClientSourceParent.cpp b/dom/clients/manager/ClientSourceParent.cpp
+--- a/dom/clients/manager/ClientSourceParent.cpp
++++ b/dom/clients/manager/ClientSourceParent.cpp
+@@ -220,16 +220,22 @@ ClientSourceParent::IsFrozen() const
+ }
+ 
+ bool
+ ClientSourceParent::ExecutionReady() const
+ {
+   return mExecutionReady;
+ }
+ 
++const Maybe<ServiceWorkerDescriptor>&
++ClientSourceParent::GetController() const
++{
++  return mController;
++}
++
+ void
+ ClientSourceParent::AttachHandle(ClientHandleParent* aClientHandle)
+ {
+   MOZ_DIAGNOSTIC_ASSERT(aClientHandle);
+   MOZ_DIAGNOSTIC_ASSERT(!mFrozen);
+   MOZ_ASSERT(!mHandleList.Contains(aClientHandle));
+   mHandleList.AppendElement(aClientHandle);
+ }
+diff --git a/dom/clients/manager/ClientSourceParent.h b/dom/clients/manager/ClientSourceParent.h
+--- a/dom/clients/manager/ClientSourceParent.h
++++ b/dom/clients/manager/ClientSourceParent.h
+@@ -65,16 +65,19 @@ public:
+   Info() const;
+ 
+   bool
+   IsFrozen() const;
+ 
+   bool
+   ExecutionReady() const;
+ 
++  const Maybe<ServiceWorkerDescriptor>&
++  GetController() const;
++
+   void
+   AttachHandle(ClientHandleParent* aClientSource);
+ 
+   void
+   DetachHandle(ClientHandleParent* aClientSource);
+ 
+   RefPtr<ClientOpPromise>
+   StartOp(const ClientOpConstructorArgs& aArgs);

+ 330 - 0
frg/work-js/mozilla-release/patches/1424338-2-59a1.patch

@@ -0,0 +1,330 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1512762402 18000
+# Node ID b73ceb5d7db6111e9de30c84010030c624bcbd31
+# Parent  d8ece6bd6c5620097a6201e823a2833bd2afde8d
+Bug 1424338 P2 Add ClientManager::Claim() message to matching ClientSource objects. r=baku
+
+diff --git a/dom/clients/manager/ClientIPCTypes.ipdlh b/dom/clients/manager/ClientIPCTypes.ipdlh
+--- a/dom/clients/manager/ClientIPCTypes.ipdlh
++++ b/dom/clients/manager/ClientIPCTypes.ipdlh
+@@ -75,30 +75,36 @@ union ClientEndPoint
+ 
+ struct ClientMatchAllArgs
+ {
+   ClientEndPoint endpoint;
+   ClientType type;
+   bool includeUncontrolled;
+ };
+ 
++struct ClientClaimArgs
++{
++  IPCServiceWorkerDescriptor serviceWorker;
++};
++
+ struct ClientGetInfoAndStateArgs
+ {
+   nsID id;
+   PrincipalInfo principalInfo;
+ };
+ 
+ struct ClientOpenWindowArgs
+ {
+ };
+ 
+ union ClientOpConstructorArgs
+ {
+   ClientControlledArgs;
+   ClientMatchAllArgs;
++  ClientClaimArgs;
+   ClientGetInfoAndStateArgs;
+ };
+ 
+ struct ClientList
+ {
+   ClientInfoAndState[] values;
+ };
+ 
+diff --git a/dom/clients/manager/ClientManager.cpp b/dom/clients/manager/ClientManager.cpp
+--- a/dom/clients/manager/ClientManager.cpp
++++ b/dom/clients/manager/ClientManager.cpp
+@@ -263,16 +263,25 @@ ClientManager::MatchAll(const ClientMatc
+                         nsISerialEventTarget* aSerialEventTarget)
+ {
+   RefPtr<ClientManager> mgr = GetOrCreateForCurrentThread();
+   return mgr->StartOp(aArgs, aSerialEventTarget);
+ }
+ 
+ // static
+ RefPtr<ClientOpPromise>
++ClientManager::Claim(const ClientClaimArgs& aArgs,
++                     nsISerialEventTarget* aSerialEventTarget)
++{
++  RefPtr<ClientManager> mgr = GetOrCreateForCurrentThread();
++  return mgr->StartOp(aArgs, aSerialEventTarget);
++}
++
++// static
++RefPtr<ClientOpPromise>
+ ClientManager::GetInfoAndState(const ClientGetInfoAndStateArgs& aArgs,
+                                nsISerialEventTarget* aSerialEventTarget)
+ {
+   RefPtr<ClientManager> mgr = GetOrCreateForCurrentThread();
+   return mgr->StartOp(aArgs, aSerialEventTarget);
+ }
+ 
+ } // namespace dom
+diff --git a/dom/clients/manager/ClientManager.h b/dom/clients/manager/ClientManager.h
+--- a/dom/clients/manager/ClientManager.h
++++ b/dom/clients/manager/ClientManager.h
+@@ -13,16 +13,17 @@ class nsIPrincipal;
+ 
+ namespace mozilla {
+ namespace ipc {
+ class PBackgroundChild;
+ class PrincipalInfo;
+ } // namespace ipc
+ namespace dom {
+ 
++class ClientClaimArgs;
+ class ClientGetInfoAndStateArgs;
+ class ClientHandle;
+ class ClientInfo;
+ class ClientManagerChild;
+ class ClientMatchAllArgs;
+ class ClientOpConstructorArgs;
+ class ClientSource;
+ enum class ClientType : uint8_t;
+@@ -94,16 +95,19 @@ public:
+   static already_AddRefed<ClientHandle>
+   CreateHandle(const ClientInfo& aClientInfo,
+                nsISerialEventTarget* aSerialEventTarget);
+ 
+   static RefPtr<ClientOpPromise>
+   MatchAll(const ClientMatchAllArgs& aArgs, nsISerialEventTarget* aTarget);
+ 
+   static RefPtr<ClientOpPromise>
++  Claim(const ClientClaimArgs& aArgs, nsISerialEventTarget* aSerialEventTarget);
++
++  static RefPtr<ClientOpPromise>
+   GetInfoAndState(const ClientGetInfoAndStateArgs& aArgs,
+                   nsISerialEventTarget* aSerialEventTarget);
+ 
+   NS_INLINE_DECL_REFCOUNTING(mozilla::dom::ClientManager)
+ };
+ 
+ } // namespace dom
+ } // namespace mozilla
+diff --git a/dom/clients/manager/ClientManagerOpParent.cpp b/dom/clients/manager/ClientManagerOpParent.cpp
+--- a/dom/clients/manager/ClientManagerOpParent.cpp
++++ b/dom/clients/manager/ClientManagerOpParent.cpp
+@@ -49,16 +49,21 @@ ClientManagerOpParent::Init(const Client
+ {
+   switch (aArgs.type()) {
+     case ClientOpConstructorArgs::TClientMatchAllArgs:
+     {
+       DoServiceOp(&ClientManagerService::MatchAll,
+                   aArgs.get_ClientMatchAllArgs());
+       break;
+     }
++    case ClientOpConstructorArgs::TClientClaimArgs:
++    {
++      DoServiceOp(&ClientManagerService::Claim, aArgs.get_ClientClaimArgs());
++      break;
++    }
+     case ClientOpConstructorArgs::TClientGetInfoAndStateArgs:
+     {
+       DoServiceOp(&ClientManagerService::GetInfoAndState,
+                   aArgs.get_ClientGetInfoAndStateArgs());
+       break;
+     }
+     default:
+     {
+diff --git a/dom/clients/manager/ClientManagerService.cpp b/dom/clients/manager/ClientManagerService.cpp
+--- a/dom/clients/manager/ClientManagerService.cpp
++++ b/dom/clients/manager/ClientManagerService.cpp
+@@ -443,16 +443,64 @@ ClientManagerService::MatchAll(const Cli
+ 
+   // Maybe finish the promise now in case we didn't find any matching clients.
+   promiseList->MaybeFinish();
+ 
+   return promiseList->GetResultPromise();
+ }
+ 
+ RefPtr<ClientOpPromise>
++ClientManagerService::Claim(const ClientClaimArgs& aArgs)
++{
++  AssertIsOnBackgroundThread();
++
++  const IPCServiceWorkerDescriptor& serviceWorker = aArgs.serviceWorker();
++  const PrincipalInfo& principalInfo = serviceWorker.principalInfo();
++
++  RefPtr<PromiseListHolder> promiseList = new PromiseListHolder();
++
++  for (auto iter = mSourceTable.Iter(); !iter.Done(); iter.Next()) {
++    ClientSourceParent* source = iter.UserData();
++    MOZ_DIAGNOSTIC_ASSERT(source);
++
++    if (source->IsFrozen()) {
++      continue;
++    }
++
++    if (!MatchPrincipalInfo(source->Info().PrincipalInfo(), principalInfo)) {
++      continue;
++    }
++
++    const Maybe<ServiceWorkerDescriptor>& controller = source->GetController();
++    if (controller.isSome() &&
++        controller.ref().Scope() == serviceWorker.scope() &&
++        controller.ref().Id() == serviceWorker.id()) {
++      continue;
++    }
++
++    // TODO: This logic to determine if a service worker should control
++    //       a particular client should be moved to the ServiceWorkerManager.
++    //       This can't happen until the SWM is moved to the parent process,
++    //       though.
++    if (!source->ExecutionReady() ||
++        source->Info().Type() == ClientType::Serviceworker ||
++        source->Info().URL().Find(serviceWorker.scope()) != 0) {
++      continue;
++    }
++
++    promiseList->AddPromise(source->StartOp(aArgs));
++  }
++
++  // Maybe finish the promise now in case we didn't find any matching clients.
++  promiseList->MaybeFinish();
++
++  return promiseList->GetResultPromise();
++}
++
++RefPtr<ClientOpPromise>
+ ClientManagerService::GetInfoAndState(const ClientGetInfoAndStateArgs& aArgs)
+ {
+   RefPtr<ClientOpPromise> ref;
+ 
+   ClientSourceParent* source = FindSource(aArgs.id(), aArgs.principalInfo());
+   if (!source || !source->ExecutionReady()) {
+     ref = ClientOpPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+     return ref.forget();
+diff --git a/dom/clients/manager/ClientManagerService.h b/dom/clients/manager/ClientManagerService.h
+--- a/dom/clients/manager/ClientManagerService.h
++++ b/dom/clients/manager/ClientManagerService.h
+@@ -58,16 +58,19 @@ public:
+ 
+   void
+   RemoveManager(ClientManagerParent* aManager);
+ 
+   RefPtr<ClientOpPromise>
+   MatchAll(const ClientMatchAllArgs& aArgs);
+ 
+   RefPtr<ClientOpPromise>
++  Claim(const ClientClaimArgs& aArgs);
++
++  RefPtr<ClientOpPromise>
+   GetInfoAndState(const ClientGetInfoAndStateArgs& aArgs);
+ 
+   NS_INLINE_DECL_REFCOUNTING(mozilla::dom::ClientManagerService)
+ };
+ 
+ } // namespace dom
+ } // namespace mozilla
+ 
+diff --git a/dom/clients/manager/ClientSource.cpp b/dom/clients/manager/ClientSource.cpp
+--- a/dom/clients/manager/ClientSource.cpp
++++ b/dom/clients/manager/ClientSource.cpp
+@@ -357,16 +357,27 @@ ClientSource::Control(const ClientContro
+ 
+ const Maybe<ServiceWorkerDescriptor>&
+ ClientSource::GetController() const
+ {
+   return mController;
+ }
+ 
+ RefPtr<ClientOpPromise>
++ClientSource::Claim(const ClientClaimArgs& aArgs)
++{
++  SetController(ServiceWorkerDescriptor(aArgs.serviceWorker()));
++
++  RefPtr<ClientOpPromise> ref =
++    ClientOpPromise::CreateAndResolve(NS_OK, __func__);
++
++  return ref.forget();
++}
++
++RefPtr<ClientOpPromise>
+ ClientSource::GetInfoAndState(const ClientGetInfoAndStateArgs& aArgs)
+ {
+   RefPtr<ClientOpPromise> ref;
+ 
+   ClientState state;
+   nsresult rv = SnapshotState(&state);
+   if (NS_FAILED(rv)) {
+     ref = ClientOpPromise::CreateAndReject(rv, __func__);
+diff --git a/dom/clients/manager/ClientSource.h b/dom/clients/manager/ClientSource.h
+--- a/dom/clients/manager/ClientSource.h
++++ b/dom/clients/manager/ClientSource.h
+@@ -14,16 +14,17 @@
+ 
+ class nsIDocShell;
+ class nsISerialEventTarget;
+ class nsPIDOMWindowInner;
+ 
+ namespace mozilla {
+ namespace dom {
+ 
++class ClientClaimArgs;
+ class ClientControlledArgs;
+ class ClientManager;
+ class ClientSourceChild;
+ class ClientSourceConstructorArgs;
+ class ClientSourceExecutionReadyArgs;
+ class PClientManagerChild;
+ 
+ namespace workers {
+@@ -127,16 +128,19 @@ public:
+   Control(const ClientControlledArgs& aArgs);
+ 
+   // Get the ClientSource's current controlling service worker, if one has
+   // been set.
+   const Maybe<ServiceWorkerDescriptor>&
+   GetController() const;
+ 
+   RefPtr<ClientOpPromise>
++  Claim(const ClientClaimArgs& aArgs);
++
++  RefPtr<ClientOpPromise>
+   GetInfoAndState(const ClientGetInfoAndStateArgs& aArgs);
+ 
+   nsresult
+   SnapshotState(ClientState* aStateOut);
+ 
+   nsISerialEventTarget*
+   EventTarget() const;
+ 
+diff --git a/dom/clients/manager/ClientSourceOpChild.cpp b/dom/clients/manager/ClientSourceOpChild.cpp
+--- a/dom/clients/manager/ClientSourceOpChild.cpp
++++ b/dom/clients/manager/ClientSourceOpChild.cpp
+@@ -74,16 +74,21 @@ void
+ ClientSourceOpChild::Init(const ClientOpConstructorArgs& aArgs)
+ {
+   switch (aArgs.type()) {
+     case ClientOpConstructorArgs::TClientControlledArgs:
+     {
+       DoSourceOp(&ClientSource::Control, aArgs.get_ClientControlledArgs());
+       break;
+     }
++    case ClientOpConstructorArgs::TClientClaimArgs:
++    {
++      DoSourceOp(&ClientSource::Claim, aArgs.get_ClientClaimArgs());
++      break;
++    }
+     case ClientOpConstructorArgs::TClientGetInfoAndStateArgs:
+     {
+       DoSourceOp(&ClientSource::GetInfoAndState,
+                  aArgs.get_ClientGetInfoAndStateArgs());
+       break;
+     }
+     default:
+     {

+ 746 - 0
frg/work-js/mozilla-release/patches/1424338-3-59a1.patch

@@ -0,0 +1,746 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1512762402 18000
+# Node ID e6225232364ee363cf97b2a981eaab94d8bccc27
+# Parent  32634176d49e9fb9b0d0dec0cef3f369be3fb23c
+Bug 1424338 P3 Add ClientManager::Navigate() method. r=baku
+
+diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp
+--- a/dom/base/nsGlobalWindow.cpp
++++ b/dom/base/nsGlobalWindow.cpp
+@@ -14,16 +14,17 @@
+ #include "Navigator.h"
+ #include "nsContentSecurityManager.h"
+ #include "nsScreen.h"
+ #include "nsHistory.h"
+ #include "nsDOMNavigationTiming.h"
+ #include "nsIDOMStorageManager.h"
+ #include "mozilla/dom/ClientManager.h"
+ #include "mozilla/dom/ClientSource.h"
++#include "mozilla/dom/ClientState.h"
+ #include "mozilla/dom/LocalStorage.h"
+ #include "mozilla/dom/Storage.h"
+ #include "mozilla/dom/IdleRequest.h"
+ #include "mozilla/dom/Performance.h"
+ #include "mozilla/dom/StorageEvent.h"
+ #include "mozilla/dom/StorageEventBinding.h"
+ #include "mozilla/dom/StorageNotifierService.h"
+ #include "mozilla/dom/StorageUtils.h"
+@@ -4422,16 +4423,22 @@ nsPIDOMWindowInner::SyncStateFromParentW
+ }
+ 
+ Maybe<ClientInfo>
+ nsPIDOMWindowInner::GetClientInfo() const
+ {
+   return Move(nsGlobalWindow::Cast(this)->GetClientInfo());
+ }
+ 
++Maybe<ClientState>
++nsPIDOMWindowInner::GetClientState() const
++{
++  return Move(nsGlobalWindow::Cast(this)->GetClientState());
++}
++
+ Maybe<ServiceWorkerDescriptor>
+ nsPIDOMWindowInner::GetController() const
+ {
+   return Move(nsGlobalWindow::Cast(this)->GetController());
+ }
+ 
+ void
+ nsGlobalWindow::UpdateTopInnerWindow()
+@@ -12621,16 +12628,31 @@ nsGlobalWindow::GetClientInfo() const
+   MOZ_ASSERT(NS_IsMainThread());
+   Maybe<ClientInfo> clientInfo;
+   if (mClientSource) {
+     clientInfo.emplace(mClientSource->Info());
+   }
+   return Move(clientInfo);
+ }
+ 
++Maybe<ClientState>
++nsGlobalWindow::GetClientState() const
++{
++  MOZ_ASSERT(NS_IsMainThread());
++  Maybe<ClientState> clientState;
++  if (mClientSource) {
++    ClientState state;
++    nsresult rv = mClientSource->SnapshotState(&state);
++    if (NS_SUCCEEDED(rv)) {
++      clientState.emplace(state);
++    }
++  }
++  return Move(clientState);
++}
++
+ Maybe<ServiceWorkerDescriptor>
+ nsGlobalWindow::GetController() const
+ {
+   MOZ_ASSERT(NS_IsMainThread());
+   Maybe<ServiceWorkerDescriptor> controller;
+   if (mClientSource) {
+     controller = mClientSource->GetController();
+   }
+diff --git a/dom/base/nsGlobalWindow.h b/dom/base/nsGlobalWindow.h
+--- a/dom/base/nsGlobalWindow.h
++++ b/dom/base/nsGlobalWindow.h
+@@ -399,16 +399,17 @@ public:
+   void Resume();
+   virtual bool IsSuspended() const override;
+   void Freeze();
+   void Thaw();
+   virtual bool IsFrozen() const override;
+   void SyncStateFromParentWindow();
+ 
+   mozilla::Maybe<mozilla::dom::ClientInfo> GetClientInfo() const;
++  mozilla::Maybe<mozilla::dom::ClientState> GetClientState() const;
+   mozilla::Maybe<mozilla::dom::ServiceWorkerDescriptor> GetController() const;
+ 
+   virtual nsresult FireDelayedDOMEvents() override;
+ 
+   // Outer windows only.
+   virtual bool WouldReuseInnerWindow(nsIDocument* aNewDocument) override;
+ 
+   virtual void SetDocShell(nsIDocShell* aDocShell) override;
+diff --git a/dom/base/nsPIDOMWindow.h b/dom/base/nsPIDOMWindow.h
+--- a/dom/base/nsPIDOMWindow.h
++++ b/dom/base/nsPIDOMWindow.h
+@@ -41,16 +41,17 @@ class nsXBLPrototypeHandler;
+ 
+ typedef uint32_t SuspendTypes;
+ 
+ namespace mozilla {
+ class ThrottledEventQueue;
+ namespace dom {
+ class AudioContext;
+ class ClientInfo;
++class ClientState;
+ class DocGroup;
+ class TabGroup;
+ class Element;
+ class Performance;
+ class ServiceWorkerDescriptor;
+ class ServiceWorkerRegistration;
+ class Timeout;
+ class TimeoutManager;
+@@ -933,16 +934,17 @@ public:
+   // Increase/Decrease the number of open WebSockets.
+   void UpdateWebSocketCount(int32_t aDelta);
+ 
+   // Return true if there are any open WebSockets that could block
+   // timeout-throttling.
+   bool HasOpenWebSockets() const;
+ 
+   mozilla::Maybe<mozilla::dom::ClientInfo> GetClientInfo() const;
++  mozilla::Maybe<mozilla::dom::ClientState> GetClientState() const;
+   mozilla::Maybe<mozilla::dom::ServiceWorkerDescriptor> GetController() const;
+ 
+ protected:
+   void CreatePerformanceObjectIfNeeded();
+ };
+ 
+ NS_DEFINE_STATIC_IID_ACCESSOR(nsPIDOMWindowInner, NS_PIDOMWINDOWINNER_IID)
+ 
+diff --git a/dom/clients/manager/ClientIPCTypes.ipdlh b/dom/clients/manager/ClientIPCTypes.ipdlh
+--- a/dom/clients/manager/ClientIPCTypes.ipdlh
++++ b/dom/clients/manager/ClientIPCTypes.ipdlh
+@@ -62,16 +62,23 @@ struct ClientSourceExecutionReadyArgs
+   FrameType frameType;
+ };
+ 
+ struct ClientControlledArgs
+ {
+   IPCServiceWorkerDescriptor serviceWorker;
+ };
+ 
++struct ClientNavigateArgs
++{
++  IPCClientInfo target;
++  nsCString url;
++  nsCString baseURL;
++};
++
+ union ClientEndPoint
+ {
+   IPCClientInfo;
+   IPCServiceWorkerDescriptor;
+ };
+ 
+ struct ClientMatchAllArgs
+ {
+@@ -93,28 +100,32 @@ struct ClientGetInfoAndStateArgs
+ 
+ struct ClientOpenWindowArgs
+ {
+ };
+ 
+ union ClientOpConstructorArgs
+ {
+   ClientControlledArgs;
++  ClientNavigateArgs;
+   ClientMatchAllArgs;
+   ClientClaimArgs;
+   ClientGetInfoAndStateArgs;
+ };
+ 
+ struct ClientList
+ {
+   ClientInfoAndState[] values;
+ };
+ 
+ struct ClientNavigateOpConstructorArgs
+ {
++  PClientSource target;
++  nsCString url;
++  nsCString baseURL;
+ };
+ 
+ union ClientOpResult
+ {
+   nsresult;
+   ClientInfoAndState;
+   ClientList;
+ };
+diff --git a/dom/clients/manager/ClientManager.cpp b/dom/clients/manager/ClientManager.cpp
+--- a/dom/clients/manager/ClientManager.cpp
++++ b/dom/clients/manager/ClientManager.cpp
+@@ -279,10 +279,19 @@ ClientManager::Claim(const ClientClaimAr
+ RefPtr<ClientOpPromise>
+ ClientManager::GetInfoAndState(const ClientGetInfoAndStateArgs& aArgs,
+                                nsISerialEventTarget* aSerialEventTarget)
+ {
+   RefPtr<ClientManager> mgr = GetOrCreateForCurrentThread();
+   return mgr->StartOp(aArgs, aSerialEventTarget);
+ }
+ 
++// static
++RefPtr<ClientOpPromise>
++ClientManager::Navigate(const ClientNavigateArgs& aArgs,
++                        nsISerialEventTarget* aSerialEventTarget)
++{
++  RefPtr<ClientManager> mgr = GetOrCreateForCurrentThread();
++  return mgr->StartOp(aArgs, aSerialEventTarget);
++}
++
+ } // namespace dom
+ } // namespace mozilla
+diff --git a/dom/clients/manager/ClientManager.h b/dom/clients/manager/ClientManager.h
+--- a/dom/clients/manager/ClientManager.h
++++ b/dom/clients/manager/ClientManager.h
+@@ -19,16 +19,17 @@ class PrincipalInfo;
+ namespace dom {
+ 
+ class ClientClaimArgs;
+ class ClientGetInfoAndStateArgs;
+ class ClientHandle;
+ class ClientInfo;
+ class ClientManagerChild;
+ class ClientMatchAllArgs;
++class ClientNavigateArgs;
+ class ClientOpConstructorArgs;
+ class ClientSource;
+ enum class ClientType : uint8_t;
+ 
+ namespace workers {
+ class WorkerPrivate;
+ } // workers namespace
+ 
+@@ -101,15 +102,19 @@ public:
+ 
+   static RefPtr<ClientOpPromise>
+   Claim(const ClientClaimArgs& aArgs, nsISerialEventTarget* aSerialEventTarget);
+ 
+   static RefPtr<ClientOpPromise>
+   GetInfoAndState(const ClientGetInfoAndStateArgs& aArgs,
+                   nsISerialEventTarget* aSerialEventTarget);
+ 
++  static RefPtr<ClientOpPromise>
++  Navigate(const ClientNavigateArgs& aArgs,
++           nsISerialEventTarget* aSerialEventTarget);
++
+   NS_INLINE_DECL_REFCOUNTING(mozilla::dom::ClientManager)
+ };
+ 
+ } // namespace dom
+ } // namespace mozilla
+ 
+ #endif // _mozilla_dom_ClientManager_h
+diff --git a/dom/clients/manager/ClientManagerOpParent.cpp b/dom/clients/manager/ClientManagerOpParent.cpp
+--- a/dom/clients/manager/ClientManagerOpParent.cpp
++++ b/dom/clients/manager/ClientManagerOpParent.cpp
+@@ -43,16 +43,22 @@ ClientManagerOpParent::ClientManagerOpPa
+ {
+   MOZ_DIAGNOSTIC_ASSERT(mService);
+ }
+ 
+ void
+ ClientManagerOpParent::Init(const ClientOpConstructorArgs& aArgs)
+ {
+   switch (aArgs.type()) {
++    case ClientOpConstructorArgs::TClientNavigateArgs:
++    {
++      DoServiceOp(&ClientManagerService::Navigate,
++                  aArgs.get_ClientNavigateArgs());
++      break;
++    }
+     case ClientOpConstructorArgs::TClientMatchAllArgs:
+     {
+       DoServiceOp(&ClientManagerService::MatchAll,
+                   aArgs.get_ClientMatchAllArgs());
+       break;
+     }
+     case ClientOpConstructorArgs::TClientClaimArgs:
+     {
+diff --git a/dom/clients/manager/ClientManagerService.cpp b/dom/clients/manager/ClientManagerService.cpp
+--- a/dom/clients/manager/ClientManagerService.cpp
++++ b/dom/clients/manager/ClientManagerService.cpp
+@@ -2,16 +2,17 @@
+ /* 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 "ClientManagerService.h"
+ 
+ #include "ClientManagerParent.h"
++#include "ClientNavigateOpParent.h"
+ #include "ClientSourceParent.h"
+ #include "mozilla/ipc/BackgroundParent.h"
+ #include "mozilla/ipc/PBackgroundSharedTypes.h"
+ #include "mozilla/ClearOnShutdown.h"
+ #include "mozilla/SystemGroup.h"
+ #include "nsIAsyncShutdown.h"
+ 
+ namespace mozilla {
+@@ -297,16 +298,57 @@ void
+ ClientManagerService::RemoveManager(ClientManagerParent* aManager)
+ {
+   AssertIsOnBackgroundThread();
+   MOZ_DIAGNOSTIC_ASSERT(aManager);
+   DebugOnly<bool> removed = mManagerList.RemoveElement(aManager);
+   MOZ_ASSERT(removed);
+ }
+ 
++RefPtr<ClientOpPromise>
++ClientManagerService::Navigate(const ClientNavigateArgs& aArgs)
++{
++  RefPtr<ClientOpPromise> ref;
++
++  ClientSourceParent* source = FindSource(aArgs.target().id(),
++                                          aArgs.target().principalInfo());
++  if (!source) {
++    ref = ClientOpPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
++    return ref.forget();
++  }
++
++  PClientManagerParent* manager = source->Manager();
++  MOZ_DIAGNOSTIC_ASSERT(manager);
++
++  ClientNavigateOpConstructorArgs args;
++  args.url() = aArgs.url();
++  args.baseURL() = aArgs.baseURL();
++
++  // This is safe to do because the ClientSourceChild cannot directly delete
++  // itself.  Instead it sends a Teardown message to the parent which then
++  // calls delete.  That means we can be sure that we are not racing with
++  // source destruction here.
++  args.targetParent() = source;
++
++  RefPtr<ClientOpPromise::Private> promise =
++    new ClientOpPromise::Private(__func__);
++
++  ClientNavigateOpParent* op = new ClientNavigateOpParent(args, promise);
++  PClientNavigateOpParent* result =
++    manager->SendPClientNavigateOpConstructor(op, args);
++  if (!result) {
++    promise->Reject(NS_ERROR_FAILURE, __func__);
++    ref = promise;
++    return ref.forget();
++  }
++
++  ref = promise;
++  return ref.forget();
++}
++
+ namespace
+ {
+ 
+ class PromiseListHolder final
+ {
+   RefPtr<ClientOpPromise::Private> mResultPromise;
+   nsTArray<RefPtr<ClientOpPromise>> mPromiseList;
+   nsTArray<ClientInfoAndState> mResultList;
+diff --git a/dom/clients/manager/ClientManagerService.h b/dom/clients/manager/ClientManagerService.h
+--- a/dom/clients/manager/ClientManagerService.h
++++ b/dom/clients/manager/ClientManagerService.h
+@@ -55,16 +55,19 @@ public:
+ 
+   void
+   AddManager(ClientManagerParent* aManager);
+ 
+   void
+   RemoveManager(ClientManagerParent* aManager);
+ 
+   RefPtr<ClientOpPromise>
++  Navigate(const ClientNavigateArgs& aArgs);
++
++  RefPtr<ClientOpPromise>
+   MatchAll(const ClientMatchAllArgs& aArgs);
+ 
+   RefPtr<ClientOpPromise>
+   Claim(const ClientClaimArgs& aArgs);
+ 
+   RefPtr<ClientOpPromise>
+   GetInfoAndState(const ClientGetInfoAndStateArgs& aArgs);
+ 
+diff --git a/dom/clients/manager/ClientNavigateOpChild.cpp b/dom/clients/manager/ClientNavigateOpChild.cpp
+--- a/dom/clients/manager/ClientNavigateOpChild.cpp
++++ b/dom/clients/manager/ClientNavigateOpChild.cpp
+@@ -1,23 +1,290 @@
+ /* -*- 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 "ClientNavigateOpChild.h"
+ 
++#include "ClientState.h"
++#include "mozilla/Unused.h"
++#include "nsIDocShell.h"
++#include "nsIDocShellLoadInfo.h"
++#include "nsIWebNavigation.h"
++#include "nsIWebProgress.h"
++#include "nsIWebProgressListener.h"
++#include "nsNetUtil.h"
++#include "nsPIDOMWindow.h"
++
+ namespace mozilla {
+ namespace dom {
+ 
++namespace {
++
++class NavigateLoadListener final : public nsIWebProgressListener
++                                 , public nsSupportsWeakReference
++{
++  RefPtr<ClientOpPromise::Private> mPromise;
++  RefPtr<nsPIDOMWindowOuter> mOuterWindow;
++  nsCOMPtr<nsIURI> mBaseURL;
++
++  ~NavigateLoadListener() = default;
++
++public:
++  NavigateLoadListener(ClientOpPromise::Private* aPromise,
++                       nsPIDOMWindowOuter* aOuterWindow,
++                       nsIURI* aBaseURL)
++    : mPromise(aPromise)
++    , mOuterWindow(aOuterWindow)
++    , mBaseURL(aBaseURL)
++  {
++    MOZ_DIAGNOSTIC_ASSERT(mPromise);
++    MOZ_DIAGNOSTIC_ASSERT(mOuterWindow);
++    MOZ_DIAGNOSTIC_ASSERT(mBaseURL);
++  }
++
++  NS_IMETHOD
++  OnStateChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
++                uint32_t aStateFlags, nsresult aResult) override
++  {
++    if (!(aStateFlags & STATE_IS_DOCUMENT) ||
++        !(aStateFlags & (STATE_STOP | STATE_TRANSFERRING))) {
++      return NS_OK;
++    }
++
++    aWebProgress->RemoveProgressListener(this);
++
++    nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
++    if (!channel) {
++      mPromise->Reject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__);
++      return NS_OK;
++    }
++
++    nsCOMPtr<nsIURI> channelURL;
++    nsresult rv = NS_GetFinalChannelURI(channel, getter_AddRefs(channelURL));
++    if (NS_FAILED(rv)) {
++      mPromise->Reject(rv, __func__);
++      return NS_OK;
++    }
++
++    nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
++    MOZ_DIAGNOSTIC_ASSERT(ssm);
++
++    // If the resulting window is not same origin, then resolve immediately
++    // without returning any information about the new Client.  This is
++    // step 6.10 in the Client.navigate(url) spec.
++    rv = ssm->CheckSameOriginURI(mBaseURL, channelURL, false);
++    if (NS_FAILED(rv)) {
++      mPromise->Resolve(NS_OK, __func__);
++      return NS_OK;
++    }
++
++    nsPIDOMWindowInner* innerWindow = mOuterWindow->GetCurrentInnerWindow();
++    MOZ_DIAGNOSTIC_ASSERT(innerWindow);
++
++    Maybe<ClientInfo> clientInfo = innerWindow->GetClientInfo();
++    MOZ_DIAGNOSTIC_ASSERT(clientInfo.isSome());
++
++    Maybe<ClientState> clientState = innerWindow->GetClientState();
++    MOZ_DIAGNOSTIC_ASSERT(clientState.isSome());
++
++    // Otherwise, if the new window is same-origin we want to return a
++    // ClientInfoAndState object so we can provide a Client snapshot
++    // to the caller.  This is step 6.11 and 6.12 in the Client.navigate(url)
++    // spec.
++    mPromise->Resolve(ClientInfoAndState(clientInfo.ref().ToIPC(),
++                                         clientState.ref().ToIPC()), __func__);
++
++    return NS_OK;
++  }
++
++  NS_IMETHOD
++  OnProgressChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
++                   int32_t aCurSelfProgress, int32_t aMaxSelfProgress,
++                   int32_t aCurTotalProgress, int32_t aMaxTotalProgress) override
++  {
++    MOZ_CRASH("Unexpected notification.");
++    return NS_OK;
++  }
++
++  NS_IMETHOD
++  OnLocationChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
++                   nsIURI* aLocation, uint32_t aFlags) override
++  {
++    MOZ_CRASH("Unexpected notification.");
++    return NS_OK;
++  }
++
++  NS_IMETHOD
++  OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
++                 nsresult aStatus, const char16_t* aMessage) override
++  {
++    MOZ_CRASH("Unexpected notification.");
++    return NS_OK;
++  }
++
++  NS_IMETHOD
++  OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
++                   uint32_t aState) override
++  {
++    MOZ_CRASH("Unexpected notification.");
++    return NS_OK;
++  }
++
++  NS_DECL_ISUPPORTS
++};
++
++NS_IMPL_ISUPPORTS(NavigateLoadListener, nsIWebProgressListener,
++                                        nsISupportsWeakReference);
++
++} // anonymous namespace
++
++already_AddRefed<ClientOpPromise>
++ClientNavigateOpChild::DoNavigate(const ClientNavigateOpConstructorArgs& aArgs)
++{
++  RefPtr<ClientOpPromise> ref;
++  nsCOMPtr<nsPIDOMWindowInner> window;
++
++  // Navigating the target client window will result in the original
++  // ClientSource being destroyed.  To avoid potential UAF mistakes
++  // we use a small scope to access the ClientSource object.  Once
++  // we have a strong reference to the window object we should not
++  // access the ClientSource again.
++  {
++    ClientSourceChild* targetActor =
++      static_cast<ClientSourceChild*>(aArgs.targetChild());
++    MOZ_DIAGNOSTIC_ASSERT(targetActor);
++
++    ClientSource* target = targetActor->GetSource();
++    if (!target) {
++      ref = ClientOpPromise::CreateAndReject(NS_ERROR_DOM_INVALID_STATE_ERR,
++                                             __func__);
++      return ref.forget();
++    }
++
++    window = target->GetInnerWindow();
++    if (!window) {
++      ref = ClientOpPromise::CreateAndReject(NS_ERROR_DOM_INVALID_STATE_ERR,
++                                             __func__);
++      return ref.forget();
++    }
++  }
++
++  MOZ_ASSERT(NS_IsMainThread());
++
++  mSerialEventTarget = window->EventTargetFor(TaskCategory::Other);
++
++  // In theory we could do the URL work before paying the IPC overhead
++  // cost, but in practice its easier to do it here.  The ClientHandle
++  // may be off-main-thread while this method is guaranteed to always
++  // be main thread.
++  nsCOMPtr<nsIURI> baseURL;
++  nsresult rv = NS_NewURI(getter_AddRefs(baseURL), aArgs.baseURL());
++  if (NS_FAILED(rv)) {
++    ref = ClientOpPromise::CreateAndReject(rv, __func__);
++    return ref.forget();
++  }
++
++  nsCOMPtr<nsIURI> url;
++  rv = NS_NewURI(getter_AddRefs(url), aArgs.url(), nullptr, baseURL);
++  if (NS_FAILED(rv)) {
++    ref = ClientOpPromise::CreateAndReject(rv, __func__);
++    return ref.forget();
++  }
++
++  if (url->GetSpecOrDefault().EqualsLiteral("about:blank")) {
++    ref = ClientOpPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
++    return ref.forget();
++  }
++
++  nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
++  if (!doc || !doc->IsActive()) {
++    ref = ClientOpPromise::CreateAndReject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__);
++    return ref.forget();
++  }
++
++  nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
++  if (!principal) {
++    ref = ClientOpPromise::CreateAndReject(rv, __func__);
++    return ref.forget();
++  }
++
++  nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
++  nsCOMPtr<nsIWebProgress> webProgress = do_GetInterface(docShell);
++  if (!docShell || !webProgress) {
++    ref = ClientOpPromise::CreateAndReject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__);
++    return ref.forget();
++  }
++
++  nsCOMPtr<nsIDocShellLoadInfo> loadInfo;
++  rv = docShell->CreateLoadInfo(getter_AddRefs(loadInfo));
++  if (NS_FAILED(rv)) {
++    ref = ClientOpPromise::CreateAndReject(rv, __func__);
++    return ref.forget();
++  }
++
++  loadInfo->SetTriggeringPrincipal(principal);
++  loadInfo->SetReferrerPolicy(doc->GetReferrerPolicy());
++  loadInfo->SetLoadType(nsIDocShellLoadInfo::loadStopContent);
++  loadInfo->SetSourceDocShell(docShell);
++  rv = docShell->LoadURI(url, loadInfo, nsIWebNavigation::LOAD_FLAGS_NONE, true);
++  if (NS_FAILED(rv)) {
++    ref = ClientOpPromise::CreateAndReject(rv, __func__);
++    return ref.forget();
++  }
++
++  RefPtr<ClientOpPromise::Private> promise =
++    new ClientOpPromise::Private(__func__);
++
++  nsCOMPtr<nsIWebProgressListener> listener =
++    new NavigateLoadListener(promise, window->GetOuterWindow(), baseURL);
++
++  rv = webProgress->AddProgressListener(listener,
++                                        nsIWebProgress::NOTIFY_STATE_DOCUMENT);
++  if (NS_FAILED(rv)) {
++    promise->Reject(rv, __func__);
++    ref = promise;
++    return ref.forget();
++  }
++
++  ref = promise.get();
++
++  ref->Then(mSerialEventTarget, __func__,
++    [listener] (const ClientOpResult& aResult) { },
++    [listener] (nsresult aResult) { });
++
++  return ref.forget();
++}
++
+ void
+ ClientNavigateOpChild::ActorDestroy(ActorDestroyReason aReason)
+ {
++  mPromiseRequestHolder.DisconnectIfExists();
+ }
+ 
+ void
+ ClientNavigateOpChild::Init(const ClientNavigateOpConstructorArgs& aArgs)
+ {
++  RefPtr<ClientOpPromise> promise = DoNavigate(aArgs);
++
++  // Normally we get the event target from the window in DoNavigate().  If a
++  // failure occurred, though, we may need to fall back to the current thread
++  // target.
++  if (!mSerialEventTarget) {
++    mSerialEventTarget = GetCurrentThreadSerialEventTarget();
++  }
++
++  // Capturing `this` is safe here since we clear the mPromiseRequestHolder in
++  // ActorDestroy.
++  promise->Then(mSerialEventTarget, __func__,
++    [this] (const ClientOpResult& aResult) {
++      mPromiseRequestHolder.Complete();
++      PClientNavigateOpChild::Send__delete__(this, aResult);
++    }, [this] (nsresult aResult) {
++      mPromiseRequestHolder.Complete();
++      PClientNavigateOpChild::Send__delete__(this, aResult);
++  })->Track(mPromiseRequestHolder);
+ }
+ 
+ } // namespace dom
+ } // namespace mozilla
+diff --git a/dom/clients/manager/ClientNavigateOpChild.h b/dom/clients/manager/ClientNavigateOpChild.h
+--- a/dom/clients/manager/ClientNavigateOpChild.h
++++ b/dom/clients/manager/ClientNavigateOpChild.h
+@@ -2,22 +2,29 @@
+ /* vim: set ts=8 sts=2 et sw=2 tw=80: */
+ /* This Source Code Form is subject to the terms of the Mozilla Public
+  * License, v. 2.0. If a copy of the MPL was not distributed with this
+  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+ #ifndef _mozilla_dom_ClientNavigateOpChild_h
+ #define _mozilla_dom_ClientNavigateOpChild_h
+ 
+ #include "mozilla/dom/PClientNavigateOpChild.h"
++#include "ClientOpPromise.h"
+ 
+ namespace mozilla {
+ namespace dom {
+ 
+ class ClientNavigateOpChild final : public PClientNavigateOpChild
+ {
++  MozPromiseRequestHolder<ClientOpPromise> mPromiseRequestHolder;
++  nsCOMPtr<nsISerialEventTarget> mSerialEventTarget;
++
++  already_AddRefed<ClientOpPromise>
++  DoNavigate(const ClientNavigateOpConstructorArgs& aArgs);
++
+   // PClientNavigateOpChild interface
+   void
+   ActorDestroy(ActorDestroyReason aReason) override;
+ 
+ public:
+   ClientNavigateOpChild() = default;
+   ~ClientNavigateOpChild() = default;
+ 
+diff --git a/dom/clients/manager/ClientNavigateOpParent.cpp b/dom/clients/manager/ClientNavigateOpParent.cpp
+--- a/dom/clients/manager/ClientNavigateOpParent.cpp
++++ b/dom/clients/manager/ClientNavigateOpParent.cpp
+@@ -9,16 +9,20 @@
+ namespace mozilla {
+ namespace dom {
+ 
+ using mozilla::ipc::IPCResult;
+ 
+ void
+ ClientNavigateOpParent::ActorDestroy(ActorDestroyReason aReason)
+ {
++  if (mPromise) {
++    mPromise->Reject(NS_ERROR_DOM_ABORT_ERR, __func__);
++    mPromise = nullptr;
++  }
+ }
+ 
+ IPCResult
+ ClientNavigateOpParent::Recv__delete__(const ClientOpResult& aResult)
+ {
+   if (aResult.type() == ClientOpResult::Tnsresult &&
+       NS_FAILED(aResult.get_nsresult())) {
+     mPromise->Reject(aResult.get_nsresult(), __func__);

+ 968 - 0
frg/work-js/mozilla-release/patches/1424338-4-59a1.patch

@@ -0,0 +1,968 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1512762403 18000
+# Node ID 0e4e71faf362f2a045361b23edbd673b13a8de7a
+# Parent  05b1e9cf4c301c1d138f179e6d93f2e35643b37a
+Bug 1424338 P4 Implement ClientManager::OpenWindow(). r=baku
+
+diff --git a/dom/clients/manager/ClientIPCTypes.ipdlh b/dom/clients/manager/ClientIPCTypes.ipdlh
+--- a/dom/clients/manager/ClientIPCTypes.ipdlh
++++ b/dom/clients/manager/ClientIPCTypes.ipdlh
+@@ -95,25 +95,29 @@ struct ClientClaimArgs
+ struct ClientGetInfoAndStateArgs
+ {
+   nsID id;
+   PrincipalInfo principalInfo;
+ };
+ 
+ struct ClientOpenWindowArgs
+ {
++  PrincipalInfo principalInfo;
++  nsCString url;
++  nsCString baseURL;
+ };
+ 
+ union ClientOpConstructorArgs
+ {
+   ClientControlledArgs;
+   ClientNavigateArgs;
+   ClientMatchAllArgs;
+   ClientClaimArgs;
+   ClientGetInfoAndStateArgs;
++  ClientOpenWindowArgs;
+ };
+ 
+ struct ClientList
+ {
+   ClientInfoAndState[] values;
+ };
+ 
+ struct ClientNavigateOpConstructorArgs
+diff --git a/dom/clients/manager/ClientManager.cpp b/dom/clients/manager/ClientManager.cpp
+--- a/dom/clients/manager/ClientManager.cpp
++++ b/dom/clients/manager/ClientManager.cpp
+@@ -288,10 +288,19 @@ ClientManager::GetInfoAndState(const Cli
+ RefPtr<ClientOpPromise>
+ ClientManager::Navigate(const ClientNavigateArgs& aArgs,
+                         nsISerialEventTarget* aSerialEventTarget)
+ {
+   RefPtr<ClientManager> mgr = GetOrCreateForCurrentThread();
+   return mgr->StartOp(aArgs, aSerialEventTarget);
+ }
+ 
++// static
++RefPtr<ClientOpPromise>
++ClientManager::OpenWindow(const ClientOpenWindowArgs& aArgs,
++                          nsISerialEventTarget* aSerialEventTarget)
++{
++  RefPtr<ClientManager> mgr = GetOrCreateForCurrentThread();
++  return mgr->StartOp(aArgs, aSerialEventTarget);
++}
++
+ } // namespace dom
+ } // namespace mozilla
+diff --git a/dom/clients/manager/ClientManager.h b/dom/clients/manager/ClientManager.h
+--- a/dom/clients/manager/ClientManager.h
++++ b/dom/clients/manager/ClientManager.h
+@@ -21,16 +21,17 @@ namespace dom {
+ class ClientClaimArgs;
+ class ClientGetInfoAndStateArgs;
+ class ClientHandle;
+ class ClientInfo;
+ class ClientManagerChild;
+ class ClientMatchAllArgs;
+ class ClientNavigateArgs;
+ class ClientOpConstructorArgs;
++class ClientOpenWindowArgs;
+ class ClientSource;
+ enum class ClientType : uint8_t;
+ 
+ namespace workers {
+ class WorkerPrivate;
+ } // workers namespace
+ 
+ // The ClientManager provides a per-thread singleton interface workering
+@@ -106,15 +107,19 @@ public:
+   static RefPtr<ClientOpPromise>
+   GetInfoAndState(const ClientGetInfoAndStateArgs& aArgs,
+                   nsISerialEventTarget* aSerialEventTarget);
+ 
+   static RefPtr<ClientOpPromise>
+   Navigate(const ClientNavigateArgs& aArgs,
+            nsISerialEventTarget* aSerialEventTarget);
+ 
++  static RefPtr<ClientOpPromise>
++  OpenWindow(const ClientOpenWindowArgs& aArgs,
++             nsISerialEventTarget* aSerialEventTarget);
++
+   NS_INLINE_DECL_REFCOUNTING(mozilla::dom::ClientManager)
+ };
+ 
+ } // namespace dom
+ } // namespace mozilla
+ 
+ #endif // _mozilla_dom_ClientManager_h
+diff --git a/dom/clients/manager/ClientManagerOpParent.cpp b/dom/clients/manager/ClientManagerOpParent.cpp
+--- a/dom/clients/manager/ClientManagerOpParent.cpp
++++ b/dom/clients/manager/ClientManagerOpParent.cpp
+@@ -2,20 +2,23 @@
+ /* 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 "ClientManagerOpParent.h"
+ 
+ #include "ClientManagerService.h"
++#include "mozilla/ipc/BackgroundParent.h"
+ 
+ namespace mozilla {
+ namespace dom {
+ 
++using mozilla::ipc::BackgroundParent;
++
+ template <typename Method, typename... Args>
+ void
+ ClientManagerOpParent::DoServiceOp(Method aMethod, Args&&... aArgs)
+ {
+   // Note, we need perfect forarding of the template type in order
+   // to allow already_AddRefed<> to be passed as an arg.
+   RefPtr<ClientOpPromise> p = (mService->*aMethod)(Forward<Args>(aArgs)...);
+ 
+@@ -66,16 +69,24 @@ ClientManagerOpParent::Init(const Client
+       break;
+     }
+     case ClientOpConstructorArgs::TClientGetInfoAndStateArgs:
+     {
+       DoServiceOp(&ClientManagerService::GetInfoAndState,
+                   aArgs.get_ClientGetInfoAndStateArgs());
+       break;
+     }
++    case ClientOpConstructorArgs::TClientOpenWindowArgs:
++    {
++      RefPtr<ContentParent> contentParent =
++        BackgroundParent::GetContentParent(Manager()->Manager());
++      DoServiceOp(&ClientManagerService::OpenWindow,
++                  aArgs.get_ClientOpenWindowArgs(), contentParent.forget());
++      break;
++    }
+     default:
+     {
+       MOZ_ASSERT_UNREACHABLE("Unknown Client operation!");
+       break;
+     }
+   }
+ }
+ 
+diff --git a/dom/clients/manager/ClientManagerService.cpp b/dom/clients/manager/ClientManagerService.cpp
+--- a/dom/clients/manager/ClientManagerService.cpp
++++ b/dom/clients/manager/ClientManagerService.cpp
+@@ -3,22 +3,27 @@
+ /* 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 "ClientManagerService.h"
+ 
+ #include "ClientManagerParent.h"
+ #include "ClientNavigateOpParent.h"
++#include "ClientOpenWindowOpParent.h"
++#include "ClientOpenWindowUtils.h"
+ #include "ClientSourceParent.h"
++#include "mozilla/dom/ContentParent.h"
+ #include "mozilla/ipc/BackgroundParent.h"
+ #include "mozilla/ipc/PBackgroundSharedTypes.h"
+ #include "mozilla/ClearOnShutdown.h"
+ #include "mozilla/SystemGroup.h"
+ #include "nsIAsyncShutdown.h"
++#include "nsIXULRuntime.h"
++#include "nsProxyRelease.h"
+ 
+ namespace mozilla {
+ namespace dom {
+ 
+ using mozilla::ipc::AssertIsOnBackgroundThread;
+ using mozilla::ipc::ContentPrincipalInfo;
+ using mozilla::ipc::PrincipalInfo;
+ 
+@@ -546,10 +551,95 @@ ClientManagerService::GetInfoAndState(co
+   if (!source || !source->ExecutionReady()) {
+     ref = ClientOpPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+     return ref.forget();
+   }
+ 
+   return source->StartOp(aArgs);
+ }
+ 
++namespace {
++
++class OpenWindowRunnable final : public Runnable
++{
++  RefPtr<ClientOpPromise::Private> mPromise;
++  const ClientOpenWindowArgs mArgs;
++  RefPtr<ContentParent> mSourceProcess;
++
++  ~OpenWindowRunnable()
++  {
++    NS_ReleaseOnMainThreadSystemGroup(mSourceProcess.forget());
++  }
++
++public:
++  OpenWindowRunnable(ClientOpPromise::Private* aPromise,
++                     const ClientOpenWindowArgs& aArgs,
++                     already_AddRefed<ContentParent> aSourceProcess)
++    : Runnable("ClientManagerService::OpenWindowRunnable")
++    , mPromise(aPromise)
++    , mArgs(aArgs)
++    , mSourceProcess(aSourceProcess)
++  {
++    MOZ_DIAGNOSTIC_ASSERT(mPromise);
++  }
++
++  NS_IMETHOD
++  Run() override
++  {
++    MOZ_ASSERT(NS_IsMainThread());
++
++    if (!BrowserTabsRemoteAutostart()) {
++      RefPtr<ClientOpPromise> p = ClientOpenWindowInCurrentProcess(mArgs);
++      p->ChainTo(mPromise.forget(), __func__);
++      return NS_OK;
++    }
++
++    RefPtr<ContentParent> targetProcess;
++
++    // Possibly try to open the window in the same process that called
++    // openWindow().  This is a temporary compat setting until the
++    // multi-e10s service worker refactor is complete.
++    if (Preferences::GetBool("dom.clients.openwindow_favors_same_process",
++                             false)) {
++      targetProcess = mSourceProcess;
++    }
++
++    // Otherwise, use our normal remote process selection mechanism for
++    // opening the window.  This will start a process if one is not
++    // present.
++    if (!targetProcess) {
++      targetProcess =
++        ContentParent::GetNewOrUsedBrowserProcess(NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE),
++                                                  ContentParent::GetInitialProcessPriority(nullptr),
++                                                  nullptr);
++    }
++
++    ClientOpenWindowOpParent* actor =
++      new ClientOpenWindowOpParent(mArgs, mPromise);
++
++    // If this fails the actor will be automatically destroyed which will
++    // reject the promise.
++    Unused << targetProcess->SendPClientOpenWindowOpConstructor(actor, mArgs);
++
++    return NS_OK;
++  }
++};
++
++} // anonymous namespace
++
++RefPtr<ClientOpPromise>
++ClientManagerService::OpenWindow(const ClientOpenWindowArgs& aArgs,
++                                 already_AddRefed<ContentParent> aSourceProcess)
++{
++  RefPtr<ClientOpPromise::Private> promise =
++    new ClientOpPromise::Private(__func__);
++
++  nsCOMPtr<nsIRunnable> r = new OpenWindowRunnable(promise, aArgs,
++                                                   Move(aSourceProcess));
++  MOZ_ALWAYS_SUCCEEDS(SystemGroup::Dispatch(TaskCategory::Other,
++                                            r.forget()));
++
++  RefPtr<ClientOpPromise> ref = promise;
++  return ref.forget();
++}
++
+ } // namespace dom
+ } // namespace mozilla
+diff --git a/dom/clients/manager/ClientManagerService.h b/dom/clients/manager/ClientManagerService.h
+--- a/dom/clients/manager/ClientManagerService.h
++++ b/dom/clients/manager/ClientManagerService.h
+@@ -66,15 +66,19 @@ public:
+   MatchAll(const ClientMatchAllArgs& aArgs);
+ 
+   RefPtr<ClientOpPromise>
+   Claim(const ClientClaimArgs& aArgs);
+ 
+   RefPtr<ClientOpPromise>
+   GetInfoAndState(const ClientGetInfoAndStateArgs& aArgs);
+ 
++  RefPtr<ClientOpPromise>
++  OpenWindow(const ClientOpenWindowArgs& aArgs,
++             already_AddRefed<ContentParent> aSourceProcess);
++
+   NS_INLINE_DECL_REFCOUNTING(mozilla::dom::ClientManagerService)
+ };
+ 
+ } // namespace dom
+ } // namespace mozilla
+ 
+ #endif // _mozilla_dom_ClientManagerService_h
+diff --git a/dom/clients/manager/ClientOpenWindowOpChild.cpp b/dom/clients/manager/ClientOpenWindowOpChild.cpp
+--- a/dom/clients/manager/ClientOpenWindowOpChild.cpp
++++ b/dom/clients/manager/ClientOpenWindowOpChild.cpp
+@@ -1,23 +1,43 @@
+ /* -*- 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 "ClientOpenWindowOpChild.h"
++#include "ClientOpenWindowUtils.h"
++#include "mozilla/SystemGroup.h"
+ 
+ namespace mozilla {
+ namespace dom {
+ 
++already_AddRefed<ClientOpPromise>
++ClientOpenWindowOpChild::DoOpenWindow(const ClientOpenWindowArgs& aArgs)
++{
++  RefPtr<ClientOpPromise> ref =
++    ClientOpenWindowInCurrentProcess(aArgs);
++  return ref.forget();
++}
++
+ void
+ ClientOpenWindowOpChild::ActorDestroy(ActorDestroyReason aReason)
+ {
++  mPromiseRequestHolder.DisconnectIfExists();
+ }
+ 
+ void
+ ClientOpenWindowOpChild::Init(const ClientOpenWindowArgs& aArgs)
+ {
++  RefPtr<ClientOpPromise> promise = DoOpenWindow(aArgs);
++  promise->Then(SystemGroup::EventTargetFor(TaskCategory::Other), __func__,
++    [this] (const ClientOpResult& aResult) {
++      mPromiseRequestHolder.Complete();
++      PClientOpenWindowOpChild::Send__delete__(this, aResult);
++    }, [this] (nsresult aResult) {
++      mPromiseRequestHolder.Complete();
++      PClientOpenWindowOpChild::Send__delete__(this, aResult);
++  })->Track(mPromiseRequestHolder);
+ }
+ 
+ } // namespace dom
+ } // namespace mozilla
+diff --git a/dom/clients/manager/ClientOpenWindowOpChild.h b/dom/clients/manager/ClientOpenWindowOpChild.h
+--- a/dom/clients/manager/ClientOpenWindowOpChild.h
++++ b/dom/clients/manager/ClientOpenWindowOpChild.h
+@@ -2,22 +2,28 @@
+ /* vim: set ts=8 sts=2 et sw=2 tw=80: */
+ /* This Source Code Form is subject to the terms of the Mozilla Public
+  * License, v. 2.0. If a copy of the MPL was not distributed with this
+  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+ #ifndef _mozilla_dom_ClientOpenWindowOpChild_h
+ #define _mozilla_dom_ClientOpenWindowOpChild_h
+ 
+ #include "mozilla/dom/PClientOpenWindowOpChild.h"
++#include "ClientOpPromise.h"
+ 
+ namespace mozilla {
+ namespace dom {
+ 
+ class ClientOpenWindowOpChild final : public PClientOpenWindowOpChild
+ {
++  MozPromiseRequestHolder<ClientOpPromise> mPromiseRequestHolder;
++
++  already_AddRefed<ClientOpPromise>
++  DoOpenWindow(const ClientOpenWindowArgs& aArgs);
++
+   // PClientOpenWindowOpChild interface
+   void
+   ActorDestroy(ActorDestroyReason aReason) override;
+ 
+ public:
+   ClientOpenWindowOpChild() = default;
+   ~ClientOpenWindowOpChild() = default;
+ 
+diff --git a/dom/clients/manager/ClientOpenWindowUtils.cpp b/dom/clients/manager/ClientOpenWindowUtils.cpp
+new file mode 100644
+--- /dev/null
++++ b/dom/clients/manager/ClientOpenWindowUtils.cpp
+@@ -0,0 +1,463 @@
++/* -*- 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 "ClientOpenWindowUtils.h"
++
++#include "ClientInfo.h"
++#include "ClientState.h"
++#include "nsContentUtils.h"
++#include "nsIBrowserDOMWindow.h"
++#include "nsIDocShell.h"
++#include "nsIDOMChromeWindow.h"
++#include "nsIURI.h"
++#include "nsIWebProgress.h"
++#include "nsIWebProgressListener.h"
++#include "nsIWindowWatcher.h"
++#include "nsNetUtil.h"
++#include "nsPIDOMWindow.h"
++#include "nsPIWindowWatcher.h"
++
++#ifdef MOZ_WIDGET_ANDROID
++#include "FennecJNIWrappers.h"
++#endif
++
++namespace mozilla {
++namespace dom {
++
++namespace {
++
++class WebProgressListener final : public nsIWebProgressListener
++                                , public nsSupportsWeakReference
++{
++public:
++  NS_DECL_ISUPPORTS
++
++  WebProgressListener(nsPIDOMWindowOuter* aWindow,
++                      nsIURI* aBaseURI,
++                      already_AddRefed<ClientOpPromise::Private> aPromise)
++  : mPromise(aPromise)
++  , mWindow(aWindow)
++  , mBaseURI(aBaseURI)
++  {
++    MOZ_ASSERT(aWindow);
++    MOZ_ASSERT(aBaseURI);
++    MOZ_ASSERT(NS_IsMainThread());
++  }
++
++  NS_IMETHOD
++  OnStateChange(nsIWebProgress* aWebProgress,
++                nsIRequest* aRequest,
++                uint32_t aStateFlags, nsresult aStatus) override
++  {
++    if (!(aStateFlags & STATE_IS_DOCUMENT) ||
++         !(aStateFlags & (STATE_STOP | STATE_TRANSFERRING))) {
++      return NS_OK;
++    }
++
++    // Our caller keeps a strong reference, so it is safe to remove the listener
++    // from ServiceWorkerPrivate.
++    aWebProgress->RemoveProgressListener(this);
++
++    nsCOMPtr<nsIDocument> doc = mWindow->GetExtantDoc();
++    if (NS_WARN_IF(!doc)) {
++      mPromise->Reject(NS_ERROR_FAILURE, __func__);
++      mPromise = nullptr;
++      return NS_OK;
++    }
++
++    // Check same origin.
++    nsCOMPtr<nsIScriptSecurityManager> securityManager =
++      nsContentUtils::GetSecurityManager();
++    nsresult rv = securityManager->CheckSameOriginURI(doc->GetOriginalURI(),
++                                                      mBaseURI, false);
++    if (NS_FAILED(rv)) {
++      mPromise->Resolve(NS_OK, __func__);
++      mPromise = nullptr;
++      return NS_OK;
++    }
++
++    nsPIDOMWindowInner* innerWindow = doc->GetInnerWindow();
++    if (NS_WARN_IF(!innerWindow)) {
++      mPromise->Reject(NS_ERROR_FAILURE, __func__);
++      mPromise = nullptr;
++      return NS_OK;
++    }
++
++    Maybe<ClientInfo> info = innerWindow->GetClientInfo();
++    Maybe<ClientState> state = innerWindow->GetClientState();
++
++    if (NS_WARN_IF(info.isNothing() || state.isNothing())) {
++      mPromise->Reject(NS_ERROR_FAILURE, __func__);
++      mPromise = nullptr;
++      return NS_OK;
++    }
++
++    mPromise->Resolve(ClientInfoAndState(info.ref().ToIPC(), state.ref().ToIPC()),
++                      __func__);
++    mPromise = nullptr;
++
++    return NS_OK;
++  }
++
++  NS_IMETHOD
++  OnProgressChange(nsIWebProgress* aWebProgress,
++                   nsIRequest* aRequest,
++                   int32_t aCurSelfProgress,
++                   int32_t aMaxSelfProgress,
++                   int32_t aCurTotalProgress,
++                   int32_t aMaxTotalProgress) override
++  {
++    MOZ_ASSERT(false, "Unexpected notification.");
++    return NS_OK;
++  }
++
++  NS_IMETHOD
++  OnLocationChange(nsIWebProgress* aWebProgress,
++                   nsIRequest* aRequest,
++                   nsIURI* aLocation,
++                   uint32_t aFlags) override
++  {
++    MOZ_ASSERT(false, "Unexpected notification.");
++    return NS_OK;
++  }
++
++  NS_IMETHOD
++  OnStatusChange(nsIWebProgress* aWebProgress,
++                 nsIRequest* aRequest,
++                 nsresult aStatus, const char16_t* aMessage) override
++  {
++    MOZ_ASSERT(false, "Unexpected notification.");
++    return NS_OK;
++  }
++
++  NS_IMETHOD
++  OnSecurityChange(nsIWebProgress* aWebProgress,
++                   nsIRequest* aRequest,
++                   uint32_t aState) override
++  {
++    MOZ_ASSERT(false, "Unexpected notification.");
++    return NS_OK;
++  }
++
++private:
++  ~WebProgressListener()
++  {
++    if (mPromise) {
++      mPromise->Reject(NS_ERROR_ABORT, __func__);
++      mPromise = nullptr;
++    }
++  }
++
++  RefPtr<ClientOpPromise::Private> mPromise;
++  // TODO: make window a weak ref and stop cycle collecting
++  nsCOMPtr<nsPIDOMWindowOuter> mWindow;
++  nsCOMPtr<nsIURI> mBaseURI;
++};
++
++NS_IMPL_ISUPPORTS(WebProgressListener, nsIWebProgressListener,
++                                       nsISupportsWeakReference);
++
++nsresult
++OpenWindow(const ClientOpenWindowArgs& aArgs,
++           nsPIDOMWindowOuter** aWindow)
++{
++  MOZ_DIAGNOSTIC_ASSERT(aWindow);
++
++  // [[1. Let url be the result of parsing url with entry settings object's API
++  //   base URL.]]
++  nsCOMPtr<nsIURI> uri;
++
++  nsCOMPtr<nsIURI> baseURI;
++  nsresult rv = NS_NewURI(getter_AddRefs(baseURI), aArgs.baseURL());
++  if (NS_WARN_IF(NS_FAILED(rv))) {
++    return NS_ERROR_TYPE_ERR;
++  }
++
++  rv = NS_NewURI(getter_AddRefs(uri), aArgs.url(), nullptr, baseURI);
++  if (NS_WARN_IF(NS_FAILED(rv))) {
++    return NS_ERROR_TYPE_ERR;
++  }
++
++  nsCOMPtr<nsIPrincipal> principal =
++    PrincipalInfoToPrincipal(aArgs.principalInfo());
++  MOZ_DIAGNOSTIC_ASSERT(principal);
++
++  // [[6.1 Open Window]]
++  if (XRE_IsContentProcess()) {
++
++    // Let's create a sandbox in order to have a valid JSContext and correctly
++    // propagate the SubjectPrincipal.
++    AutoJSAPI jsapi;
++    jsapi.Init();
++
++    JSContext* cx = jsapi.cx();
++
++    nsIXPConnect* xpc = nsContentUtils::XPConnect();
++    MOZ_DIAGNOSTIC_ASSERT(xpc);
++
++    JS::Rooted<JSObject*> sandbox(cx);
++    rv = xpc->CreateSandbox(cx, principal, sandbox.address());
++    if (NS_WARN_IF(NS_FAILED(rv))) {
++      return NS_ERROR_TYPE_ERR;
++    }
++
++    JSAutoCompartment ac(cx, sandbox);
++
++    // ContentProcess
++    nsCOMPtr<nsIWindowWatcher> wwatch =
++      do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
++    if (NS_WARN_IF(NS_FAILED(rv))) {
++      return rv;
++    }
++    nsCOMPtr<nsPIWindowWatcher> pwwatch(do_QueryInterface(wwatch));
++    NS_ENSURE_STATE(pwwatch);
++
++    nsCString spec;
++    rv = uri->GetSpec(spec);
++    if (NS_WARN_IF(NS_FAILED(rv))) {
++      return rv;
++    }
++
++    nsCOMPtr<mozIDOMWindowProxy> newWindow;
++    rv = pwwatch->OpenWindow2(nullptr,
++                              spec.get(),
++                              nullptr,
++                              nullptr,
++                              false, false, true, nullptr,
++                              // Not a spammy popup; we got permission, we swear!
++                              /* aIsPopupSpam = */ false,
++                              // Don't force noopener.  We're not passing in an
++                              // opener anyway, and we _do_ want the returned
++                              // window.
++                              /* aForceNoOpener = */ false,
++                              /* aLoadInfp = */ nullptr,
++                              getter_AddRefs(newWindow));
++    if (NS_WARN_IF(NS_FAILED(rv))) {
++      return rv;
++    }
++    nsCOMPtr<nsPIDOMWindowOuter> pwindow = nsPIDOMWindowOuter::From(newWindow);
++    pwindow.forget(aWindow);
++    MOZ_DIAGNOSTIC_ASSERT(*aWindow);
++    return NS_OK;
++  }
++
++  // Find the most recent browser window and open a new tab in it.
++  nsCOMPtr<nsPIDOMWindowOuter> browserWindow =
++    nsContentUtils::GetMostRecentNonPBWindow();
++  if (!browserWindow) {
++    // It is possible to be running without a browser window on Mac OS, so
++    // we need to open a new chrome window.
++    // TODO(catalinb): open new chrome window. Bug 1218080
++    return NS_ERROR_NOT_AVAILABLE;
++  }
++
++  nsCOMPtr<nsIDOMChromeWindow> chromeWin = do_QueryInterface(browserWindow);
++  if (NS_WARN_IF(!chromeWin)) {
++    return NS_ERROR_FAILURE;
++  }
++
++  nsCOMPtr<nsIBrowserDOMWindow> bwin;
++  chromeWin->GetBrowserDOMWindow(getter_AddRefs(bwin));
++
++  if (NS_WARN_IF(!bwin)) {
++    return NS_ERROR_FAILURE;
++  }
++
++  nsCOMPtr<mozIDOMWindowProxy> win;
++  rv = bwin->OpenURI(uri, nullptr,
++                     nsIBrowserDOMWindow::OPEN_DEFAULTWINDOW,
++                     nsIBrowserDOMWindow::OPEN_NEW,
++                     principal,
++                     getter_AddRefs(win));
++  if (NS_WARN_IF(NS_FAILED(rv))) {
++    return rv;
++  }
++  NS_ENSURE_STATE(win);
++
++  nsCOMPtr<nsPIDOMWindowOuter> pWin = nsPIDOMWindowOuter::From(win);
++  pWin.forget(aWindow);
++  MOZ_DIAGNOSTIC_ASSERT(*aWindow);
++
++  return NS_OK;
++}
++
++void
++WaitForLoad(const ClientOpenWindowArgs& aArgs,
++            nsPIDOMWindowOuter* aOuterWindow,
++            ClientOpPromise::Private* aPromise)
++{
++  MOZ_DIAGNOSTIC_ASSERT(aOuterWindow);
++
++  RefPtr<ClientOpPromise::Private> promise = aPromise;
++
++  nsresult rv = nsContentUtils::DispatchFocusChromeEvent(aOuterWindow);
++  if (NS_WARN_IF(NS_FAILED(rv))) {
++    promise->Reject(rv, __func__);
++    return;
++  }
++
++  nsCOMPtr<nsIURI> baseURI;
++  rv = NS_NewURI(getter_AddRefs(baseURI), aArgs.baseURL());
++  if (NS_WARN_IF(NS_FAILED(rv))) {
++    promise->Reject(rv, __func__);
++    return;
++  }
++
++  nsCOMPtr<nsIDocShell> docShell = aOuterWindow->GetDocShell();
++  nsCOMPtr<nsIWebProgress> webProgress = do_GetInterface(docShell);
++
++  if (NS_WARN_IF(!webProgress)) {
++    promise->Reject(NS_ERROR_FAILURE, __func__);
++    return;
++  }
++
++  RefPtr<ClientOpPromise> ref = promise;
++
++  RefPtr<WebProgressListener> listener =
++    new WebProgressListener(aOuterWindow, baseURI, promise.forget());
++
++
++  rv = webProgress->AddProgressListener(listener,
++                                        nsIWebProgress::NOTIFY_STATE_DOCUMENT);
++  if (NS_WARN_IF(NS_FAILED(rv))) {
++    promise->Reject(rv, __func__);
++    return;
++  }
++
++  // Hold the listener alive until the promise settles
++  ref->Then(aOuterWindow->EventTargetFor(TaskCategory::Other), __func__,
++    [listener] (const ClientOpResult& aResult) { },
++    [listener] (nsresult aResult) { });
++}
++
++#ifdef MOZ_WIDGET_ANDROID
++
++class LaunchObserver final : public nsIObserver
++{
++  RefPtr<GenericPromise::Private> mPromise;
++
++  LaunchObserver()
++    : mPromise(new GenericPromise::Private(__func__))
++  {
++  }
++
++  ~LaunchObserver() = default;
++
++  NS_IMETHOD
++  Observe(nsISupports* aSubject, const char* aTopic, const char16_t * aData) override
++  {
++    nsCOMPtr<nsIObserverService> os = services::GetObserverService();
++    if (os) {
++      os->RemoveObserver(this, "BrowserChrome:Ready");
++    }
++    mPromise->Resolve(true, __func__);
++    return NS_OK;
++  }
++
++public:
++  static already_AddRefed<LaunchObserver>
++  Create()
++  {
++    nsCOMPtr<nsIObserverService> os = services::GetObserverService();
++    if (NS_WARN_IF(!os)) {
++      return nullptr;
++    }
++
++    RefPtr<LaunchObserver> ref = new LaunchObserver();
++
++    nsresult rv = os->AddObserver(ref, "BrowserChrome:Ready", /* weakRef */ false);
++    if (NS_WARN_IF(NS_FAILED(rv))) {
++      return nullptr;
++    }
++
++    return ref.forget();
++  }
++
++  void
++  Cancel()
++  {
++    nsCOMPtr<nsIObserverService> os = services::GetObserverService();
++    if (os) {
++      os->RemoveObserver(this, "BrowserChrome:Ready");
++    }
++    mPromise->Reject(NS_ERROR_ABORT, __func__);
++  }
++
++  GenericPromise*
++  Promise()
++  {
++    return mPromise;
++  }
++
++  NS_DECL_ISUPPORTS
++};
++
++NS_IMPL_ISUPPORTS(LaunchObserver, nsIObserver);
++
++#endif // MOZ_WIDGET_ANDROID
++
++} // anonymous namespace
++
++already_AddRefed<ClientOpPromise>
++ClientOpenWindowInCurrentProcess(const ClientOpenWindowArgs& aArgs)
++{
++  RefPtr<ClientOpPromise::Private> promise =
++    new ClientOpPromise::Private(__func__);
++  RefPtr<ClientOpPromise> ref = promise;
++
++#ifdef MOZ_WIDGET_ANDROID
++  // This fires an intent that will start launching Fennec and foreground it,
++  // if necessary.  We create an observer so that we can determine when
++  // the launch has completed.
++  RefPtr<LaunchObserver> launchObserver = LaunchObserver::Create();
++  java::GeckoApp::LaunchOrBringToFront();
++#endif // MOZ_WIDGET_ANDROID
++
++  nsCOMPtr<nsPIDOMWindowOuter> outerWindow;
++  nsresult rv = OpenWindow(aArgs, getter_AddRefs(outerWindow));
++
++#ifdef MOZ_WIDGET_ANDROID
++  // If we get the NOT_AVAILABLE error that means the browser is still
++  // launching on android.  Use the observer we created above to wait
++  // until the launch completes and then try to open the window again.
++  if (rv == NS_ERROR_NOT_AVAILABLE && launchObserver) {
++    RefPtr<GenericPromise> p = launchObserver->Promise();
++    p->Then(outerWindow->EventTargetFor(TaskCategory::Other), __func__,
++      [aArgs, promise] (bool aResult) {
++        nsCOMPtr<nsPIDOMWindowOuter> outerWindow;
++        nsresult rv = OpenWindow(aArgs, getter_AddRefs(outerWindow));
++        if (NS_WARN_IF(NS_FAILED(rv))) {
++          promise->Reject(rv, __func__);
++        }
++
++        WaitForLoad(aArgs, outerWindow, promise);
++      }, [promise] (nsresult aResult) {
++        promise->Reject(aResult, __func__);
++      });
++    return ref.forget();
++  }
++
++  // If we didn't get the NOT_AVAILABLE error then there is no need
++  // wait for the browser to launch.  Cancel the observer so that it
++  // will release.
++  if (launchObserver) {
++    launchObserver->Cancel();
++  }
++#endif // MOZ_WIDGET_ANDROID
++
++  if (NS_WARN_IF(NS_FAILED(rv))) {
++    promise->Reject(rv, __func__);
++    return ref.forget();
++  }
++
++  MOZ_DIAGNOSTIC_ASSERT(outerWindow);
++  WaitForLoad(aArgs, outerWindow, promise);
++
++  return ref.forget();
++}
++
++} // namespace dom
++} // namespace mozilla
+diff --git a/dom/clients/manager/ClientOpenWindowUtils.h b/dom/clients/manager/ClientOpenWindowUtils.h
+new file mode 100644
+--- /dev/null
++++ b/dom/clients/manager/ClientOpenWindowUtils.h
+@@ -0,0 +1,21 @@
++/* -*- 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/. */
++#ifndef _mozilla_dom_ClientOpenWindowUtils_h
++#define _mozilla_dom_ClientOpenWindowUtils_h
++
++#include "ClientOpPromise.h"
++#include "mozilla/dom/ClientIPCTypes.h"
++
++namespace mozilla {
++namespace dom {
++
++already_AddRefed<ClientOpPromise>
++ClientOpenWindowInCurrentProcess(const ClientOpenWindowArgs &aArgs);
++
++} // namespace dom
++} // namespace mozilla
++
++#endif // _mozilla_dom_ClientOpenWindowUtils_h
+diff --git a/dom/clients/manager/moz.build b/dom/clients/manager/moz.build
+--- a/dom/clients/manager/moz.build
++++ b/dom/clients/manager/moz.build
+@@ -33,16 +33,17 @@ UNIFIED_SOURCES += [
+   'ClientManagerOpParent.cpp',
+   'ClientManagerParent.cpp',
+   'ClientManagerService.cpp',
+   'ClientNavigateOpChild.cpp',
+   'ClientNavigateOpParent.cpp',
+   'ClientOpenWindowOpActors.cpp',
+   'ClientOpenWindowOpChild.cpp',
+   'ClientOpenWindowOpParent.cpp',
++  'ClientOpenWindowUtils.cpp',
+   'ClientPrefs.cpp',
+   'ClientSource.cpp',
+   'ClientSourceChild.cpp',
+   'ClientSourceOpChild.cpp',
+   'ClientSourceOpParent.cpp',
+   'ClientSourceParent.cpp',
+   'ClientState.cpp',
+   'ClientValidation.cpp',
+diff --git a/dom/ipc/ContentParent.h b/dom/ipc/ContentParent.h
+--- a/dom/ipc/ContentParent.h
++++ b/dom/ipc/ContentParent.h
+@@ -629,16 +629,18 @@ public:
+   void OnCompositorDeviceReset() override;
+ 
+   virtual PClientOpenWindowOpParent*
+   AllocPClientOpenWindowOpParent(const ClientOpenWindowArgs& aArgs) override;
+ 
+   virtual bool
+   DeallocPClientOpenWindowOpParent(PClientOpenWindowOpParent* aActor) override;
+ 
++  static hal::ProcessPriority GetInitialProcessPriority(Element* aFrameElement);
++
+   // Control the priority of the IPC messages for input events.
+   void SetInputPriorityEventEnabled(bool aEnabled);
+   bool IsInputPriorityEventEnabled()
+   {
+     return mIsInputPriorityEventEnabled;
+   }
+ 
+   static bool IsInputEventQueueSupported();
+@@ -662,18 +664,16 @@ private:
+    * removed from this list, but will still be in the sContentParents list for
+    * the GetAll/GetAllEvenIfDead APIs.
+    */
+   static nsClassHashtable<nsStringHashKey, nsTArray<ContentParent*>>* sBrowserContentParents;
+   static nsTArray<ContentParent*>* sPrivateContent;
+   static nsDataHashtable<nsUint32HashKey, ContentParent*> *sJSPluginContentParents;
+   static StaticAutoPtr<LinkedList<ContentParent> > sContentParents;
+ 
+-  static hal::ProcessPriority GetInitialProcessPriority(Element* aFrameElement);
+-
+   static ContentBridgeParent* CreateContentBridgeParent(const TabContext& aContext,
+                                                         const hal::ProcessPriority& aPriority,
+                                                         const TabId& aOpenerTabId,
+                                                         const TabId& aTabId);
+ 
+   // Hide the raw constructor methods since we don't want client code
+   // using them.
+   virtual PBrowserParent* SendPBrowserConstructor(
+diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js
+--- a/modules/libpref/init/all.js
++++ b/modules/libpref/init/all.js
+@@ -5930,16 +5930,23 @@ pref("layers.advanced.columnRule-layers"
+ pref("layers.advanced.image-layers", 2);
+ pref("layers.advanced.outline-layers", 2);
+ pref("layers.advanced.solid-color", false);
+ pref("layers.advanced.table", false);
+ 
+ // Enable lowercased response header name
+ pref("dom.xhr.lowercase_header.enabled", false);
+ 
++// Control whether clients.openWindow() opens windows in the same process
++// that called the API vs following our normal multi-process selection
++// algorithm.  Restricting openWindow to same process improves service worker
++// web compat in the short term.  Once the SW multi-e10s refactor is complete
++// this can be removed.
++pref("dom.clients.openwindow_favors_same_process", true);
++
+ // When a crash happens, whether to include heap regions of the crash context
+ // in the minidump. Enabled by default on nightly and aurora.
+ #ifdef RELEASE_OR_BETA
+ pref("toolkit.crashreporter.include_context_heap", false);
+ #else
+ pref("toolkit.crashreporter.include_context_heap", true);
+ #endif
+ 

+ 294 - 0
frg/work-js/mozilla-release/patches/1424338-5-59a1.patch

@@ -0,0 +1,294 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1512762403 18000
+# Node ID 156315ff3c4c6b48e14eba799b182519baf8dae1
+# Parent  292eec18ec99655e8e110424f0ad4dda51261fd9
+Bug 1424338 P5 Implement ClientHandle::Focus(). r=baku
+
+diff --git a/dom/clients/manager/ClientHandle.cpp b/dom/clients/manager/ClientHandle.cpp
+--- a/dom/clients/manager/ClientHandle.cpp
++++ b/dom/clients/manager/ClientHandle.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 "ClientHandle.h"
+ 
+ #include "ClientHandleChild.h"
+ #include "ClientHandleOpChild.h"
+ #include "ClientManager.h"
++#include "ClientState.h"
+ #include "mozilla/dom/PClientManagerChild.h"
+ #include "mozilla/dom/ServiceWorkerDescriptor.h"
+ 
+ namespace mozilla {
+ namespace dom {
+ 
+ ClientHandle::~ClientHandle()
+ {
+@@ -117,10 +118,29 @@ ClientHandle::Control(const ServiceWorke
+     },
+     [outerPromise](const ClientOpResult& aResult) {
+       outerPromise->Reject(aResult.get_nsresult(), __func__);
+     });
+ 
+   return outerPromise.forget();
+ }
+ 
++RefPtr<ClientStatePromise>
++ClientHandle::Focus()
++{
++  RefPtr<ClientStatePromise::Private> outerPromise =
++    new ClientStatePromise::Private(__func__);
++
++  RefPtr<ClientOpPromise> innerPromise = StartOp(ClientFocusArgs());
++
++  innerPromise->Then(mSerialEventTarget, __func__,
++    [outerPromise](const ClientOpResult& aResult) {
++      outerPromise->Resolve(ClientState::FromIPC(aResult.get_IPCClientState()), __func__);
++    }, [outerPromise](const ClientOpResult& aResult) {
++      outerPromise->Reject(aResult.get_nsresult(), __func__);
++    });
++
++  RefPtr<ClientStatePromise> ref = outerPromise.get();
++  return ref.forget();
++}
++
+ } // namespace dom
+ } // namespace mozilla
+diff --git a/dom/clients/manager/ClientHandle.h b/dom/clients/manager/ClientHandle.h
+--- a/dom/clients/manager/ClientHandle.h
++++ b/dom/clients/manager/ClientHandle.h
+@@ -65,15 +65,21 @@ public:
+   Info() const;
+ 
+   // Mark the ClientSource attached to this handle as controlled by the
+   // given service worker.  The promise will resolve true if the ClientSource
+   // is successfully marked or reject if the operation could not be completed.
+   RefPtr<GenericPromise>
+   Control(const ServiceWorkerDescriptor& aServiceWorker);
+ 
++  // Focus the Client if possible.  If successful the promise will resolve with
++  // a new ClientState snapshot after focus has completed.  If focusing fails
++  // for any reason then the promise will reject.
++  RefPtr<ClientStatePromise>
++  Focus();
++
+   NS_INLINE_DECL_REFCOUNTING(ClientHandle);
+ };
+ 
+ } // namespace dom
+ } // namespace mozilla
+ 
+ #endif // _mozilla_dom_ClientHandle_h
+diff --git a/dom/clients/manager/ClientIPCTypes.ipdlh b/dom/clients/manager/ClientIPCTypes.ipdlh
+--- a/dom/clients/manager/ClientIPCTypes.ipdlh
++++ b/dom/clients/manager/ClientIPCTypes.ipdlh
+@@ -62,16 +62,20 @@ struct ClientSourceExecutionReadyArgs
+   FrameType frameType;
+ };
+ 
+ struct ClientControlledArgs
+ {
+   IPCServiceWorkerDescriptor serviceWorker;
+ };
+ 
++struct ClientFocusArgs
++{
++};
++
+ struct ClientNavigateArgs
+ {
+   IPCClientInfo target;
+   nsCString url;
+   nsCString baseURL;
+ };
+ 
+ union ClientEndPoint
+@@ -103,16 +107,17 @@ struct ClientOpenWindowArgs
+   PrincipalInfo principalInfo;
+   nsCString url;
+   nsCString baseURL;
+ };
+ 
+ union ClientOpConstructorArgs
+ {
+   ClientControlledArgs;
++  ClientFocusArgs;
+   ClientNavigateArgs;
+   ClientMatchAllArgs;
+   ClientClaimArgs;
+   ClientGetInfoAndStateArgs;
+   ClientOpenWindowArgs;
+ };
+ 
+ struct ClientList
+@@ -125,14 +130,15 @@ struct ClientNavigateOpConstructorArgs
+   PClientSource target;
+   nsCString url;
+   nsCString baseURL;
+ };
+ 
+ union ClientOpResult
+ {
+   nsresult;
++  IPCClientState;
+   ClientInfoAndState;
+   ClientList;
+ };
+ 
+ } // namespace dom
+ } // namespace mozilla
+diff --git a/dom/clients/manager/ClientSource.cpp b/dom/clients/manager/ClientSource.cpp
+--- a/dom/clients/manager/ClientSource.cpp
++++ b/dom/clients/manager/ClientSource.cpp
+@@ -8,16 +8,17 @@
+ 
+ #include "ClientManager.h"
+ #include "ClientManagerChild.h"
+ #include "ClientSourceChild.h"
+ #include "ClientState.h"
+ #include "ClientValidation.h"
+ #include "mozilla/dom/ClientIPCTypes.h"
+ #include "mozilla/dom/WorkerPrivate.h"
++#include "nsContentUtils.h"
+ #include "nsIDocShell.h"
+ #include "nsPIDOMWindow.h"
+ 
+ namespace mozilla {
+ namespace dom {
+ 
+ using mozilla::dom::workers::WorkerPrivate;
+ using mozilla::ipc::PrincipalInfo;
+@@ -357,16 +358,65 @@ ClientSource::Control(const ClientContro
+ 
+ const Maybe<ServiceWorkerDescriptor>&
+ ClientSource::GetController() const
+ {
+   return mController;
+ }
+ 
+ RefPtr<ClientOpPromise>
++ClientSource::Focus(const ClientFocusArgs& aArgs)
++{
++  NS_ASSERT_OWNINGTHREAD(ClientSource);
++
++  RefPtr<ClientOpPromise> ref;
++
++  if (mClientInfo.Type() != ClientType::Window) {
++    ref = ClientOpPromise::CreateAndReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
++                                           __func__);
++    return ref.forget();
++  }
++  nsPIDOMWindowOuter* outer = nullptr;
++
++  nsPIDOMWindowInner* inner = GetInnerWindow();
++  if (inner) {
++    outer = inner->GetOuterWindow();
++  } else {
++    nsIDocShell* docshell = GetDocShell();
++    if (docshell) {
++      outer = docshell->GetWindow();
++    }
++  }
++
++  if (!outer) {
++    ref = ClientOpPromise::CreateAndReject(NS_ERROR_DOM_INVALID_STATE_ERR,
++                                           __func__);
++    return ref.forget();
++  }
++
++  MOZ_ASSERT(NS_IsMainThread());
++
++  nsresult rv = nsContentUtils::DispatchFocusChromeEvent(outer);
++  if (NS_FAILED(rv)) {
++    ref = ClientOpPromise::CreateAndReject(rv, __func__);
++    return ref.forget();
++  }
++
++  ClientState state;
++  rv = SnapshotState(&state);
++  if (NS_FAILED(rv)) {
++    ref = ClientOpPromise::CreateAndReject(rv, __func__);
++    return ref.forget();
++  }
++
++  ref = ClientOpPromise::CreateAndResolve(state.ToIPC(), __func__);
++  return ref.forget();
++}
++
++RefPtr<ClientOpPromise>
+ ClientSource::Claim(const ClientClaimArgs& aArgs)
+ {
+   SetController(ServiceWorkerDescriptor(aArgs.serviceWorker()));
+ 
+   RefPtr<ClientOpPromise> ref =
+     ClientOpPromise::CreateAndResolve(NS_OK, __func__);
+ 
+   return ref.forget();
+diff --git a/dom/clients/manager/ClientSource.h b/dom/clients/manager/ClientSource.h
+--- a/dom/clients/manager/ClientSource.h
++++ b/dom/clients/manager/ClientSource.h
+@@ -16,16 +16,17 @@ class nsIDocShell;
+ class nsISerialEventTarget;
+ class nsPIDOMWindowInner;
+ 
+ namespace mozilla {
+ namespace dom {
+ 
+ class ClientClaimArgs;
+ class ClientControlledArgs;
++class ClientFocusArgs;
+ class ClientManager;
+ class ClientSourceChild;
+ class ClientSourceConstructorArgs;
+ class ClientSourceExecutionReadyArgs;
+ class PClientManagerChild;
+ 
+ namespace workers {
+ class WorkerPrivate;
+@@ -128,16 +129,19 @@ public:
+   Control(const ClientControlledArgs& aArgs);
+ 
+   // Get the ClientSource's current controlling service worker, if one has
+   // been set.
+   const Maybe<ServiceWorkerDescriptor>&
+   GetController() const;
+ 
+   RefPtr<ClientOpPromise>
++  Focus(const ClientFocusArgs& aArgs);
++
++  RefPtr<ClientOpPromise>
+   Claim(const ClientClaimArgs& aArgs);
+ 
+   RefPtr<ClientOpPromise>
+   GetInfoAndState(const ClientGetInfoAndStateArgs& aArgs);
+ 
+   nsresult
+   SnapshotState(ClientState* aStateOut);
+ 
+diff --git a/dom/clients/manager/ClientSourceOpChild.cpp b/dom/clients/manager/ClientSourceOpChild.cpp
+--- a/dom/clients/manager/ClientSourceOpChild.cpp
++++ b/dom/clients/manager/ClientSourceOpChild.cpp
+@@ -74,16 +74,21 @@ void
+ ClientSourceOpChild::Init(const ClientOpConstructorArgs& aArgs)
+ {
+   switch (aArgs.type()) {
+     case ClientOpConstructorArgs::TClientControlledArgs:
+     {
+       DoSourceOp(&ClientSource::Control, aArgs.get_ClientControlledArgs());
+       break;
+     }
++    case ClientOpConstructorArgs::TClientFocusArgs:
++    {
++      DoSourceOp(&ClientSource::Focus, aArgs.get_ClientFocusArgs());
++      break;
++    }
+     case ClientOpConstructorArgs::TClientClaimArgs:
+     {
+       DoSourceOp(&ClientSource::Claim, aArgs.get_ClientClaimArgs());
+       break;
+     }
+     case ClientOpConstructorArgs::TClientGetInfoAndStateArgs:
+     {
+       DoSourceOp(&ClientSource::GetInfoAndState,

+ 509 - 0
frg/work-js/mozilla-release/patches/1424338-6-59a1.patch

@@ -0,0 +1,509 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1512762403 18000
+# Node ID 3d35790aaf573a84e737b336c815cde017cdb882
+# Parent  cc506d7ead55ad04984013672d139b0067c82799
+Bug 1424338 P6 Implement ClientHandle::PostMessage() r=baku
+
+diff --git a/dom/clients/manager/ClientHandle.cpp b/dom/clients/manager/ClientHandle.cpp
+--- a/dom/clients/manager/ClientHandle.cpp
++++ b/dom/clients/manager/ClientHandle.cpp
+@@ -7,20 +7,23 @@
+ #include "ClientHandle.h"
+ 
+ #include "ClientHandleChild.h"
+ #include "ClientHandleOpChild.h"
+ #include "ClientManager.h"
+ #include "ClientState.h"
+ #include "mozilla/dom/PClientManagerChild.h"
+ #include "mozilla/dom/ServiceWorkerDescriptor.h"
++#include "mozilla/dom/ipc/StructuredCloneData.h"
+ 
+ namespace mozilla {
+ namespace dom {
+ 
++using mozilla::dom::ipc::StructuredCloneData;
++
+ ClientHandle::~ClientHandle()
+ {
+   Shutdown();
+ }
+ 
+ void
+ ClientHandle::Shutdown()
+ {
+@@ -137,10 +140,45 @@ ClientHandle::Focus()
+     }, [outerPromise](const ClientOpResult& aResult) {
+       outerPromise->Reject(aResult.get_nsresult(), __func__);
+     });
+ 
+   RefPtr<ClientStatePromise> ref = outerPromise.get();
+   return ref.forget();
+ }
+ 
++RefPtr<GenericPromise>
++ClientHandle::PostMessage(StructuredCloneData& aData,
++                          const ServiceWorkerDescriptor& aSource)
++{
++  RefPtr<GenericPromise> ref;
++
++  if (IsShutdown()) {
++    ref = GenericPromise::CreateAndReject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__);
++    return ref.forget();
++  }
++
++  ClientPostMessageArgs args;
++  args.serviceWorker() = aSource.ToIPC();
++
++  if (!aData.BuildClonedMessageDataForBackgroundChild(GetActor()->Manager()->Manager(),
++                                                      args.clonedData())) {
++    ref = GenericPromise::CreateAndReject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__);
++    return ref.forget();
++  }
++
++  RefPtr<GenericPromise::Private> outerPromise =
++    new GenericPromise::Private(__func__);
++
++  RefPtr<ClientOpPromise> innerPromise = StartOp(args);
++  innerPromise->Then(mSerialEventTarget, __func__,
++    [outerPromise](const ClientOpResult& aResult) {
++      outerPromise->Resolve(true, __func__);
++    }, [outerPromise](const ClientOpResult& aResult) {
++      outerPromise->Reject(aResult.get_nsresult(), __func__);
++    });
++
++  ref = outerPromise.get();
++  return ref.forget();
++}
++
+ } // namespace dom
+ } // namespace mozilla
+diff --git a/dom/clients/manager/ClientHandle.h b/dom/clients/manager/ClientHandle.h
+--- a/dom/clients/manager/ClientHandle.h
++++ b/dom/clients/manager/ClientHandle.h
+@@ -20,16 +20,20 @@ namespace mozilla {
+ namespace dom {
+ 
+ class ClientManager;
+ class ClientHandleChild;
+ class ClientOpConstructorArgs;
+ class PClientManagerChild;
+ class ServiceWorkerDescriptor;
+ 
++namespace ipc {
++class StructuredCloneData;
++}
++
+ // The ClientHandle allows code to take a simple ClientInfo struct and
+ // convert it into a live actor-backed object attached to a particular
+ // ClientSource somewhere in the browser.  If the ClientSource is
+ // destroyed then the ClientHandle will simply begin to reject operations.
+ // We do not currently provide a way to be notified when the ClientSource
+ // is destroyed, but this could be added in the future.
+ class ClientHandle final : public ClientThing<ClientHandleChild>
+ {
+@@ -71,15 +75,25 @@ public:
+   Control(const ServiceWorkerDescriptor& aServiceWorker);
+ 
+   // Focus the Client if possible.  If successful the promise will resolve with
+   // a new ClientState snapshot after focus has completed.  If focusing fails
+   // for any reason then the promise will reject.
+   RefPtr<ClientStatePromise>
+   Focus();
+ 
++  // Send a postMessage() call to the target Client.  Currently this only
++  // supports sending from a ServiceWorker source and the MessageEvent is
++  // dispatched to the Client's navigator.serviceWorker event target.  The
++  // returned promise will resolve if the MessageEvent is dispatched or if
++  // it triggers an error handled in the Client's context.  Other errors
++  // will result in the promise rejecting.
++  RefPtr<GenericPromise>
++  PostMessage(ipc::StructuredCloneData& aData,
++              const ServiceWorkerDescriptor& aSource);
++
+   NS_INLINE_DECL_REFCOUNTING(ClientHandle);
+ };
+ 
+ } // namespace dom
+ } // namespace mozilla
+ 
+ #endif // _mozilla_dom_ClientHandle_h
+diff --git a/dom/clients/manager/ClientHandleOpParent.cpp b/dom/clients/manager/ClientHandleOpParent.cpp
+--- a/dom/clients/manager/ClientHandleOpParent.cpp
++++ b/dom/clients/manager/ClientHandleOpParent.cpp
+@@ -3,16 +3,17 @@
+ /* 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 "ClientHandleOpParent.h"
+ 
+ #include "ClientHandleParent.h"
+ #include "ClientSourceParent.h"
++#include "mozilla/dom/PClientManagerParent.h"
+ 
+ namespace mozilla {
+ namespace dom {
+ 
+ ClientSourceParent*
+ ClientHandleOpParent::GetSource() const
+ {
+   auto handle = static_cast<ClientHandleParent*>(Manager());
+@@ -29,17 +30,43 @@ void
+ ClientHandleOpParent::Init(const ClientOpConstructorArgs& aArgs)
+ {
+   ClientSourceParent* source = GetSource();
+   if (!source) {
+     Unused << PClientHandleOpParent::Send__delete__(this, NS_ERROR_DOM_ABORT_ERR);
+     return;
+   }
+ 
+-  RefPtr<ClientOpPromise> p = source->StartOp(aArgs);
++  RefPtr<ClientOpPromise> p;
++
++  // ClientPostMessageArgs can contain PBlob actors.  This means we
++  // can't just forward the args from one PBackground manager to
++  // another.  Instead, unpack the structured clone data and repack
++  // it into a new set of arguments.
++  if (aArgs.type() == ClientOpConstructorArgs::TClientPostMessageArgs) {
++    const ClientPostMessageArgs& orig = aArgs.get_ClientPostMessageArgs();
++
++    ClientPostMessageArgs rebuild;
++    rebuild.serviceWorker() = orig.serviceWorker();
++
++    StructuredCloneData data;
++    data.BorrowFromClonedMessageDataForBackgroundParent(orig.clonedData());
++    if (!data.BuildClonedMessageDataForBackgroundParent(source->Manager()->Manager(),
++                                                        rebuild.clonedData())) {
++      Unused << PClientHandleOpParent::Send__delete__(this, NS_ERROR_DOM_ABORT_ERR);
++      return;
++    }
++
++    p = source->StartOp(rebuild);
++  }
++
++  // Other argument types can just be forwarded straight through.
++  else {
++    p = source->StartOp(aArgs);
++  }
+ 
+   // Capturing 'this' is safe here because we disconnect the promise in
+   // ActorDestroy() which ensures neither lambda is called if the actor
+   // is destroyed before the source operation completes.
+   p->Then(GetCurrentThreadSerialEventTarget(), __func__,
+     [this] (const ClientOpResult& aResult) {
+       mPromiseRequestHolder.Complete();
+       Unused << PClientHandleOpParent::Send__delete__(this, aResult);
+diff --git a/dom/clients/manager/ClientIPCTypes.ipdlh b/dom/clients/manager/ClientIPCTypes.ipdlh
+--- a/dom/clients/manager/ClientIPCTypes.ipdlh
++++ b/dom/clients/manager/ClientIPCTypes.ipdlh
+@@ -79,16 +79,22 @@ struct ClientNavigateArgs
+ };
+ 
+ union ClientEndPoint
+ {
+   IPCClientInfo;
+   IPCServiceWorkerDescriptor;
+ };
+ 
++struct ClientPostMessageArgs
++{
++  ClonedMessageData clonedData;
++  IPCServiceWorkerDescriptor serviceWorker;
++};
++
+ struct ClientMatchAllArgs
+ {
+   ClientEndPoint endpoint;
+   ClientType type;
+   bool includeUncontrolled;
+ };
+ 
+ struct ClientClaimArgs
+@@ -109,16 +115,17 @@ struct ClientOpenWindowArgs
+   nsCString baseURL;
+ };
+ 
+ union ClientOpConstructorArgs
+ {
+   ClientControlledArgs;
+   ClientFocusArgs;
+   ClientNavigateArgs;
++  ClientPostMessageArgs;
+   ClientMatchAllArgs;
+   ClientClaimArgs;
+   ClientGetInfoAndStateArgs;
+   ClientOpenWindowArgs;
+ };
+ 
+ struct ClientList
+ {
+diff --git a/dom/clients/manager/ClientSource.cpp b/dom/clients/manager/ClientSource.cpp
+--- a/dom/clients/manager/ClientSource.cpp
++++ b/dom/clients/manager/ClientSource.cpp
+@@ -7,26 +7,39 @@
+ #include "ClientSource.h"
+ 
+ #include "ClientManager.h"
+ #include "ClientManagerChild.h"
+ #include "ClientSourceChild.h"
+ #include "ClientState.h"
+ #include "ClientValidation.h"
+ #include "mozilla/dom/ClientIPCTypes.h"
++#include "mozilla/dom/ipc/StructuredCloneData.h"
++#include "mozilla/dom/MessageEvent.h"
++#include "mozilla/dom/MessageEventBinding.h"
++#include "mozilla/dom/Navigator.h"
+ #include "mozilla/dom/WorkerPrivate.h"
++#include "mozilla/dom/WorkerScope.h"
++#include "mozilla/dom/ServiceWorkerContainer.h"
++#include "mozilla/dom/workers/ServiceWorkerManager.h"
++#include "mozilla/dom/workers/bindings/ServiceWorker.h"
+ #include "nsContentUtils.h"
+ #include "nsIDocShell.h"
+ #include "nsPIDOMWindow.h"
+ 
+ namespace mozilla {
+ namespace dom {
+ 
++using mozilla::dom::ipc::StructuredCloneData;
++using mozilla::dom::workers::ServiceWorkerInfo;
++using mozilla::dom::workers::ServiceWorkerManager;
++using mozilla::dom::workers::ServiceWorkerRegistrationInfo;
+ using mozilla::dom::workers::WorkerPrivate;
+ using mozilla::ipc::PrincipalInfo;
++using mozilla::ipc::PrincipalInfoToPrincipal;
+ 
+ void
+ ClientSource::Shutdown()
+ {
+   NS_ASSERT_OWNINGTHREAD(ClientSource);
+   if (IsShutdown()) {
+     return;
+   }
+@@ -407,16 +420,138 @@ ClientSource::Focus(const ClientFocusArg
+     return ref.forget();
+   }
+ 
+   ref = ClientOpPromise::CreateAndResolve(state.ToIPC(), __func__);
+   return ref.forget();
+ }
+ 
+ RefPtr<ClientOpPromise>
++ClientSource::PostMessage(const ClientPostMessageArgs& aArgs)
++{
++  NS_ASSERT_OWNINGTHREAD(ClientSource);
++  RefPtr<ClientOpPromise> ref;
++
++  ServiceWorkerDescriptor source(aArgs.serviceWorker());
++  const PrincipalInfo& principalInfo = source.PrincipalInfo();
++
++  StructuredCloneData clonedData;
++  clonedData.BorrowFromClonedMessageDataForBackgroundChild(aArgs.clonedData());
++
++  // Currently we only support firing these messages on window Clients.
++  // Once we expose ServiceWorkerContainer and the ServiceWorker on Worker
++  // threads then this will need to change.  See bug 1113522.
++  if (mClientInfo.Type() != ClientType::Window) {
++    ref = ClientOpPromise::CreateAndReject(NS_ERROR_NOT_IMPLEMENTED, __func__);
++    return ref.forget();
++  }
++
++  MOZ_ASSERT(NS_IsMainThread());
++
++  RefPtr<ServiceWorkerContainer> target;
++  nsCOMPtr<nsIGlobalObject> globalObject;
++
++  // We don't need to force the creation of the about:blank document
++  // here because there is no postMessage listener.  If a listener
++  // was registered then the document will already be created.
++  nsPIDOMWindowInner* window = GetInnerWindow();
++  if (window) {
++    globalObject = do_QueryInterface(window);
++    RefPtr<Navigator> navigator =
++      static_cast<Navigator*>(window->GetNavigator());
++    if (navigator) {
++      target = navigator->ServiceWorker();
++    }
++  }
++
++  if (NS_WARN_IF(!target)) {
++    ref = ClientOpPromise::CreateAndReject(NS_ERROR_DOM_INVALID_STATE_ERR,
++                                           __func__);
++    return ref.forget();
++  }
++
++  // If AutoJSAPI::Init() fails then either global is nullptr or not
++  // in a usable state.
++  AutoJSAPI jsapi;
++  if (!jsapi.Init(globalObject)) {
++    ref = ClientOpPromise::CreateAndResolve(NS_OK, __func__);
++    return ref.forget();
++  }
++
++  JSContext* cx = jsapi.cx();
++
++  ErrorResult result;
++  JS::Rooted<JS::Value> messageData(cx);
++  clonedData.Read(cx, &messageData, result);
++  if (result.MaybeSetPendingException(cx)) {
++    // We reported the error in the current window context.  Resolve
++    // promise instead of rejecting.
++    ref = ClientOpPromise::CreateAndResolve(NS_OK, __func__);
++    return ref.forget();
++  }
++
++  RootedDictionary<MessageEventInit> init(cx);
++
++  init.mData = messageData;
++  if (!clonedData.TakeTransferredPortsAsSequence(init.mPorts)) {
++    // Report the error in the current window context and resolve the
++    // promise instead of rejecting.
++    xpc::Throw(cx, NS_ERROR_OUT_OF_MEMORY);
++    ref = ClientOpPromise::CreateAndResolve(NS_OK, __func__);
++    return ref.forget();
++  }
++
++  nsresult rv = NS_OK;
++  nsCOMPtr<nsIPrincipal> principal =
++    PrincipalInfoToPrincipal(principalInfo, &rv);
++  if (NS_FAILED(rv) || !principal) {
++    ref = ClientOpPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
++    return ref.forget();
++  }
++
++  nsAutoCString origin;
++  rv = principal->GetOriginNoSuffix(origin);
++  if (NS_SUCCEEDED(rv)) {
++    CopyUTF8toUTF16(origin, init.mOrigin);
++  }
++
++  RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
++  if (!swm) {
++    // Shutting down. Just don't deliver this message.
++    ref = ClientOpPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
++    return ref.forget();
++  }
++
++  RefPtr<ServiceWorkerRegistrationInfo> reg =
++    swm->GetRegistration(principal, source.Scope());
++  if (reg) {
++    RefPtr<ServiceWorkerInfo> serviceWorker = reg->GetByID(source.Id());
++    if (serviceWorker) {
++      init.mSource.SetValue().SetAsServiceWorker() =
++        serviceWorker->GetOrCreateInstance(GetInnerWindow());
++    }
++  }
++
++  RefPtr<MessageEvent> event =
++    MessageEvent::Constructor(target, NS_LITERAL_STRING("message"), init);
++  event->SetTrusted(true);
++
++  bool preventDefaultCalled = false;
++  rv = target->DispatchEvent(static_cast<dom::Event*>(event.get()),
++                             &preventDefaultCalled);
++  if (NS_FAILED(rv)) {
++    ref = ClientOpPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
++    return ref.forget();
++  }
++
++  ref = ClientOpPromise::CreateAndResolve(NS_OK, __func__);
++  return ref.forget();
++}
++
++RefPtr<ClientOpPromise>
+ ClientSource::Claim(const ClientClaimArgs& aArgs)
+ {
+   SetController(ServiceWorkerDescriptor(aArgs.serviceWorker()));
+ 
+   RefPtr<ClientOpPromise> ref =
+     ClientOpPromise::CreateAndResolve(NS_OK, __func__);
+ 
+   return ref.forget();
+diff --git a/dom/clients/manager/ClientSource.h b/dom/clients/manager/ClientSource.h
+--- a/dom/clients/manager/ClientSource.h
++++ b/dom/clients/manager/ClientSource.h
+@@ -7,30 +7,38 @@
+ #define _mozilla_dom_ClientSource_h
+ 
+ #include "mozilla/dom/ClientInfo.h"
+ #include "mozilla/dom/ClientOpPromise.h"
+ #include "mozilla/dom/ClientThing.h"
+ #include "mozilla/dom/ServiceWorkerDescriptor.h"
+ #include "mozilla/Variant.h"
+ 
++#ifdef XP_WIN
++#undef PostMessage
++#endif
++
+ class nsIDocShell;
+ class nsISerialEventTarget;
+ class nsPIDOMWindowInner;
+ 
+ namespace mozilla {
+ namespace dom {
+ 
+ class ClientClaimArgs;
+ class ClientControlledArgs;
+ class ClientFocusArgs;
++class ClientGetInfoAndStateArgs;
+ class ClientManager;
++class ClientPostMessageArgs;
+ class ClientSourceChild;
+ class ClientSourceConstructorArgs;
+ class ClientSourceExecutionReadyArgs;
++class ClientState;
++class ClientWindowState;
+ class PClientManagerChild;
+ 
+ namespace workers {
+ class WorkerPrivate;
+ } // workers namespace
+ 
+ // ClientSource is an RAII style class that is designed to be held via
+ // a UniquePtr<>.  When created ClientSource will register the existence
+@@ -132,16 +140,19 @@ public:
+   // been set.
+   const Maybe<ServiceWorkerDescriptor>&
+   GetController() const;
+ 
+   RefPtr<ClientOpPromise>
+   Focus(const ClientFocusArgs& aArgs);
+ 
+   RefPtr<ClientOpPromise>
++  PostMessage(const ClientPostMessageArgs& aArgs);
++
++  RefPtr<ClientOpPromise>
+   Claim(const ClientClaimArgs& aArgs);
+ 
+   RefPtr<ClientOpPromise>
+   GetInfoAndState(const ClientGetInfoAndStateArgs& aArgs);
+ 
+   nsresult
+   SnapshotState(ClientState* aStateOut);
+ 
+diff --git a/dom/clients/manager/ClientSourceOpChild.cpp b/dom/clients/manager/ClientSourceOpChild.cpp
+--- a/dom/clients/manager/ClientSourceOpChild.cpp
++++ b/dom/clients/manager/ClientSourceOpChild.cpp
+@@ -79,16 +79,21 @@ ClientSourceOpChild::Init(const ClientOp
+       DoSourceOp(&ClientSource::Control, aArgs.get_ClientControlledArgs());
+       break;
+     }
+     case ClientOpConstructorArgs::TClientFocusArgs:
+     {
+       DoSourceOp(&ClientSource::Focus, aArgs.get_ClientFocusArgs());
+       break;
+     }
++    case ClientOpConstructorArgs::TClientPostMessageArgs:
++    {
++      DoSourceOp(&ClientSource::PostMessage, aArgs.get_ClientPostMessageArgs());
++      break;
++    }
+     case ClientOpConstructorArgs::TClientClaimArgs:
+     {
+       DoSourceOp(&ClientSource::Claim, aArgs.get_ClientClaimArgs());
+       break;
+     }
+     case ClientOpConstructorArgs::TClientGetInfoAndStateArgs:
+     {
+       DoSourceOp(&ClientSource::GetInfoAndState,

+ 105 - 0
frg/work-js/mozilla-release/patches/1424338-7-59a1.patch

@@ -0,0 +1,105 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1512762403 18000
+# Node ID 22b5172a00a377f2cd6b2bff9920af845d602922
+# Parent  1f0aed28227e986a7705041c3928c866f271ea23
+Bug 1424338 P7 Remove the ClientEndPoint union type for now. r=baku
+
+diff --git a/dom/clients/manager/ClientIPCTypes.ipdlh b/dom/clients/manager/ClientIPCTypes.ipdlh
+--- a/dom/clients/manager/ClientIPCTypes.ipdlh
++++ b/dom/clients/manager/ClientIPCTypes.ipdlh
+@@ -73,31 +73,25 @@ struct ClientFocusArgs
+ 
+ struct ClientNavigateArgs
+ {
+   IPCClientInfo target;
+   nsCString url;
+   nsCString baseURL;
+ };
+ 
+-union ClientEndPoint
+-{
+-  IPCClientInfo;
+-  IPCServiceWorkerDescriptor;
+-};
+-
+ struct ClientPostMessageArgs
+ {
+   ClonedMessageData clonedData;
+   IPCServiceWorkerDescriptor serviceWorker;
+ };
+ 
+ struct ClientMatchAllArgs
+ {
+-  ClientEndPoint endpoint;
++  IPCServiceWorkerDescriptor serviceWorker;
+   ClientType type;
+   bool includeUncontrolled;
+ };
+ 
+ struct ClientClaimArgs
+ {
+   IPCServiceWorkerDescriptor serviceWorker;
+ };
+diff --git a/dom/clients/manager/ClientManagerService.cpp b/dom/clients/manager/ClientManagerService.cpp
+--- a/dom/clients/manager/ClientManagerService.cpp
++++ b/dom/clients/manager/ClientManagerService.cpp
+@@ -432,22 +432,18 @@ public:
+ 
+ } // anonymous namespace
+ 
+ RefPtr<ClientOpPromise>
+ ClientManagerService::MatchAll(const ClientMatchAllArgs& aArgs)
+ {
+   AssertIsOnBackgroundThread();
+ 
+-  const ClientEndPoint& endpoint = aArgs.endpoint();
+-
+-  const PrincipalInfo& principalInfo =
+-    endpoint.type() == ClientEndPoint::TIPCClientInfo
+-      ? endpoint.get_IPCClientInfo().principalInfo()
+-      : endpoint.get_IPCServiceWorkerDescriptor().principalInfo();
++  ServiceWorkerDescriptor swd(aArgs.serviceWorker());
++  const PrincipalInfo& principalInfo = swd.PrincipalInfo();
+ 
+   RefPtr<PromiseListHolder> promiseList = new PromiseListHolder();
+ 
+   for (auto iter = mSourceTable.Iter(); !iter.Done(); iter.Next()) {
+     ClientSourceParent* source = iter.UserData();
+     MOZ_DIAGNOSTIC_ASSERT(source);
+ 
+     if (source->IsFrozen() || !source->ExecutionReady()) {
+@@ -459,31 +455,24 @@ ClientManagerService::MatchAll(const Cli
+       continue;
+     }
+ 
+     if (!MatchPrincipalInfo(source->Info().PrincipalInfo(), principalInfo)) {
+       continue;
+     }
+ 
+     if (!aArgs.includeUncontrolled()) {
+-      if (endpoint.type() != ClientEndPoint::TIPCServiceWorkerDescriptor) {
+-        continue;
+-      }
+-
+       const Maybe<ServiceWorkerDescriptor>& controller =
+         source->GetController();
+       if (controller.isNothing()) {
+         continue;
+       }
+ 
+-      const IPCServiceWorkerDescriptor& serviceWorker =
+-        endpoint.get_IPCServiceWorkerDescriptor();
+-
+-      if(controller.ref().Id() != serviceWorker.id() ||
+-         controller.ref().Scope() != serviceWorker.scope()) {
++      if(controller.ref().Id() != swd.Id() ||
++         controller.ref().Scope() != swd.Scope()) {
+         continue;
+       }
+     }
+ 
+     promiseList->AddPromise(
+       source->StartOp(Move(ClientGetInfoAndStateArgs(source->Info().Id(),
+                                                      source->Info().PrincipalInfo()))));
+   }

+ 395 - 0
frg/work-js/mozilla-release/patches/1424452-59a1.patch

@@ -0,0 +1,395 @@
+# HG changeset patch
+# User Gijs Kruitbosch <gijskruitbosch@gmail.com>
+# Date 1512823040 0
+# Node ID eb65296afea1c4f65949440cea58f8018a02533f
+# Parent  e8e8c02d83451f7022d1fa36e87c8c1e739988b4
+Bug 1424452 - stop having 2 different values for the 'panel' area type, r=jaws
+
+MozReview-Commit-ID: 3spDhRhlOTF
+
+diff --git a/browser/base/content/browser.css b/browser/base/content/browser.css
+--- a/browser/base/content/browser.css
++++ b/browser/base/content/browser.css
+@@ -340,17 +340,17 @@ toolbarpaletteitem {
+ 
+ /* Rules to help integrate SDK widgets */
+ toolbaritem[sdkstylewidget="true"] > toolbarbutton,
+ toolbarpaletteitem > toolbaritem[sdkstylewidget="true"] > iframe,
+ toolbarpaletteitem > toolbaritem[sdkstylewidget="true"] > .toolbarbutton-text {
+   display: none;
+ }
+ 
+-toolbarpaletteitem:-moz-any([place="palette"], [place="panel"]) > toolbaritem[sdkstylewidget="true"] > toolbarbutton {
++toolbarpaletteitem:-moz-any([place="palette"], [place="menu-panel"]) > toolbaritem[sdkstylewidget="true"] > toolbarbutton {
+   display: -moz-box;
+ }
+ 
+ toolbarpaletteitem > toolbaritem[sdkstylewidget="true"][cui-areatype="toolbar"] > .toolbarbutton-text {
+   display: -moz-box;
+ }
+ 
+ .webextension-browser-action > .toolbarbutton-badge-stack > .toolbarbutton-icon {
+@@ -439,17 +439,17 @@ toolbarbutton.webextension-menuitem > .t
+ 
+ toolbarpaletteitem[removable="false"] {
+   opacity: 0.5;
+   cursor: default;
+ }
+ 
+ %ifndef XP_MACOSX
+ toolbarpaletteitem[place="palette"],
+-toolbarpaletteitem[place="panel"],
++toolbarpaletteitem[place="menu-panel"],
+ toolbarpaletteitem[place="toolbar"] {
+   -moz-user-focus: normal;
+ }
+ %endif
+ 
+ #bookmarks-toolbar-placeholder,
+ toolbarpaletteitem > #personal-bookmarks > #PlacesToolbar,
+ #personal-bookmarks[cui-areatype="menu-panel"] > #PlacesToolbar,
+diff --git a/browser/components/customizableui/CustomizableUI.jsm b/browser/components/customizableui/CustomizableUI.jsm
+--- a/browser/components/customizableui/CustomizableUI.jsm
++++ b/browser/components/customizableui/CustomizableUI.jsm
+@@ -873,17 +873,17 @@ var CustomizableUIInternal = {
+   },
+ 
+   ensureButtonContextMenu(aNode, aAreaNode, forcePanel) {
+     const kPanelItemContextMenu = "customizationPanelItemContextMenu";
+ 
+     let currentContextMenu = aNode.getAttribute("context") ||
+                              aNode.getAttribute("contextmenu");
+     let contextMenuForPlace =
+-      (forcePanel || "panel" == CustomizableUI.getPlaceForItem(aAreaNode)) ?
++      (forcePanel || "menu-panel" == CustomizableUI.getPlaceForItem(aAreaNode)) ?
+       kPanelItemContextMenu :
+       null;
+     if (contextMenuForPlace && !currentContextMenu) {
+       aNode.setAttribute("context", contextMenuForPlace);
+     } else if (currentContextMenu == kPanelItemContextMenu &&
+                contextMenuForPlace != kPanelItemContextMenu) {
+       aNode.removeAttribute("context");
+       aNode.removeAttribute("contextmenu");
+@@ -3811,17 +3811,17 @@ var CustomizableUI = {
+    */
+   getPlaceForItem(aElement) {
+     let place;
+     let node = aElement;
+     while (node && !place) {
+       if (node.localName == "toolbar")
+         place = "toolbar";
+       else if (node.id == CustomizableUI.AREA_PANEL || node.id == CustomizableUI.AREA_FIXED_OVERFLOW_PANEL)
+-        place = "panel";
++        place = "menu-panel";
+       else if (node.id == "customization-palette")
+         place = "palette";
+ 
+       node = node.parentNode;
+     }
+     return place;
+   },
+ 
+diff --git a/browser/components/customizableui/CustomizeMode.jsm b/browser/components/customizableui/CustomizeMode.jsm
+--- a/browser/components/customizableui/CustomizeMode.jsm
++++ b/browser/components/customizableui/CustomizeMode.jsm
+@@ -965,17 +965,17 @@ CustomizeMode.prototype = {
+ 
+     let contextMenuAttrName = "";
+     if (aNode.getAttribute("context")) {
+       contextMenuAttrName = "context";
+     } else if (aNode.getAttribute("contextmenu")) {
+       contextMenuAttrName = "contextmenu";
+     }
+     let currentContextMenu = aNode.getAttribute(contextMenuAttrName);
+-    let contextMenuForPlace = aPlace == "panel" ?
++    let contextMenuForPlace = aPlace == "menu-panel" ?
+                                 kPanelItemContextMenu :
+                                 kPaletteItemContextMenu;
+     if (aPlace != "toolbar") {
+       wrapper.setAttribute("context", contextMenuForPlace);
+     }
+     // Only keep track of the menu if it is non-default.
+     if (currentContextMenu &&
+         currentContextMenu != contextMenuForPlace) {
+@@ -1049,17 +1049,17 @@ CustomizeMode.prototype = {
+     }
+ 
+     let wrappedContext = toolbarItem.getAttribute("wrapped-context");
+     if (wrappedContext) {
+       let contextAttrName = toolbarItem.getAttribute("wrapped-contextAttrName");
+       toolbarItem.setAttribute(contextAttrName, wrappedContext);
+       toolbarItem.removeAttribute("wrapped-contextAttrName");
+       toolbarItem.removeAttribute("wrapped-context");
+-    } else if (place == "panel") {
++    } else if (place == "menu-panel") {
+       toolbarItem.setAttribute("context", kPanelItemContextMenu);
+     }
+ 
+     if (aWrapper.parentNode) {
+       aWrapper.parentNode.replaceChild(toolbarItem, aWrapper);
+     }
+     return toolbarItem;
+   },
+@@ -1741,17 +1741,17 @@ CustomizeMode.prototype = {
+       // For automated tests, we sometimes start exiting customization mode
+       // before this fires, which leaves us with placeholders inserted after
+       // we've exited. So we need to check that we are indeed customizing.
+       if (this._customizing && !this._transitioning) {
+         item.hidden = true;
+         this._showPanelCustomizationPlaceholders();
+         DragPositionManager.start(this.window);
+         let canUsePrevSibling = placeForItem == "toolbar" ||
+-                                (placeForItem == "panel" && gPhotonStructure);
++                                (placeForItem == "menu-panel" && gPhotonStructure);
+         if (item.nextSibling) {
+           this._setDragActive(item.nextSibling, "before", draggedItem.id, placeForItem);
+           this._dragOverItem = item.nextSibling;
+         } else if (canUsePrevSibling && item.previousSibling) {
+           this._setDragActive(item.previousSibling, "after", draggedItem.id, placeForItem);
+           this._dragOverItem = item.previousSibling;
+         }
+       }
+@@ -2121,21 +2121,16 @@ CustomizeMode.prototype = {
+            mozSourceNode.ownerGlobal != this.window;
+   },
+ 
+   _setDragActive(aItem, aValue, aDraggedItemId, aAreaType) {
+     if (!aItem) {
+       return;
+     }
+ 
+-    // getPlaceForItem and getAreaType return different things. Hack-around
+-    // rather than having to update every. single. consumer. (and break add-ons)
+-    if (aAreaType == "panel") {
+-      aAreaType = "menu-panel";
+-    }
+     if (aItem.getAttribute("dragover") != aValue) {
+       aItem.setAttribute("dragover", aValue);
+ 
+       let window = aItem.ownerGlobal;
+       let draggedItem = window.document.getElementById(aDraggedItemId);
+       if (aAreaType == "palette" || (aAreaType == "menu-panel" && !gPhotonStructure)) {
+         this._setGridDragActive(aItem, draggedItem, aValue);
+       } else {
+diff --git a/browser/themes/shared/customizableui/customizeMode.inc.css b/browser/themes/shared/customizableui/customizeMode.inc.css
+--- a/browser/themes/shared/customizableui/customizeMode.inc.css
++++ b/browser/themes/shared/customizableui/customizeMode.inc.css
+@@ -278,49 +278,49 @@
+                     linear-gradient(to bottom, #3e86ce, #3878ba);
+   background-position: center top, left center, left top, left top, left top;
+   background-repeat: no-repeat, no-repeat, repeat, repeat, no-repeat;
+   background-size: auto 12px, 12px 100%, auto, auto, auto;
+   background-attachment: scroll, scroll, fixed, fixed, scroll;
+ }
+ %endif
+ 
+-#widget-overflow-fixed-list > toolbarpaletteitem[place="panel"],
++#widget-overflow-fixed-list > toolbarpaletteitem[place="menu-panel"],
+ toolbarpaletteitem[place="toolbar"] {
+   transition: border-width 250ms ease-in-out;
+ }
+ 
+ toolbarpaletteitem[mousedown] {
+   cursor: -moz-grabbing;
+ }
+ 
+ .panel-customization-placeholder,
+ toolbarpaletteitem[place="palette"],
+-toolbarpaletteitem[place="panel"] {
++toolbarpaletteitem[place="menu-panel"] {
+   transition: transform var(--drag-drop-transition-duration) ease-in-out;
+ }
+ 
+ #customization-palette {
+   transition: opacity .3s ease-in-out;
+   opacity: 0;
+ }
+ 
+ #customization-palette[showing="true"] {
+   opacity: 1;
+ }
+ 
+ toolbarpaletteitem toolbarbutton[disabled] {
+   color: inherit !important;
+ }
+ 
+-#widget-overflow-fixed-list > toolbarpaletteitem[notransition][place="panel"],
++#widget-overflow-fixed-list > toolbarpaletteitem[notransition][place="menu-panel"],
+ toolbarpaletteitem[notransition].panel-customization-placeholder,
+ toolbarpaletteitem[notransition][place="toolbar"],
+ toolbarpaletteitem[notransition][place="palette"],
+-toolbarpaletteitem[notransition][place="panel"] {
++toolbarpaletteitem[notransition][place="menu-panel"] {
+   transition: none;
+ }
+ 
+ toolbarpaletteitem > toolbarbutton > .toolbarbutton-icon,
+ toolbarpaletteitem > toolbarbutton > .toolbarbutton-badge-stack > .toolbarbutton-icon,
+ toolbarpaletteitem > toolbaritem.panel-wide-item,
+ toolbarpaletteitem > toolbarbutton[type="menu-button"] {
+   transition: transform var(--drag-drop-transition-duration) cubic-bezier(.6, 2, .75, 1.5) !important;
+@@ -338,33 +338,33 @@ toolbarpaletteitem[mousedown] > toolbarb
+ 
+ /* Override the toolkit styling for items being dragged over. */
+ toolbarpaletteitem[place="toolbar"] {
+   border-left-width: 0;
+   border-right-width: 0;
+   margin-right: 0;
+   margin-left: 0;
+ }
+-#widget-overflow-fixed-list > toolbarpaletteitem[place="panel"] {
++#widget-overflow-fixed-list > toolbarpaletteitem[place="menu-panel"] {
+   border-top: 0px solid transparent;
+   border-bottom: 0px solid transparent;
+ }
+ 
+ #customization-palette:not([hidden]) {
+   margin-bottom: 20px;
+ }
+ 
+ toolbarpaletteitem[place="palette"]:-moz-focusring,
+-toolbarpaletteitem[place="panel"]:-moz-focusring,
++toolbarpaletteitem[place="menu-panel"]:-moz-focusring,
+ toolbarpaletteitem[place="toolbar"]:-moz-focusring {
+   outline-width: 0;
+ }
+ 
+ toolbarpaletteitem[place="palette"]:not([mousedown="true"]):-moz-focusring,
+-toolbarpaletteitem[place="panel"]:not([mousedown="true"]):-moz-focusring,
++toolbarpaletteitem[place="menu-panel"]:not([mousedown="true"]):-moz-focusring,
+ toolbarpaletteitem[place="toolbar"]:not([mousedown="true"]):-moz-focusring {
+   /* Delay adding the focusring back until after the transform transition completes. */
+   transition: outline-width .01s linear var(--drag-drop-transition-duration);
+   outline: 1px dotted;
+   -moz-outline-radius: 2.5px;
+ }
+ 
+ toolbarpaletteitem[place="toolbar"]:not([mousedown="true"]):-moz-focusring {
+diff --git a/browser/themes/shared/customizableui/panelUI.inc.css b/browser/themes/shared/customizableui/panelUI.inc.css
+--- a/browser/themes/shared/customizableui/panelUI.inc.css
++++ b/browser/themes/shared/customizableui/panelUI.inc.css
+@@ -295,18 +295,18 @@ panel[photon] > .panel-arrowcontainer > 
+   margin: 2px 0 0 !important;
+ }
+ 
+ .panelUI-grid .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-multiline-text {
+   text-align: center;
+   margin: -1px 0 0;
+ }
+ 
+-#wrapper-edit-controls:-moz-any([place="palette"],[place="panel"]) > #edit-controls,
+-#wrapper-zoom-controls:-moz-any([place="palette"],[place="panel"]) > #zoom-controls {
++#wrapper-edit-controls:-moz-any([place="palette"],[place="menu-panel"]) > #edit-controls,
++#wrapper-zoom-controls:-moz-any([place="palette"],[place="menu-panel"]) > #zoom-controls {
+   margin-inline-start: 0;
+ }
+ 
+ #PanelUI-contents {
+   max-width: @menuPanelWidth@;
+ }
+ 
+ #BMB_bookmarksPopup,
+@@ -408,25 +408,25 @@ photonpanelmultiview .panel-subview-body
+   -moz-box-orient: vertical;
+   width: calc(@menuPanelButtonWidth@);
+   height: calc(51px + 2.2em);
+ }
+ 
+ /* In order to have button labels constrained appropriately, items inside the toolbarpaletteitem
+  * should have a min-width set so they abide by the width set above (which they do outside of
+  * customize mode because they're in a flexed container) */
+-:root:not([photon-structure]) toolbarpaletteitem[place="panel"]:not([haswideitem=true]) > .toolbarbutton-1 {
++:root:not([photon-structure]) toolbarpaletteitem[place="menu-panel"]:not([haswideitem=true]) > .toolbarbutton-1 {
+   min-width: 0.01px;
+ }
+ 
+ /* Help SDK buttons fit in. */
+ toolbarpaletteitem[place="palette"] > toolbarbutton[constrain-size="true"] > .toolbarbutton-icon,
+ toolbarpaletteitem[place="palette"] > toolbarbutton[constrain-size="true"] > .toolbarbutton-badge-stack > .toolbarbutton-icon,
+ toolbarpaletteitem[place="palette"] > toolbaritem[sdkstylewidget="true"] > .toolbarbutton-1 > .toolbarbutton-icon,
+-toolbarpaletteitem[place="panel"] > toolbaritem[sdkstylewidget="true"] > .toolbarbutton-1 > .toolbarbutton-icon,
++toolbarpaletteitem[place="menu-panel"] > toolbaritem[sdkstylewidget="true"] > .toolbarbutton-1 > .toolbarbutton-icon,
+ toolbarbutton[constrain-size="true"][cui-areatype="menu-panel"] > .toolbarbutton-icon,
+ toolbarbutton[constrain-size="true"][cui-areatype="menu-panel"] > .toolbarbutton-badge-stack > .toolbarbutton-icon {
+   height: @panelPaletteIconSize@;
+   width: @panelPaletteIconSize@;
+ }
+ 
+ .customization-palette .toolbarbutton-1 {
+   -moz-appearance: none;
+@@ -1266,17 +1266,17 @@ panelview:not([mainView]) .subviewbutton
+ 
+ .cui-widget-panelview .subviewbutton:not(.panel-subview-footer) {
+   margin-left: 4px;
+   margin-right: 4px;
+ }
+ 
+ /* START photon adjustments */
+ 
+-.widget-overflow-list > toolbarpaletteitem[place=panel] .toolbarbutton-1,
++.widget-overflow-list > toolbarpaletteitem[place="menu-panel"] .toolbarbutton-1,
+ photonpanelmultiview .widget-overflow-list .toolbarbutton-1,
+ photonpanelmultiview .PanelUI-subView .subviewbutton,
+ photonpanelmultiview .cui-widget-panelview .subviewbutton:not(.panel-subview-footer),
+ photonpanelmultiview .subview-subheader {
+   border-radius: 0;
+   border-width: 0;
+   margin: 0;
+   padding: 4px 12px;
+@@ -1602,17 +1602,17 @@ toolbarpaletteitem[place="palette"] > .t
+ #bookmarks-menu-button[cui-areatype="menu-panel"] > .toolbarbutton-menu-dropmarker,
+ #bookmarks-menu-button[overflowedItem] > .toolbarbutton-menu-dropmarker,
+ toolbarpaletteitem[place="palette"] > .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker,
+ #bookmarks-menu-button[cui-areatype="menu-panel"] > .toolbarbutton-menubutton-dropmarker {
+   display: none;
+ }
+ 
+ #search-container[cui-areatype="menu-panel"],
+-#wrapper-search-container[place="panel"] {
++#wrapper-search-container[place="menu-panel"] {
+   width: @menuPanelWidth@;
+ }
+ 
+ #search-container[cui-areatype="menu-panel"] {
+   margin-top: 6px;
+   margin-bottom: 6px;
+ }
+ 
+@@ -1635,17 +1635,17 @@ toolbarpaletteitem[place="palette"] > #s
+ }
+ 
+ /* Make direct siblings overlap borders: */
+ .toolbaritem-combined-buttons + .toolbaritem-combined-buttons@inAnyPanel@ {
+   border-top-color: transparent !important;
+ }
+ 
+ .toolbaritem-combined-buttons + .toolbaritem-combined-buttons@inAnyPanel@,
+-toolbarpaletteitem[haswideitem][place="panel"] + toolbarpaletteitem[haswideitem][place="panel"] {
++toolbarpaletteitem[haswideitem][place="menu-panel"] + toolbarpaletteitem[haswideitem][place="menu-panel"] {
+   margin-top: -1px;
+ }
+ %endif
+ 
+ .toolbaritem-combined-buttons@inAnyPanel@ > toolbarbutton {
+   border: 0;
+   margin: 0;
+   -moz-box-flex: 1;
+@@ -1661,17 +1661,17 @@ toolbarpaletteitem[haswideitem][place="p
+   padding-top: 4px;
+   padding-bottom: 4px;
+ %endif
+   -moz-box-orient: horizontal;
+ }
+ 
+ %ifdef MOZ_PHOTON_THEME
+ /* In customize mode, extend the buttons *only* in the panel, just to make them not look stupid */
+-toolbarpaletteitem[place=panel] > .toolbaritem-combined-buttons > toolbarbutton {
++toolbarpaletteitem[place="menu-panel"] > .toolbaritem-combined-buttons > toolbarbutton {
+   min-width: calc(@menuPanelButtonWidth@ - 1px);
+   max-width: calc(@menuPanelButtonWidth@ - 1px);
+ }
+ %else
+ #edit-controls@inAnyPanel@ > #copy-button,
+ #zoom-controls@inAnyPanel@ > #zoom-reset-button {
+   /* reduce the width with 2px for this button to compensate for two separators
+      of 1px. */

+ 55 - 0
frg/work-js/mozilla-release/patches/1424943-59a1.patch

@@ -0,0 +1,55 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1513177106 18000
+# Node ID c885e730dded79619acf357346b4954ef24a79b0
+# Parent  a6ef349582d9f68b7e131f92485dfed3dc3920b9
+Bug 1424943 Fix test_error_reporting.html not to race network vs Clients API. r=tt
+
+diff --git a/dom/workers/test/serviceworkers/sw_storage_not_allow.js b/dom/workers/test/serviceworkers/sw_storage_not_allow.js
+--- a/dom/workers/test/serviceworkers/sw_storage_not_allow.js
++++ b/dom/workers/test/serviceworkers/sw_storage_not_allow.js
+@@ -1,21 +1,34 @@
+ let clientId;
+ addEventListener('fetch', function(event) {
+-  if (event.request.url.includes('getClients')) {
+-    // Excepted to fail since the storage access is not allowed.
+-    self.clients.matchAll();
+-  } else if (event.request.url.includes('getClient-stage1')) {
+-    self.clients.matchAll().then(function(clients) {
++  event.respondWith(async function() {
++    if (event.request.url.includes('getClients')) {
++      // Expected to fail since the storage access is not allowed.
++      try {
++        await self.clients.matchAll();
++      } catch (e) {
++        // expected failure
++      }
++    } else if (event.request.url.includes('getClient-stage1')) {
++      let clients = await self.clients.matchAll();
+       clientId = clients[0].id;
+-    });
+-  } else if (event.request.url.includes('getClient-stage2')) {
+-    // Excepted to fail since the storage access is not allowed.
+-    self.clients.get(clientId);
+-  }
++    } else if (event.request.url.includes('getClient-stage2')) {
++      // Expected to fail since the storage access is not allowed.
++      try {
++        await self.clients.get(clientId);
++      } catch(e) {
++        // expected failure
++      }
++    }
++
++    // Pass through the network request once our various Clients API
++    // promises have completed.
++    return await fetch(event.request);
++  }());
+ });
+ 
+ addEventListener('message', function(event) {
+   if (event.data === 'claim') {
+     event.waitUntil(clients.claim());
+   }
+ });
+ 

+ 196 - 0
frg/work-js/mozilla-release/patches/1425316-1-59a1.patch

@@ -0,0 +1,196 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1513695887 18000
+# Node ID 56b3aab1d763d41b4672b06cc1034f6f5895357d
+# Parent  b8380e17e90aa8c45d3c0cc76429aa1d0a585d8b
+Bug 1425316 P1 Expose a chrome-only Window.shouldReportForServiceWorkerScope() method that initially forwards to ServiceWorkerManager. r=asuth
+
+diff --git a/devtools/server/actors/webconsole/listeners.js b/devtools/server/actors/webconsole/listeners.js
+--- a/devtools/server/actors/webconsole/listeners.js
++++ b/devtools/server/actors/webconsole/listeners.js
+@@ -278,17 +278,17 @@ ConsoleAPIListener.prototype =
+     let workerType = WebConsoleUtils.getWorkerType(message);
+ 
+     if (this.window && workerType === "ServiceWorker") {
+       // For messages from Service Workers, message.ID is the
+       // scope, which can be used to determine whether it's controlling
+       // a window.
+       let scope = message.ID;
+ 
+-      if (!swm.shouldReportToWindow(this.window, scope)) {
++      if (!this.window.shouldReportForServiceWorkerScope(scope)) {
+         return false;
+       }
+     }
+ 
+     if (this.window && !workerType) {
+       let msgWindow = Services.wm.getCurrentInnerWindowWithId(message.innerID);
+       if (!msgWindow || !isWindowIncluded(this.window, msgWindow)) {
+         // Not the same window!
+diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp
+--- a/dom/base/nsGlobalWindow.cpp
++++ b/dom/base/nsGlobalWindow.cpp
+@@ -78,16 +78,17 @@
+ #include "mozilla/Sprintf.h"
+ #include "mozilla/Unused.h"
+ 
+ // Other Classes
+ #include "mozilla/dom/BarProps.h"
+ #include "nsContentCID.h"
+ #include "nsLayoutStatics.h"
+ #include "nsCCUncollectableMarker.h"
++#include "mozilla/dom/workers/ServiceWorkerManager.h"
+ #include "mozilla/dom/workers/Workers.h"
+ #include "mozilla/dom/ToJSValue.h"
+ #include "nsJSPrincipals.h"
+ #include "mozilla/Attributes.h"
+ #include "mozilla/Debug.h"
+ #include "mozilla/EventListenerManager.h"
+ #include "mozilla/EventStates.h"
+ #include "mozilla/MouseEvents.h"
+@@ -4435,16 +4436,31 @@ nsPIDOMWindowInner::GetClientState() con
+ }
+ 
+ Maybe<ServiceWorkerDescriptor>
+ nsPIDOMWindowInner::GetController() const
+ {
+   return Move(nsGlobalWindow::Cast(this)->GetController());
+ }
+ 
++bool
++nsGlobalWindow::ShouldReportForServiceWorkerScope(const nsAString& aScope)
++{
++  RefPtr<workers::ServiceWorkerManager> swm =
++    workers::ServiceWorkerManager::GetInstance();
++  NS_ENSURE_TRUE(swm, false);
++
++  bool aResult = false;
++  nsresult rv = swm->ShouldReportToWindow(GetOuterWindowInternal(),
++                                          NS_ConvertUTF16toUTF8(aScope), &aResult);
++  NS_ENSURE_SUCCESS(rv, false);
++
++  return aResult;
++}
++
+ void
+ nsGlobalWindow::UpdateTopInnerWindow()
+ {
+   if (!IsInnerWindow() || AsInner()->IsTopInnerWindow()) {
+     return;
+   }
+ 
+   AsInner()->UpdateWebSocketCount(-(int32_t)mNumOfOpenWebSockets);
+diff --git a/dom/base/nsGlobalWindow.h b/dom/base/nsGlobalWindow.h
+--- a/dom/base/nsGlobalWindow.h
++++ b/dom/base/nsGlobalWindow.h
+@@ -1314,16 +1314,18 @@ public:
+                     JS::MutableHandle<JS::Value> aRetval,
+                     mozilla::ErrorResult& aError);
+ 
+   already_AddRefed<nsWindowRoot> GetWindowRootOuter();
+   already_AddRefed<nsWindowRoot> GetWindowRoot(mozilla::ErrorResult& aError);
+ 
+   mozilla::dom::Performance* GetPerformance();
+ 
++  bool ShouldReportForServiceWorkerScope(const nsAString& aScope);
++
+   void UpdateTopInnerWindow();
+ 
+   virtual bool IsInSyncOperation() override
+   {
+     return GetExtantDoc() && GetExtantDoc()->IsInSyncOperation();
+   }
+ 
+ protected:
+diff --git a/dom/interfaces/base/nsIServiceWorkerManager.idl b/dom/interfaces/base/nsIServiceWorkerManager.idl
+--- a/dom/interfaces/base/nsIServiceWorkerManager.idl
++++ b/dom/interfaces/base/nsIServiceWorkerManager.idl
+@@ -229,15 +229,13 @@ interface nsIServiceWorkerManager : nsIS
+                                      [optional] in uint32_t aDataLength,
+                                      [optional, array, size_is(aDataLength)] in uint8_t aDataBytes);
+   void sendPushSubscriptionChangeEvent(in ACString aOriginAttributes,
+                                        in ACString scope);
+ 
+   void addListener(in nsIServiceWorkerManagerListener aListener);
+ 
+   void removeListener(in nsIServiceWorkerManagerListener aListener);
+-
+-  bool shouldReportToWindow(in mozIDOMWindowProxy aWindow, in ACString aScope);
+ };
+ 
+ %{ C++
+ #define SERVICEWORKERMANAGER_CONTRACTID "@mozilla.org/serviceworkers/manager;1"
+ %}
+diff --git a/dom/webidl/Window.webidl b/dom/webidl/Window.webidl
+--- a/dom/webidl/Window.webidl
++++ b/dom/webidl/Window.webidl
+@@ -336,16 +336,23 @@ partial interface Window {
+ 
+   [Throws, ChromeOnly] any getInterface(IID iid);
+ 
+   /**
+    * Same as nsIDOMWindow.windowRoot, useful for event listener targeting.
+    */
+   [ChromeOnly, Throws]
+   readonly attribute WindowRoot? windowRoot;
++
++  /**
++   * ChromeOnly method to determine if a particular window should see console
++   * reports from service workers of the given scope.
++   */
++  [ChromeOnly]
++  boolean shouldReportForServiceWorkerScope(USVString aScope);
+ };
+ 
+ Window implements TouchEventHandlers;
+ 
+ Window implements OnErrorEventHandlerForWindow;
+ 
+ #if defined(MOZ_WIDGET_ANDROID)
+ // https://compat.spec.whatwg.org/#windoworientation-interface
+diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp
+--- a/dom/workers/ServiceWorkerManager.cpp
++++ b/dom/workers/ServiceWorkerManager.cpp
+@@ -3626,17 +3626,17 @@ ServiceWorkerManager::RemoveListener(nsI
+     return NS_ERROR_INVALID_ARG;
+   }
+ 
+   mListeners.RemoveElement(aListener);
+ 
+   return NS_OK;
+ }
+ 
+-NS_IMETHODIMP
++nsresult
+ ServiceWorkerManager::ShouldReportToWindow(mozIDOMWindowProxy* aWindow,
+                                            const nsACString& aScope,
+                                            bool* aResult)
+ {
+   AssertIsOnMainThread();
+   MOZ_ASSERT(aResult);
+ 
+   *aResult = false;
+diff --git a/dom/workers/ServiceWorkerManager.h b/dom/workers/ServiceWorkerManager.h
+--- a/dom/workers/ServiceWorkerManager.h
++++ b/dom/workers/ServiceWorkerManager.h
+@@ -319,16 +319,20 @@ public:
+   NotifyUnregister(nsIPrincipal* aPrincipal, const nsAString& aScope);
+ 
+   void
+   WorkerIsIdle(ServiceWorkerInfo* aWorker);
+ 
+   void
+   CheckPendingReadyPromises();
+ 
++  nsresult
++  ShouldReportToWindow(mozIDOMWindowProxy* aWindow, const nsACString& aScope,
++                       bool* aResult);
++
+ private:
+   ServiceWorkerManager();
+   ~ServiceWorkerManager();
+ 
+   void
+   Init(ServiceWorkerRegistrar* aRegistrar);
+ 
+   void

+ 134 - 0
frg/work-js/mozilla-release/patches/1425316-2-59a1.patch

@@ -0,0 +1,134 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1513695888 18000
+# Node ID 81fe3f741dc336b2ce1df1c5d9718ee5b179a24a
+# Parent  1bf366f04bc32a49f72379eea9a49a61779817d5
+Bug 1425316 P2 Improve nsGlobalWindowInner::CallOnChildren() to take variable arguments and allow iteration to be aborted. r=asuth
+
+diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp
+--- a/dom/base/nsGlobalWindow.cpp
++++ b/dom/base/nsGlobalWindow.cpp
+@@ -12593,27 +12593,29 @@ nsGlobalWindow::SyncStateFromParentWindo
+ 
+   // Now apply only the number of Suspend() calls to reach the target
+   // suspend count after applying the Freeze() calls.
+   for (uint32_t i = 0; i < (parentSuspendDepth - parentFreezeDepth); ++i) {
+     Suspend();
+   }
+ }
+ 
+-template<typename Method>
+-void
+-nsGlobalWindow::CallOnChildren(Method aMethod)
++template<typename Method, typename... Args>
++nsGlobalWindow::CallState
++nsGlobalWindow::CallOnChildren(Method aMethod, Args& ...aArgs)
+ {
+   MOZ_ASSERT(NS_IsMainThread());
+   MOZ_ASSERT(IsInnerWindow());
+   MOZ_ASSERT(AsInner()->IsCurrentInnerWindow());
+ 
++  CallState state = CallState::Continue;
++
+   nsCOMPtr<nsIDocShell> docShell = GetDocShell();
+   if (!docShell) {
+-    return;
++    return state;
+   }
+ 
+   int32_t childCount = 0;
+   docShell->GetChildCount(&childCount);
+ 
+   for (int32_t i = 0; i < childCount; ++i) {
+     nsCOMPtr<nsIDocShellTreeItem> childShell;
+     docShell->GetChildAt(i, getter_AddRefs(childShell));
+@@ -12629,18 +12631,29 @@ nsGlobalWindow::CallOnChildren(Method aM
+ 
+     // This is a bit hackish. Only freeze/suspend windows which are truly our
+     // subwindows.
+     nsCOMPtr<Element> frame = pWin->GetFrameElementInternal();
+     if (!mDoc || !frame || mDoc != frame->OwnerDoc() || !inner) {
+       continue;
+     }
+ 
+-    (inner->*aMethod)();
+-  }
++    // Call the child method using our helper CallChild() template method.
++    // This allows us to handle both void returning methods and methods
++    // that return CallState explicitly.  For void returning methods we
++    // assume CallState::Continue.
++    typedef decltype((inner->*aMethod)(aArgs...)) returnType;
++    state = CallChild<returnType>(inner, aMethod, aArgs...);
++
++    if (state == CallState::Stop) {
++      return state;
++    }
++  }
++
++  return state;
+ }
+ 
+ Maybe<ClientInfo>
+ nsGlobalWindow::GetClientInfo() const
+ {
+   MOZ_ASSERT(NS_IsMainThread());
+   Maybe<ClientInfo> clientInfo;
+   if (mClientSource) {
+diff --git a/dom/base/nsGlobalWindow.h b/dom/base/nsGlobalWindow.h
+--- a/dom/base/nsGlobalWindow.h
++++ b/dom/base/nsGlobalWindow.h
+@@ -1566,18 +1566,52 @@ private:
+                         bool aDoJSFixups,
+                         bool aNavigate,
+                         nsIArray *argv,
+                         nsISupports *aExtraArgument,
+                         nsIDocShellLoadInfo* aLoadInfo,
+                         bool aForceNoOpener,
+                         nsPIDOMWindowOuter **aReturn);
+ 
+-  template<typename Method>
+-  void CallOnChildren(Method aMethod);
++  // A type that methods called by CallOnChildren can return.  If Stop
++  // is returned then CallOnChildren will stop calling further children.
++  // If Continue is returned then CallOnChildren will keep calling further
++  // children.
++  enum class CallState
++  {
++    Continue,
++    Stop,
++  };
++
++  // Call the given method on the immediate children of this window.  The
++  // CallState returned by the last child method invocation is returned or
++  // CallState::Continue if the method returns void.
++  template<typename Method, typename... Args>
++  CallState CallOnChildren(Method aMethod, Args& ...aArgs);
++
++  // Helper to convert a void returning child method into an implicit
++  // CallState::Continue value.
++  template<typename Return, typename Method, typename... Args>
++  typename std::enable_if<std::is_void<Return>::value,
++                          nsGlobalWindow::CallState>::type
++  CallChild(nsGlobalWindow* aWindow, Method aMethod, Args& ...aArgs)
++  {
++    (aWindow->*aMethod)(aArgs...);
++    return nsGlobalWindow::CallState::Continue;
++  }
++
++  // Helper that passes through the CallState value from a child method.
++  template<typename Return, typename Method, typename... Args>
++  typename std::enable_if<std::is_same<Return,
++                                       nsGlobalWindow::CallState>::value,
++                          nsGlobalWindow::CallState>::type
++  CallChild(nsGlobalWindow* aWindow, Method aMethod, Args& ...aArgs)
++  {
++    return (aWindow->*aMethod)(aArgs...);
++  }
+ 
+   void FreezeInternal();
+   void ThawInternal();
+ 
+ public:
+   // Timeout Functions
+   // |interval| is in milliseconds.
+   int32_t SetTimeoutOrInterval(JSContext* aCx,

+ 146 - 0
frg/work-js/mozilla-release/patches/1425316-3-59a1.patch

@@ -0,0 +1,146 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1513695888 18000
+# Node ID ca80e585ab6d3e85030c583d2139167e8c112ff6
+# Parent  2ead4fc25786ba837330bd4cedee1aa729303bd8
+Bug 1425316 P3 Make nsGlobalWindowInner::ShouldReportForServiceWorkerScope() handle checks for controlled windows. r=asuth
+
+diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp
+--- a/dom/base/nsGlobalWindow.cpp
++++ b/dom/base/nsGlobalWindow.cpp
+@@ -4439,28 +4439,63 @@ Maybe<ServiceWorkerDescriptor>
+ nsPIDOMWindowInner::GetController() const
+ {
+   return Move(nsGlobalWindow::Cast(this)->GetController());
+ }
+ 
+ bool
+ nsGlobalWindow::ShouldReportForServiceWorkerScope(const nsAString& aScope)
+ {
++  bool result = false;
++
++  nsPIDOMWindowOuter* topOuter = GetScriptableTop();
++  NS_ENSURE_TRUE(topOuter, false);
++
++  nsGlobalWindow* topInner =
++    nsGlobalWindow::Cast(topOuter->GetCurrentInnerWindow());
++  NS_ENSURE_TRUE(topInner, false);
++
++  topInner->ShouldReportForServiceWorkerScopeInternal(NS_ConvertUTF16toUTF8(aScope),
++                                                      &result);
++  if (result) {
++    return true;
++  }
++
+   RefPtr<workers::ServiceWorkerManager> swm =
+     workers::ServiceWorkerManager::GetInstance();
+   NS_ENSURE_TRUE(swm, false);
+ 
+   bool aResult = false;
+-  nsresult rv = swm->ShouldReportToWindow(GetOuterWindowInternal(),
++  nsresult rv = swm->ShouldReportToWindow(topOuter,
+                                           NS_ConvertUTF16toUTF8(aScope), &aResult);
+   NS_ENSURE_SUCCESS(rv, false);
+ 
+   return aResult;
+ }
+ 
++nsGlobalWindow::CallState
++nsGlobalWindow::ShouldReportForServiceWorkerScopeInternal(const nsACString& aScope,
++                                                          bool* aResultOut)
++{
++  MOZ_DIAGNOSTIC_ASSERT(aResultOut);
++
++  // First check to see if this window is controlled.  If so, then we have
++  // found a match and are done.
++  const Maybe<ServiceWorkerDescriptor> swd = GetController();
++  if (swd.isSome() && swd.ref().Scope() == aScope) {
++    *aResultOut = true;
++    return CallState::Stop;
++  }
++
++  // The current window doesn't care about this service worker, but maybe
++  // one of our child frames does.
++  return CallOnChildren(&nsGlobalWindow::ShouldReportForServiceWorkerScopeInternal,
++                        aScope, aResultOut);
++}
++
+ void
+ nsGlobalWindow::UpdateTopInnerWindow()
+ {
+   if (!IsInnerWindow() || AsInner()->IsTopInnerWindow()) {
+     return;
+   }
+ 
+   AsInner()->UpdateWebSocketCount(-(int32_t)mNumOfOpenWebSockets);
+diff --git a/dom/base/nsGlobalWindow.h b/dom/base/nsGlobalWindow.h
+--- a/dom/base/nsGlobalWindow.h
++++ b/dom/base/nsGlobalWindow.h
+@@ -1606,16 +1606,20 @@ private:
+   CallChild(nsGlobalWindow* aWindow, Method aMethod, Args& ...aArgs)
+   {
+     return (aWindow->*aMethod)(aArgs...);
+   }
+ 
+   void FreezeInternal();
+   void ThawInternal();
+ 
++  CallState ShouldReportForServiceWorkerScopeInternal(const nsACString& aScope,
++                                                      bool* aResultOut);
++
++
+ public:
+   // Timeout Functions
+   // |interval| is in milliseconds.
+   int32_t SetTimeoutOrInterval(JSContext* aCx,
+                                mozilla::dom::Function& aFunction,
+                                int32_t aTimeout,
+                                const mozilla::dom::Sequence<JS::Value>& aArguments,
+                                bool aIsInterval, mozilla::ErrorResult& aError);
+diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp
+--- a/dom/workers/ServiceWorkerManager.cpp
++++ b/dom/workers/ServiceWorkerManager.cpp
+@@ -3713,43 +3713,16 @@ ServiceWorkerManager::ShouldReportToWind
+       // Match.  We should report to this window.
+       if (outer && winId == outer->WindowID()) {
+         *aResult = true;
+         return NS_OK;
+       }
+     }
+   }
+ 
+-  // Next examine controlled documents to see if the windows match.
+-  for (auto iter = mControlledDocuments.Iter(); !iter.Done(); iter.Next()) {
+-    ServiceWorkerRegistrationInfo* reg = iter.UserData();
+-    MOZ_ASSERT(reg);
+-    if (!reg->mScope.Equals(aScope)) {
+-      continue;
+-    }
+-
+-    nsCOMPtr<nsIDocument> doc = do_QueryInterface(iter.Key());
+-    if (!doc || !doc->IsCurrentActiveDocument()) {
+-      continue;
+-    }
+-
+-    nsCOMPtr<nsPIDOMWindowOuter> win = doc->GetWindow();
+-    if (!win) {
+-      continue;
+-    }
+-
+-    win = win->GetScriptableTop();
+-
+-    // Match.  We should report to this window.
+-    if (win && winId == win->WindowID()) {
+-      *aResult = true;
+-      return NS_OK;
+-    }
+-  }
+-
+   // No match.  We should not report to this window.
+   return NS_OK;
+ }
+ 
+ NS_IMETHODIMP
+ ServiceWorkerManager::Observe(nsISupports* aSubject,
+                               const char* aTopic,
+                               const char16_t* aData)

+ 270 - 0
frg/work-js/mozilla-release/patches/1425316-4-59a1.patch

@@ -0,0 +1,270 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1513695888 18000
+# Node ID c08edd442321e8d3b1c04ca25489e6f1634abbeb
+# Parent  003675998a8d6997d1032beb9d5969bc60c98175
+Bug 1425316 P4 Note that a ClientSource has called register() for a SW scope and use it to match window console reports. r=asuth
+
+diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp
+--- a/dom/base/nsGlobalWindow.cpp
++++ b/dom/base/nsGlobalWindow.cpp
+@@ -4436,16 +4436,22 @@ nsPIDOMWindowInner::GetClientState() con
+ }
+ 
+ Maybe<ServiceWorkerDescriptor>
+ nsPIDOMWindowInner::GetController() const
+ {
+   return Move(nsGlobalWindow::Cast(this)->GetController());
+ }
+ 
++void
++nsPIDOMWindowInner::NoteCalledRegisterForServiceWorkerScope(const nsACString& aScope)
++{
++  nsGlobalWindow::Cast(this)->NoteCalledRegisterForServiceWorkerScope(aScope);
++}
++
+ bool
+ nsGlobalWindow::ShouldReportForServiceWorkerScope(const nsAString& aScope)
+ {
+   bool result = false;
+ 
+   nsPIDOMWindowOuter* topOuter = GetScriptableTop();
+   NS_ENSURE_TRUE(topOuter, false);
+ 
+@@ -4480,23 +4486,41 @@ nsGlobalWindow::ShouldReportForServiceWo
+   // First check to see if this window is controlled.  If so, then we have
+   // found a match and are done.
+   const Maybe<ServiceWorkerDescriptor> swd = GetController();
+   if (swd.isSome() && swd.ref().Scope() == aScope) {
+     *aResultOut = true;
+     return CallState::Stop;
+   }
+ 
++  // Next, check to see if this window has called navigator.serviceWorker.register()
++  // for this scope.  If so, then treat this as a match so console reports
++  // appear in the devtools console.
++  if (mClientSource && mClientSource->CalledRegisterForServiceWorkerScope(aScope)) {
++    *aResultOut = true;
++    return CallState::Stop;
++  }
++
+   // The current window doesn't care about this service worker, but maybe
+   // one of our child frames does.
+   return CallOnChildren(&nsGlobalWindow::ShouldReportForServiceWorkerScopeInternal,
+                         aScope, aResultOut);
+ }
+ 
+ void
++nsGlobalWindow::NoteCalledRegisterForServiceWorkerScope(const nsACString& aScope)
++{
++  if (!mClientSource) {
++    return;
++  }
++
++  mClientSource->NoteCalledRegisterForServiceWorkerScope(aScope);
++}
++
++void
+ nsGlobalWindow::UpdateTopInnerWindow()
+ {
+   if (!IsInnerWindow() || AsInner()->IsTopInnerWindow()) {
+     return;
+   }
+ 
+   AsInner()->UpdateWebSocketCount(-(int32_t)mNumOfOpenWebSockets);
+ }
+diff --git a/dom/base/nsGlobalWindow.h b/dom/base/nsGlobalWindow.h
+--- a/dom/base/nsGlobalWindow.h
++++ b/dom/base/nsGlobalWindow.h
+@@ -402,16 +402,18 @@ public:
+   void Thaw();
+   virtual bool IsFrozen() const override;
+   void SyncStateFromParentWindow();
+ 
+   mozilla::Maybe<mozilla::dom::ClientInfo> GetClientInfo() const;
+   mozilla::Maybe<mozilla::dom::ClientState> GetClientState() const;
+   mozilla::Maybe<mozilla::dom::ServiceWorkerDescriptor> GetController() const;
+ 
++  void NoteCalledRegisterForServiceWorkerScope(const nsACString& aScope);
++
+   virtual nsresult FireDelayedDOMEvents() override;
+ 
+   // Outer windows only.
+   virtual bool WouldReuseInnerWindow(nsIDocument* aNewDocument) override;
+ 
+   virtual void SetDocShell(nsIDocShell* aDocShell) override;
+   virtual void DetachFromDocShell() override;
+   virtual nsresult SetNewDocument(nsIDocument *aDocument,
+diff --git a/dom/base/nsPIDOMWindow.h b/dom/base/nsPIDOMWindow.h
+--- a/dom/base/nsPIDOMWindow.h
++++ b/dom/base/nsPIDOMWindow.h
+@@ -937,16 +937,18 @@ public:
+   // Return true if there are any open WebSockets that could block
+   // timeout-throttling.
+   bool HasOpenWebSockets() const;
+ 
+   mozilla::Maybe<mozilla::dom::ClientInfo> GetClientInfo() const;
+   mozilla::Maybe<mozilla::dom::ClientState> GetClientState() const;
+   mozilla::Maybe<mozilla::dom::ServiceWorkerDescriptor> GetController() const;
+ 
++  void NoteCalledRegisterForServiceWorkerScope(const nsACString& aScope);
++
+ protected:
+   void CreatePerformanceObjectIfNeeded();
+ };
+ 
+ NS_DEFINE_STATIC_IID_ACCESSOR(nsPIDOMWindowInner, NS_PIDOMWINDOWINNER_IID)
+ 
+ // NB: It's very very important that these two classes have identical vtables
+ // and memory layout!
+diff --git a/dom/clients/manager/ClientSource.cpp b/dom/clients/manager/ClientSource.cpp
+--- a/dom/clients/manager/ClientSource.cpp
++++ b/dom/clients/manager/ClientSource.cpp
+@@ -672,10 +672,25 @@ ClientSource::Traverse(nsCycleCollection
+                                 aName, aFlags);
+   } else if (mOwner.is<nsCOMPtr<nsIDocShell>>()) {
+     ImplCycleCollectionTraverse(aCallback,
+                                 mOwner.as<nsCOMPtr<nsIDocShell>>(),
+                                 aName, aFlags);
+   }
+ }
+ 
++void
++ClientSource::NoteCalledRegisterForServiceWorkerScope(const nsACString& aScope)
++{
++  if (mRegisteringScopeList.Contains(aScope)) {
++    return;
++  }
++  mRegisteringScopeList.AppendElement(aScope);
++}
++
++bool
++ClientSource::CalledRegisterForServiceWorkerScope(const nsACString& aScope)
++{
++  return mRegisteringScopeList.Contains(aScope);
++}
++
+ } // namespace dom
+ } // namespace mozilla
+diff --git a/dom/clients/manager/ClientSource.h b/dom/clients/manager/ClientSource.h
+--- a/dom/clients/manager/ClientSource.h
++++ b/dom/clients/manager/ClientSource.h
+@@ -58,16 +58,22 @@ class ClientSource final : public Client
+   Variant<Nothing,
+           RefPtr<nsPIDOMWindowInner>,
+           nsCOMPtr<nsIDocShell>,
+           mozilla::dom::workers::WorkerPrivate*> mOwner;
+ 
+   ClientInfo mClientInfo;
+   Maybe<ServiceWorkerDescriptor> mController;
+ 
++  // Contained a de-duplicated list of ServiceWorker scope strings
++  // for which this client has called navigator.serviceWorker.register().
++  // Typically there will be either be zero or one scope strings, but
++  // there could be more.  We keep this list until the client is closed.
++  AutoTArray<nsCString, 1> mRegisteringScopeList;
++
+   void
+   Shutdown();
+ 
+   void
+   ExecutionReady(const ClientSourceExecutionReadyArgs& aArgs);
+ 
+   mozilla::dom::workers::WorkerPrivate*
+   GetWorkerPrivate() const;
+@@ -158,16 +164,22 @@ public:
+ 
+   nsISerialEventTarget*
+   EventTarget() const;
+ 
+   void
+   Traverse(nsCycleCollectionTraversalCallback& aCallback,
+            const char* aName,
+            uint32_t aFlags);
++
++  void
++  NoteCalledRegisterForServiceWorkerScope(const nsACString& aScope);
++
++  bool
++  CalledRegisterForServiceWorkerScope(const nsACString& aScope);
+ };
+ 
+ inline void
+ ImplCycleCollectionUnlink(UniquePtr<ClientSource>& aField)
+ {
+   aField.reset();
+ }
+ 
+diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp
+--- a/dom/workers/ServiceWorkerManager.cpp
++++ b/dom/workers/ServiceWorkerManager.cpp
+@@ -833,16 +833,17 @@ ServiceWorkerManager::Register(mozIDOMWi
+   }
+ 
+   nsAutoCString scopeKey;
+   rv = PrincipalToScopeKey(documentPrincipal, scopeKey);
+   if (NS_WARN_IF(NS_FAILED(rv))) {
+     return rv;
+   }
+ 
++  window->NoteCalledRegisterForServiceWorkerScope(cleanedScope);
+   AddRegisteringDocument(cleanedScope, doc);
+ 
+   RefPtr<ServiceWorkerJobQueue> queue = GetOrCreateJobQueue(scopeKey,
+                                                             cleanedScope);
+ 
+   RefPtr<ServiceWorkerResolveWindowPromiseOnRegisterCallback> cb =
+     new ServiceWorkerResolveWindowPromiseOnRegisterCallback(window, promise);
+ 
+@@ -3645,51 +3646,16 @@ ServiceWorkerManager::ShouldReportToWind
+   nsCOMPtr<nsPIDOMWindowOuter> targetWin = nsPIDOMWindowOuter::From(aWindow);
+   if (NS_WARN_IF(!targetWin)) {
+     return NS_OK;
+   }
+ 
+   targetWin = targetWin->GetScriptableTop();
+   uint64_t winId = targetWin->WindowID();
+ 
+-  // Check our weak registering document references first.  This way we clear
+-  // out as many dead weak references as possible when this method is called.
+-  WeakDocumentList* list = mRegisteringDocuments.Get(aScope);
+-  if (list) {
+-    for (int32_t i = list->Length() - 1; i >= 0; --i) {
+-      nsCOMPtr<nsIDocument> doc = do_QueryReferent(list->ElementAt(i));
+-      if (!doc) {
+-        list->RemoveElementAt(i);
+-        continue;
+-      }
+-
+-      if (!doc->IsCurrentActiveDocument()) {
+-        continue;
+-      }
+-
+-      nsCOMPtr<nsPIDOMWindowOuter> win = doc->GetWindow();
+-      if (!win) {
+-        continue;
+-      }
+-
+-      win = win->GetScriptableTop();
+-
+-      // Match.  We should report to this window.
+-      if (win && winId == win->WindowID()) {
+-        *aResult = true;
+-        return NS_OK;
+-      }
+-    }
+-
+-    if (list->IsEmpty()) {
+-      list = nullptr;
+-      mRegisteringDocuments.Remove(aScope);
+-    }
+-  }
+-
+   // Examine any windows performing a navigation that we are currently
+   // intercepting.
+   InterceptionList* intList = mNavigationInterceptions.Get(aScope);
+   if (intList) {
+     for (uint32_t i = 0; i < intList->Length(); ++i) {
+       nsCOMPtr<nsIInterceptedChannel> channel = intList->ElementAt(i);
+ 
+       nsCOMPtr<nsIChannel> inner;

+ 138 - 0
frg/work-js/mozilla-release/patches/1425316-5-59a1.patch

@@ -0,0 +1,138 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1513695889 18000
+# Node ID 8c2c417bbe76fe6f49f2e55503232b5cd54fe16f
+# Parent  ebff0951b29a2df5802be90520eaf01f2b88ebdb
+Bug 1425316 P5 Match service worker console reports against currently loading navigation channel. r=asuth
+
+diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp
+--- a/dom/base/nsGlobalWindow.cpp
++++ b/dom/base/nsGlobalWindow.cpp
+@@ -41,16 +41,17 @@
+ #include "nsISizeOfEventTarget.h"
+ #include "nsDOMJSUtils.h"
+ #include "nsArrayUtils.h"
+ #include "nsIDOMWindowCollection.h"
+ #include "nsDOMWindowList.h"
+ #include "mozilla/dom/WakeLock.h"
+ #include "mozilla/dom/power/PowerManagerService.h"
+ #include "nsIDocShellTreeOwner.h"
++#include "nsIDocumentLoader.h"
+ #include "nsIInterfaceRequestorUtils.h"
+ #include "nsIPermissionManager.h"
+ #include "nsIScriptContext.h"
+ #include "nsIScriptTimeoutHandler.h"
+ #include "nsITimeoutHandler.h"
+ #include "nsIController.h"
+ #include "nsScriptNameSpaceManager.h"
+ #include "nsISlowScriptDebug.h"
+@@ -78,17 +79,16 @@
+ #include "mozilla/Sprintf.h"
+ #include "mozilla/Unused.h"
+ 
+ // Other Classes
+ #include "mozilla/dom/BarProps.h"
+ #include "nsContentCID.h"
+ #include "nsLayoutStatics.h"
+ #include "nsCCUncollectableMarker.h"
+-#include "mozilla/dom/workers/ServiceWorkerManager.h"
+ #include "mozilla/dom/workers/Workers.h"
+ #include "mozilla/dom/ToJSValue.h"
+ #include "nsJSPrincipals.h"
+ #include "mozilla/Attributes.h"
+ #include "mozilla/Debug.h"
+ #include "mozilla/EventListenerManager.h"
+ #include "mozilla/EventStates.h"
+ #include "mozilla/MouseEvents.h"
+@@ -4450,30 +4450,17 @@ nsGlobalWindow::ShouldReportForServiceWo
+   NS_ENSURE_TRUE(topOuter, false);
+ 
+   nsGlobalWindow* topInner =
+     nsGlobalWindow::Cast(topOuter->GetCurrentInnerWindow());
+   NS_ENSURE_TRUE(topInner, false);
+ 
+   topInner->ShouldReportForServiceWorkerScopeInternal(NS_ConvertUTF16toUTF8(aScope),
+                                                       &result);
+-  if (result) {
+-    return true;
+-  }
+-
+-  RefPtr<workers::ServiceWorkerManager> swm =
+-    workers::ServiceWorkerManager::GetInstance();
+-  NS_ENSURE_TRUE(swm, false);
+-
+-  bool aResult = false;
+-  nsresult rv = swm->ShouldReportToWindow(topOuter,
+-                                          NS_ConvertUTF16toUTF8(aScope), &aResult);
+-  NS_ENSURE_SUCCESS(rv, false);
+-
+-  return aResult;
++  return result;
+ }
+ 
+ nsGlobalWindow::CallState
+ nsGlobalWindow::ShouldReportForServiceWorkerScopeInternal(const nsACString& aScope,
+                                                           bool* aResultOut)
+ {
+   MOZ_DIAGNOSTIC_ASSERT(aResultOut);
+ 
+@@ -4488,16 +4475,59 @@ nsGlobalWindow::ShouldReportForServiceWo
+   // Next, check to see if this window has called navigator.serviceWorker.register()
+   // for this scope.  If so, then treat this as a match so console reports
+   // appear in the devtools console.
+   if (mClientSource && mClientSource->CalledRegisterForServiceWorkerScope(aScope)) {
+     *aResultOut = true;
+     return CallState::Stop;
+   }
+ 
++  // Finally check the current docshell nsILoadGroup to see if there are any
++  // outstanding navigation requests.  If so, match the scope against the
++  // channel's URL.  We want to show console reports during the FetchEvent
++  // intercepting the navigation itself.
++  nsCOMPtr<nsIDocumentLoader> loader(do_QueryInterface(GetDocShell()));
++  if (loader) {
++    nsCOMPtr<nsILoadGroup> loadgroup;
++    Unused << loader->GetLoadGroup(getter_AddRefs(loadgroup));
++    if (loadgroup) {
++      nsCOMPtr<nsISimpleEnumerator> iter;
++      Unused << loadgroup->GetRequests(getter_AddRefs(iter));
++      if (iter) {
++        nsCOMPtr<nsISupports> tmp;
++        bool hasMore = true;
++        // Check each network request in the load group.
++        while (NS_SUCCEEDED(iter->HasMoreElements(&hasMore)) && hasMore) {
++          iter->GetNext(getter_AddRefs(tmp));
++          nsCOMPtr<nsIChannel> loadingChannel(do_QueryInterface(tmp));
++          // Ignore subresource requests.  Logging for a subresource
++          // FetchEvent should be handled above since the client is
++          // already controlled.
++          if (!loadingChannel ||
++              !nsContentUtils::IsNonSubresourceRequest(loadingChannel)) {
++            continue;
++          }
++          nsCOMPtr<nsIURI> loadingURL;
++          Unused << loadingChannel->GetURI(getter_AddRefs(loadingURL));
++          if (!loadingURL) {
++            continue;
++          }
++          nsAutoCString loadingSpec;
++          Unused << loadingURL->GetSpec(loadingSpec);
++          // Perform a simple substring comparison to match the scope
++          // against the channel URL.
++          if (StringBeginsWith(loadingSpec, aScope)) {
++            *aResultOut = true;
++            return CallState::Stop;
++          }
++        }
++      }
++    }
++  }
++
+   // The current window doesn't care about this service worker, but maybe
+   // one of our child frames does.
+   return CallOnChildren(&nsGlobalWindow::ShouldReportForServiceWorkerScopeInternal,
+                         aScope, aResultOut);
+ }
+ 
+ void
+ nsGlobalWindow::NoteCalledRegisterForServiceWorkerScope(const nsACString& aScope)

+ 107 - 0
frg/work-js/mozilla-release/patches/1425316-6-59a1.patch

@@ -0,0 +1,107 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1513695889 18000
+# Node ID 1e2457625325ac08b5820c186d82a8f1bdc7b20f
+# Parent  624263b006912c4fd30750ee55b1d1b3657634e5
+Bug 1425316 P6 Remove ServiceWorkerManager::ShouldReportToWindow(). r=asuth
+
+diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp
+--- a/dom/workers/ServiceWorkerManager.cpp
++++ b/dom/workers/ServiceWorkerManager.cpp
+@@ -3627,72 +3627,16 @@ ServiceWorkerManager::RemoveListener(nsI
+     return NS_ERROR_INVALID_ARG;
+   }
+ 
+   mListeners.RemoveElement(aListener);
+ 
+   return NS_OK;
+ }
+ 
+-nsresult
+-ServiceWorkerManager::ShouldReportToWindow(mozIDOMWindowProxy* aWindow,
+-                                           const nsACString& aScope,
+-                                           bool* aResult)
+-{
+-  AssertIsOnMainThread();
+-  MOZ_ASSERT(aResult);
+-
+-  *aResult = false;
+-
+-  // Get the inner window ID to compare to our document windows below.
+-  nsCOMPtr<nsPIDOMWindowOuter> targetWin = nsPIDOMWindowOuter::From(aWindow);
+-  if (NS_WARN_IF(!targetWin)) {
+-    return NS_OK;
+-  }
+-
+-  targetWin = targetWin->GetScriptableTop();
+-  uint64_t winId = targetWin->WindowID();
+-
+-  // Examine any windows performing a navigation that we are currently
+-  // intercepting.
+-  InterceptionList* intList = mNavigationInterceptions.Get(aScope);
+-  if (intList) {
+-    for (uint32_t i = 0; i < intList->Length(); ++i) {
+-      nsCOMPtr<nsIInterceptedChannel> channel = intList->ElementAt(i);
+-
+-      nsCOMPtr<nsIChannel> inner;
+-      nsresult rv = channel->GetChannel(getter_AddRefs(inner));
+-      if (NS_WARN_IF(NS_FAILED(rv))) {
+-        continue;
+-      }
+-
+-      uint64_t id = nsContentUtils::GetInnerWindowID(inner);
+-      if (id == 0) {
+-        continue;
+-      }
+-
+-      nsCOMPtr<nsPIDOMWindowInner> win = nsGlobalWindow::GetInnerWindowWithId(id)->AsInner();
+-      if (!win) {
+-        continue;
+-      }
+-
+-      nsCOMPtr<nsPIDOMWindowOuter> outer = win->GetScriptableTop();
+-
+-      // Match.  We should report to this window.
+-      if (outer && winId == outer->WindowID()) {
+-        *aResult = true;
+-        return NS_OK;
+-      }
+-    }
+-  }
+-
+-  // No match.  We should not report to this window.
+-  return NS_OK;
+-}
+-
+ NS_IMETHODIMP
+ ServiceWorkerManager::Observe(nsISupports* aSubject,
+                               const char* aTopic,
+                               const char16_t* aData)
+ {
+   if (strcmp(aTopic, PURGE_SESSION_HISTORY) == 0) {
+     MOZ_ASSERT(XRE_IsParentProcess());
+     RemoveAll();
+diff --git a/dom/workers/ServiceWorkerManager.h b/dom/workers/ServiceWorkerManager.h
+--- a/dom/workers/ServiceWorkerManager.h
++++ b/dom/workers/ServiceWorkerManager.h
+@@ -319,20 +319,16 @@ public:
+   NotifyUnregister(nsIPrincipal* aPrincipal, const nsAString& aScope);
+ 
+   void
+   WorkerIsIdle(ServiceWorkerInfo* aWorker);
+ 
+   void
+   CheckPendingReadyPromises();
+ 
+-  nsresult
+-  ShouldReportToWindow(mozIDOMWindowProxy* aWindow, const nsACString& aScope,
+-                       bool* aResult);
+-
+ private:
+   ServiceWorkerManager();
+   ~ServiceWorkerManager();
+ 
+   void
+   Init(ServiceWorkerRegistrar* aRegistrar);
+ 
+   void

+ 676 - 0
frg/work-js/mozilla-release/patches/1425574-1-59a1.patch

@@ -0,0 +1,676 @@
+# HG changeset patch
+# User Andrea Marchesini <amarchesini@mozilla.com>
+# Date 1515089983 -3600
+# Node ID e6a832b492dd99ce7d41dca8b6f797b150558344
+# Parent  21d487a16caca68dd554d82a0f70d4a189c44389
+Bug 1425574 - Fill the feature gap between Console.jsm and Console API - part 1 - Console.createInstance(), r=smaug
+
+diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf
+--- a/dom/bindings/Bindings.conf
++++ b/dom/bindings/Bindings.conf
+@@ -152,16 +152,20 @@ DOMInterfaces = {
+     'headerFile': 'mozilla/dom/WorkerPrivate.h',
+     'nativeType': 'mozilla::dom::workers::ChromeWorkerPrivate',
+ },
+ 
+ 'console': {
+     'nativeType': 'mozilla::dom::Console',
+ },
+ 
++'ConsoleInstance': {
++    'implicitJSContext': ['clear', 'count', 'groupEnd', 'time', 'timeEnd'],
++},
++
+ 'ConvolverNode': {
+     'implicitJSContext': [ 'buffer' ],
+ },
+ 
+ 'Coordinates': {
+     'headerFile': 'nsGeoPosition.h'
+ },
+ 
+diff --git a/dom/console/Console.cpp b/dom/console/Console.cpp
+--- a/dom/console/Console.cpp
++++ b/dom/console/Console.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 "mozilla/dom/Console.h"
++#include "mozilla/dom/ConsoleInstance.h"
+ #include "mozilla/dom/ConsoleBinding.h"
+ 
+ #include "mozilla/dom/BlobBinding.h"
+ #include "mozilla/dom/Exceptions.h"
+ #include "mozilla/dom/File.h"
+ #include "mozilla/dom/FunctionBinding.h"
+ #include "mozilla/dom/Performance.h"
+ #include "mozilla/dom/ScriptSettings.h"
+@@ -948,30 +949,32 @@ METHOD(Log, "log")
+ METHOD(Info, "info")
+ METHOD(Warn, "warn")
+ METHOD(Error, "error")
+ METHOD(Exception, "exception")
+ METHOD(Debug, "debug")
+ METHOD(Table, "table")
+ METHOD(Trace, "trace")
+ 
++// Displays an interactive listing of all the properties of an object.
++METHOD(Dir, "dir");
++METHOD(Dirxml, "dirxml");
++
++METHOD(Group, "group")
++METHOD(GroupCollapsed, "groupCollapsed")
++
++#undef METHOD
++
+ /* static */ void
+ Console::Clear(const GlobalObject& aGlobal)
+ {
+   const Sequence<JS::Value> data;
+   Method(aGlobal, MethodClear, NS_LITERAL_STRING("clear"), data);
+ }
+ 
+-// Displays an interactive listing of all the properties of an object.
+-METHOD(Dir, "dir");
+-METHOD(Dirxml, "dirxml");
+-
+-METHOD(Group, "group")
+-METHOD(GroupCollapsed, "groupCollapsed")
+-
+ /* static */ void
+ Console::GroupEnd(const GlobalObject& aGlobal)
+ {
+   const Sequence<JS::Value> data;
+   Method(aGlobal, MethodGroupEnd, NS_LITERAL_STRING("groupEnd"), data);
+ }
+ 
+ /* static */ void
+@@ -985,33 +988,45 @@ Console::TimeEnd(const GlobalObject& aGl
+ {
+   StringMethod(aGlobal, aLabel, MethodTimeEnd, NS_LITERAL_STRING("timeEnd"));
+ }
+ 
+ /* static */ void
+ Console::StringMethod(const GlobalObject& aGlobal, const nsAString& aLabel,
+                       MethodName aMethodName, const nsAString& aMethodString)
+ {
+-  JSContext* cx = aGlobal.Context();
+-
+-  ClearException ce(cx);
++  RefPtr<Console> console = GetConsole(aGlobal);
++  if (!console) {
++    return;
++  }
++
++  console->StringMethodInternal(aGlobal.Context(), aLabel, aMethodName,
++                                aMethodString);
++}
++
++void
++Console::StringMethodInternal(JSContext* aCx, const nsAString& aLabel,
++                              MethodName aMethodName,
++                              const nsAString& aMethodString)
++{
++  ClearException ce(aCx);
+ 
+   Sequence<JS::Value> data;
+-  SequenceRooter<JS::Value> rooter(cx, &data);
+-
+-  JS::Rooted<JS::Value> value(cx);
+-  if (!dom::ToJSValue(cx, aLabel, &value)) {
++  SequenceRooter<JS::Value> rooter(aCx, &data);
++
++  JS::Rooted<JS::Value> value(aCx);
++  if (!dom::ToJSValue(aCx, aLabel, &value)) {
+     return;
+   }
+ 
+   if (!data.AppendElement(value, fallible)) {
+     return;
+   }
+ 
+-  Method(aGlobal, aMethodName, aMethodString, data);
++  MethodInternal(aCx, aMethodName, aMethodString, data);
+ }
+ 
+ /* static */ void
+ Console::TimeStamp(const GlobalObject& aGlobal,
+                    const JS::Handle<JS::Value> aData)
+ {
+   JSContext* cx = aGlobal.Context();
+ 
+@@ -2506,16 +2521,24 @@ Console::MonotonicTimer(JSContext* aCx, 
+ 
+   WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+   MOZ_ASSERT(workerPrivate);
+ 
+   *aTimeStamp = workerPrivate->TimeStampToDOMHighRes(TimeStamp::Now());
+   return true;
+ }
+ 
++/* static */ already_AddRefed<ConsoleInstance>
++Console::CreateInstance(const GlobalObject& aGlobal,
++                        const ConsoleInstanceOptions& aOptions)
++{
++  RefPtr<ConsoleInstance> console = new ConsoleInstance(aOptions);
++  return console.forget();
++}
++
+ //  Bug 1425574 part 4 adds this.
+ //  void
+ //  Console::MaybeExecuteDumpFunctionForTrace(JSContext* aCx, nsIStackFrame* aStack)
+ //  {
+ //    if (!aStack || (!mDumpFunction && !mDumpToStdout)) {
+ //      return;
+ //    }
+ //
+diff --git a/dom/console/Console.h b/dom/console/Console.h
+--- a/dom/console/Console.h
++++ b/dom/console/Console.h
+@@ -22,19 +22,21 @@
+ class nsIConsoleAPIStorage;
+ class nsIPrincipal;
+ 
+ namespace mozilla {
+ namespace dom {
+ 
+ class AnyCallback;
+ class ConsoleCallData;
++class ConsoleInstance;
+ class ConsoleRunnable;
+ class ConsoleCallDataRunnable;
+ class ConsoleProfileRunnable;
++struct ConsoleInstanceOptions;
+ struct ConsoleTimerError;
+ struct ConsoleStackEntry;
+ 
+ class Console final : public nsIObserver
+                     , public nsSupportsWeakReference
+ {
+ public:
+   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+@@ -109,16 +111,20 @@ public:
+          const Sequence<JS::Value>& aData);
+ 
+   static void
+   Count(const GlobalObject& aGlobal, const nsAString& aLabel);
+ 
+   static void
+   Clear(const GlobalObject& aGlobal);
+ 
++  static already_AddRefed<ConsoleInstance>
++  CreateInstance(const GlobalObject& aGlobal,
++                 const ConsoleInstanceOptions& aOptions);
++
+   void
+   ClearStorage();
+ 
+   void
+   RetrieveConsoleEvents(JSContext* aCx, nsTArray<JS::Value>& aEvents,
+                         ErrorResult& aRv);
+ 
+   void
+@@ -178,16 +184,20 @@ private:
+   void
+   MethodInternal(JSContext* aCx, MethodName aName,
+                  const nsAString& aString, const Sequence<JS::Value>& aData);
+ 
+   static void
+   StringMethod(const GlobalObject& aGlobal, const nsAString& aLabel,
+                MethodName aMethodName, const nsAString& aMethodString);
+ 
++  void
++  StringMethodInternal(JSContext* aCx, const nsAString& aLabel,
++                       MethodName aMethodName, const nsAString& aMethodString);
++
+   // This method must receive aCx and aArguments in the same JSCompartment.
+   void
+   ProcessCallData(JSContext* aCx,
+                   ConsoleCallData* aData,
+                   const Sequence<JS::Value>& aArguments);
+ 
+   void
+   StoreCallData(ConsoleCallData* aData);
+@@ -412,16 +422,17 @@ private:
+     eShuttingDown
+   } mStatus;
+ 
+   // This is used when Console is created and it's used only for JSM custom
+   // console instance.
+   mozilla::TimeStamp mCreationTimeStamp;
+ 
+   friend class ConsoleCallData;
++  friend class ConsoleInstance;
+   friend class ConsoleRunnable;
+   friend class ConsoleCallDataRunnable;
+   friend class ConsoleProfileRunnable;
+ };
+ 
+ } // namespace dom
+ } // namespace mozilla
+ 
+diff --git a/dom/console/ConsoleInstance.cpp b/dom/console/ConsoleInstance.cpp
+new file mode 100644
+--- /dev/null
++++ b/dom/console/ConsoleInstance.cpp
+@@ -0,0 +1,134 @@
++/* -*- 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 "mozilla/dom/ConsoleInstance.h"
++#include "mozilla/dom/ConsoleBinding.h"
++
++namespace mozilla {
++namespace dom {
++
++NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ConsoleInstance, mConsole)
++
++NS_IMPL_CYCLE_COLLECTING_ADDREF(ConsoleInstance)
++NS_IMPL_CYCLE_COLLECTING_RELEASE(ConsoleInstance)
++
++NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ConsoleInstance)
++  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
++NS_INTERFACE_MAP_END
++
++ConsoleInstance::ConsoleInstance(const ConsoleInstanceOptions& aOptions)
++  : mConsole(new Console(nullptr))
++{}
++
++ConsoleInstance::~ConsoleInstance()
++{}
++
++JSObject*
++ConsoleInstance::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
++{
++  return ConsoleInstanceBinding::Wrap(aCx, this, aGivenProto);
++}
++
++#define METHOD(name, string)                                               \
++  void                                                                     \
++  ConsoleInstance::name(JSContext* aCx, const Sequence<JS::Value>& aData)  \
++  {                                                                        \
++    mConsole->MethodInternal(aCx, Console::Method##name,                   \
++                             NS_LITERAL_STRING(string), aData);            \
++  }
++
++METHOD(Log, "log")
++METHOD(Info, "info")
++METHOD(Warn, "warn")
++METHOD(Error, "error")
++METHOD(Exception, "exception")
++METHOD(Debug, "debug")
++METHOD(Table, "table")
++METHOD(Trace, "trace")
++METHOD(Dir, "dir");
++METHOD(Dirxml, "dirxml");
++METHOD(Group, "group")
++METHOD(GroupCollapsed, "groupCollapsed")
++
++#undef METHOD
++
++void
++ConsoleInstance::GroupEnd(JSContext* aCx)
++{
++  const Sequence<JS::Value> data;
++  mConsole->MethodInternal(aCx, Console::MethodGroupEnd,
++                           NS_LITERAL_STRING("groupEnd"), data);
++}
++
++void
++ConsoleInstance::Time(JSContext* aCx, const nsAString& aLabel)
++{
++  mConsole->StringMethodInternal(aCx, aLabel, Console::MethodTime,
++                                 NS_LITERAL_STRING("time"));
++}
++
++void
++ConsoleInstance::TimeEnd(JSContext* aCx, const nsAString& aLabel)
++{
++  mConsole->StringMethodInternal(aCx, aLabel, Console::MethodTimeEnd,
++                                 NS_LITERAL_STRING("timeEnd"));
++}
++
++void
++ConsoleInstance::TimeStamp(JSContext* aCx, const JS::Handle<JS::Value> aData)
++{
++  ClearException ce(aCx);
++
++  Sequence<JS::Value> data;
++  SequenceRooter<JS::Value> rooter(aCx, &data);
++
++  if (aData.isString() && !data.AppendElement(aData, fallible)) {
++    return;
++  }
++
++  mConsole->MethodInternal(aCx, Console::MethodTimeStamp,
++                           NS_LITERAL_STRING("timeStamp"), data);
++}
++
++void
++ConsoleInstance::Profile(JSContext* aCx, const Sequence<JS::Value>& aData)
++{
++  mConsole->ProfileMethodInternal(aCx, NS_LITERAL_STRING("profile"), aData);
++}
++
++void
++ConsoleInstance::ProfileEnd(JSContext* aCx, const Sequence<JS::Value>& aData)
++{
++  mConsole->ProfileMethodInternal(aCx, NS_LITERAL_STRING("profileEnd"), aData);
++}
++
++void
++ConsoleInstance::Assert(JSContext* aCx, bool aCondition,
++                        const Sequence<JS::Value>& aData)
++{
++  if (!aCondition) {
++    mConsole->MethodInternal(aCx, Console::MethodAssert,
++                             NS_LITERAL_STRING("assert"), aData);
++  }
++}
++
++void
++ConsoleInstance::Count(JSContext* aCx, const nsAString& aLabel)
++{
++  mConsole->StringMethodInternal(aCx, aLabel, Console::MethodCount,
++                                 NS_LITERAL_STRING("count"));
++}
++
++void
++ConsoleInstance::Clear(JSContext* aCx)
++{
++  const Sequence<JS::Value> data;
++  mConsole->MethodInternal(aCx, Console::MethodClear,
++                           NS_LITERAL_STRING("clear"), data);
++}
++
++} // namespace dom
++} // namespace mozilla
+diff --git a/dom/console/ConsoleInstance.h b/dom/console/ConsoleInstance.h
+new file mode 100644
+--- /dev/null
++++ b/dom/console/ConsoleInstance.h
+@@ -0,0 +1,106 @@
++/* -*- 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/. */
++
++#ifndef mozilla_dom_ConsoleInstance_h
++#define mozilla_dom_ConsoleInstance_h
++
++#include "mozilla/dom/Console.h"
++
++
++namespace mozilla {
++namespace dom {
++
++class ConsoleInstance final : public nsISupports
++                            , public nsWrapperCache
++{
++public:
++  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
++  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ConsoleInstance)
++
++  explicit ConsoleInstance(const ConsoleInstanceOptions& aOptions);
++
++  // WebIDL methods
++  JSObject*
++  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
++
++  nsPIDOMWindowInner* GetParentObject() const
++  {
++    return nullptr;
++  }
++
++  void
++  Log(JSContext* aCx, const Sequence<JS::Value>& aData);
++
++  void
++  Info(JSContext* aCx, const Sequence<JS::Value>& aData);
++
++  void
++  Warn(JSContext* aCx, const Sequence<JS::Value>& aData);
++
++  void
++  Error(JSContext* aCx, const Sequence<JS::Value>& aData);
++
++  void
++  Exception(JSContext* aCx, const Sequence<JS::Value>& aData);
++
++  void
++  Debug(JSContext* aCx, const Sequence<JS::Value>& aData);
++
++  void
++  Table(JSContext* aCx, const Sequence<JS::Value>& aData);
++
++  void
++  Trace(JSContext* aCx, const Sequence<JS::Value>& aData);
++
++  void
++  Dir(JSContext* aCx, const Sequence<JS::Value>& aData);
++
++  void
++  Dirxml(JSContext* aCx, const Sequence<JS::Value>& aData);
++
++  void
++  Group(JSContext* aCx, const Sequence<JS::Value>& aData);
++
++  void
++  GroupCollapsed(JSContext* aCx, const Sequence<JS::Value>& aData);
++
++  void
++  GroupEnd(JSContext* aCx);
++
++  void
++  Time(JSContext* aCx, const nsAString& aLabel);
++
++  void
++  TimeEnd(JSContext* aCx, const nsAString& aLabel);
++
++  void
++  TimeStamp(JSContext* aCx, const JS::Handle<JS::Value> aData);
++
++  void
++  Profile(JSContext* aCx, const Sequence<JS::Value>& aData);
++
++  void
++  ProfileEnd(JSContext* aCx, const Sequence<JS::Value>& aData);
++
++  void
++  Assert(JSContext* aCx, bool aCondition, const Sequence<JS::Value>& aData);
++
++  void
++  Count(JSContext* aCx, const nsAString& aLabel);
++
++  void
++  Clear(JSContext* aCx);
++
++private:
++  ~ConsoleInstance();
++
++  RefPtr<Console> mConsole;
++};
++
++} // dom namespace
++} // mozilla namespace
++
++#endif // mozilla_dom_ConsoleInstance_h
+diff --git a/dom/console/moz.build b/dom/console/moz.build
+--- a/dom/console/moz.build
++++ b/dom/console/moz.build
+@@ -18,20 +18,22 @@ EXPORTS += [
+ ]
+ 
+ EXPORTS.mozilla += [
+     'ConsoleReportCollector.h',
+ ]
+ 
+ EXPORTS.mozilla.dom += [
+     'Console.h',
++    'ConsoleInstance.h',
+ ]
+ 
+ UNIFIED_SOURCES += [
+     'Console.cpp',
++    'ConsoleInstance.cpp',
+     'ConsoleReportCollector.cpp',
+ ]
+ 
+ EXTRA_COMPONENTS += [
+     'ConsoleAPI.manifest',
+     'ConsoleAPIStorage.js',
+ ]
+ 
+diff --git a/dom/console/tests/console.jsm b/dom/console/tests/console.jsm
+--- a/dom/console/tests/console.jsm
++++ b/dom/console/tests/console.jsm
+@@ -2,10 +2,11 @@
+  * Any copyright is dedicated to the Public Domain.
+  * http://creativecommons.org/publicdomain/zero/1.0/
+  */
+ this.EXPORTED_SYMBOLS = [ "ConsoleTest" ];
+ 
+ this.ConsoleTest = {
+   go: function() {
+     console.log("Hello world!");
++    console.createInstance().log("Hello world!");
+   }
+ };
+diff --git a/dom/console/tests/test_jsm.xul b/dom/console/tests/test_jsm.xul
+--- a/dom/console/tests/test_jsm.xul
++++ b/dom/console/tests/test_jsm.xul
+@@ -17,25 +17,31 @@
+ 
+ const JSM = "chrome://mochitests/content/chrome/dom/console/tests/console.jsm";
+ 
+ function consoleListener() {
+   SpecialPowers.addObserver(this, "console-api-log-event");
+ }
+ 
+ consoleListener.prototype  = {
++  count: 0,
++
+   observe: function(aSubject, aTopic, aData) {
+     if (aTopic == "console-api-log-event") {
+       var obj = aSubject.wrappedJSObject;
+       if (obj.innerID == JSM) {
+         is(obj.ID, "jsm", "ID and InnerID are correctly set.");
+         is (obj.arguments[0], "Hello world!", "Message matches");
+ 
+-        SpecialPowers.removeObserver(this, "console-api-log-event");
+-        SimpleTest.finish();
++        // We want to see 2 messages, the first is generated by console.log,
++        // the second one from createInstance().log();
++        if (++this.count == 2) {
++          SpecialPowers.removeObserver(this, "console-api-log-event");
++          SimpleTest.finish();
++        }
+       }
+     }
+   }
+ }
+ function test() {
+   SimpleTest.waitForExplicitFinish();
+ 
+   var cl = new consoleListener();
+diff --git a/dom/webidl/Console.webidl b/dom/webidl/Console.webidl
+--- a/dom/webidl/Console.webidl
++++ b/dom/webidl/Console.webidl
+@@ -7,16 +7,20 @@
+  * For more information on this interface, please see
+  * https://console.spec.whatwg.org/#console-namespace
+  */
+ 
+ [Exposed=(Window,Worker,WorkerDebugger,Worklet,System),
+  ClassString="Console",
+  ProtoObjectHack]
+ namespace console {
++
++  // NOTE: if you touch this namespace, remember to update the ConsoleInstance
++  // interface as well!
++
+   // Logging
+   void assert(optional boolean condition = false, any... data);
+   void clear();
+   void count(optional DOMString label = "default");
+   void debug(any... data);
+   void error(any... data);
+   void info(any... data);
+   void log(any... data);
+@@ -40,16 +44,19 @@ namespace console {
+   void _exception(any... data);
+   void timeStamp(optional any data);
+ 
+   void profile(any... data);
+   void profileEnd(any... data);
+ 
+   [ChromeOnly]
+   const boolean IS_NATIVE_CONSOLE = true;
++
++  [ChromeOnly, NewObject]
++  ConsoleInstance createInstance(optional ConsoleInstanceOptions options);
+ };
+ 
+ // This is used to propagate console events to the observers.
+ dictionary ConsoleEvent {
+   (unsigned long long or DOMString) ID;
+   (unsigned long long or DOMString) innerID;
+   DOMString addonId = "";
+   DOMString level = "";
+@@ -104,8 +111,48 @@ dictionary ConsoleTimerError {
+ dictionary ConsoleCounter {
+   DOMString label = "";
+   unsigned long count = 0;
+ };
+ 
+ dictionary ConsoleCounterError {
+   DOMString error = "maxCountersExceeded";
+ };
++
++[ChromeOnly,
++ Exposed=(Window,Worker,WorkerDebugger,Worklet,System)]
++// This is basically a copy of the console namespace.
++interface ConsoleInstance {
++  // Logging
++  void assert(optional boolean condition = false, any... data);
++  void clear();
++  void count(optional DOMString label = "default");
++  void debug(any... data);
++  void error(any... data);
++  void info(any... data);
++  void log(any... data);
++  void table(any... data); // FIXME: The spec is still unclear about this.
++  void trace(any... data);
++  void warn(any... data);
++  void dir(any... data); // FIXME: This doesn't follow the spec yet.
++  void dirxml(any... data);
++
++  // Grouping
++  void group(any... data);
++  void groupCollapsed(any... data);
++  void groupEnd();
++
++  // Timing
++  void time(optional DOMString label = "default");
++  void timeEnd(optional DOMString label = "default");
++
++  // Mozilla only or Webcompat methods
++
++  void _exception(any... data);
++  void timeStamp(optional any data);
++
++  void profile(any... data);
++  void profileEnd(any... data);
++};
++
++dictionary ConsoleInstanceOptions {
++  // TODO
++};

+ 156 - 0
frg/work-js/mozilla-release/patches/1425574-2-59a1.patch

@@ -0,0 +1,156 @@
+# HG changeset patch
+# User Andrea Marchesini <amarchesini@mozilla.com>
+# Date 1515089983 -3600
+# Node ID bb19ee84ce26ee97dc599704ec8ba178bab1e9f5
+# Parent  49b981835673d69deff8e03235af2ebe5237d1ec
+Bug 1425574 - Fill the feature gap between Console.jsm and Console API - part 2 - consoleID in ConsoleEvent, r=smaug, r=bgrins
+
+diff --git a/dom/console/Console.cpp b/dom/console/Console.cpp
+--- a/dom/console/Console.cpp
++++ b/dom/console/Console.cpp
+@@ -1476,16 +1476,17 @@ Console::PopulateConsoleNotificationInTh
+     event.mInnerID.Value().SetAsUnsignedLongLong() = aData->mInnerIDNumber;
+   } else {
+     // aData->mIDType can be eUnknown when we dispatch notifications via
+     // mConsoleEventNotifier.
+     event.mID.Value().SetAsUnsignedLongLong() = 0;
+     event.mInnerID.Value().SetAsUnsignedLongLong() = 0;
+   }
+ 
++  event.mConsoleID = mConsoleID;
+   event.mLevel = aData->mMethodString;
+   event.mFilename = frame.mFilename;
+ 
+   nsCOMPtr<nsIURI> filenameURI;
+   nsAutoCString pass;
+   if (NS_IsMainThread() &&
+       NS_SUCCEEDED(NS_NewURI(getter_AddRefs(filenameURI), frame.mFilename)) &&
+       NS_SUCCEEDED(filenameURI->GetPassword(pass)) && !pass.IsEmpty()) {
+diff --git a/dom/console/Console.h b/dom/console/Console.h
+--- a/dom/console/Console.h
++++ b/dom/console/Console.h
+@@ -410,16 +410,17 @@ private:
+ 
+   RefPtr<AnyCallback> mConsoleEventNotifier;
+ 
+   // This is the stack for groupping.
+   nsTArray<nsString> mGroupStack;
+ 
+   uint64_t mOuterID;
+   uint64_t mInnerID;
++  nsString mConsoleID;
+ 
+   enum {
+     eUnknown,
+     eInitialized,
+     eShuttingDown
+   } mStatus;
+ 
+   // This is used when Console is created and it's used only for JSM custom
+diff --git a/dom/console/ConsoleInstance.cpp b/dom/console/ConsoleInstance.cpp
+--- a/dom/console/ConsoleInstance.cpp
++++ b/dom/console/ConsoleInstance.cpp
+@@ -16,17 +16,19 @@ NS_IMPL_CYCLE_COLLECTING_ADDREF(ConsoleI
+ NS_IMPL_CYCLE_COLLECTING_RELEASE(ConsoleInstance)
+ 
+ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ConsoleInstance)
+   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_END
+ 
+ ConsoleInstance::ConsoleInstance(const ConsoleInstanceOptions& aOptions)
+   : mConsole(new Console(nullptr))
+-{}
++{
++  mConsole->mConsoleID = aOptions.mConsoleID;
++}
+ 
+ ConsoleInstance::~ConsoleInstance()
+ {}
+ 
+ JSObject*
+ ConsoleInstance::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+ {
+   return ConsoleInstanceBinding::Wrap(aCx, this, aGivenProto);
+diff --git a/dom/console/tests/console.jsm b/dom/console/tests/console.jsm
+--- a/dom/console/tests/console.jsm
++++ b/dom/console/tests/console.jsm
+@@ -2,11 +2,13 @@
+  * Any copyright is dedicated to the Public Domain.
+  * http://creativecommons.org/publicdomain/zero/1.0/
+  */
+ this.EXPORTED_SYMBOLS = [ "ConsoleTest" ];
+ 
+ this.ConsoleTest = {
+   go: function() {
+     console.log("Hello world!");
+-    console.createInstance().log("Hello world!");
++    console.createInstance({
++      consoleID: "wow",
++    }).log("Hello world!");
+   }
+ };
+diff --git a/dom/console/tests/test_jsm.xul b/dom/console/tests/test_jsm.xul
+--- a/dom/console/tests/test_jsm.xul
++++ b/dom/console/tests/test_jsm.xul
+@@ -26,16 +26,22 @@ consoleListener.prototype  = {
+ 
+   observe: function(aSubject, aTopic, aData) {
+     if (aTopic == "console-api-log-event") {
+       var obj = aSubject.wrappedJSObject;
+       if (obj.innerID == JSM) {
+         is(obj.ID, "jsm", "ID and InnerID are correctly set.");
+         is (obj.arguments[0], "Hello world!", "Message matches");
+ 
++        if (this.count == 0) {
++          is(obj.consoleID, "", "No consoleID for console API");
++        } else {
++          is(obj.consoleID, "wow", "consoleID is set by consoleInstance");
++        }
++
+         // We want to see 2 messages, the first is generated by console.log,
+         // the second one from createInstance().log();
+         if (++this.count == 2) {
+           SpecialPowers.removeObserver(this, "console-api-log-event");
+           SimpleTest.finish();
+         }
+       }
+     }
+diff --git a/dom/webidl/Console.webidl b/dom/webidl/Console.webidl
+--- a/dom/webidl/Console.webidl
++++ b/dom/webidl/Console.webidl
+@@ -53,16 +53,17 @@ namespace console {
+   [ChromeOnly, NewObject]
+   ConsoleInstance createInstance(optional ConsoleInstanceOptions options);
+ };
+ 
+ // This is used to propagate console events to the observers.
+ dictionary ConsoleEvent {
+   (unsigned long long or DOMString) ID;
+   (unsigned long long or DOMString) innerID;
++  DOMString consoleID = "";
+   DOMString addonId = "";
+   DOMString level = "";
+   DOMString filename = "";
+   unsigned long lineNumber = 0;
+   unsigned long columnNumber = 0;
+   DOMString functionName = "";
+   double timeStamp = 0;
+   sequence<any> arguments;
+@@ -149,10 +150,16 @@ interface ConsoleInstance {
+   void _exception(any... data);
+   void timeStamp(optional any data);
+ 
+   void profile(any... data);
+   void profileEnd(any... data);
+ };
+ 
+ dictionary ConsoleInstanceOptions {
+-  // TODO
++/* TODO:
++  boolean dump = false;
++  DOMString prefix = "";
++  DOMString maxLogLevel = "";
++  DOMString innerID = "";
++*/
++  DOMString consoleID = "";
+ };

+ 157 - 0
frg/work-js/mozilla-release/patches/1425574-3-59a1.patch

@@ -0,0 +1,157 @@
+# HG changeset patch
+# User Andrea Marchesini <amarchesini@mozilla.com>
+# Date 1515089983 -3600
+# Node ID 1bd2d2b4d9e628a9d4c7376787da289e2f50abb9
+# Parent  3e30d8ac7f857952fd69852ab5cd6c7c308f07ee
+Bug 1425574 - Fill the feature gap between Console.jsm and Console API - part 3 - custom innerID, r=smaug
+
+diff --git a/dom/console/Console.cpp b/dom/console/Console.cpp
+--- a/dom/console/Console.cpp
++++ b/dom/console/Console.cpp
+@@ -1312,16 +1312,18 @@ Console::MethodInternal(JSContext* aCx, 
+     if (!callData->mCountValue) {
+       return;
+     }
+   }
+ 
+   if (NS_IsMainThread()) {
+     if (mWindow) {
+       callData->SetIDs(mOuterID, mInnerID);
++    } else if (!mPassedInnerID.IsEmpty()) {
++      callData->SetIDs(NS_LITERAL_STRING("jsm"), mPassedInnerID);
+     } else {
+       nsAutoString filename;
+       if (callData->mTopStackFrame.isSome()) {
+         filename = callData->mTopStackFrame->mFilename;
+       }
+ 
+       callData->SetIDs(NS_LITERAL_STRING("jsm"), filename);
+     }
+diff --git a/dom/console/Console.h b/dom/console/Console.h
+--- a/dom/console/Console.h
++++ b/dom/console/Console.h
+@@ -410,17 +410,20 @@ private:
+ 
+   RefPtr<AnyCallback> mConsoleEventNotifier;
+ 
+   // This is the stack for groupping.
+   nsTArray<nsString> mGroupStack;
+ 
+   uint64_t mOuterID;
+   uint64_t mInnerID;
++
++  // Set only by ConsoleInstance:
+   nsString mConsoleID;
++  nsString mPassedInnerID;
+ 
+   enum {
+     eUnknown,
+     eInitialized,
+     eShuttingDown
+   } mStatus;
+ 
+   // This is used when Console is created and it's used only for JSM custom
+diff --git a/dom/console/ConsoleInstance.cpp b/dom/console/ConsoleInstance.cpp
+--- a/dom/console/ConsoleInstance.cpp
++++ b/dom/console/ConsoleInstance.cpp
+@@ -18,16 +18,17 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(Console
+ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ConsoleInstance)
+   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_END
+ 
+ ConsoleInstance::ConsoleInstance(const ConsoleInstanceOptions& aOptions)
+   : mConsole(new Console(nullptr))
+ {
+   mConsole->mConsoleID = aOptions.mConsoleID;
++  mConsole->mPassedInnerID = aOptions.mInnerID;
+ }
+ 
+ ConsoleInstance::~ConsoleInstance()
+ {}
+ 
+ JSObject*
+ ConsoleInstance::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+ {
+diff --git a/dom/console/tests/console.jsm b/dom/console/tests/console.jsm
+--- a/dom/console/tests/console.jsm
++++ b/dom/console/tests/console.jsm
+@@ -2,13 +2,15 @@
+  * Any copyright is dedicated to the Public Domain.
+  * http://creativecommons.org/publicdomain/zero/1.0/
+  */
+ this.EXPORTED_SYMBOLS = [ "ConsoleTest" ];
+ 
+ this.ConsoleTest = {
+   go: function() {
+     console.log("Hello world!");
++    console.createInstance().log("Hello world!");
+     console.createInstance({
+       consoleID: "wow",
++      innerID: "CUSTOM INNER",
+     }).log("Hello world!");
+   }
+ };
+diff --git a/dom/console/tests/test_jsm.xul b/dom/console/tests/test_jsm.xul
+--- a/dom/console/tests/test_jsm.xul
++++ b/dom/console/tests/test_jsm.xul
+@@ -25,29 +25,31 @@ consoleListener.prototype  = {
+   count: 0,
+ 
+   observe: function(aSubject, aTopic, aData) {
+     if (aTopic == "console-api-log-event") {
+       var obj = aSubject.wrappedJSObject;
+       if (obj.innerID == JSM) {
+         is(obj.ID, "jsm", "ID and InnerID are correctly set.");
+         is (obj.arguments[0], "Hello world!", "Message matches");
+-
+-        if (this.count == 0) {
+-          is(obj.consoleID, "", "No consoleID for console API");
+-        } else {
+-          is(obj.consoleID, "wow", "consoleID is set by consoleInstance");
+-        }
++        is(obj.consoleID, "", "No consoleID for console API");
+ 
+-        // We want to see 2 messages, the first is generated by console.log,
+-        // the second one from createInstance().log();
+-        if (++this.count == 2) {
+-          SpecialPowers.removeObserver(this, "console-api-log-event");
+-          SimpleTest.finish();
+-        }
++        // We want to see 2 messages from this innerID, the first is generated
++        // by console.log, the second one from createInstance().log();
++        ++this.count;
++      } else if (obj.innerID = "CUSTOM INNER") {
++        is(obj.ID, "jsm", "ID and InnerID are correctly set.");
++        is (obj.arguments[0], "Hello world!", "Message matches");
++        is(obj.consoleID, "wow", "consoleID is set by consoleInstance");
++        ++this.count;
++      }
++
++      if (this.count == 3) {
++        SpecialPowers.removeObserver(this, "console-api-log-event");
++        SimpleTest.finish();
+       }
+     }
+   }
+ }
+ function test() {
+   SimpleTest.waitForExplicitFinish();
+ 
+   var cl = new consoleListener();
+diff --git a/dom/webidl/Console.webidl b/dom/webidl/Console.webidl
+--- a/dom/webidl/Console.webidl
++++ b/dom/webidl/Console.webidl
+@@ -154,12 +154,12 @@ interface ConsoleInstance {
+   void profileEnd(any... data);
+ };
+ 
+ dictionary ConsoleInstanceOptions {
+ /* TODO:
+   boolean dump = false;
+   DOMString prefix = "";
+   DOMString maxLogLevel = "";
++*/
+   DOMString innerID = "";
+-*/
+   DOMString consoleID = "";
+ };

+ 500 - 0
frg/work-js/mozilla-release/patches/1425574-4-59a1.patch

@@ -0,0 +1,500 @@
+# HG changeset patch
+# User Andrea Marchesini <amarchesini@mozilla.com>
+# Date 1515089984 -3600
+# Node ID 465b929da94d51a1224dd0972432d6a2cf783b30
+# Parent  088612381a359035f50639e4fdccbaab26efb967
+Bug 1425574 - Fill the feature gap between Console.jsm and Console API - part 4 - dump function, r=smaug, r=bgrins
+
+diff --git a/dom/console/Console.cpp b/dom/console/Console.cpp
+--- a/dom/console/Console.cpp
++++ b/dom/console/Console.cpp
+@@ -768,22 +768,24 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(Console)
+ 
+ // We don't need to traverse/unlink mStorage and mSandbox because they are not
+ // CCed objects and they are only used on the main thread, even when this
+ // Console object is used on workers.
+ 
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Console)
+   NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
+   NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsoleEventNotifier)
++  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDumpFunction)
+   tmp->Shutdown();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+ 
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Console)
+   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsoleEventNotifier)
++  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDumpFunction)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+ 
+ NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Console)
+   for (uint32_t i = 0; i < tmp->mCallDataStorage.Length(); ++i) {
+     tmp->mCallDataStorage[i]->Trace(aCallbacks, aClosure);
+   }
+ 
+   for (uint32_t i = 0; i < tmp->mCallDataStoragePending.Length(); ++i) {
+@@ -814,16 +816,17 @@ Console::Create(nsPIDOMWindowInner* aWin
+ 
+   return console.forget();
+ }
+ 
+ Console::Console(nsPIDOMWindowInner* aWindow)
+   : mWindow(aWindow)
+   , mOuterID(0)
+   , mInnerID(0)
++  , mDumpToStdout(false)
+   , mStatus(eUnknown)
+   , mCreationTimeStamp(TimeStamp::Now())
+ {
+   if (mWindow) {
+     MOZ_ASSERT(mWindow->IsInnerWindow());
+     mInnerID = mWindow->WindowID();
+ 
+     // Without outerwindow any console message coming from this object will not
+@@ -1072,16 +1075,18 @@ void
+ Console::ProfileMethodInternal(JSContext* aCx, const nsAString& aAction,
+                                const Sequence<JS::Value>& aData)
+ {
+   // Make all Console API no-op if DevTools aren't enabled.
+   if (!nsContentUtils::DevToolsEnabled(aCx)) {
+     return;
+   }
+ 
++  MaybeExecuteDumpFunction(aCx, aAction, aData);
++
+   if (!NS_IsMainThread()) {
+     // Here we are in a worker thread.
+     RefPtr<ConsoleProfileRunnable> runnable =
+       new ConsoleProfileRunnable(this, aAction, aData);
+ 
+     runnable->Dispatch(aCx);
+     return;
+   }
+@@ -1206,16 +1211,17 @@ void
+ Console::MethodInternal(JSContext* aCx, MethodName aMethodName,
+                         const nsAString& aMethodString,
+                         const Sequence<JS::Value>& aData)
+ {
+   // Make all Console API no-op if DevTools aren't enabled.
+   if (!nsContentUtils::DevToolsEnabled(aCx)) {
+     return;
+   }
++
+   AssertIsOnOwningThread();
+ 
+   RefPtr<ConsoleCallData> callData(new ConsoleCallData());
+ 
+   ClearException ce(aCx);
+ 
+   if (NS_WARN_IF(!callData->Initialize(aCx, aMethodName, aMethodString,
+                                        aData, this))) {
+@@ -1309,16 +1315,24 @@ Console::MethodInternal(JSContext* aCx, 
+ 
+   else if (aMethodName == MethodCount) {
+     callData->mCountValue = IncreaseCounter(aCx, aData, callData->mCountLabel);
+     if (!callData->mCountValue) {
+       return;
+     }
+   }
+ 
++  // Before processing this CallData differently, it's time to call the dump
++  // function.
++  if (aMethodName == MethodTrace) {
++    MaybeExecuteDumpFunctionForTrace(aCx, stack);
++  } else {
++    MaybeExecuteDumpFunction(aCx, aMethodString, aData);
++  }
++
+   if (NS_IsMainThread()) {
+     if (mWindow) {
+       callData->SetIDs(mOuterID, mInnerID);
+     } else if (!mPassedInnerID.IsEmpty()) {
+       callData->SetIDs(NS_LITERAL_STRING("jsm"), mPassedInnerID);
+     } else {
+       nsAutoString filename;
+       if (callData->mTopStackFrame.isSome()) {
+@@ -2532,66 +2546,106 @@ Console::MonotonicTimer(JSContext* aCx, 
+ /* static */ already_AddRefed<ConsoleInstance>
+ Console::CreateInstance(const GlobalObject& aGlobal,
+                         const ConsoleInstanceOptions& aOptions)
+ {
+   RefPtr<ConsoleInstance> console = new ConsoleInstance(aOptions);
+   return console.forget();
+ }
+ 
+-//  Bug 1425574 part 4 adds this.
+-//  void
+-//  Console::MaybeExecuteDumpFunctionForTrace(JSContext* aCx, nsIStackFrame* aStack)
+-//  {
+-//    if (!aStack || (!mDumpFunction && !mDumpToStdout)) {
+-//      return;
+-//    }
+-//
+-//    nsAutoString message;
+-//    message.AssignLiteral("console.trace:\n");
+-//
+-//    nsCOMPtr<nsIStackFrame> stack(aStack);
+-//  
+-//    while (stack) {
+-//      nsAutoString filename;
+-// -    nsresult rv = stack->GetFilename(aCx, filename);
+-// -    NS_ENSURE_SUCCESS_VOID(rv);
+-// +    stack->GetFilename(aCx, filename);
+-//
+-//      message.Append(filename);
+-//      message.AppendLiteral(" ");
+-//  
+-// -    int32_t lineNumber;
+-// -    nsresult rv = stack->GetLineNumber(aCx, &lineNumber);
+-// -    NS_ENSURE_SUCCESS_VOID(rv);
+-// -
+-// -    message.AppendInt(lineNumber);
+-// +    message.AppendInt(stack->GetLineNumber(aCx));
+-//      message.AppendLiteral(" ");
+-//
+-//      nsAutoString functionName;
+-// -    nsresult rv = stack->GetName(aCx, functionName);
+-// -    NS_ENSURE_SUCCESS_VOID(rv);
+-// +    stack->GetName(aCx, functionName);
+-//  
+-//      message.Append(filename);
+-//      message.AppendLiteral("\n");
+-//  
+-// -    nsCOMPtr<nsIStackFrame> caller;
+-// -    nsresult rv = stack->GetCaller(aCx, getter_AddRefs(caller));
+-// -    NS_ENSURE_SUCCESS_VOID(rv);
+-// +    nsCOMPtr<nsIStackFrame> caller = stack->GetCaller(aCx);
+-//  
+-//      if (!caller) {
+-// -      rv = stack->GetAsyncCaller(aCx, getter_AddRefs(caller));
+-// -      NS_ENSURE_SUCCESS_VOID(rv);
+-// +      caller = stack->GetAsyncCaller(aCx);
+-//      }
+-//
+-//      stack.swap(caller);
+-//    }
+-//  
+-//    message.AppendLiteral("\n");
+-//    ExecuteDumpFunction(message);
+-//  }
++void
++Console::MaybeExecuteDumpFunction(JSContext* aCx,
++                                  const nsAString& aMethodName,
++                                  const Sequence<JS::Value>& aData)
++{
++  if (!mDumpFunction && !mDumpToStdout) {
++    return;
++  }
++
++  nsAutoString message;
++  message.AssignLiteral("console.");
++  message.Append(aMethodName);
++  message.AppendLiteral(": ");
++
++  for (uint32_t i = 0; i < aData.Length(); ++i) {
++    JS::Rooted<JS::Value> v(aCx, aData[i]);
++    JS::Rooted<JSString*> jsString(aCx, JS_ValueToSource(aCx, v));
++    if (!jsString) {
++      continue;
++    }
++
++    nsAutoJSString string;
++    if (NS_WARN_IF(!string.init(aCx, jsString))) {
++      return;
++    }
++
++    if (i != 0) {
++      message.AppendLiteral(" ");
++    }
++
++    message.Append(string);
++  }
++
++  message.AppendLiteral("\n");
++  ExecuteDumpFunction(message);
++}
++
++void
++Console::MaybeExecuteDumpFunctionForTrace(JSContext* aCx, nsIStackFrame* aStack)
++{
++  if (!aStack || (!mDumpFunction && !mDumpToStdout)) {
++    return;
++  }
++
++  nsAutoString message;
++  message.AssignLiteral("console.trace:\n");
++
++  nsCOMPtr<nsIStackFrame> stack(aStack);
++
++  while (stack) {
++    nsAutoString filename;
++    stack->GetFilename(aCx, filename);
++
++    message.Append(filename);
++    message.AppendLiteral(" ");
++
++    message.AppendInt(stack->GetLineNumber(aCx));
++    message.AppendLiteral(" ");
++
++    nsAutoString functionName;
++    stack->GetName(aCx, functionName);
++
++    message.Append(filename);
++    message.AppendLiteral("\n");
++
++    nsCOMPtr<nsIStackFrame> caller = stack->GetCaller(aCx);
++
++    if (!caller) {
++      caller = stack->GetAsyncCaller(aCx);
++    }
++
++    stack.swap(caller);
++  }
++
++  message.AppendLiteral("\n");
++  ExecuteDumpFunction(message);
++}
++
++void
++Console::ExecuteDumpFunction(const nsAString& aMessage)
++{
++  if (mDumpFunction) {
++    IgnoredErrorResult rv;
++    mDumpFunction->Call(aMessage, rv);
++    return;
++  }
++
++  NS_ConvertUTF16toUTF8 str(aMessage);
++  MOZ_LOG(nsContentUtils::DOMDumpLog(), LogLevel::Debug, ("%s", str.get()));
++#ifdef ANDROID
++  __android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", str.get());
++#endif
++  fputs(str.get(), stdout);
++  fflush(stdout);
++}
+ 
+ } // namespace dom
+ } // namespace mozilla
+diff --git a/dom/console/Console.h b/dom/console/Console.h
+--- a/dom/console/Console.h
++++ b/dom/console/Console.h
+@@ -16,23 +16,25 @@
+ #include "nsHashKeys.h"
+ #include "nsIObserver.h"
+ #include "nsWeakReference.h"
+ #include "nsDOMNavigationTiming.h"
+ #include "nsPIDOMWindow.h"
+ 
+ class nsIConsoleAPIStorage;
+ class nsIPrincipal;
++class nsIStackFrame;
+ 
+ namespace mozilla {
+ namespace dom {
+ 
+ class AnyCallback;
+ class ConsoleCallData;
+ class ConsoleInstance;
++class ConsoleInstanceDumpCallback;
+ class ConsoleRunnable;
+ class ConsoleCallDataRunnable;
+ class ConsoleProfileRunnable;
+ struct ConsoleInstanceOptions;
+ struct ConsoleTimerError;
+ struct ConsoleStackEntry;
+ 
+ class Console final : public nsIObserver
+@@ -381,16 +383,26 @@ private:
+   bool
+   IsShuttingDown() const;
+ 
+   bool
+   MonotonicTimer(JSContext* aCx, MethodName aMethodName,
+                  const Sequence<JS::Value>& aData,
+                  DOMHighResTimeStamp* aTimeStamp);
+ 
++  void
++  MaybeExecuteDumpFunction(JSContext* aCx, const nsAString& aMethodName,
++                           const Sequence<JS::Value>& aData);
++
++  void
++  MaybeExecuteDumpFunctionForTrace(JSContext* aCx, nsIStackFrame* aStack);
++
++  void
++  ExecuteDumpFunction(const nsAString& aMessage);
++
+   // All these nsCOMPtr are touched on main thread only.
+   nsCOMPtr<nsPIDOMWindowInner> mWindow;
+   nsCOMPtr<nsIConsoleAPIStorage> mStorage;
+   RefPtr<JSObjectHolder> mSandbox;
+ 
+   // Touched on the owner thread.
+   nsDataHashtable<nsStringHashKey, DOMHighResTimeStamp> mTimerRegistry;
+   nsDataHashtable<nsStringHashKey, uint32_t> mCounterRegistry;
+@@ -414,16 +426,18 @@ private:
+   nsTArray<nsString> mGroupStack;
+ 
+   uint64_t mOuterID;
+   uint64_t mInnerID;
+ 
+   // Set only by ConsoleInstance:
+   nsString mConsoleID;
+   nsString mPassedInnerID;
++  RefPtr<ConsoleInstanceDumpCallback> mDumpFunction;
++  bool mDumpToStdout;
+ 
+   enum {
+     eUnknown,
+     eInitialized,
+     eShuttingDown
+   } mStatus;
+ 
+   // This is used when Console is created and it's used only for JSM custom
+diff --git a/dom/console/ConsoleInstance.cpp b/dom/console/ConsoleInstance.cpp
+--- a/dom/console/ConsoleInstance.cpp
++++ b/dom/console/ConsoleInstance.cpp
+@@ -19,16 +19,23 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
+   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_END
+ 
+ ConsoleInstance::ConsoleInstance(const ConsoleInstanceOptions& aOptions)
+   : mConsole(new Console(nullptr))
+ {
+   mConsole->mConsoleID = aOptions.mConsoleID;
+   mConsole->mPassedInnerID = aOptions.mInnerID;
++
++  if (aOptions.mDump.WasPassed()) {
++    mConsole->mDumpFunction = &aOptions.mDump.Value();
++  } else {
++    // For historical reasons, ConsoleInstance prints messages on stdout.
++    mConsole->mDumpToStdout = true;
++  }
+ }
+ 
+ ConsoleInstance::~ConsoleInstance()
+ {}
+ 
+ JSObject*
+ ConsoleInstance::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+ {
+diff --git a/dom/console/tests/console.jsm b/dom/console/tests/console.jsm
+--- a/dom/console/tests/console.jsm
++++ b/dom/console/tests/console.jsm
+@@ -1,16 +1,21 @@
+ /**
+  * Any copyright is dedicated to the Public Domain.
+  * http://creativecommons.org/publicdomain/zero/1.0/
+  */
+ this.EXPORTED_SYMBOLS = [ "ConsoleTest" ];
+ 
+ this.ConsoleTest = {
+-  go: function() {
++  go: function(dumpFunction) {
+     console.log("Hello world!");
+     console.createInstance().log("Hello world!");
+-    console.createInstance({
++
++    let c = console.createInstance({
+       consoleID: "wow",
+       innerID: "CUSTOM INNER",
+-    }).log("Hello world!");
++      dump: dumpFunction,
++    });
++
++    c.log("Hello world!");
++    c.trace("Hello world!");
+   }
+ };
+diff --git a/dom/console/tests/test_jsm.xul b/dom/console/tests/test_jsm.xul
+--- a/dom/console/tests/test_jsm.xul
++++ b/dom/console/tests/test_jsm.xul
+@@ -12,54 +12,61 @@
+   <script type="application/javascript"
+           src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ 
+   <script type="application/javascript">
+   <![CDATA[
+ 
+ const JSM = "chrome://mochitests/content/chrome/dom/console/tests/console.jsm";
+ 
++let dumpCalled = 0;
++function dumpFunction(msg) {
++  dump("Message received: " + msg); // Just for debugging
++  dumpCalled++;
++}
++
+ function consoleListener() {
+   SpecialPowers.addObserver(this, "console-api-log-event");
+ }
+ 
+ consoleListener.prototype  = {
+   count: 0,
+ 
+   observe: function(aSubject, aTopic, aData) {
+     if (aTopic == "console-api-log-event") {
+       var obj = aSubject.wrappedJSObject;
+       if (obj.innerID == JSM) {
+         is(obj.ID, "jsm", "ID and InnerID are correctly set.");
+-        is (obj.arguments[0], "Hello world!", "Message matches");
++        is(obj.arguments[0], "Hello world!", "Message matches");
+         is(obj.consoleID, "", "No consoleID for console API");
+ 
+         // We want to see 2 messages from this innerID, the first is generated
+         // by console.log, the second one from createInstance().log();
+         ++this.count;
+       } else if (obj.innerID = "CUSTOM INNER") {
+         is(obj.ID, "jsm", "ID and InnerID are correctly set.");
+-        is (obj.arguments[0], "Hello world!", "Message matches");
++        is(obj.arguments[0], "Hello world!", "Message matches");
+         is(obj.consoleID, "wow", "consoleID is set by consoleInstance");
+         ++this.count;
+       }
+ 
+-      if (this.count == 3) {
++      if (this.count == 4) {
++        is(dumpCalled, 2, "Dump has been called!");
+         SpecialPowers.removeObserver(this, "console-api-log-event");
+         SimpleTest.finish();
+       }
+     }
+   }
+ }
+ function test() {
+   SimpleTest.waitForExplicitFinish();
+ 
+   var cl = new consoleListener();
+   Components.utils.import(JSM);
+-  ConsoleTest.go();
++  ConsoleTest.go(dumpFunction);
+ }
+ 
+   ]]>
+   </script>
+ 
+   <body xmlns="http://www.w3.org/1999/xhtml">
+   </body>
+ </window>
+diff --git a/dom/webidl/Console.webidl b/dom/webidl/Console.webidl
+--- a/dom/webidl/Console.webidl
++++ b/dom/webidl/Console.webidl
+@@ -149,17 +149,19 @@ interface ConsoleInstance {
+ 
+   void _exception(any... data);
+   void timeStamp(optional any data);
+ 
+   void profile(any... data);
+   void profileEnd(any... data);
+ };
+ 
++callback ConsoleInstanceDumpCallback = void (DOMString message);
++
+ dictionary ConsoleInstanceOptions {
++  ConsoleInstanceDumpCallback dump;
+ /* TODO:
+-  boolean dump = false;
+   DOMString prefix = "";
+   DOMString maxLogLevel = "";
+ */
+   DOMString innerID = "";
+   DOMString consoleID = "";
+ };

+ 157 - 0
frg/work-js/mozilla-release/patches/1425574-5-59a1.patch

@@ -0,0 +1,157 @@
+# HG changeset patch
+# User Andrea Marchesini <amarchesini@mozilla.com>
+# Date 1515089984 -3600
+# Node ID 69dc8f8bea227722540cab47773f97a4fbb49e04
+# Parent  3409e99041cc282582428f0769fa113421bb4709
+Bug 1425574 - Fill the feature gap between Console.jsm and Console API - part 5 - prefix, r=smaug, r=bgrins
+
+diff --git a/dom/console/Console.cpp b/dom/console/Console.cpp
+--- a/dom/console/Console.cpp
++++ b/dom/console/Console.cpp
+@@ -2560,16 +2560,21 @@ Console::MaybeExecuteDumpFunction(JSCont
+     return;
+   }
+ 
+   nsAutoString message;
+   message.AssignLiteral("console.");
+   message.Append(aMethodName);
+   message.AppendLiteral(": ");
+ 
++  if (!mDumpPrefix.IsEmpty()) {
++    message.Append(mDumpPrefix);
++    message.AppendLiteral(": ");
++  }
++
+   for (uint32_t i = 0; i < aData.Length(); ++i) {
+     JS::Rooted<JS::Value> v(aCx, aData[i]);
+     JS::Rooted<JSString*> jsString(aCx, JS_ValueToSource(aCx, v));
+     if (!jsString) {
+       continue;
+     }
+ 
+     nsAutoJSString string;
+@@ -2593,16 +2598,21 @@ Console::MaybeExecuteDumpFunctionForTrac
+ {
+   if (!aStack || (!mDumpFunction && !mDumpToStdout)) {
+     return;
+   }
+ 
+   nsAutoString message;
+   message.AssignLiteral("console.trace:\n");
+ 
++  if (!mDumpPrefix.IsEmpty()) {
++    message.Append(mDumpPrefix);
++    message.AppendLiteral(": ");
++  }
++
+   nsCOMPtr<nsIStackFrame> stack(aStack);
+ 
+   while (stack) {
+     nsAutoString filename;
+     stack->GetFilename(aCx, filename);
+ 
+     message.Append(filename);
+     message.AppendLiteral(" ");
+diff --git a/dom/console/Console.h b/dom/console/Console.h
+--- a/dom/console/Console.h
++++ b/dom/console/Console.h
+@@ -428,16 +428,17 @@ private:
+   uint64_t mOuterID;
+   uint64_t mInnerID;
+ 
+   // Set only by ConsoleInstance:
+   nsString mConsoleID;
+   nsString mPassedInnerID;
+   RefPtr<ConsoleInstanceDumpCallback> mDumpFunction;
+   bool mDumpToStdout;
++  nsString mDumpPrefix;
+ 
+   enum {
+     eUnknown,
+     eInitialized,
+     eShuttingDown
+   } mStatus;
+ 
+   // This is used when Console is created and it's used only for JSM custom
+diff --git a/dom/console/ConsoleInstance.cpp b/dom/console/ConsoleInstance.cpp
+--- a/dom/console/ConsoleInstance.cpp
++++ b/dom/console/ConsoleInstance.cpp
+@@ -26,16 +26,18 @@ ConsoleInstance::ConsoleInstance(const C
+   mConsole->mPassedInnerID = aOptions.mInnerID;
+ 
+   if (aOptions.mDump.WasPassed()) {
+     mConsole->mDumpFunction = &aOptions.mDump.Value();
+   } else {
+     // For historical reasons, ConsoleInstance prints messages on stdout.
+     mConsole->mDumpToStdout = true;
+   }
++
++  mConsole->mDumpPrefix = aOptions.mPrefix;
+ }
+ 
+ ConsoleInstance::~ConsoleInstance()
+ {}
+ 
+ JSObject*
+ ConsoleInstance::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+ {
+diff --git a/dom/console/tests/console.jsm b/dom/console/tests/console.jsm
+--- a/dom/console/tests/console.jsm
++++ b/dom/console/tests/console.jsm
+@@ -8,14 +8,15 @@ this.ConsoleTest = {
+   go: function(dumpFunction) {
+     console.log("Hello world!");
+     console.createInstance().log("Hello world!");
+ 
+     let c = console.createInstance({
+       consoleID: "wow",
+       innerID: "CUSTOM INNER",
+       dump: dumpFunction,
++      prefix: "_PREFIX_";
+     });
+ 
+     c.log("Hello world!");
+     c.trace("Hello world!");
+   }
+ };
+diff --git a/dom/console/tests/test_jsm.xul b/dom/console/tests/test_jsm.xul
+--- a/dom/console/tests/test_jsm.xul
++++ b/dom/console/tests/test_jsm.xul
+@@ -14,16 +14,17 @@
+ 
+   <script type="application/javascript">
+   <![CDATA[
+ 
+ const JSM = "chrome://mochitests/content/chrome/dom/console/tests/console.jsm";
+ 
+ let dumpCalled = 0;
+ function dumpFunction(msg) {
++  ok(msg.indexOf("_PREFIX_") != -1, "we have a prefix");
+   dump("Message received: " + msg); // Just for debugging
+   dumpCalled++;
+ }
+ 
+ function consoleListener() {
+   SpecialPowers.addObserver(this, "console-api-log-event");
+ }
+ 
+diff --git a/dom/webidl/Console.webidl b/dom/webidl/Console.webidl
+--- a/dom/webidl/Console.webidl
++++ b/dom/webidl/Console.webidl
+@@ -153,15 +153,15 @@ interface ConsoleInstance {
+   void profile(any... data);
+   void profileEnd(any... data);
+ };
+ 
+ callback ConsoleInstanceDumpCallback = void (DOMString message);
+ 
+ dictionary ConsoleInstanceOptions {
+   ConsoleInstanceDumpCallback dump;
++  DOMString prefix = "";
+ /* TODO:
+-  DOMString prefix = "";
+   DOMString maxLogLevel = "";
+ */
+   DOMString innerID = "";
+   DOMString consoleID = "";
+ };

+ 25 - 0
frg/work-js/mozilla-release/patches/1425574-6-59a1.patch

@@ -0,0 +1,25 @@
+# HG changeset patch
+# User Andrea Marchesini <amarchesini@mozilla.com>
+# Date 1515089984 -3600
+# Node ID 0882de3b96b9e39a5075f4c2885ff9a8a2ac0d8b
+# Parent  87e5b25fe3c17491a5997bd7e851c4331e9d3f5b
+Bug 1425574 - Fill the feature gap between Console.jsm and Console API - part 6 - no maxLogLevel support, r=smaug
+
+diff --git a/dom/webidl/Console.webidl b/dom/webidl/Console.webidl
+--- a/dom/webidl/Console.webidl
++++ b/dom/webidl/Console.webidl
+@@ -154,14 +154,11 @@ interface ConsoleInstance {
+   void profileEnd(any... data);
+ };
+ 
+ callback ConsoleInstanceDumpCallback = void (DOMString message);
+ 
+ dictionary ConsoleInstanceOptions {
+   ConsoleInstanceDumpCallback dump;
+   DOMString prefix = "";
+-/* TODO:
+-  DOMString maxLogLevel = "";
+-*/
+   DOMString innerID = "";
+   DOMString consoleID = "";
+ };

+ 148 - 0
frg/work-js/mozilla-release/patches/1425574-7-59a1.patch

@@ -0,0 +1,148 @@
+# HG changeset patch
+# User Andrea Marchesini <amarchesini@mozilla.com>
+# Date 1515089984 -3600
+# Node ID 9f44d89eda685123154dfb9a7ce5736a57b72429
+# Parent  f6d3617953505b1b2780294bc8fa00eeff5fda32
+Bug 1425574 - Fill the feature gap between Console.jsm and Console API - part 7 - Console active, r=smaug
+
+diff --git a/dom/console/Console.cpp b/dom/console/Console.cpp
+--- a/dom/console/Console.cpp
++++ b/dom/console/Console.cpp
+@@ -817,16 +817,17 @@ Console::Create(nsPIDOMWindowInner* aWin
+   return console.forget();
+ }
+ 
+ Console::Console(nsPIDOMWindowInner* aWindow)
+   : mWindow(aWindow)
+   , mOuterID(0)
+   , mInnerID(0)
+   , mDumpToStdout(false)
++  , mChromeInstance(false)
+   , mStatus(eUnknown)
+   , mCreationTimeStamp(TimeStamp::Now())
+ {
+   if (mWindow) {
+     MOZ_ASSERT(mWindow->IsInnerWindow());
+     mInnerID = mWindow->WindowID();
+ 
+     // Without outerwindow any console message coming from this object will not
+@@ -1066,22 +1067,33 @@ Console::ProfileMethod(const GlobalObjec
+   if (!console) {
+     return;
+   }
+ 
+   JSContext* cx = aGlobal.Context();
+   console->ProfileMethodInternal(cx, aAction, aData);
+ }
+ 
++bool
++Console::IsEnabled(JSContext* aCx) const
++{
++  // Console is always enabled if it is a custom Chrome-Only instance.
++  if (mChromeInstance) {
++    return true;
++  }
++
++  // Make all Console API no-op if DevTools aren't enabled.
++  return nsContentUtils::DevToolsEnabled(aCx);
++}
++
+ void
+ Console::ProfileMethodInternal(JSContext* aCx, const nsAString& aAction,
+                                const Sequence<JS::Value>& aData)
+ {
+-  // Make all Console API no-op if DevTools aren't enabled.
+-  if (!nsContentUtils::DevToolsEnabled(aCx)) {
++  if (!IsEnabled(aCx)) {
+     return;
+   }
+ 
+   MaybeExecuteDumpFunction(aCx, aAction, aData);
+ 
+   if (!NS_IsMainThread()) {
+     // Here we are in a worker thread.
+     RefPtr<ConsoleProfileRunnable> runnable =
+@@ -1207,18 +1219,17 @@ Console::Method(const GlobalObject& aGlo
+                           aData);
+ }
+ 
+ void
+ Console::MethodInternal(JSContext* aCx, MethodName aMethodName,
+                         const nsAString& aMethodString,
+                         const Sequence<JS::Value>& aData)
+ {
+-  // Make all Console API no-op if DevTools aren't enabled.
+-  if (!nsContentUtils::DevToolsEnabled(aCx)) {
++  if (!IsEnabled(aCx)) {
+     return;
+   }
+ 
+   AssertIsOnOwningThread();
+ 
+   RefPtr<ConsoleCallData> callData(new ConsoleCallData());
+ 
+   ClearException ce(aCx);
+diff --git a/dom/console/Console.h b/dom/console/Console.h
+--- a/dom/console/Console.h
++++ b/dom/console/Console.h
+@@ -393,16 +393,19 @@ private:
+                            const Sequence<JS::Value>& aData);
+ 
+   void
+   MaybeExecuteDumpFunctionForTrace(JSContext* aCx, nsIStackFrame* aStack);
+ 
+   void
+   ExecuteDumpFunction(const nsAString& aMessage);
+ 
++  bool
++  IsEnabled(JSContext* aCx) const;
++
+   // All these nsCOMPtr are touched on main thread only.
+   nsCOMPtr<nsPIDOMWindowInner> mWindow;
+   nsCOMPtr<nsIConsoleAPIStorage> mStorage;
+   RefPtr<JSObjectHolder> mSandbox;
+ 
+   // Touched on the owner thread.
+   nsDataHashtable<nsStringHashKey, DOMHighResTimeStamp> mTimerRegistry;
+   nsDataHashtable<nsStringHashKey, uint32_t> mCounterRegistry;
+@@ -429,16 +432,17 @@ private:
+   uint64_t mInnerID;
+ 
+   // Set only by ConsoleInstance:
+   nsString mConsoleID;
+   nsString mPassedInnerID;
+   RefPtr<ConsoleInstanceDumpCallback> mDumpFunction;
+   bool mDumpToStdout;
+   nsString mDumpPrefix;
++  bool mChromeInstance;
+ 
+   enum {
+     eUnknown,
+     eInitialized,
+     eShuttingDown
+   } mStatus;
+ 
+   // This is used when Console is created and it's used only for JSM custom
+diff --git a/dom/console/ConsoleInstance.cpp b/dom/console/ConsoleInstance.cpp
+--- a/dom/console/ConsoleInstance.cpp
++++ b/dom/console/ConsoleInstance.cpp
+@@ -28,16 +28,19 @@ ConsoleInstance::ConsoleInstance(const C
+   if (aOptions.mDump.WasPassed()) {
+     mConsole->mDumpFunction = &aOptions.mDump.Value();
+   } else {
+     // For historical reasons, ConsoleInstance prints messages on stdout.
+     mConsole->mDumpToStdout = true;
+   }
+ 
+   mConsole->mDumpPrefix = aOptions.mPrefix;
++
++  // Let's inform that this is a custom instance.
++  mConsole->mChromeInstance = true;
+ }
+ 
+ ConsoleInstance::~ConsoleInstance()
+ {}
+ 
+ JSObject*
+ ConsoleInstance::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+ {

+ 459 - 0
frg/work-js/mozilla-release/patches/1425574-8-59a1.patch

@@ -0,0 +1,459 @@
+# HG changeset patch
+# User Andrea Marchesini <amarchesini@mozilla.com>
+# Date 1515089984 -3600
+# Node ID 7a4e812ee5adc626ba19828f770d720f0b9db9cd
+# Parent  b922efc65bbb8b02b8abc57a0fc135c3bdace741
+Bug 1425574 - Fill the feature gap between Console.jsm and Console API - part 8 - maxLogLevel, r=smaug
+
+diff --git a/dom/console/Console.cpp b/dom/console/Console.cpp
+--- a/dom/console/Console.cpp
++++ b/dom/console/Console.cpp
+@@ -661,19 +661,21 @@ private:
+ 
+   RefPtr<ConsoleCallData> mCallData;
+ };
+ 
+ // This runnable calls ProfileMethod() on the console on the main-thread.
+ class ConsoleProfileRunnable final : public ConsoleRunnable
+ {
+ public:
+-  ConsoleProfileRunnable(Console* aConsole, const nsAString& aAction,
++  ConsoleProfileRunnable(Console* aConsole, Console::MethodName aName,
++                         const nsAString& aAction,
+                          const Sequence<JS::Value>& aArguments)
+     : ConsoleRunnable(aConsole)
++    , mName(aName)
+     , mAction(aAction)
+     , mArguments(aArguments)
+   {
+     MOZ_ASSERT(aConsole);
+   }
+ 
+ private:
+   bool
+@@ -744,23 +746,24 @@ private:
+         return;
+       }
+ 
+       if (!arguments.AppendElement(value, fallible)) {
+         return;
+       }
+     }
+ 
+-    mConsole->ProfileMethodInternal(aCx, mAction, arguments);
++    mConsole->ProfileMethodInternal(aCx, mName, mAction, arguments);
+   }
+ 
+   virtual void
+   ReleaseData() override
+   {}
+ 
++  Console::MethodName mName;
+   nsString mAction;
+ 
+   // This is a reference of the sequence of arguments we receive from the DOM
+   // bindings and it's rooted by them. It's only used on the owning thread in
+   // PreDispatch().
+   const Sequence<JS::Value>& mArguments;
+ };
+ 
+@@ -818,16 +821,17 @@ Console::Create(nsPIDOMWindowInner* aWin
+ }
+ 
+ Console::Console(nsPIDOMWindowInner* aWindow)
+   : mWindow(aWindow)
+   , mOuterID(0)
+   , mInnerID(0)
+   , mDumpToStdout(false)
+   , mChromeInstance(false)
++  , mMaxLogLevel(ConsoleLogLevel::All)
+   , mStatus(eUnknown)
+   , mCreationTimeStamp(TimeStamp::Now())
+ {
+   if (mWindow) {
+     MOZ_ASSERT(mWindow->IsInnerWindow());
+     mInnerID = mWindow->WindowID();
+ 
+     // Without outerwindow any console message coming from this object will not
+@@ -1044,65 +1048,72 @@ Console::TimeStamp(const GlobalObject& a
+   }
+ 
+   Method(aGlobal, MethodTimeStamp, NS_LITERAL_STRING("timeStamp"), data);
+ }
+ 
+ /* static */ void
+ Console::Profile(const GlobalObject& aGlobal, const Sequence<JS::Value>& aData)
+ {
+-  ProfileMethod(aGlobal, NS_LITERAL_STRING("profile"), aData);
++  ProfileMethod(aGlobal, MethodProfile, NS_LITERAL_STRING("profile"), aData);
+ }
+ 
+ /* static */ void
+ Console::ProfileEnd(const GlobalObject& aGlobal,
+                     const Sequence<JS::Value>& aData)
+ {
+-  ProfileMethod(aGlobal, NS_LITERAL_STRING("profileEnd"), aData);
++  ProfileMethod(aGlobal, MethodProfileEnd, NS_LITERAL_STRING("profileEnd"),
++                aData);
+ }
+ 
+ /* static */ void
+-Console::ProfileMethod(const GlobalObject& aGlobal, const nsAString& aAction,
++Console::ProfileMethod(const GlobalObject& aGlobal, MethodName aName,
++                       const nsAString& aAction,
+                        const Sequence<JS::Value>& aData)
+ {
+   RefPtr<Console> console = GetConsole(aGlobal);
+   if (!console) {
+     return;
+   }
+ 
+   JSContext* cx = aGlobal.Context();
+-  console->ProfileMethodInternal(cx, aAction, aData);
++  console->ProfileMethodInternal(cx, aName, aAction, aData);
+ }
+ 
+ bool
+ Console::IsEnabled(JSContext* aCx) const
+ {
+   // Console is always enabled if it is a custom Chrome-Only instance.
+   if (mChromeInstance) {
+     return true;
+   }
+ 
+   // Make all Console API no-op if DevTools aren't enabled.
+   return nsContentUtils::DevToolsEnabled(aCx);
+ }
+ 
+ void
+-Console::ProfileMethodInternal(JSContext* aCx, const nsAString& aAction,
++Console::ProfileMethodInternal(JSContext* aCx, MethodName aMethodName,
++                               const nsAString& aAction,
+                                const Sequence<JS::Value>& aData)
+ {
+   if (!IsEnabled(aCx)) {
+     return;
+   }
+ 
++  if (!ShouldProceed(aMethodName)) {
++    return;
++  }
++
+   MaybeExecuteDumpFunction(aCx, aAction, aData);
+ 
+   if (!NS_IsMainThread()) {
+     // Here we are in a worker thread.
+     RefPtr<ConsoleProfileRunnable> runnable =
+-      new ConsoleProfileRunnable(this, aAction, aData);
++      new ConsoleProfileRunnable(this, aMethodName, aAction, aData);
+ 
+     runnable->Dispatch(aCx);
+     return;
+   }
+ 
+   ClearException ce(aCx);
+ 
+   RootedDictionary<ConsoleProfileEvent> event(aCx);
+@@ -1223,16 +1234,20 @@ void
+ Console::MethodInternal(JSContext* aCx, MethodName aMethodName,
+                         const nsAString& aMethodString,
+                         const Sequence<JS::Value>& aData)
+ {
+   if (!IsEnabled(aCx)) {
+     return;
+   }
+ 
++  if (!ShouldProceed(aMethodName)) {
++    return;
++  }
++
+   AssertIsOnOwningThread();
+ 
+   RefPtr<ConsoleCallData> callData(new ConsoleCallData());
+ 
+   ClearException ce(aCx);
+ 
+   if (NS_WARN_IF(!callData->Initialize(aCx, aMethodName, aMethodString,
+                                        aData, this))) {
+@@ -2663,10 +2678,79 @@ Console::ExecuteDumpFunction(const nsASt
+   MOZ_LOG(nsContentUtils::DOMDumpLog(), LogLevel::Debug, ("%s", str.get()));
+ #ifdef ANDROID
+   __android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", str.get());
+ #endif
+   fputs(str.get(), stdout);
+   fflush(stdout);
+ }
+ 
++bool
++Console::ShouldProceed(MethodName aName) const
++{
++  return WebIDLLogLevelToInteger(mMaxLogLevel) <=
++           InternalLogLevelToInteger(aName);
++}
++
++uint32_t
++Console::WebIDLLogLevelToInteger(ConsoleLogLevel aLevel) const
++{
++  switch (aLevel) {
++    case ConsoleLogLevel::All: return 0;
++    case ConsoleLogLevel::Debug: return 2;
++    case ConsoleLogLevel::Log: return 3;
++    case ConsoleLogLevel::Info: return 3;
++    case ConsoleLogLevel::Clear: return 3;
++    case ConsoleLogLevel::Trace: return 3;
++    case ConsoleLogLevel::TimeEnd: return 3;
++    case ConsoleLogLevel::Time: return 3;
++    case ConsoleLogLevel::Group: return 3;
++    case ConsoleLogLevel::GroupEnd: return 3;
++    case ConsoleLogLevel::Profile: return 3;
++    case ConsoleLogLevel::ProfileEnd: return 3;
++    case ConsoleLogLevel::Dir: return 3;
++    case ConsoleLogLevel::Dirxml: return 3;
++    case ConsoleLogLevel::Warn: return 4;
++    case ConsoleLogLevel::Error: return 5;
++    case ConsoleLogLevel::Off: return UINT32_MAX;
++    default:
++      MOZ_CRASH("ConsoleLogLevel is out of sync with the Console implementation!");
++      return 0;
++  }
++
++  return 0;
++}
++
++uint32_t
++Console::InternalLogLevelToInteger(MethodName aName) const
++{
++  switch (aName) {
++    case MethodLog: return 3;
++    case MethodInfo: return 3;
++    case MethodWarn: return 4;
++    case MethodError: return 5;
++    case MethodException: return 5;
++    case MethodDebug: return 2;
++    case MethodTable: return 3;
++    case MethodTrace: return 3;
++    case MethodDir: return 3;
++    case MethodDirxml: return 3;
++    case MethodGroup: return 3;
++    case MethodGroupCollapsed: return 3;
++    case MethodGroupEnd: return 3;
++    case MethodTime: return 3;
++    case MethodTimeEnd: return 3;
++    case MethodTimeStamp: return 3;
++    case MethodAssert: return 3;
++    case MethodCount: return 3;
++    case MethodClear: return 3;
++    case MethodProfile: return 3;
++    case MethodProfileEnd: return 3;
++    default:
++      MOZ_CRASH("MethodName is out of sync with the Console implementation!");
++      return 0;
++  }
++
++  return 0;
++}
++
+ } // namespace dom
+ } // namespace mozilla
+diff --git a/dom/console/Console.h b/dom/console/Console.h
+--- a/dom/console/Console.h
++++ b/dom/console/Console.h
+@@ -2,18 +2,17 @@
+ /* vim: set ts=8 sts=2 et sw=2 tw=80: */
+ /* This Source Code Form is subject to the terms of the Mozilla Public
+  * License, v. 2.0. If a copy of the MPL was not distributed with this
+  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+ 
+ #ifndef mozilla_dom_Console_h
+ #define mozilla_dom_Console_h
+ 
+-#include "mozilla/dom/BindingDeclarations.h"
+-#include "mozilla/ErrorResult.h"
++#include "mozilla/dom/ConsoleBinding.h"
+ #include "mozilla/JSObjectHolder.h"
+ #include "mozilla/TimeStamp.h"
+ #include "nsCycleCollectionParticipant.h"
+ #include "nsDataHashtable.h"
+ #include "nsHashKeys.h"
+ #include "nsIObserver.h"
+ #include "nsWeakReference.h"
+ #include "nsDOMNavigationTiming.h"
+@@ -28,19 +27,16 @@ namespace dom {
+ 
+ class AnyCallback;
+ class ConsoleCallData;
+ class ConsoleInstance;
+ class ConsoleInstanceDumpCallback;
+ class ConsoleRunnable;
+ class ConsoleCallDataRunnable;
+ class ConsoleProfileRunnable;
+-struct ConsoleInstanceOptions;
+-struct ConsoleTimerError;
+-struct ConsoleStackEntry;
+ 
+ class Console final : public nsIObserver
+                     , public nsSupportsWeakReference
+ {
+ public:
+   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(Console, nsIObserver)
+   NS_DECL_NSIOBSERVER
+@@ -157,31 +153,34 @@ private:
+     MethodGroup,
+     MethodGroupCollapsed,
+     MethodGroupEnd,
+     MethodTime,
+     MethodTimeEnd,
+     MethodTimeStamp,
+     MethodAssert,
+     MethodCount,
+-    MethodClear
++    MethodClear,
++    MethodProfile,
++    MethodProfileEnd,
+   };
+ 
+   static already_AddRefed<Console>
+   GetConsole(const GlobalObject& aGlobal);
+ 
+   static already_AddRefed<Console>
+   GetConsoleInternal(const GlobalObject& aGlobal, ErrorResult &aRv);
+ 
+   static void
+-  ProfileMethod(const GlobalObject& aGlobal, const nsAString& aAction,
+-                const Sequence<JS::Value>& aData);
++  ProfileMethod(const GlobalObject& aGlobal, MethodName aName,
++                const nsAString& aAction, const Sequence<JS::Value>& aData);
+ 
+   void
+-  ProfileMethodInternal(JSContext* aCx, const nsAString& aAction,
++  ProfileMethodInternal(JSContext* aCx, MethodName aName,
++                        const nsAString& aAction,
+                         const Sequence<JS::Value>& aData);
+ 
+   static void
+   Method(const GlobalObject& aGlobal, MethodName aName,
+          const nsAString& aString, const Sequence<JS::Value>& aData);
+ 
+   void
+   MethodInternal(JSContext* aCx, MethodName aName,
+@@ -396,16 +395,25 @@ private:
+   MaybeExecuteDumpFunctionForTrace(JSContext* aCx, nsIStackFrame* aStack);
+ 
+   void
+   ExecuteDumpFunction(const nsAString& aMessage);
+ 
+   bool
+   IsEnabled(JSContext* aCx) const;
+ 
++  bool
++  ShouldProceed(MethodName aName) const;
++
++  uint32_t
++  WebIDLLogLevelToInteger(ConsoleLogLevel aLevel) const;
++
++  uint32_t
++  InternalLogLevelToInteger(MethodName aName) const;
++
+   // All these nsCOMPtr are touched on main thread only.
+   nsCOMPtr<nsPIDOMWindowInner> mWindow;
+   nsCOMPtr<nsIConsoleAPIStorage> mStorage;
+   RefPtr<JSObjectHolder> mSandbox;
+ 
+   // Touched on the owner thread.
+   nsDataHashtable<nsStringHashKey, DOMHighResTimeStamp> mTimerRegistry;
+   nsDataHashtable<nsStringHashKey, uint32_t> mCounterRegistry;
+@@ -433,16 +441,17 @@ private:
+ 
+   // Set only by ConsoleInstance:
+   nsString mConsoleID;
+   nsString mPassedInnerID;
+   RefPtr<ConsoleInstanceDumpCallback> mDumpFunction;
+   bool mDumpToStdout;
+   nsString mDumpPrefix;
+   bool mChromeInstance;
++  ConsoleLogLevel mMaxLogLevel;
+ 
+   enum {
+     eUnknown,
+     eInitialized,
+     eShuttingDown
+   } mStatus;
+ 
+   // This is used when Console is created and it's used only for JSM custom
+diff --git a/dom/console/ConsoleInstance.cpp b/dom/console/ConsoleInstance.cpp
+--- a/dom/console/ConsoleInstance.cpp
++++ b/dom/console/ConsoleInstance.cpp
+@@ -31,16 +31,20 @@ ConsoleInstance::ConsoleInstance(const C
+     // For historical reasons, ConsoleInstance prints messages on stdout.
+     mConsole->mDumpToStdout = true;
+   }
+ 
+   mConsole->mDumpPrefix = aOptions.mPrefix;
+ 
+   // Let's inform that this is a custom instance.
+   mConsole->mChromeInstance = true;
++
++  if (aOptions.mMaxLogLevel.WasPassed()) {
++    mConsole->mMaxLogLevel = aOptions.mMaxLogLevel.Value();
++  }
+ }
+ 
+ ConsoleInstance::~ConsoleInstance()
+ {}
+ 
+ JSObject*
+ ConsoleInstance::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+ {
+@@ -106,23 +110,25 @@ ConsoleInstance::TimeStamp(JSContext* aC
+ 
+   mConsole->MethodInternal(aCx, Console::MethodTimeStamp,
+                            NS_LITERAL_STRING("timeStamp"), data);
+ }
+ 
+ void
+ ConsoleInstance::Profile(JSContext* aCx, const Sequence<JS::Value>& aData)
+ {
+-  mConsole->ProfileMethodInternal(aCx, NS_LITERAL_STRING("profile"), aData);
++  mConsole->ProfileMethodInternal(aCx, Console::MethodProfile,
++                                  NS_LITERAL_STRING("profile"), aData);
+ }
+ 
+ void
+ ConsoleInstance::ProfileEnd(JSContext* aCx, const Sequence<JS::Value>& aData)
+ {
+-  mConsole->ProfileMethodInternal(aCx, NS_LITERAL_STRING("profileEnd"), aData);
++  mConsole->ProfileMethodInternal(aCx, Console::MethodProfileEnd,
++                                  NS_LITERAL_STRING("profileEnd"), aData);
+ }
+ 
+ void
+ ConsoleInstance::Assert(JSContext* aCx, bool aCondition,
+                         const Sequence<JS::Value>& aData)
+ {
+   if (!aCondition) {
+     mConsole->MethodInternal(aCx, Console::MethodAssert,
+diff --git a/dom/webidl/Console.webidl b/dom/webidl/Console.webidl
+--- a/dom/webidl/Console.webidl
++++ b/dom/webidl/Console.webidl
+@@ -151,14 +151,20 @@ interface ConsoleInstance {
+   void timeStamp(optional any data);
+ 
+   void profile(any... data);
+   void profileEnd(any... data);
+ };
+ 
+ callback ConsoleInstanceDumpCallback = void (DOMString message);
+ 
++enum ConsoleLogLevel {
++  "all", "debug", "log", "info", "clear", "trace", "timeEnd", "time", "group",
++  "groupEnd", "profile", "profileEnd", "dir", "dirxml", "warn", "error", "off"
++};
++
+ dictionary ConsoleInstanceOptions {
+   ConsoleInstanceDumpCallback dump;
+   DOMString prefix = "";
+   DOMString innerID = "";
+   DOMString consoleID = "";
++  ConsoleLogLevel maxLogLevel;
+ };

+ 190 - 0
frg/work-js/mozilla-release/patches/1425574-9-59a1.patch

@@ -0,0 +1,190 @@
+# HG changeset patch
+# User Andrea Marchesini <amarchesini@mozilla.com>
+# Date 1515089984 -3600
+# Node ID edb150980a7f29cb7e7125b4805964e7b094c503
+# Parent  30420e3090bb207a8892703604ed3108fc3ba9b4
+Bug 1425574 - Fill the feature gap between Console.jsm and Console API - part 9 - maxLogLevel pref, r=smaug, r=bgrins
+
+diff --git a/dom/console/ConsoleInstance.cpp b/dom/console/ConsoleInstance.cpp
+--- a/dom/console/ConsoleInstance.cpp
++++ b/dom/console/ConsoleInstance.cpp
+@@ -14,16 +14,44 @@ NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Co
+ 
+ NS_IMPL_CYCLE_COLLECTING_ADDREF(ConsoleInstance)
+ NS_IMPL_CYCLE_COLLECTING_RELEASE(ConsoleInstance)
+ 
+ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ConsoleInstance)
+   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_END
+ 
++namespace {
++
++ConsoleLogLevel
++PrefToValue(const nsCString& aPref)
++{
++  if (!NS_IsMainThread()) {
++    NS_WARNING("Console.maxLogLevelPref is not supported on workers!");
++    return ConsoleLogLevel::All;
++  }
++
++  nsAutoCString value;
++  nsresult rv = Preferences::GetCString(aPref.get(), value);
++  if (NS_WARN_IF(NS_FAILED(rv))) {
++    return ConsoleLogLevel::All;
++  }
++
++  int index = FindEnumStringIndexImpl(value.get(), value.Length(),
++                                      ConsoleLogLevelValues::strings);
++  if (NS_WARN_IF(index < 0)) {
++    return ConsoleLogLevel::All;
++  }
++
++  MOZ_ASSERT(index < (int)ConsoleLogLevel::EndGuard_);
++  return static_cast<ConsoleLogLevel>(index);
++}
++
++} // anonymous
++
+ ConsoleInstance::ConsoleInstance(const ConsoleInstanceOptions& aOptions)
+   : mConsole(new Console(nullptr))
+ {
+   mConsole->mConsoleID = aOptions.mConsoleID;
+   mConsole->mPassedInnerID = aOptions.mInnerID;
+ 
+   if (aOptions.mDump.WasPassed()) {
+     mConsole->mDumpFunction = &aOptions.mDump.Value();
+@@ -35,16 +63,21 @@ ConsoleInstance::ConsoleInstance(const C
+   mConsole->mDumpPrefix = aOptions.mPrefix;
+ 
+   // Let's inform that this is a custom instance.
+   mConsole->mChromeInstance = true;
+ 
+   if (aOptions.mMaxLogLevel.WasPassed()) {
+     mConsole->mMaxLogLevel = aOptions.mMaxLogLevel.Value();
+   }
++
++  if (!aOptions.mMaxLogLevelPref.IsEmpty()) {
++    mConsole->mMaxLogLevel =
++      PrefToValue(NS_ConvertUTF16toUTF8(aOptions.mMaxLogLevelPref));
++  }
+ }
+ 
+ ConsoleInstance::~ConsoleInstance()
+ {}
+ 
+ JSObject*
+ ConsoleInstance::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+ {
+diff --git a/dom/console/tests/console.jsm b/dom/console/tests/console.jsm
+--- a/dom/console/tests/console.jsm
++++ b/dom/console/tests/console.jsm
+@@ -8,15 +8,20 @@ this.ConsoleTest = {
+   go: function(dumpFunction) {
+     console.log("Hello world!");
+     console.createInstance().log("Hello world!");
+ 
+     let c = console.createInstance({
+       consoleID: "wow",
+       innerID: "CUSTOM INNER",
+       dump: dumpFunction,
+-      prefix: "_PREFIX_";
++      prefix: "_PREFIX_",
+     });
+ 
+     c.log("Hello world!");
+     c.trace("Hello world!");
++
++    console.createInstance({ innerID: "LEVEL", maxLogLevel: "off" }).log("Invisible!");
++    console.createInstance({ innerID: "LEVEL",  maxLogLevel: "all" }).log("Hello world!");
++    console.createInstance({ innerID: "LEVEL", maxLogLevelPref: "foo.pref" }).log("Invisible!");
++    console.createInstance({ innerID: "LEVEL", maxLogLevelPref: "pref.test.console" }).log("Hello world!");
+   }
+ };
+diff --git a/dom/console/tests/test_jsm.xul b/dom/console/tests/test_jsm.xul
+--- a/dom/console/tests/test_jsm.xul
++++ b/dom/console/tests/test_jsm.xul
+@@ -37,37 +37,44 @@ consoleListener.prototype  = {
+       if (obj.innerID == JSM) {
+         is(obj.ID, "jsm", "ID and InnerID are correctly set.");
+         is(obj.arguments[0], "Hello world!", "Message matches");
+         is(obj.consoleID, "", "No consoleID for console API");
+ 
+         // We want to see 2 messages from this innerID, the first is generated
+         // by console.log, the second one from createInstance().log();
+         ++this.count;
+-      } else if (obj.innerID = "CUSTOM INNER") {
++      } else if (obj.innerID == "CUSTOM INNER") {
+         is(obj.ID, "jsm", "ID and InnerID are correctly set.");
+         is(obj.arguments[0], "Hello world!", "Message matches");
+         is(obj.consoleID, "wow", "consoleID is set by consoleInstance");
+         ++this.count;
++      } else if (obj.innerID == "LEVEL") {
++        // Nothing special... just we don't want to see 'invisible' messages.
++        is(obj.ID, "jsm", "ID and InnerID are correctly set.");
++        is(obj.arguments[0], "Hello world!", "Message matches");
++        ++this.count;
+       }
+ 
+       if (this.count == 4) {
+         is(dumpCalled, 2, "Dump has been called!");
+         SpecialPowers.removeObserver(this, "console-api-log-event");
+         SimpleTest.finish();
+       }
+     }
+   }
+ }
+ function test() {
+   SimpleTest.waitForExplicitFinish();
+ 
+-  var cl = new consoleListener();
+-  Components.utils.import(JSM);
+-  ConsoleTest.go(dumpFunction);
++  SpecialPowers.pushPrefEnv({set: [["pref.test.console", "log"]]}).then(() => {
++    var cl = new consoleListener();
++    Components.utils.import(JSM);
++    ConsoleTest.go(dumpFunction);
++  });
+ }
+ 
+   ]]>
+   </script>
+ 
+   <body xmlns="http://www.w3.org/1999/xhtml">
+   </body>
+ </window>
+diff --git a/dom/webidl/Console.webidl b/dom/webidl/Console.webidl
+--- a/dom/webidl/Console.webidl
++++ b/dom/webidl/Console.webidl
+@@ -157,14 +157,32 @@ interface ConsoleInstance {
+ callback ConsoleInstanceDumpCallback = void (DOMString message);
+ 
+ enum ConsoleLogLevel {
+   "all", "debug", "log", "info", "clear", "trace", "timeEnd", "time", "group",
+   "groupEnd", "profile", "profileEnd", "dir", "dirxml", "warn", "error", "off"
+ };
+ 
+ dictionary ConsoleInstanceOptions {
++  // An optional function to intercept all strings written to stdout.
+   ConsoleInstanceDumpCallback dump;
++
++  // An optional prefix string to be printed before the actual logged message.
+   DOMString prefix = "";
++
++  // An ID representing the source of the message. Normally the inner ID of a
++  // DOM window.
+   DOMString innerID = "";
++
++  // String identified for the console, this will be passed through the console
++  // notifications.
+   DOMString consoleID = "";
++
++  // Identifier that allows to filter which messages are logged based on their
++  // log level.
+   ConsoleLogLevel maxLogLevel;
++
++  // String pref name which contains the level to use for maxLogLevel. If the
++  // pref doesn't exist, gets removed or it is used in workers, the maxLogLevel
++  // will default to the value passed to this constructor (or "all" if it wasn't
++  // specified).
++  DOMString maxLogLevelPref = "";
+ };

+ 38 - 0
frg/work-js/mozilla-release/patches/1425614-1-59a1.patch

@@ -0,0 +1,38 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1513633330 18000
+# Node ID 926123cda34755f06f7b5be385ac94a721aaf2a2
+# Parent  36bcea224d06c54d69408f4253b0a250debae700
+Bug 1425614 P1 Make ClientThing::MaybeExecute() call an optional failure callback. r=asuth
+
+diff --git a/dom/clients/manager/ClientThing.h b/dom/clients/manager/ClientThing.h
+--- a/dom/clients/manager/ClientThing.h
++++ b/dom/clients/manager/ClientThing.h
+@@ -44,23 +44,25 @@ protected:
+   IsShutdown() const
+   {
+     return mShutdown;
+   }
+ 
+   // Conditionally execute the given callable based on the current state.
+   template<typename Callable>
+   void
+-  MaybeExecute(const Callable& aCallable)
++  MaybeExecute(const Callable& aSuccess,
++               const std::function<void()>& aFailure = []{})
+   {
+     if (mShutdown) {
++      aFailure();
+       return;
+     }
+     MOZ_DIAGNOSTIC_ASSERT(mActor);
+-    aCallable(mActor);
++    aSuccess(mActor);
+   }
+ 
+   // Attach activate the thing by attaching its underlying IPC actor.  This
+   // will make the thing register as the actor's owner as well.  The actor
+   // must call RevokeActor() to clear this weak back reference before its
+   // destroyed.
+   void
+   ActivateThing(ActorType* aActor)

+ 51 - 0
frg/work-js/mozilla-release/patches/1425614-2-59a1.patch

@@ -0,0 +1,51 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1513633330 18000
+# Node ID 10b24604cf6b3bafb2a6ba39b1e2a34281ebbe9e
+# Parent  3ec838c182ee0666ec921a1391a56932965d31e1
+Bug 1425614 P2 Make ClientManager and ClientHandle operation promises reject if called on a worker thread already shutting down. r=asuth
+
+diff --git a/dom/clients/manager/ClientHandle.cpp b/dom/clients/manager/ClientHandle.cpp
+--- a/dom/clients/manager/ClientHandle.cpp
++++ b/dom/clients/manager/ClientHandle.cpp
+@@ -52,16 +52,18 @@ ClientHandle::StartOp(const ClientOpCons
+                 [kungFuGrip] (nsresult) { });
+ 
+   MaybeExecute([aArgs, promise] (ClientHandleChild* aActor) {
+     ClientHandleOpChild* actor = new ClientHandleOpChild(aArgs, promise);
+     if (!aActor->SendPClientHandleOpConstructor(actor, aArgs)) {
+       // Constructor failure will reject promise via ActorDestroy()
+       return;
+     }
++  }, [promise] {
++    promise->Reject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__);
+   });
+ 
+   RefPtr<ClientOpPromise> ref = promise.get();
+   return ref.forget();
+ }
+ 
+ ClientHandle::ClientHandle(ClientManager* aManager,
+                            nsISerialEventTarget* aSerialEventTarget,
+diff --git a/dom/clients/manager/ClientManager.cpp b/dom/clients/manager/ClientManager.cpp
+--- a/dom/clients/manager/ClientManager.cpp
++++ b/dom/clients/manager/ClientManager.cpp
+@@ -166,16 +166,18 @@ ClientManager::StartOp(const ClientOpCon
+                 [kungFuGrip] (nsresult) { });
+ 
+   MaybeExecute([aArgs, promise] (ClientManagerChild* aActor) {
+     ClientManagerOpChild* actor = new ClientManagerOpChild(aArgs, promise);
+     if (!aActor->SendPClientManagerOpConstructor(actor, aArgs)) {
+       // Constructor failure will reject promise via ActorDestroy()
+       return;
+     }
++  }, [promise] {
++    promise->Reject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__);
+   });
+ 
+   RefPtr<ClientOpPromise> ref = promise.get();
+   return ref.forget();
+ }
+ 
+ // static
+ already_AddRefed<ClientManager>

+ 47 - 0
frg/work-js/mozilla-release/patches/1425704-59a1.patch

@@ -0,0 +1,47 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1513616118 18000
+# Node ID 332dc60a05c2be6783adbb42312c77367e5d49b9
+# Parent  0195d038b2bb6dd66c51c287318fc75bc0230142
+Bug 1425704 Fix nullptr deref in android-only Clients.openWindow() when browser is not running. r=asuth
+
+diff --git a/dom/clients/manager/ClientOpenWindowUtils.cpp b/dom/clients/manager/ClientOpenWindowUtils.cpp
+--- a/dom/clients/manager/ClientOpenWindowUtils.cpp
++++ b/dom/clients/manager/ClientOpenWindowUtils.cpp
+@@ -3,16 +3,17 @@
+ /* 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 "ClientOpenWindowUtils.h"
+ 
+ #include "ClientInfo.h"
+ #include "ClientState.h"
++#include "mozilla/SystemGroup.h"
+ #include "nsContentUtils.h"
+ #include "nsIBrowserDOMWindow.h"
+ #include "nsIDocShell.h"
+ #include "nsIDOMChromeWindow.h"
+ #include "nsIURI.h"
+ #include "nsIWebProgress.h"
+ #include "nsIWebProgressListener.h"
+ #include "nsIWindowWatcher.h"
+@@ -420,17 +421,17 @@ ClientOpenWindowInCurrentProcess(const C
+   nsresult rv = OpenWindow(aArgs, getter_AddRefs(outerWindow));
+ 
+ #ifdef MOZ_WIDGET_ANDROID
+   // If we get the NOT_AVAILABLE error that means the browser is still
+   // launching on android.  Use the observer we created above to wait
+   // until the launch completes and then try to open the window again.
+   if (rv == NS_ERROR_NOT_AVAILABLE && launchObserver) {
+     RefPtr<GenericPromise> p = launchObserver->Promise();
+-    p->Then(outerWindow->EventTargetFor(TaskCategory::Other), __func__,
++    p->Then(SystemGroup::EventTargetFor(TaskCategory::Other), __func__,
+       [aArgs, promise] (bool aResult) {
+         nsCOMPtr<nsPIDOMWindowOuter> outerWindow;
+         nsresult rv = OpenWindow(aArgs, getter_AddRefs(outerWindow));
+         if (NS_WARN_IF(NS_FAILED(rv))) {
+           promise->Reject(rv, __func__);
+         }
+ 
+         WaitForLoad(aArgs, outerWindow, promise);

+ 193 - 0
frg/work-js/mozilla-release/patches/1425965-1-59a1.patch

@@ -0,0 +1,193 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1515777363 18000
+# Node ID bbc20dc8387908c28ad7c60341c99b6d17ae5e6b
+# Parent  ddb1c786f7940274b410dafffbbce8fde828b10e
+Bug 1425965 P1 Make ServiceWorkerManager::ReportToAllClients use ConsoleUtils::ReportForServiceWorkerScope. r=asuth
+
+diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp
+--- a/dom/workers/ServiceWorkerManager.cpp
++++ b/dom/workers/ServiceWorkerManager.cpp
+@@ -34,16 +34,17 @@
+ #include "mozilla/ClearOnShutdown.h"
+ #include "mozilla/ErrorNames.h"
+ #include "mozilla/LoadContext.h"
+ #include "mozilla/SystemGroup.h"
+ #include "mozilla/Telemetry.h"
+ #include "mozilla/dom/BindingUtils.h"
+ #include "mozilla/dom/ClientHandle.h"
+ #include "mozilla/dom/ClientManager.h"
++#include "mozilla/dom/ConsoleUtils.h"
+ #include "mozilla/dom/ContentParent.h"
+ #include "mozilla/dom/ErrorEvent.h"
+ #include "mozilla/dom/Headers.h"
+ #include "mozilla/dom/InternalHeaders.h"
+ #include "mozilla/dom/Navigator.h"
+ #include "mozilla/dom/NotificationEvent.h"
+ #include "mozilla/dom/PromiseNativeHandler.h"
+ #include "mozilla/dom/PromiseWindowProxy.h"
+@@ -1720,158 +1721,22 @@ void
+ ServiceWorkerManager::ReportToAllClients(const nsCString& aScope,
+                                          const nsString& aMessage,
+                                          const nsString& aFilename,
+                                          const nsString& aLine,
+                                          uint32_t aLineNumber,
+                                          uint32_t aColumnNumber,
+                                          uint32_t aFlags)
+ {
+-  nsCOMPtr<nsIURI> uri;
+-  nsresult rv;
+-
+-  if (!aFilename.IsEmpty()) {
+-    rv = NS_NewURI(getter_AddRefs(uri), aFilename);
+-    if (NS_WARN_IF(NS_FAILED(rv))) {
+-      return;
+-    }
+-  }
+-
+-  AutoTArray<uint64_t, 16> windows;
+-
+-  // Report errors to every controlled document.
+-  for (auto iter = mControlledDocuments.Iter(); !iter.Done(); iter.Next()) {
+-    ServiceWorkerRegistrationInfo* reg = iter.UserData();
+-    MOZ_ASSERT(reg);
+-    if (!reg->mScope.Equals(aScope)) {
+-      continue;
+-    }
+-
+-    nsCOMPtr<nsIDocument> doc = do_QueryInterface(iter.Key());
+-    if (!doc || !doc->IsCurrentActiveDocument() || !doc->GetWindow()) {
+-      continue;
+-    }
+-
+-    windows.AppendElement(doc->InnerWindowID());
+-
+-    nsContentUtils::ReportToConsoleNonLocalized(aMessage,
+-                                                aFlags,
+-                                                NS_LITERAL_CSTRING("Service Workers"),
+-                                                doc,
+-                                                uri,
+-                                                aLine,
+-                                                aLineNumber,
+-                                                aColumnNumber,
+-                                                nsContentUtils::eOMIT_LOCATION);
+-  }
+-
+-  // Report to any documents that have called .register() for this scope.  They
+-  // may not be controlled, but will still want to see error reports.
+-  WeakDocumentList* regList = mRegisteringDocuments.Get(aScope);
+-  if (regList) {
+-    for (int32_t i = regList->Length() - 1; i >= 0; --i) {
+-      nsCOMPtr<nsIDocument> doc = do_QueryReferent(regList->ElementAt(i));
+-      if (!doc) {
+-        regList->RemoveElementAt(i);
+-        continue;
+-      }
+-
+-      if (!doc->IsCurrentActiveDocument()) {
+-        continue;
+-      }
+-
+-      uint64_t innerWindowId = doc->InnerWindowID();
+-      if (windows.Contains(innerWindowId)) {
+-        continue;
+-      }
+-
+-      windows.AppendElement(innerWindowId);
+-
+-      nsContentUtils::ReportToConsoleNonLocalized(aMessage,
+-                                                  aFlags,
+-                                                  NS_LITERAL_CSTRING("Service Workers"),
+-                                                  doc,
+-                                                  uri,
+-                                                  aLine,
+-                                                  aLineNumber,
+-                                                  aColumnNumber,
+-                                                  nsContentUtils::eOMIT_LOCATION);
+-    }
+-
+-    if (regList->IsEmpty()) {
+-      regList = nullptr;
+-      mRegisteringDocuments.Remove(aScope);
+-    }
+-  }
+-
+-  InterceptionList* intList = mNavigationInterceptions.Get(aScope);
+-  if (intList) {
+-    nsIConsoleService* consoleService = nullptr;
+-    for (uint32_t i = 0; i < intList->Length(); ++i) {
+-      nsCOMPtr<nsIInterceptedChannel> channel = intList->ElementAt(i);
+-
+-      nsCOMPtr<nsIChannel> inner;
+-      rv = channel->GetChannel(getter_AddRefs(inner));
+-      if (NS_WARN_IF(NS_FAILED(rv))) {
+-        continue;
+-      }
+-
+-      uint64_t innerWindowId = nsContentUtils::GetInnerWindowID(inner);
+-      if (innerWindowId == 0 || windows.Contains(innerWindowId)) {
+-        continue;
+-      }
+-
+-      windows.AppendElement(innerWindowId);
+-
+-      // Unfortunately the nsContentUtils helpers don't provide a convenient
+-      // way to log to a window ID without a document.  Use console service
+-      // directly.
+-      nsCOMPtr<nsIScriptError> errorObject =
+-        do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv);
+-      if (NS_WARN_IF(NS_FAILED(rv))) {
+-        return;
+-      }
+-
+-      rv = errorObject->InitWithWindowID(aMessage,
+-                                         aFilename,
+-                                         aLine,
+-                                         aLineNumber,
+-                                         aColumnNumber,
+-                                         aFlags,
+-                                         NS_LITERAL_CSTRING("Service Workers"),
+-                                         innerWindowId);
+-      if (NS_WARN_IF(NS_FAILED(rv))) {
+-        return;
+-      }
+-
+-      if (!consoleService) {
+-        rv = CallGetService(NS_CONSOLESERVICE_CONTRACTID, &consoleService);
+-        if (NS_WARN_IF(NS_FAILED(rv))) {
+-          return;
+-        }
+-      }
+-
+-      consoleService->LogMessage(errorObject);
+-    }
+-  }
+-
+-  // If there are no documents to report to, at least report something to the
+-  // browser console.
+-  if (windows.IsEmpty()) {
+-    nsContentUtils::ReportToConsoleNonLocalized(aMessage,
+-                                                aFlags,
+-                                                NS_LITERAL_CSTRING("Service Workers"),
+-                                                nullptr,  // document
+-                                                uri,
+-                                                aLine,
+-                                                aLineNumber,
+-                                                aColumnNumber,
+-                                                nsContentUtils::eOMIT_LOCATION);
+-    return;
+-  }
++  ConsoleUtils::ReportForServiceWorkerScope(NS_ConvertUTF8toUTF16(aScope),
++                                            aMessage,
++                                            aFilename,
++                                            aLineNumber,
++                                            aColumnNumber,
++                                            ConsoleUtils::eError);
+ }
+ 
+ /* static */
+ void
+ ServiceWorkerManager::LocalizeAndReportToAllClients(
+                                           const nsCString& aScope,
+                                           const char* aStringKey,
+                                           const nsTArray<nsString>& aParamArray,

+ 209 - 0
frg/work-js/mozilla-release/patches/1425965-2-59a1.patch

@@ -0,0 +1,209 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1515777364 18000
+# Node ID 57b875846062f8dcb318f9edfc7d2d3a5321e7fe
+# Parent  9de31aa12fea036c7e5e4abac39c4912ba110187
+Bug 1425965 P2 Add nsIConsoleReportCollector::FlushReportsToConsoleForServiceWorkerScope(). r=baku
+
+diff --git a/dom/console/ConsoleReportCollector.cpp b/dom/console/ConsoleReportCollector.cpp
+--- a/dom/console/ConsoleReportCollector.cpp
++++ b/dom/console/ConsoleReportCollector.cpp
+@@ -1,22 +1,25 @@
+ /* -*- 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 "mozilla/ConsoleReportCollector.h"
+ 
++#include "ConsoleUtils.h"
+ #include "nsIConsoleService.h"
+ #include "nsIScriptError.h"
+ #include "nsNetUtil.h"
+ 
+ namespace mozilla {
+ 
++using mozilla::dom::ConsoleUtils;
++
+ NS_IMPL_ISUPPORTS(ConsoleReportCollector, nsIConsoleReportCollector)
+ 
+ ConsoleReportCollector::ConsoleReportCollector()
+   : mMutex("mozilla::ConsoleReportCollector")
+ {
+ }
+ 
+ void
+@@ -90,16 +93,73 @@ ConsoleReportCollector::FlushReportsToCo
+                                               uri,
+                                               EmptyString(),
+                                               report.mLineNumber,
+                                               report.mColumnNumber);
+   }
+ }
+ 
+ void
++ConsoleReportCollector::FlushReportsToConsoleForServiceWorkerScope(const nsACString& aScope,
++                                                                   ReportAction aAction)
++{
++  nsTArray<PendingReport> reports;
++
++  {
++    MutexAutoLock lock(mMutex);
++    if (aAction == ReportAction::Forget) {
++      mPendingReports.SwapElements(reports);
++    } else {
++      reports = mPendingReports;
++    }
++  }
++
++  for (uint32_t i = 0; i < reports.Length(); ++i) {
++    PendingReport& report = reports[i];
++
++    nsAutoString errorText;
++    nsresult rv;
++    if (!report.mStringParams.IsEmpty()) {
++      rv = nsContentUtils::FormatLocalizedString(report.mPropertiesFile,
++                                                 report.mMessageName.get(),
++                                                 report.mStringParams,
++                                                 errorText);
++    } else {
++      rv = nsContentUtils::GetLocalizedString(report.mPropertiesFile,
++                                              report.mMessageName.get(),
++                                              errorText);
++    }
++    if (NS_WARN_IF(NS_FAILED(rv))) {
++      continue;
++    }
++
++    ConsoleUtils::Level level = ConsoleUtils::eLog;
++    switch (report.mErrorFlags) {
++      case nsIScriptError::errorFlag:
++      case nsIScriptError::exceptionFlag:
++        level = ConsoleUtils::eError;
++        break;
++      case nsIScriptError::warningFlag:
++        level = ConsoleUtils::eWarning;
++        break;
++      default:
++        // default to log otherwise
++        break;
++    }
++
++    ConsoleUtils::ReportForServiceWorkerScope(NS_ConvertUTF8toUTF16(aScope),
++                                              errorText,
++                                              NS_ConvertUTF8toUTF16(report.mSourceFileURI),
++                                              report.mLineNumber,
++                                              report.mColumnNumber,
++                                              level);
++  }
++}
++
++void
+ ConsoleReportCollector::FlushConsoleReports(nsIDocument* aDocument,
+                                             ReportAction aAction)
+ {
+   MOZ_ASSERT(NS_IsMainThread());
+ 
+   FlushReportsToConsole(aDocument ? aDocument->InnerWindowID() : 0, aAction);
+ }
+ 
+diff --git a/dom/console/ConsoleReportCollector.h b/dom/console/ConsoleReportCollector.h
+--- a/dom/console/ConsoleReportCollector.h
++++ b/dom/console/ConsoleReportCollector.h
+@@ -26,16 +26,20 @@ public:
+                    const nsACString& aMessageName,
+                    const nsTArray<nsString>& aStringParams) override;
+ 
+   void
+   FlushReportsToConsole(uint64_t aInnerWindowID,
+                         ReportAction aAction = ReportAction::Forget) override;
+ 
+   void
++  FlushReportsToConsoleForServiceWorkerScope(const nsACString& aScope,
++                                             ReportAction aAction = ReportAction::Forget) override;
++
++  void
+   FlushConsoleReports(nsIDocument* aDocument,
+                       ReportAction aAction = ReportAction::Forget) override;
+ 
+   void
+   FlushConsoleReports(nsILoadGroup* aLoadGroup,
+                       ReportAction aAction = ReportAction::Forget) override;
+ 
+   void
+diff --git a/dom/console/nsIConsoleReportCollector.h b/dom/console/nsIConsoleReportCollector.h
+--- a/dom/console/nsIConsoleReportCollector.h
++++ b/dom/console/nsIConsoleReportCollector.h
+@@ -77,16 +77,20 @@ public:
+   //
+   // aInnerWindowID A inner window ID representing where to flush the reports.
+   // aAction        An action to determine whether to reserve the pending
+   //                reports. Defalut action is to forget the report.
+   virtual void
+   FlushReportsToConsole(uint64_t aInnerWindowID,
+                         ReportAction aAction = ReportAction::Forget) = 0;
+ 
++  virtual void
++  FlushReportsToConsoleForServiceWorkerScope(const nsACString& aScope,
++                                             ReportAction aAction = ReportAction::Forget) = 0;
++
+   // Flush all pending reports to the console.  Main thread only.
+   //
+   // aDocument      An optional document representing where to flush the
+   //                reports.  If provided, then the corresponding window's
+   //                web console will get the reports.  Otherwise the reports
+   //                go to the browser console.
+   // aAction        An action to determine whether to reserve the pending
+   //                reports. Defalut action is to forget the report.
+diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp
+--- a/netwerk/protocol/http/HttpBaseChannel.cpp
++++ b/netwerk/protocol/http/HttpBaseChannel.cpp
+@@ -2965,16 +2965,23 @@ HttpBaseChannel::AddConsoleReport(uint32
+ void
+ HttpBaseChannel::FlushReportsToConsole(uint64_t aInnerWindowID,
+                                        ReportAction aAction)
+ {
+   mReportCollector->FlushReportsToConsole(aInnerWindowID, aAction);
+ }
+ 
+ void
++HttpBaseChannel::FlushReportsToConsoleForServiceWorkerScope(const nsACString& aScope,
++                                                            ReportAction aAction)
++{
++  mReportCollector->FlushReportsToConsoleForServiceWorkerScope(aScope, aAction);
++}
++
++void
+ HttpBaseChannel::FlushConsoleReports(nsIDocument* aDocument,
+                                      ReportAction aAction)
+ {
+   mReportCollector->FlushConsoleReports(aDocument, aAction);
+ }
+ 
+ void
+ HttpBaseChannel::FlushConsoleReports(nsILoadGroup* aLoadGroup,
+diff --git a/netwerk/protocol/http/HttpBaseChannel.h b/netwerk/protocol/http/HttpBaseChannel.h
+--- a/netwerk/protocol/http/HttpBaseChannel.h
++++ b/netwerk/protocol/http/HttpBaseChannel.h
+@@ -293,16 +293,20 @@ public:
+                    const nsACString& aMessageName,
+                    const nsTArray<nsString>& aStringParams) override;
+ 
+   void
+   FlushReportsToConsole(uint64_t aInnerWindowID,
+                         ReportAction aAction = ReportAction::Forget) override;
+ 
+   void
++  FlushReportsToConsoleForServiceWorkerScope(const nsACString& aScope,
++                                             ReportAction aAction = ReportAction::Forget) override;
++
++  void
+   FlushConsoleReports(nsIDocument* aDocument,
+                       ReportAction aAction = ReportAction::Forget) override;
+ 
+   void
+   FlushConsoleReports(nsILoadGroup* aLoadGroup,
+                       ReportAction aAction = ReportAction::Forget) override;
+ 
+   void

+ 210 - 0
frg/work-js/mozilla-release/patches/1425965-3-59a1.patch

@@ -0,0 +1,210 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1515777364 18000
+# Node ID 79280bcea4451e8f64d329f6ee3c46bc1321dff9
+# Parent  392509ed05f342d1cbd6c1dc58500ead3a8da702
+Bug 1425965 P3 Make ServiceWorkerManager::FlushReportsToAllClients() use FlushReportsToConsoleForServiceWorkerScope(). r=asuth
+
+diff --git a/dom/fetch/Fetch.cpp b/dom/fetch/Fetch.cpp
+--- a/dom/fetch/Fetch.cpp
++++ b/dom/fetch/Fetch.cpp
+@@ -33,17 +33,16 @@
+ #include "mozilla/dom/Headers.h"
+ #include "mozilla/dom/MutableBlobStreamListener.h"
+ #include "mozilla/dom/Promise.h"
+ #include "mozilla/dom/PromiseWorkerProxy.h"
+ #include "mozilla/dom/Request.h"
+ #include "mozilla/dom/Response.h"
+ #include "mozilla/dom/ScriptSettings.h"
+ #include "mozilla/dom/URLSearchParams.h"
+-#include "mozilla/dom/workers/ServiceWorkerManager.h"
+ 
+ #include "BodyExtractor.h"
+ #include "FetchObserver.h"
+ #include "InternalRequest.h"
+ #include "InternalResponse.h"
+ 
+ #include "WorkerPrivate.h"
+ #include "WorkerRunnable.h"
+@@ -857,23 +856,17 @@ WorkerFetchResolver::FlushConsoleReport(
+   workers::WorkerPrivate* worker = mPromiseProxy->GetWorkerPrivate();
+   if (!worker) {
+     mReporter->FlushReportsToConsole(0);
+     return;
+   }
+ 
+   if (worker->IsServiceWorker()) {
+     // Flush to service worker
+-    RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+-    if (!swm) {
+-      mReporter->FlushReportsToConsole(0);
+-      return;
+-    }
+-
+-    swm->FlushReportsToAllClients(worker->ServiceWorkerScope(), mReporter);
++    mReporter->FlushReportsToConsoleForServiceWorkerScope(worker->ServiceWorkerScope());
+     return;
+   }
+ 
+   if (worker->IsSharedWorker()) {
+     // Flush to shared worker
+     worker->FlushReportsToSharedWorkers(mReporter);
+     return;
+   }
+diff --git a/dom/fetch/FetchConsumer.cpp b/dom/fetch/FetchConsumer.cpp
+--- a/dom/fetch/FetchConsumer.cpp
++++ b/dom/fetch/FetchConsumer.cpp
+@@ -2,16 +2,17 @@
+ /* 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 "Fetch.h"
+ #include "FetchConsumer.h"
+ 
++#include "mozilla/ipc/PBackgroundSharedTypes.h"
+ #include "nsIInputStreamPump.h"
+ #include "nsProxyRelease.h"
+ #include "WorkerPrivate.h"
+ #include "WorkerRunnable.h"
+ #include "WorkerScope.h"
+ #include "Workers.h"
+ 
+ namespace mozilla {
+diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp
+--- a/dom/workers/ServiceWorkerManager.cpp
++++ b/dom/workers/ServiceWorkerManager.cpp
+@@ -1760,108 +1760,16 @@ ServiceWorkerManager::LocalizeAndReportT
+                             aFilename, aLine, aLineNumber, aColumnNumber,
+                             aFlags);
+   } else {
+     NS_WARNING("Failed to format and therefore report localized error.");
+   }
+ }
+ 
+ void
+-ServiceWorkerManager::FlushReportsToAllClients(const nsACString& aScope,
+-                                               nsIConsoleReportCollector* aReporter)
+-{
+-  AutoTArray<uint64_t, 16> windows;
+-
+-  // Report errors to every controlled document.
+-  for (auto iter = mControlledDocuments.Iter(); !iter.Done(); iter.Next()) {
+-    ServiceWorkerRegistrationInfo* reg = iter.UserData();
+-    MOZ_ASSERT(reg);
+-    if (!reg->mScope.Equals(aScope)) {
+-      continue;
+-    }
+-
+-    nsCOMPtr<nsIDocument> doc = do_QueryInterface(iter.Key());
+-    if (!doc || !doc->IsCurrentActiveDocument() || !doc->GetWindow()) {
+-      continue;
+-    }
+-
+-    uint64_t innerWindowId = doc->InnerWindowID();
+-    windows.AppendElement(innerWindowId);
+-
+-    aReporter->FlushReportsToConsole(
+-      innerWindowId, nsIConsoleReportCollector::ReportAction::Save);
+-  }
+-
+-  // Report to any documents that have called .register() for this scope.  They
+-  // may not be controlled, but will still want to see error reports.
+-  WeakDocumentList* regList = mRegisteringDocuments.Get(aScope);
+-  if (regList) {
+-    for (int32_t i = regList->Length() - 1; i >= 0; --i) {
+-      nsCOMPtr<nsIDocument> doc = do_QueryReferent(regList->ElementAt(i));
+-      if (!doc) {
+-        regList->RemoveElementAt(i);
+-        continue;
+-      }
+-
+-      if (!doc->IsCurrentActiveDocument()) {
+-        continue;
+-      }
+-
+-      uint64_t innerWindowId = doc->InnerWindowID();
+-      if (windows.Contains(innerWindowId)) {
+-        continue;
+-      }
+-
+-      windows.AppendElement(innerWindowId);
+-
+-      aReporter->FlushReportsToConsole(
+-        innerWindowId, nsIConsoleReportCollector::ReportAction::Save);
+-    }
+-
+-    if (regList->IsEmpty()) {
+-      regList = nullptr;
+-      mRegisteringDocuments.Remove(aScope);
+-    }
+-  }
+-
+-  nsresult rv;
+-  InterceptionList* intList = mNavigationInterceptions.Get(aScope);
+-  if (intList) {
+-    for (uint32_t i = 0; i < intList->Length(); ++i) {
+-      nsCOMPtr<nsIInterceptedChannel> channel = intList->ElementAt(i);
+-
+-      nsCOMPtr<nsIChannel> inner;
+-      rv = channel->GetChannel(getter_AddRefs(inner));
+-      if (NS_WARN_IF(NS_FAILED(rv))) {
+-        continue;
+-      }
+-
+-      uint64_t innerWindowId = nsContentUtils::GetInnerWindowID(inner);
+-      if (innerWindowId == 0 || windows.Contains(innerWindowId)) {
+-        continue;
+-      }
+-
+-      windows.AppendElement(innerWindowId);
+-
+-      aReporter->FlushReportsToConsole(
+-        innerWindowId, nsIConsoleReportCollector::ReportAction::Save);
+-    }
+-  }
+-
+-  // If there are no documents to report to, at least report something to the
+-  // browser console.
+-  if (windows.IsEmpty()) {
+-    aReporter->FlushReportsToConsole(0);
+-    return;
+-  }
+-
+-  aReporter->ClearConsoleReports();
+-}
+-
+-void
+ ServiceWorkerManager::HandleError(JSContext* aCx,
+                                   nsIPrincipal* aPrincipal,
+                                   const nsCString& aScope,
+                                   const nsString& aWorkerURL,
+                                   const nsString& aMessage,
+                                   const nsString& aFilename,
+                                   const nsString& aLine,
+                                   uint32_t aLineNumber,
+diff --git a/dom/workers/ServiceWorkerManager.h b/dom/workers/ServiceWorkerManager.h
+--- a/dom/workers/ServiceWorkerManager.h
++++ b/dom/workers/ServiceWorkerManager.h
+@@ -260,20 +260,16 @@ public:
+                                 const char* aStringKey,
+                                 const nsTArray<nsString>& aParamArray,
+                                 uint32_t aFlags = 0x0,
+                                 const nsString& aFilename = EmptyString(),
+                                 const nsString& aLine = EmptyString(),
+                                 uint32_t aLineNumber = 0,
+                                 uint32_t aColumnNumber = 0);
+ 
+-  void
+-  FlushReportsToAllClients(const nsACString& aScope,
+-                           nsIConsoleReportCollector* aReporter);
+-
+   // Always consumes the error by reporting to consoles of all controlled
+   // documents.
+   void
+   HandleError(JSContext* aCx,
+               nsIPrincipal* aPrincipal,
+               const nsCString& aScope,
+               const nsString& aWorkerURL,
+               const nsString& aMessage,

+ 507 - 0
frg/work-js/mozilla-release/patches/1425965-4-59a1.patch

@@ -0,0 +1,507 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1515777365 18000
+# Node ID 4027039552fea211647109c3d45b4215d759bd52
+# Parent  6f132aee4f2a1aa7a39572e9fbc0ecdd2ad3a87a
+Bug 1425965 P4 Remove mControlledDocument, mRegisteringDocuments, and mNavigationIntercepts from ServiceWorkerManager. r=asuth
+
+diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp
+--- a/dom/base/nsDocument.cpp
++++ b/dom/base/nsDocument.cpp
+@@ -4898,34 +4898,29 @@ nsDocument::SetScriptGlobalObject(nsIScr
+     // Also make sure to remove our onload blocker now if we haven't done it yet
+     if (mOnloadBlockCount != 0) {
+       nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
+       if (loadGroup) {
+         loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK);
+       }
+     }
+ 
+-    using mozilla::dom::workers::ServiceWorkerManager;
+-    RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+-    if (swm) {
+-      ErrorResult error;
+-      if (GetController().isSome()) {
+-        imgLoader* loader = nsContentUtils::GetImgLoaderForDocument(this);
+-        if (loader) {
+-          loader->ClearCacheForControlledDocument(this);
+-        }
+-
+-        // We may become controlled again if this document comes back out
+-        // of bfcache.  Clear our state to allow that to happen.  Only
+-        // clear this flag if we are actually controlled, though, so pages
+-        // that were force reloaded don't become controlled when they
+-        // come out of bfcache.
+-        mMaybeServiceWorkerControlled = false;
+-      }
+-      swm->MaybeStopControlling(this);
++    ErrorResult error;
++    if (GetController().isSome()) {
++      imgLoader* loader = nsContentUtils::GetImgLoaderForDocument(this);
++      if (loader) {
++        loader->ClearCacheForControlledDocument(this);
++      }
++
++      // We may become controlled again if this document comes back out
++      // of bfcache.  Clear our state to allow that to happen.  Only
++      // clear this flag if we are actually controlled, though, so pages
++      // that were force reloaded don't become controlled when they
++      // come out of bfcache.
++      mMaybeServiceWorkerControlled = false;
+     }
+   }
+ 
+   // BlockOnload() might be called before mScriptGlobalObject is set.
+   // We may need to add the blocker once mScriptGlobalObject is set.
+   bool needOnloadBlocker = !mScriptGlobalObject && aScriptGlobalObject;
+ 
+   mScriptGlobalObject = aScriptGlobalObject;
+@@ -5035,21 +5030,17 @@ nsDocument::SetScriptGlobalObject(nsIScr
+     docShell->GetLoadType(&loadType);
+ 
+     // If we are shift-reloaded, don't associate with a ServiceWorker.
+     if (IsForceReloadType(loadType)) {
+       NS_WARNING("Page was shift reloaded, skipping ServiceWorker control");
+       return;
+     }
+ 
+-    nsCOMPtr<nsIServiceWorkerManager> swm = mozilla::services::GetServiceWorkerManager();
+-    if (swm) {
+-      swm->MaybeStartControlling(this);
+-      mMaybeServiceWorkerControlled = true;
+-    }
++    mMaybeServiceWorkerControlled = true;
+   }
+ }
+ 
+ nsIScriptGlobalObject*
+ nsDocument::GetScriptHandlingObjectInternal() const
+ {
+   MOZ_ASSERT(!mScriptGlobalObject,
+              "Do not call this when mScriptGlobalObject is set!");
+diff --git a/dom/interfaces/base/nsIServiceWorkerManager.idl b/dom/interfaces/base/nsIServiceWorkerManager.idl
+--- a/dom/interfaces/base/nsIServiceWorkerManager.idl
++++ b/dom/interfaces/base/nsIServiceWorkerManager.idl
+@@ -148,34 +148,19 @@ interface nsIServiceWorkerManager : nsIS
+   nsISupports getReadyPromise(in mozIDOMWindow aWindow);
+ 
+   // Remove ready pending Promise
+   void removeReadyPromise(in mozIDOMWindow aWindow);
+ 
+   nsIServiceWorkerRegistrationInfo getRegistrationByPrincipal(in nsIPrincipal aPrincipal,
+                                                               in DOMString aScope);
+ 
+-  /**
+-   * Call this to request that document `aDoc` be controlled by a ServiceWorker
+-   * if a registration exists for it's scope.
+-   *
+-   * This MUST only be called once per document!
+-   */
+-  [notxpcom,nostdcall] void MaybeStartControlling(in nsIDocument aDoc);
+-
+   [notxpcom, nostdcall] bool StartControlling(in const_ClientInfoRef aClientInfo,
+                                               in const_ServiceWorkerDescriptorRef aServiceWorker);
+ 
+-  /**
+-   * Documents that have called MaybeStartControlling() should call this when
+-   * they are destroyed. This function may be called multiple times, and is
+-   * idempotent.
+-   */
+-  [notxpcom,nostdcall] void MaybeStopControlling(in nsIDocument aDoc);
+-
+   /*
+    * Returns a ServiceWorker.
+    * window is the window of the caller. scope is the registration's scope and must be
+    * a valid entry that window is allowed to load, otherwise this will return nullptr.
+    * These are only meant to be called from ServiceWorkerRegistration instances.
+    */
+   [noscript] nsISupports GetInstalling(in nsPIDOMWindowInner aWindow, in DOMString aScope);
+   [noscript] nsISupports GetWaiting(in nsPIDOMWindowInner aWindow, in DOMString aScope);
+diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp
+--- a/dom/workers/ServiceWorkerManager.cpp
++++ b/dom/workers/ServiceWorkerManager.cpp
+@@ -904,17 +904,16 @@ ServiceWorkerManager::Register(mozIDOMWi
+ 
+   nsAutoCString scopeKey;
+   rv = PrincipalToScopeKey(documentPrincipal, scopeKey);
+   if (NS_WARN_IF(NS_FAILED(rv))) {
+     return rv;
+   }
+ 
+   window->NoteCalledRegisterForServiceWorkerScope(cleanedScope);
+-  AddRegisteringDocument(cleanedScope, doc);
+ 
+   RefPtr<ServiceWorkerJobQueue> queue = GetOrCreateJobQueue(scopeKey,
+                                                             cleanedScope);
+ 
+   RefPtr<ServiceWorkerResolveWindowPromiseOnRegisterCallback> cb =
+     new ServiceWorkerResolveWindowPromiseOnRegisterCallback(window, promise);
+ 
+   nsCOMPtr<nsILoadGroup> docLoadGroup = doc->GetDocumentLoadGroup();
+@@ -2123,28 +2122,16 @@ ServiceWorkerManager::RemoveScopeAndRegi
+         reg->mPrincipal->Equals(aRegistration->mPrincipal)) {
+       MOZ_DIAGNOSTIC_ASSERT(false,
+                             "controlled client when removing registration");
+       iter.Remove();
+       break;
+     }
+   }
+ 
+-  // Registration lifecycle is managed via mControlledClients now.  Do not
+-  // assert on on mControlledDocuments as races may cause this to still be
+-  // set when the registration is destroyed.
+-  for (auto iter = swm->mControlledDocuments.Iter(); !iter.Done(); iter.Next()) {
+-    ServiceWorkerRegistrationInfo* reg = iter.UserData();
+-    if (reg->mScope.Equals(aRegistration->mScope) &&
+-        reg->mPrincipal->Equals(aRegistration->mPrincipal)) {
+-      iter.Remove();
+-      break;
+-    }
+-  }
+-
+   RefPtr<ServiceWorkerRegistrationInfo> info;
+   data->mInfos.Remove(aRegistration->mScope, getter_AddRefs(info));
+   data->mOrderedScopes.RemoveElement(aRegistration->mScope);
+   swm->NotifyListenersOnUnregister(info);
+ 
+   swm->MaybeRemoveRegistrationInfo(scopeKey);
+   swm->NotifyServiceWorkerRegistrationRemoved(aRegistration);
+ }
+@@ -2155,30 +2142,16 @@ ServiceWorkerManager::MaybeRemoveRegistr
+   if (auto entry = mRegistrationInfos.Lookup(aScopeKey)) {
+     if (entry.Data()->mOrderedScopes.IsEmpty() &&
+         entry.Data()->mJobQueues.Count() == 0) {
+       entry.Remove();
+     }
+   }
+ }
+ 
+-void
+-ServiceWorkerManager::MaybeStartControlling(nsIDocument* aDoc)
+-{
+-  AssertIsOnMainThread();
+-  MOZ_ASSERT(aDoc);
+-  RefPtr<ServiceWorkerRegistrationInfo> registration =
+-    GetServiceWorkerRegistrationInfo(aDoc);
+-  if (registration && registration->GetActive() &&
+-      aDoc->GetSandboxFlags() == 0) {
+-    MOZ_ASSERT(!mControlledDocuments.Contains(aDoc));
+-    StartControllingADocument(registration, aDoc);
+-  }
+-}
+-
+ bool
+ ServiceWorkerManager::StartControlling(const ClientInfo& aClientInfo,
+                                        const ServiceWorkerDescriptor& aServiceWorker)
+ {
+   AssertIsOnMainThread();
+ 
+   nsCOMPtr<nsIPrincipal> principal =
+     PrincipalInfoToPrincipal(aServiceWorker.PrincipalInfo());
+@@ -2194,24 +2167,16 @@ ServiceWorkerManager::StartControlling(c
+   NS_ENSURE_TRUE(registration, false);
+ 
+   StartControllingClient(aClientInfo, registration);
+ 
+   return true;
+ }
+ 
+ void
+-ServiceWorkerManager::MaybeStopControlling(nsIDocument* aDoc)
+-{
+-  AssertIsOnMainThread();
+-  MOZ_ASSERT(aDoc);
+-  mControlledDocuments.Remove(aDoc);
+-}
+-
+-void
+ ServiceWorkerManager::MaybeCheckNavigationUpdate(const ClientInfo& aClientInfo)
+ {
+   AssertIsOnMainThread();
+   // We perform these success path navigation update steps when the
+   // document tells us its more or less done loading.  This avoids
+   // slowing down page load and also lets pages consistently get
+   // updatefound events when they fire.
+   //
+@@ -2221,26 +2186,16 @@ ServiceWorkerManager::MaybeCheckNavigati
+   //    algorithm.
+   ControlledClientData* data = mControlledClients.Get(aClientInfo.Id());
+   if (data && data->mRegistrationInfo) {
+     data->mRegistrationInfo->MaybeScheduleUpdate();
+   }
+ }
+ 
+ void
+-ServiceWorkerManager::StartControllingADocument(ServiceWorkerRegistrationInfo* aRegistration,
+-                                                nsIDocument* aDoc)
+-{
+-  MOZ_ASSERT(aRegistration);
+-  MOZ_ASSERT(aDoc);
+-
+-  mControlledDocuments.Put(aDoc, aRegistration);
+-}
+-
+-void
+ ServiceWorkerManager::StopControllingRegistration(ServiceWorkerRegistrationInfo* aRegistration)
+ {
+   aRegistration->StopControllingClient();
+   if (aRegistration->IsControllingClients() || !aRegistration->IsIdle()) {
+     return;
+   }
+ 
+   if (aRegistration->mPendingUninstall) {
+@@ -2573,18 +2528,16 @@ ServiceWorkerManager::DispatchFetchEvent
+       }
+ 
+       // But we also note the reserved state on the LoadInfo.  This allows the
+       // ClientSource to be updated immediately after the nsIChannel starts.
+       // This is necessary to have the correct controller in place for immediate
+       // follow-on requests.
+       loadInfo->SetController(serviceWorker->Descriptor());
+     }
+-
+-    AddNavigationInterception(serviceWorker->Scope(), aChannel);
+   }
+ 
+   if (NS_WARN_IF(aRv.Failed())) {
+     return;
+   }
+ 
+   MOZ_DIAGNOSTIC_ASSERT(serviceWorker);
+ 
+@@ -3035,17 +2988,16 @@ ServiceWorkerManager::MaybeClaimClient(n
+                         getter_AddRefs(controllingRegistration));
+ 
+   if (aWorkerRegistration != matchingRegistration ||
+       aWorkerRegistration == controllingRegistration) {
+     ref = GenericPromise::CreateAndResolve(true, __func__);
+     return ref.forget();
+   }
+ 
+-  StartControllingADocument(aWorkerRegistration, aDocument);
+   ref = StartControllingClient(clientInfo.ref(), aWorkerRegistration);
+   return ref.forget();
+ }
+ 
+ already_AddRefed<GenericPromise>
+ ServiceWorkerManager::MaybeClaimClient(nsIDocument* aDoc,
+                                        const ServiceWorkerDescriptor& aServiceWorker)
+ {
+@@ -3545,112 +3497,16 @@ ServiceWorkerManager::NotifyListenersOnU
+                                         nsIServiceWorkerRegistrationInfo* aInfo)
+ {
+   nsTArray<nsCOMPtr<nsIServiceWorkerManagerListener>> listeners(mListeners);
+   for (size_t index = 0; index < listeners.Length(); ++index) {
+     listeners[index]->OnUnregister(aInfo);
+   }
+ }
+ 
+-void
+-ServiceWorkerManager::AddRegisteringDocument(const nsACString& aScope,
+-                                             nsIDocument* aDoc)
+-{
+-  AssertIsOnMainThread();
+-  MOZ_ASSERT(!aScope.IsEmpty());
+-  MOZ_ASSERT(aDoc);
+-
+-  WeakDocumentList* list = mRegisteringDocuments.LookupOrAdd(aScope);
+-  MOZ_ASSERT(list);
+-
+-  for (int32_t i = list->Length() - 1; i >= 0; --i) {
+-    nsCOMPtr<nsIDocument> existing = do_QueryReferent(list->ElementAt(i));
+-    if (!existing) {
+-      list->RemoveElementAt(i);
+-      continue;
+-    }
+-    if (existing == aDoc) {
+-      return;
+-    }
+-  }
+-
+-  list->AppendElement(do_GetWeakReference(aDoc));
+-}
+-
+-class ServiceWorkerManager::InterceptionReleaseHandle final : public nsISupports
+-{
+-  const nsCString mScope;
+-
+-  // Weak reference to channel is safe, because the channel holds a
+-  // reference to this object.  Also, the pointer is only used for
+-  // comparison purposes.
+-  nsIInterceptedChannel* mChannel;
+-
+-  ~InterceptionReleaseHandle()
+-  {
+-    RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+-    if (swm) {
+-      swm->RemoveNavigationInterception(mScope, mChannel);
+-    }
+-  }
+-
+-public:
+-  InterceptionReleaseHandle(const nsACString& aScope,
+-                            nsIInterceptedChannel* aChannel)
+-    : mScope(aScope)
+-    , mChannel(aChannel)
+-  {
+-    AssertIsOnMainThread();
+-    MOZ_ASSERT(!aScope.IsEmpty());
+-    MOZ_ASSERT(mChannel);
+-  }
+-
+-  NS_DECL_ISUPPORTS
+-};
+-
+-NS_IMPL_ISUPPORTS0(ServiceWorkerManager::InterceptionReleaseHandle);
+-
+-void
+-ServiceWorkerManager::AddNavigationInterception(const nsACString& aScope,
+-                                                nsIInterceptedChannel* aChannel)
+-{
+-  AssertIsOnMainThread();
+-  MOZ_ASSERT(!aScope.IsEmpty());
+-  MOZ_ASSERT(aChannel);
+-
+-  InterceptionList* list =
+-    mNavigationInterceptions.LookupOrAdd(aScope);
+-  MOZ_ASSERT(list);
+-  MOZ_ASSERT(!list->Contains(aChannel));
+-
+-  nsCOMPtr<nsISupports> releaseHandle =
+-    new InterceptionReleaseHandle(aScope, aChannel);
+-  aChannel->SetReleaseHandle(releaseHandle);
+-
+-  list->AppendElement(aChannel);
+-}
+-
+-void
+-ServiceWorkerManager::RemoveNavigationInterception(const nsACString& aScope,
+-                                                   nsIInterceptedChannel* aChannel)
+-{
+-  AssertIsOnMainThread();
+-  MOZ_ASSERT(aChannel);
+-  InterceptionList* list =
+-    mNavigationInterceptions.Get(aScope);
+-  if (list) {
+-    MOZ_ALWAYS_TRUE(list->RemoveElement(aChannel));
+-    MOZ_ASSERT(!list->Contains(aChannel));
+-    if (list->IsEmpty()) {
+-      list = nullptr;
+-      mNavigationInterceptions.Remove(aScope);
+-    }
+-  }
+-}
+-
+ class UpdateTimerCallback final : public nsITimerCallback
+                                 , public nsINamed
+ {
+   nsCOMPtr<nsIPrincipal> mPrincipal;
+   const nsCString mScope;
+ 
+   ~UpdateTimerCallback()
+   {
+diff --git a/dom/workers/ServiceWorkerManager.h b/dom/workers/ServiceWorkerManager.h
+--- a/dom/workers/ServiceWorkerManager.h
++++ b/dom/workers/ServiceWorkerManager.h
+@@ -99,50 +99,31 @@ public:
+   NS_DECL_NSISERVICEWORKERMANAGER
+   NS_DECL_NSIOBSERVER
+ 
+   struct RegistrationDataPerPrincipal;
+   nsClassHashtable<nsCStringHashKey, RegistrationDataPerPrincipal> mRegistrationInfos;
+ 
+   nsTObserverArray<ServiceWorkerRegistrationListener*> mServiceWorkerRegistrationListeners;
+ 
+-  nsRefPtrHashtable<nsISupportsHashKey, ServiceWorkerRegistrationInfo> mControlledDocuments;
+-
+   struct ControlledClientData
+   {
+     RefPtr<ClientHandle> mClientHandle;
+     RefPtr<ServiceWorkerRegistrationInfo> mRegistrationInfo;
+ 
+     ControlledClientData(ClientHandle* aClientHandle,
+                          ServiceWorkerRegistrationInfo* aRegistrationInfo)
+       : mClientHandle(aClientHandle)
+       , mRegistrationInfo(aRegistrationInfo)
+     {
+     }
+   };
+ 
+   nsClassHashtable<nsIDHashKey, ControlledClientData> mControlledClients;
+ 
+-  // Track all documents that have attempted to register a service worker for a
+-  // given scope.
+-  typedef nsTArray<nsCOMPtr<nsIWeakReference>> WeakDocumentList;
+-  nsClassHashtable<nsCStringHashKey, WeakDocumentList> mRegisteringDocuments;
+-
+-  // Track all intercepted navigation channels for a given scope.  Channels are
+-  // placed in the appropriate list before dispatch the FetchEvent to the worker
+-  // thread and removed once FetchEvent processing dispatches back to the main
+-  // thread.
+-  //
+-  // Note: Its safe to use weak references here because a RAII-style callback
+-  //       is registered with the channel before its added to this list.  We
+-  //       are guaranteed the callback will fire before and remove the ref
+-  //       from this list before the channel is destroyed.
+-  typedef nsTArray<nsIInterceptedChannel*> InterceptionList;
+-  nsClassHashtable<nsCStringHashKey, InterceptionList> mNavigationInterceptions;
+-
+   bool
+   IsAvailable(nsIPrincipal* aPrincipal, nsIURI* aURI);
+ 
+   // Return true if the given content process could potentially be executing
+   // service worker code with the given principal.  At the current time, this
+   // just means that we have any registration for the origin, regardless of
+   // scope.  This is a very weak guarantee but is the best we can do when push
+   // notifications can currently spin up a service worker in content processes
+@@ -390,20 +371,16 @@ private:
+   void
+   InvalidateServiceWorkerRegistrationWorker(ServiceWorkerRegistrationInfo* aRegistration,
+                                             WhichServiceWorker aWhichOnes);
+ 
+   void
+   NotifyServiceWorkerRegistrationRemoved(ServiceWorkerRegistrationInfo* aRegistration);
+ 
+   void
+-  StartControllingADocument(ServiceWorkerRegistrationInfo* aRegistration,
+-                            nsIDocument* aDoc);
+-
+-  void
+   StopControllingRegistration(ServiceWorkerRegistrationInfo* aRegistration);
+ 
+   already_AddRefed<ServiceWorkerRegistrationInfo>
+   GetServiceWorkerRegistrationInfo(nsPIDOMWindowInner* aWindow);
+ 
+   already_AddRefed<ServiceWorkerRegistrationInfo>
+   GetServiceWorkerRegistrationInfo(nsIDocument* aDoc);
+ 
+@@ -480,29 +457,16 @@ private:
+ 
+   void
+   NotifyListenersOnRegister(nsIServiceWorkerRegistrationInfo* aRegistration);
+ 
+   void
+   NotifyListenersOnUnregister(nsIServiceWorkerRegistrationInfo* aRegistration);
+ 
+   void
+-  AddRegisteringDocument(const nsACString& aScope, nsIDocument* aDoc);
+-
+-  class InterceptionReleaseHandle;
+-
+-  void
+-  AddNavigationInterception(const nsACString& aScope,
+-                            nsIInterceptedChannel* aChannel);
+-
+-  void
+-  RemoveNavigationInterception(const nsACString& aScope,
+-                               nsIInterceptedChannel* aChannel);
+-
+-  void
+   ScheduleUpdateTimer(nsIPrincipal* aPrincipal, const nsACString& aScope);
+ 
+   void
+   UpdateTimerFired(nsIPrincipal* aPrincipal, const nsACString& aScope);
+ 
+   void
+   MaybeSendUnregister(nsIPrincipal* aPrincipal, const nsACString& aScope);
+ 

+ 108 - 0
frg/work-js/mozilla-release/patches/1425965-5-59a1.patch

@@ -0,0 +1,108 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1515777365 18000
+# Node ID 241b9e419ca4a960144320842839f4ae39be6493
+# Parent  a854d4e09e55f738b537d3af6d426d813ec210e5
+Bug 1425965 P5 Make SimpleTest.monitorConsole() and SpecialPowers.registerConsoleListener() observe dom/console events. r=baku
+
+diff --git a/testing/specialpowers/content/specialpowersAPI.js b/testing/specialpowers/content/specialpowersAPI.js
+--- a/testing/specialpowers/content/specialpowersAPI.js
++++ b/testing/specialpowers/content/specialpowersAPI.js
+@@ -302,57 +302,74 @@ SpecialPowersHandler.prototype = {
+ // tidy, XPCOM-hiding way.  Messages that are nsIScriptError objects
+ // have their properties exposed in detail.  It also auto-unregisters
+ // itself when it receives a "sentinel" message.
+ function SPConsoleListener(callback) {
+   this.callback = callback;
+ }
+ 
+ SPConsoleListener.prototype = {
+-  observe(msg) {
++  // Overload the observe method for both nsIConsoleListener and nsIObserver.
++  // The topic will be null for nsIConsoleListener.
++  observe(msg, topic) {
+     let m = { message: msg.message,
+               errorMessage: null,
+               sourceName: null,
+               sourceLine: null,
+               lineNumber: null,
+               columnNumber: null,
+               category: null,
+               windowID: null,
+               isScriptError: false,
++              isConsoleEvent: false,
+               isWarning: false,
+               isException: false,
+               isStrict: false };
+     if (msg instanceof Ci.nsIScriptError) {
+       m.errorMessage  = msg.errorMessage;
+       m.sourceName    = msg.sourceName;
+       m.sourceLine    = msg.sourceLine;
+       m.lineNumber    = msg.lineNumber;
+       m.columnNumber  = msg.columnNumber;
+       m.category      = msg.category;
+       m.windowID      = msg.outerWindowID;
+       m.innerWindowID = msg.innerWindowID;
+       m.isScriptError = true;
+       m.isWarning     = ((msg.flags & Ci.nsIScriptError.warningFlag) === 1);
+       m.isException   = ((msg.flags & Ci.nsIScriptError.exceptionFlag) === 1);
+       m.isStrict      = ((msg.flags & Ci.nsIScriptError.strictFlag) === 1);
++    } else if (topic === "console-api-log-event") {
++      // This is a dom/console event.
++      let unwrapped = msg.wrappedJSObject;
++      m.errorMessage   = unwrapped.arguments[0];
++      m.sourceName     = unwrapped.filename;
++      m.lineNumber     = unwrapped.lineNumber;
++      m.columnNumber   = unwrapped.columnNumber;
++      m.windowID       = unwrapped.ID;
++      m.innerWindowID  = unwrapped.innerID;
++      m.isConsoleEvent = true;
++      m.isWarning      = unwrapped.level === "warning";
+     }
+ 
+     Object.freeze(m);
+ 
+     // Run in a separate runnable since console listeners aren't
+     // supposed to touch content and this one might.
+     Services.tm.dispatchToMainThread(() => {
+       this.callback.call(undefined, m);
+     });
+ 
+-    if (!m.isScriptError && m.message === "SENTINEL")
++    if (!m.isScriptError && !m.isConsoleEvent && m.message === "SENTINEL") {
++      Services.obs.removeObserver(this, "console-api-log-event");
+       Services.console.unregisterListener(this);
++    }
+   },
+ 
+-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIConsoleListener])
++  QueryInterface: XPCOMUtils.generateQI([Ci.nsIConsoleListener,
++                                         Ci.nsIObserver])
+ };
+ 
+ function wrapCallback(cb) {
+   return function SpecialPowersCallbackWrapper() {
+     var args = Array.prototype.map.call(arguments, wrapIfUnwrapped);
+     return cb.apply(this, args);
+   }
+ }
+@@ -1419,16 +1436,19 @@ SpecialPowersAPI.prototype = {
+   // call to postConsoleSentinel; when the callback receives the
+   // sentinel it will unregister itself (_after_ calling the
+   // callback).  SimpleTest.expectConsoleMessages does this for you.
+   // If you register more than one console listener, a call to
+   // postConsoleSentinel will zap all of them.
+   registerConsoleListener(callback) {
+     let listener = new SPConsoleListener(callback);
+     Services.console.registerListener(listener);
++
++    // listen for dom/console events as well
++    Services.obs.addObserver(listener, "console-api-log-event");
+   },
+   postConsoleSentinel() {
+     Services.console.logStringMessage("SENTINEL");
+   },
+   resetConsole() {
+     Services.console.reset();
+   },
+ 

+ 115 - 0
frg/work-js/mozilla-release/patches/1425965-6-59a1.patch

@@ -0,0 +1,115 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1515777366 18000
+# Node ID 9972008ac10510753a6adda9af0f21b33ee861e8
+# Parent  be03f8b1739fb16f203784040ca327f2a4248a90
+Bug 1425965 P6 Change test_fetch_integrity.html not to expect exact window ID values in console messages. r=asuth
+
+diff --git a/dom/workers/test/serviceworkers/test_fetch_integrity.html b/dom/workers/test/serviceworkers/test_fetch_integrity.html
+--- a/dom/workers/test/serviceworkers/test_fetch_integrity.html
++++ b/dom/workers/test/serviceworkers/test_fetch_integrity.html
+@@ -15,25 +15,23 @@
+ "use strict";
+ 
+ let security_localizer =
+   stringBundleService.createBundle("chrome://global/locale/security/security.properties");
+ 
+ function expect_security_console_message(/* msgId, args, ... */) {
+   let expectations = [];
+   // process repeated paired arguments of: msgId, args
+-  for (let i = 0; i < arguments.length; i += 4) {
++  for (let i = 0; i < arguments.length; i += 3) {
+     let msgId = arguments[i];
+     let args = arguments[i + 1];
+     let filename = arguments[i + 2];
+-    let windowId = arguments[i + 3];
+     expectations.push({
+       errorMessage: security_localizer.formatStringFromName(msgId, args, args.length),
+       sourceName: filename,
+-      windowID: windowId
+     });
+   }
+   return new Promise(resolve => {
+     SimpleTest.monitorConsole(resolve, expectations);
+   });
+ }
+ 
+ // (This doesn't really need to be its own task, but it allows the actual test
+@@ -55,38 +53,31 @@ add_task(async function test_integrity_s
+   await waitForState(worker, 'activated');
+   worker.postMessage('claim');
+   await waitForControlled(window);
+ 
+   info("Test for mNavigationInterceptions.")
+   // The client_win will reload to another URL after opening filename2.
+   let client_win = window.open(filename2);
+ 
+-  // XXX windowID should be innerWindowID
+-  let mainWindowID = SpecialPowers.getDOMWindowUtils(window).outerWindowID;
+-  let clientWindowID = SpecialPowers.getDOMWindowUtils(client_win).outerWindowID;
+   let expectedMessage = expect_security_console_message(
+     "MalformedIntegrityHash",
+     ["abc"],
+     filename,
+-    mainWindowID,
+     "NoValidMetadata",
+     [""],
+     filename,
+-    mainWindowID
+   );
+   let expectedMessage2 = expect_security_console_message(
+     "MalformedIntegrityHash",
+     ["abc"],
+     filename,
+-    clientWindowID,
+     "NoValidMetadata",
+     [""],
+     filename,
+-    clientWindowID
+   );
+ 
+   info("Test for mControlledDocuments and report error message to console.");
+   // The fetch will succeed because the integrity value is invalid and we are
+   // looking for the console message regarding the bad integrity value.
+   await fetch("fail.html");
+ 
+   await wait_for_expected_message(expectedMessage);
+@@ -122,38 +113,31 @@ add_task(async function test_integrity_s
+         resolve();
+       } else {
+         reject();
+       }
+     }
+   });
+   await waitForBothConnected;
+ 
+-  // XXX windowID should be innerWindowID
+-  let mainWindowID = SpecialPowers.getDOMWindowUtils(window).outerWindowID;
+-  let clientWindowID = SpecialPowers.getDOMWindowUtils(client_win).outerWindowID;
+   let expectedMessage = expect_security_console_message(
+     "MalformedIntegrityHash",
+     ["abc"],
+     filename,
+-    mainWindowID,
+     "NoValidMetadata",
+     [""],
+     filename,
+-    mainWindowID
+   );
+   let expectedMessage2 = expect_security_console_message(
+     "MalformedIntegrityHash",
+     ["abc"],
+     filename,
+-    clientWindowID,
+     "NoValidMetadata",
+     [""],
+     filename,
+-    clientWindowID
+   );
+ 
+   info("Start to fetch a URL with wrong integrity.")
+   sharedWorker.port.start();
+   sharedWorker.port.postMessage("StartFetchWithWrongIntegrity");
+ 
+   let waitForSRIFailed = new Promise((resolve) => {
+     sharedWorker.port.onmessage = function (e) {

+ 35 - 0
frg/work-js/mozilla-release/patches/1425965-7-59a1.patch

@@ -0,0 +1,35 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1515777366 18000
+# Node ID d16fe1a6931e5dadaeddafd626ef5bc0bb8bf8ea
+# Parent  0298e5af5fc702c3885a79bdbdd24b648405fdcc
+Bug 1425965 P7 Fix dom/push/test/test_error_reporting.html to expect new console reporting mechanism. r=asuth
+
+diff --git a/dom/push/test/test_error_reporting.html b/dom/push/test/test_error_reporting.html
+--- a/dom/push/test/test_error_reporting.html
++++ b/dom/push/test/test_error_reporting.html
+@@ -74,20 +74,22 @@ http://creativecommons.org/licenses/publ
+     reason = await waitForDeliveryError({ type: "rejection" });
+     is(reason, SpecialPowers.Ci.nsIPushErrorReporter.DELIVERY_UNHANDLED_REJECTION,
+       "Should report unhandled rejections");
+   });
+ 
+   add_task(async function reportDecryptionError() {
+     var message = await new Promise(resolve => {
+       SpecialPowers.registerConsoleListener(message => {
+-        if (!message.isScriptError) {
++        if (!message.isScriptError && !message.isConsoleEvent) {
+           return;
+         }
+-        if (message.innerWindowID == controlledFrame.innerWindowId()) {
++        const scope = "http://mochi.test:8888/tests/dom/push/test/";
++        if (message.innerWindowID === "ServiceWorker" &&
++            message.windowID === scope) {
+           SpecialPowers.postConsoleSentinel();
+           resolve(message);
+         }
+       });
+ 
+       var principal = SpecialPowers.wrap(document).nodePrincipal;
+       pushNotifier.notifyError(registration.scope, principal, "Push error",
+         SpecialPowers.Ci.nsIScriptError.errorFlag);

+ 172 - 0
frg/work-js/mozilla-release/patches/1425975-01-59a1.patch

@@ -0,0 +1,172 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1515172220 18000
+# Node ID 21042078d8070e0e704ebacfc7abd20968d42fda
+# Parent  c3cc65e4bc2570dd52af78aabc30e47c6d9167ed
+Bug 1425975 P1 Add ClientHandle::OnDetach() which returns a MozPromise that resolves on actor destruction. r=asuth
+
+diff --git a/dom/clients/manager/ClientHandle.cpp b/dom/clients/manager/ClientHandle.cpp
+--- a/dom/clients/manager/ClientHandle.cpp
++++ b/dom/clients/manager/ClientHandle.cpp
+@@ -60,16 +60,26 @@ ClientHandle::StartOp(const ClientOpCons
+   }, [promise] {
+     promise->Reject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__);
+   });
+ 
+   RefPtr<ClientOpPromise> ref = promise.get();
+   return ref.forget();
+ }
+ 
++void
++ClientHandle::OnShutdownThing()
++{
++  NS_ASSERT_OWNINGTHREAD(ClientHandle);
++  if (!mDetachPromise) {
++    return;
++  }
++  mDetachPromise->Resolve(true, __func__);
++}
++
+ ClientHandle::ClientHandle(ClientManager* aManager,
+                            nsISerialEventTarget* aSerialEventTarget,
+                            const ClientInfo& aClientInfo)
+   : mManager(aManager)
+   , mSerialEventTarget(aSerialEventTarget)
+   , mClientInfo(aClientInfo)
+ {
+   MOZ_DIAGNOSTIC_ASSERT(mManager);
+@@ -177,10 +187,26 @@ ClientHandle::PostMessage(StructuredClon
+     }, [outerPromise](const ClientOpResult& aResult) {
+       outerPromise->Reject(aResult.get_nsresult(), __func__);
+     });
+ 
+   ref = outerPromise.get();
+   return ref.forget();
+ }
+ 
++RefPtr<GenericPromise>
++ClientHandle::OnDetach()
++{
++  NS_ASSERT_OWNINGTHREAD(ClientSource);
++
++  if (!mDetachPromise) {
++    mDetachPromise = new GenericPromise::Private(__func__);
++    if (IsShutdown()) {
++      mDetachPromise->Resolve(true, __func__);
++    }
++  }
++
++  RefPtr<GenericPromise> ref(mDetachPromise);
++  return Move(ref);
++}
++
+ } // namespace dom
+ } // namespace mozilla
+diff --git a/dom/clients/manager/ClientHandle.h b/dom/clients/manager/ClientHandle.h
+--- a/dom/clients/manager/ClientHandle.h
++++ b/dom/clients/manager/ClientHandle.h
+@@ -37,26 +37,31 @@ class StructuredCloneData;
+ // is destroyed, but this could be added in the future.
+ class ClientHandle final : public ClientThing<ClientHandleChild>
+ {
+   friend class ClientManager;
+   friend class ClientHandleChild;
+ 
+   RefPtr<ClientManager> mManager;
+   nsCOMPtr<nsISerialEventTarget> mSerialEventTarget;
++  RefPtr<GenericPromise::Private> mDetachPromise;
+   ClientInfo mClientInfo;
+ 
+   ~ClientHandle();
+ 
+   void
+   Shutdown();
+ 
+   already_AddRefed<ClientOpPromise>
+   StartOp(const ClientOpConstructorArgs& aArgs);
+ 
++  // ClientThing interface
++  void
++  OnShutdownThing() override;
++
+   // Private methods called by ClientHandleChild
+   void
+   ExecutionReady(const ClientInfo& aClientInfo);
+ 
+   // Private methods called by ClientManager
+   ClientHandle(ClientManager* aManager,
+                nsISerialEventTarget* aSerialEventTarget,
+                const ClientInfo& aClientInfo);
+@@ -85,15 +90,26 @@ public:
+   // dispatched to the Client's navigator.serviceWorker event target.  The
+   // returned promise will resolve if the MessageEvent is dispatched or if
+   // it triggers an error handled in the Client's context.  Other errors
+   // will result in the promise rejecting.
+   RefPtr<GenericPromise>
+   PostMessage(ipc::StructuredCloneData& aData,
+               const ServiceWorkerDescriptor& aSource);
+ 
++  // Return a Promise that resolves when the ClientHandle object is detached
++  // from its remote actors.  This will happen if the ClientSource is destroyed
++  // and triggers the cleanup of the handle actors.  It will also naturally
++  // happen when the ClientHandle is de-referenced and tears down its own
++  // actors.
++  //
++  // Note: This method can only be called on the ClientHandle owning thread,
++  //       but the MozPromise lets you Then() to another thread.
++  RefPtr<GenericPromise>
++  OnDetach();
++
+   NS_INLINE_DECL_REFCOUNTING(ClientHandle);
+ };
+ 
+ } // namespace dom
+ } // namespace mozilla
+ 
+ #endif // _mozilla_dom_ClientHandle_h
+diff --git a/dom/clients/manager/ClientThing.h b/dom/clients/manager/ClientThing.h
+--- a/dom/clients/manager/ClientThing.h
++++ b/dom/clients/manager/ClientThing.h
+@@ -85,31 +85,42 @@ protected:
+ 
+     // If we are shutdown before the actor, then clear the weak references
+     // between the actor and the thing.
+     if (mActor) {
+       mActor->RevokeOwner(this);
+       mActor->MaybeStartTeardown();
+       mActor = nullptr;
+     }
++
++    OnShutdownThing();
++  }
++
++  // Allow extending classes to take action when shutdown.
++  virtual void
++  OnShutdownThing()
++  {
++    // by default do nothing
+   }
+ 
+ public:
+   // Clear the weak references between the thing and its IPC actor.
+   void
+   RevokeActor(ActorType* aActor)
+   {
+     MOZ_DIAGNOSTIC_ASSERT(mActor);
+     MOZ_DIAGNOSTIC_ASSERT(mActor == aActor);
+     mActor->RevokeOwner(this);
+     mActor = nullptr;
+ 
+     // Also consider the ClientThing shutdown.  We simply set the flag
+     // instead of calling ShutdownThing() to avoid calling MaybeStartTeardown()
+     // on the destroyed actor.
+     mShutdown = true;
++
++    OnShutdownThing();
+   }
+ };
+ 
+ } // namespace dom
+ } // namespace mozilla
+ 
+ #endif // _mozilla_dom_ClientThing_h

+ 194 - 0
frg/work-js/mozilla-release/patches/1425975-02-59a1.patch

@@ -0,0 +1,194 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1515172220 18000
+# Node ID ef7cf1394d0fd6fbd9e97bed3cc705cc439107a5
+# Parent  92298e0b576f0716d79cbb10ff47455781d9a38a
+Bug 1425975 P2 Add ServiceWorkerManager mControlledClients to track controlled ClientHandle references. r=asuth
+
+diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp
+--- a/dom/workers/ServiceWorkerManager.cpp
++++ b/dom/workers/ServiceWorkerManager.cpp
+@@ -309,16 +309,51 @@ ServiceWorkerManager::Init(ServiceWorker
+     MaybeStartShutdown();
+     return;
+   }
+ 
+   mActor = static_cast<ServiceWorkerManagerChild*>(actor);
+ }
+ 
+ void
++ServiceWorkerManager::StartControllingClient(const ClientInfo& aClientInfo,
++                                             ServiceWorkerRegistrationInfo* aRegistrationInfo)
++{
++  RefPtr<ClientHandle> clientHandle =
++    ClientManager::CreateHandle(aClientInfo,
++                                SystemGroup::EventTargetFor(TaskCategory::Other));
++
++  auto entry = mControlledClients.LookupForAdd(aClientInfo.Id());
++  if (entry) {
++    entry.Data()->mRegistrationInfo = aRegistrationInfo;
++  } else {
++    entry.OrInsert([&] {
++      return new ControlledClientData(clientHandle, aRegistrationInfo);
++    });
++
++    RefPtr<ServiceWorkerManager> self(this);
++    clientHandle->OnDetach()->Then(
++      SystemGroup::EventTargetFor(TaskCategory::Other), __func__,
++      [self = Move(self), aClientInfo] {
++        self->StopControllingClient(aClientInfo);
++      });
++  }
++}
++
++void
++ServiceWorkerManager::StopControllingClient(const ClientInfo& aClientInfo)
++{
++  auto entry = mControlledClients.Lookup(aClientInfo.Id());
++  if (!entry) {
++    return;
++  }
++  entry.Remove();
++}
++
++void
+ ServiceWorkerManager::MaybeStartShutdown()
+ {
+   MOZ_ASSERT(NS_IsMainThread());
+ 
+   if (mShuttingDown) {
+     return;
+   }
+ 
+@@ -2357,33 +2392,45 @@ ServiceWorkerManager::StartControllingAD
+   MOZ_ASSERT(aRegistration);
+   MOZ_ASSERT(aDoc);
+ 
+ #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+   auto storageAllowed = nsContentUtils::StorageAllowedForDocument(aDoc);
+   MOZ_DIAGNOSTIC_ASSERT(storageAllowed == nsContentUtils::StorageAccess::eAllow);
+ #endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ 
+-  RefPtr<GenericPromise> ref = GenericPromise::CreateAndResolve(true, __func__);
++  RefPtr<GenericPromise> ref;
++
++  ServiceWorkerInfo* activeWorker = aRegistration->GetActive();
++  if (NS_WARN_IF(!activeWorker)) {
++    ref = GenericPromise::CreateAndReject(NS_ERROR_DOM_INVALID_STATE_ERR,
++                                          __func__);
++    return ref.forget();
++  }
++
++  Maybe<ClientInfo> clientInfo = aDoc->GetClientInfo();
++  if (NS_WARN_IF(clientInfo.isNothing())) {
++    ref = GenericPromise::CreateAndReject(NS_ERROR_DOM_INVALID_STATE_ERR,
++                                          __func__);
++    return ref.forget();
++  }
+ 
+   aRegistration->StartControllingADocument();
+   mControlledDocuments.Put(aDoc, aRegistration);
+ 
++  StartControllingClient(clientInfo.ref(), aRegistration);
++
+   // Mark the document's ClientSource as controlled using the ClientHandle
+   // interface.  While we could get at the ClientSource directly from the
+   // document here, our goal is to move ServiceWorkerManager to a separate
+   // process.  Using the ClientHandle supports this remote operation.
+-  ServiceWorkerInfo* activeWorker = aRegistration->GetActive();
+-  Maybe<ClientInfo> clientInfo = aDoc->GetClientInfo();
+-  if (activeWorker && clientInfo.isSome()) {
+-    RefPtr<ClientHandle> clientHandle =
+-      ClientManager::CreateHandle(clientInfo.ref(),
+-                                  SystemGroup::EventTargetFor(TaskCategory::Other));
+-    ref = Move(clientHandle->Control(activeWorker->Descriptor()));
+-  }
++  RefPtr<ClientHandle> clientHandle =
++    ClientManager::CreateHandle(clientInfo.ref(),
++                                SystemGroup::EventTargetFor(TaskCategory::Other));
++  ref = Move(clientHandle->Control(activeWorker->Descriptor()));
+ 
+   Telemetry::Accumulate(Telemetry::SERVICE_WORKER_CONTROLLED_DOCUMENTS, 1);
+   return Move(ref);
+ }
+ 
+ void
+ ServiceWorkerManager::StopControllingADocument(ServiceWorkerRegistrationInfo* aRegistration)
+ {
+diff --git a/dom/workers/ServiceWorkerManager.h b/dom/workers/ServiceWorkerManager.h
+--- a/dom/workers/ServiceWorkerManager.h
++++ b/dom/workers/ServiceWorkerManager.h
+@@ -16,16 +16,17 @@
+ #include "mozilla/ConsoleReportCollector.h"
+ #include "mozilla/LinkedList.h"
+ #include "mozilla/MozPromise.h"
+ #include "mozilla/Preferences.h"
+ #include "mozilla/TypedEnumBits.h"
+ #include "mozilla/UniquePtr.h"
+ #include "mozilla/WeakPtr.h"
+ #include "mozilla/dom/BindingUtils.h"
++#include "mozilla/dom/ClientHandle.h"
+ #include "mozilla/dom/Promise.h"
+ #include "mozilla/dom/ServiceWorkerCommon.h"
+ #include "mozilla/dom/ServiceWorkerRegistrar.h"
+ #include "mozilla/dom/ServiceWorkerRegistrarTypes.h"
+ #include "mozilla/dom/workers/ServiceWorkerRegistrationInfo.h"
+ #include "mozilla/ipc/BackgroundUtils.h"
+ #include "nsClassHashtable.h"
+ #include "nsDataHashtable.h"
+@@ -100,16 +101,31 @@ public:
+ 
+   struct RegistrationDataPerPrincipal;
+   nsClassHashtable<nsCStringHashKey, RegistrationDataPerPrincipal> mRegistrationInfos;
+ 
+   nsTObserverArray<ServiceWorkerRegistrationListener*> mServiceWorkerRegistrationListeners;
+ 
+   nsRefPtrHashtable<nsISupportsHashKey, ServiceWorkerRegistrationInfo> mControlledDocuments;
+ 
++  struct ControlledClientData
++  {
++    RefPtr<ClientHandle> mClientHandle;
++    RefPtr<ServiceWorkerRegistrationInfo> mRegistrationInfo;
++
++    ControlledClientData(ClientHandle* aClientHandle,
++                         ServiceWorkerRegistrationInfo* aRegistrationInfo)
++      : mClientHandle(aClientHandle)
++      , mRegistrationInfo(aRegistrationInfo)
++    {
++    }
++  };
++
++  nsClassHashtable<nsIDHashKey, ControlledClientData> mControlledClients;
++
+   // Track all documents that have attempted to register a service worker for a
+   // given scope.
+   typedef nsTArray<nsCOMPtr<nsIWeakReference>> WeakDocumentList;
+   nsClassHashtable<nsCStringHashKey, WeakDocumentList> mRegisteringDocuments;
+ 
+   // Track all intercepted navigation channels for a given scope.  Channels are
+   // placed in the appropriate list before dispatch the FetchEvent to the worker
+   // thread and removed once FetchEvent processing dispatches back to the main
+@@ -324,16 +340,23 @@ public:
+ private:
+   ServiceWorkerManager();
+   ~ServiceWorkerManager();
+ 
+   void
+   Init(ServiceWorkerRegistrar* aRegistrar);
+ 
+   void
++  StartControllingClient(const ClientInfo& aClientInfo,
++                         ServiceWorkerRegistrationInfo* aRegistrationInfo);
++
++  void
++  StopControllingClient(const ClientInfo& aClientInfo);
++
++  void
+   MaybeStartShutdown();
+ 
+   already_AddRefed<ServiceWorkerJobQueue>
+   GetOrCreateJobQueue(const nsACString& aOriginSuffix,
+                       const nsACString& aScope);
+ 
+   void
+   MaybeRemoveRegistrationInfo(const nsACString& aScopeKey);

+ 130 - 0
frg/work-js/mozilla-release/patches/1425975-03-59a1.patch

@@ -0,0 +1,130 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1515172220 18000
+# Node ID 818fd73be280cca813edeea710596876ce91f234
+# Parent  622bc2fa12206d667cca9c30a25892158a2aac69
+Bug 1425975 P3 Refactor ServiceWorkerManager::GetDocumentRegistration() to GetClientRegistration(). r=asuth
+
+diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp
+--- a/dom/workers/ServiceWorkerManager.cpp
++++ b/dom/workers/ServiceWorkerManager.cpp
+@@ -1462,18 +1462,23 @@ ServiceWorkerManager::GetActiveWorkerInf
+   return registration->GetActive();
+ }
+ 
+ ServiceWorkerInfo*
+ ServiceWorkerManager::GetActiveWorkerInfoForDocument(nsIDocument* aDocument)
+ {
+   AssertIsOnMainThread();
+ 
++  Maybe<ClientInfo> clientInfo(aDocument->GetClientInfo());
++  if (clientInfo.isNothing()) {
++    return nullptr;
++  }
++
+   RefPtr<ServiceWorkerRegistrationInfo> registration;
+-  GetDocumentRegistration(aDocument, getter_AddRefs(registration));
++  GetClientRegistration(clientInfo.ref(), getter_AddRefs(registration));
+ 
+   if (!registration) {
+     return nullptr;
+   }
+ 
+   return registration->GetActive();
+ }
+ 
+@@ -2827,30 +2832,31 @@ ServiceWorkerManager::IsAvailable(nsIPri
+   MOZ_ASSERT(aURI);
+ 
+   RefPtr<ServiceWorkerRegistrationInfo> registration =
+     GetServiceWorkerRegistrationInfo(aPrincipal, aURI);
+   return registration && registration->GetActive();
+ }
+ 
+ nsresult
+-ServiceWorkerManager::GetDocumentRegistration(nsIDocument* aDoc,
+-                                              ServiceWorkerRegistrationInfo** aRegistrationInfo)
++ServiceWorkerManager::GetClientRegistration(const ClientInfo& aClientInfo,
++                                            ServiceWorkerRegistrationInfo** aRegistrationInfo)
+ {
+-  RefPtr<ServiceWorkerRegistrationInfo> registration;
+-  if (!mControlledDocuments.Get(aDoc, getter_AddRefs(registration))) {
++  ControlledClientData* data = mControlledClients.Get(aClientInfo.Id());
++  if (!data || !data->mRegistrationInfo) {
+     return NS_ERROR_NOT_AVAILABLE;
+   }
+ 
+   // If the document is controlled, the current worker MUST be non-null.
+-  if (!registration->GetActive()) {
++  if (!data->mRegistrationInfo->GetActive()) {
+     return NS_ERROR_NOT_AVAILABLE;
+   }
+ 
+-  registration.forget(aRegistrationInfo);
++  RefPtr<ServiceWorkerRegistrationInfo> ref = data->mRegistrationInfo;
++  ref.forget(aRegistrationInfo);
+   return NS_OK;
+ }
+ 
+ /*
+  * The .controller is for the registration associated with the document when
+  * the document was loaded.
+  */
+ NS_IMETHODIMP
+@@ -3211,23 +3217,31 @@ ServiceWorkerManager::MaybeClaimClient(n
+   RefPtr<GenericPromise> ref;
+ 
+   // Same origin check
+   if (!aWorkerRegistration->mPrincipal->Equals(aDocument->NodePrincipal())) {
+     ref = GenericPromise::CreateAndReject(NS_ERROR_DOM_SECURITY_ERR, __func__);
+     return ref.forget();
+   }
+ 
++  Maybe<ClientInfo> clientInfo(aDocument->GetClientInfo());
++  if (NS_WARN_IF(clientInfo.isNothing())) {
++    ref = GenericPromise::CreateAndReject(NS_ERROR_DOM_INVALID_STATE_ERR,
++                                          __func__);
++    return ref.forget();
++  }
++
+   // The registration that should be controlling the client
+   RefPtr<ServiceWorkerRegistrationInfo> matchingRegistration =
+     GetServiceWorkerRegistrationInfo(aDocument);
+ 
+   // The registration currently controlling the client
+   RefPtr<ServiceWorkerRegistrationInfo> controllingRegistration;
+-  GetDocumentRegistration(aDocument, getter_AddRefs(controllingRegistration));
++  GetClientRegistration(clientInfo.ref(),
++                        getter_AddRefs(controllingRegistration));
+ 
+   if (aWorkerRegistration != matchingRegistration ||
+       aWorkerRegistration == controllingRegistration) {
+     ref = GenericPromise::CreateAndResolve(true, __func__);
+     return ref.forget();
+   }
+ 
+   if (controllingRegistration) {
+diff --git a/dom/workers/ServiceWorkerManager.h b/dom/workers/ServiceWorkerManager.h
+--- a/dom/workers/ServiceWorkerManager.h
++++ b/dom/workers/ServiceWorkerManager.h
+@@ -367,18 +367,18 @@ private:
+ 
+   void
+   AbortCurrentUpdate(ServiceWorkerRegistrationInfo* aRegistration);
+ 
+   nsresult
+   Update(ServiceWorkerRegistrationInfo* aRegistration);
+ 
+   nsresult
+-  GetDocumentRegistration(nsIDocument* aDoc,
+-                          ServiceWorkerRegistrationInfo** aRegistrationInfo);
++  GetClientRegistration(const ClientInfo& aClientInfo,
++                        ServiceWorkerRegistrationInfo** aRegistrationInfo);
+ 
+   nsresult
+   GetServiceWorkerForScope(nsPIDOMWindowInner* aWindow,
+                            const nsAString& aScope,
+                            WhichServiceWorker aWhichWorker,
+                            nsISupports** aServiceWorker);
+ 
+   ServiceWorkerInfo*

+ 59 - 0
frg/work-js/mozilla-release/patches/1425975-04-59a1.patch

@@ -0,0 +1,59 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1515172220 18000
+# Node ID d432082aabbd1aaf685263eb265748e2ae043d01
+# Parent  a7f6a9b1069014b44185f7409d4e2250d09c7063
+Bug 1425975 P4 Make ServiceWorkerManager::UpdateClientControllers use mControlledClients. r=asuth
+
+diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp
+--- a/dom/workers/ServiceWorkerManager.cpp
++++ b/dom/workers/ServiceWorkerManager.cpp
+@@ -3304,41 +3304,29 @@ ServiceWorkerManager::SetSkipWaitingFlag
+ void
+ ServiceWorkerManager::UpdateClientControllers(ServiceWorkerRegistrationInfo* aRegistration)
+ {
+   AssertIsOnMainThread();
+ 
+   RefPtr<ServiceWorkerInfo> activeWorker = aRegistration->GetActive();
+   MOZ_DIAGNOSTIC_ASSERT(activeWorker);
+ 
+-  AutoTArray<nsCOMPtr<nsIDocument>, 16> docList;
+-  for (auto iter = mControlledDocuments.Iter(); !iter.Done(); iter.Next()) {
+-    if (iter.UserData() != aRegistration) {
+-      continue;
+-    }
+-
+-    nsCOMPtr<nsIDocument> doc = do_QueryInterface(iter.Key());
+-    if (NS_WARN_IF(!doc)) {
++  AutoTArray<RefPtr<ClientHandle>, 16> handleList;
++  for (auto iter = mControlledClients.Iter(); !iter.Done(); iter.Next()) {
++    if (iter.UserData()->mRegistrationInfo != aRegistration) {
+       continue;
+     }
+ 
+-    docList.AppendElement(doc.forget());
++    handleList.AppendElement(iter.UserData()->mClientHandle);
+   }
+ 
+-  // Fire event after iterating mControlledDocuments is done to prevent
++  // Fire event after iterating mControlledClients is done to prevent
+   // modification by reentering from the event handlers during iteration.
+-  for (auto& doc : docList) {
+-    Maybe<ClientInfo> clientInfo = doc->GetClientInfo();
+-    if (clientInfo.isNothing()) {
+-      continue;
+-    }
+-    RefPtr<ClientHandle> clientHandle =
+-      ClientManager::CreateHandle(clientInfo.ref(),
+-                                  SystemGroup::EventTargetFor(TaskCategory::Other));
+-    clientHandle->Control(activeWorker->Descriptor());
++  for (auto& handle : handleList) {
++    handle->Control(activeWorker->Descriptor());
+   }
+ }
+ 
+ already_AddRefed<ServiceWorkerRegistrationInfo>
+ ServiceWorkerManager::GetRegistration(nsIPrincipal* aPrincipal,
+                                       const nsACString& aScope) const
+ {
+   MOZ_ASSERT(aPrincipal);

+ 44 - 0
frg/work-js/mozilla-release/patches/1425975-05-59a1.patch

@@ -0,0 +1,44 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1515172220 18000
+# Node ID 560f15a8009772f7b237c666e28fac92198f2c9b
+# Parent  7d35e8422edd2bee46e4ea842b6ee8fa47ee8ead
+Bug 1425975 P5 Make ServiceWorkerManager::RemoveRegistration assert there is no controlled document. r=asuth
+
+diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp
+--- a/dom/workers/ServiceWorkerManager.cpp
++++ b/dom/workers/ServiceWorkerManager.cpp
+@@ -2304,27 +2304,28 @@ ServiceWorkerManager::RemoveScopeAndRegi
+     return;
+   }
+ 
+   if (auto entry = data->mUpdateTimers.Lookup(aRegistration->mScope)) {
+     entry.Data()->Cancel();
+     entry.Remove();
+   }
+ 
+-  // The registration should generally only be removed if there are no controlled
+-  // documents, but mControlledDocuments can contain references to potentially
+-  // controlled docs.  This happens when the service worker is not active yet.
+-  // We must purge these references since we are evicting the registration.
++  // Verify there are no controlled documents for the purged registration.
++#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+   for (auto iter = swm->mControlledDocuments.Iter(); !iter.Done(); iter.Next()) {
+     ServiceWorkerRegistrationInfo* reg = iter.UserData();
+-    MOZ_ASSERT(reg);
+     if (reg->mScope.Equals(aRegistration->mScope)) {
++      MOZ_DIAGNOSTIC_ASSERT(false,
++                            "controlled document when removing registration");
+       iter.Remove();
++      break;
+     }
+   }
++#endif
+ 
+   RefPtr<ServiceWorkerRegistrationInfo> info;
+   data->mInfos.Remove(aRegistration->mScope, getter_AddRefs(info));
+   data->mOrderedScopes.RemoveElement(aRegistration->mScope);
+   swm->NotifyListenersOnUnregister(info);
+ 
+   swm->MaybeRemoveRegistrationInfo(scopeKey);
+   swm->NotifyServiceWorkerRegistrationRemoved(aRegistration);

+ 273 - 0
frg/work-js/mozilla-release/patches/1425975-06-59a1.patch

@@ -0,0 +1,273 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1515172221 18000
+# Node ID 834f38d73b6f71650ea891e2151fd155f845dc5a
+# Parent  f853e9d56c091813cbbf0a16b5d05423bff7f568
+Bug 1425975 P6 Rename some service worker methods to not reference documents. r=asuth
+
+diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp
+--- a/dom/workers/ServiceWorkerManager.cpp
++++ b/dom/workers/ServiceWorkerManager.cpp
+@@ -1613,17 +1613,17 @@ ServiceWorkerManager::WorkerIsIdle(Servi
+   if (!reg) {
+     return;
+   }
+ 
+   if (reg->GetActive() != aWorker) {
+     return;
+   }
+ 
+-  if (!reg->IsControllingDocuments() && reg->mPendingUninstall) {
++  if (!reg->IsControllingClients() && reg->mPendingUninstall) {
+     RemoveRegistration(reg);
+     return;
+   }
+ 
+   reg->TryToActivateAsync();
+ }
+ 
+ already_AddRefed<ServiceWorkerJobQueue>
+@@ -2361,17 +2361,17 @@ ServiceWorkerManager::MaybeStopControlli
+   AssertIsOnMainThread();
+   MOZ_ASSERT(aDoc);
+   RefPtr<ServiceWorkerRegistrationInfo> registration;
+   mControlledDocuments.Remove(aDoc, getter_AddRefs(registration));
+   // A document which was uncontrolled does not maintain that state itself, so
+   // it will always call MaybeStopControlling() even if there isn't an
+   // associated registration. So this check is required.
+   if (registration) {
+-    StopControllingADocument(registration);
++    StopControllingRegistration(registration);
+   }
+ }
+ 
+ void
+ ServiceWorkerManager::MaybeCheckNavigationUpdate(nsIDocument* aDoc)
+ {
+   AssertIsOnMainThread();
+   MOZ_ASSERT(aDoc);
+@@ -2414,17 +2414,17 @@ ServiceWorkerManager::StartControllingAD
+ 
+   Maybe<ClientInfo> clientInfo = aDoc->GetClientInfo();
+   if (NS_WARN_IF(clientInfo.isNothing())) {
+     ref = GenericPromise::CreateAndReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+                                           __func__);
+     return ref.forget();
+   }
+ 
+-  aRegistration->StartControllingADocument();
++  aRegistration->StartControllingClient();
+   mControlledDocuments.Put(aDoc, aRegistration);
+ 
+   StartControllingClient(clientInfo.ref(), aRegistration);
+ 
+   // Mark the document's ClientSource as controlled using the ClientHandle
+   // interface.  While we could get at the ClientSource directly from the
+   // document here, our goal is to move ServiceWorkerManager to a separate
+   // process.  Using the ClientHandle supports this remote operation.
+@@ -2433,20 +2433,20 @@ ServiceWorkerManager::StartControllingAD
+                                 SystemGroup::EventTargetFor(TaskCategory::Other));
+   ref = Move(clientHandle->Control(activeWorker->Descriptor()));
+ 
+   Telemetry::Accumulate(Telemetry::SERVICE_WORKER_CONTROLLED_DOCUMENTS, 1);
+   return Move(ref);
+ }
+ 
+ void
+-ServiceWorkerManager::StopControllingADocument(ServiceWorkerRegistrationInfo* aRegistration)
++ServiceWorkerManager::StopControllingRegistration(ServiceWorkerRegistrationInfo* aRegistration)
+ {
+-  aRegistration->StopControllingADocument();
+-  if (aRegistration->IsControllingDocuments() || !aRegistration->IsIdle()) {
++  aRegistration->StopControllingClient();
++  if (aRegistration->IsControllingClients() || !aRegistration->IsIdle()) {
+     return;
+   }
+ 
+   if (aRegistration->mPendingUninstall) {
+     RemoveRegistration(aRegistration);
+     return;
+   }
+ 
+@@ -3241,17 +3241,17 @@ ServiceWorkerManager::MaybeClaimClient(n
+ 
+   if (aWorkerRegistration != matchingRegistration ||
+       aWorkerRegistration == controllingRegistration) {
+     ref = GenericPromise::CreateAndResolve(true, __func__);
+     return ref.forget();
+   }
+ 
+   if (controllingRegistration) {
+-    StopControllingADocument(controllingRegistration);
++    StopControllingRegistration(controllingRegistration);
+   }
+ 
+   ref = StartControllingADocument(aWorkerRegistration, aDocument);
+   return ref.forget();
+ }
+ 
+ already_AddRefed<GenericPromise>
+ ServiceWorkerManager::MaybeClaimClient(nsIDocument* aDoc,
+diff --git a/dom/workers/ServiceWorkerManager.h b/dom/workers/ServiceWorkerManager.h
+--- a/dom/workers/ServiceWorkerManager.h
++++ b/dom/workers/ServiceWorkerManager.h
+@@ -398,17 +398,17 @@ private:
+   void
+   NotifyServiceWorkerRegistrationRemoved(ServiceWorkerRegistrationInfo* aRegistration);
+ 
+   RefPtr<GenericPromise>
+   StartControllingADocument(ServiceWorkerRegistrationInfo* aRegistration,
+                             nsIDocument* aDoc);
+ 
+   void
+-  StopControllingADocument(ServiceWorkerRegistrationInfo* aRegistration);
++  StopControllingRegistration(ServiceWorkerRegistrationInfo* aRegistration);
+ 
+   already_AddRefed<ServiceWorkerRegistrationInfo>
+   GetServiceWorkerRegistrationInfo(nsPIDOMWindowInner* aWindow);
+ 
+   already_AddRefed<ServiceWorkerRegistrationInfo>
+   GetServiceWorkerRegistrationInfo(nsIDocument* aDoc);
+ 
+   already_AddRefed<ServiceWorkerRegistrationInfo>
+diff --git a/dom/workers/ServiceWorkerRegistrationInfo.cpp b/dom/workers/ServiceWorkerRegistrationInfo.cpp
+--- a/dom/workers/ServiceWorkerRegistrationInfo.cpp
++++ b/dom/workers/ServiceWorkerRegistrationInfo.cpp
+@@ -76,32 +76,30 @@ ServiceWorkerRegistrationInfo::Clear()
+ 
+   NotifyChromeRegistrationListeners();
+ }
+ 
+ ServiceWorkerRegistrationInfo::ServiceWorkerRegistrationInfo(
+     const nsACString& aScope,
+     nsIPrincipal* aPrincipal,
+     ServiceWorkerUpdateViaCache aUpdateViaCache)
+-  : mControlledDocumentsCounter(0)
++  : mControlledClientsCounter(0)
+   , mUpdateState(NoUpdate)
+   , mCreationTime(PR_Now())
+   , mCreationTimeStamp(TimeStamp::Now())
+   , mLastUpdateTime(0)
+   , mUpdateViaCache(aUpdateViaCache)
+   , mScope(aScope)
+   , mPrincipal(aPrincipal)
+   , mPendingUninstall(false)
+ {}
+ 
+ ServiceWorkerRegistrationInfo::~ServiceWorkerRegistrationInfo()
+ {
+-  if (IsControllingDocuments()) {
+-    NS_WARNING("ServiceWorkerRegistrationInfo is still controlling documents. This can be a bug or a leak in ServiceWorker API or in any other API that takes the document alive.");
+-  }
++  MOZ_DIAGNOSTIC_ASSERT(!IsControllingClients());
+ }
+ 
+ NS_IMPL_ISUPPORTS(ServiceWorkerRegistrationInfo, nsIServiceWorkerRegistrationInfo)
+ 
+ NS_IMETHODIMP
+ ServiceWorkerRegistrationInfo::GetPrincipal(nsIPrincipal** aPrincipal)
+ {
+   AssertIsOnMainThread();
+@@ -243,17 +241,17 @@ ServiceWorkerRegistrationInfo::TryToActi
+ 
+ /*
+  * TryToActivate should not be called directly, use TryToActivateAsync instead.
+  */
+ void
+ ServiceWorkerRegistrationInfo::TryToActivate()
+ {
+   AssertIsOnMainThread();
+-  bool controlling = IsControllingDocuments();
++  bool controlling = IsControllingClients();
+   bool skipWaiting = mWaitingWorker && mWaitingWorker->SkipWaitingFlag();
+   bool idle = IsIdle();
+   if (idle && (!controlling || skipWaiting)) {
+     Activate();
+   }
+ }
+ 
+ void
+diff --git a/dom/workers/ServiceWorkerRegistrationInfo.h b/dom/workers/ServiceWorkerRegistrationInfo.h
+--- a/dom/workers/ServiceWorkerRegistrationInfo.h
++++ b/dom/workers/ServiceWorkerRegistrationInfo.h
+@@ -12,17 +12,17 @@
+ 
+ namespace mozilla {
+ namespace dom {
+ namespace workers {
+ 
+ class ServiceWorkerRegistrationInfo final
+   : public nsIServiceWorkerRegistrationInfo
+ {
+-  uint32_t mControlledDocumentsCounter;
++  uint32_t mControlledClientsCounter;
+ 
+   enum
+   {
+     NoUpdate,
+     NeedTimeCheckAndUpdate,
+     NeedUpdate
+   } mUpdateState;
+ 
+@@ -74,32 +74,32 @@ public:
+ 
+     return newest.forget();
+   }
+ 
+   already_AddRefed<ServiceWorkerInfo>
+   GetServiceWorkerInfoById(uint64_t aId);
+ 
+   void
+-  StartControllingADocument()
++  StartControllingClient()
+   {
+-    ++mControlledDocumentsCounter;
++    ++mControlledClientsCounter;
+   }
+ 
+   void
+-  StopControllingADocument()
++  StopControllingClient()
+   {
+-    MOZ_ASSERT(mControlledDocumentsCounter);
+-    --mControlledDocumentsCounter;
++    MOZ_ASSERT(mControlledClientsCounter);
++    --mControlledClientsCounter;
+   }
+ 
+   bool
+-  IsControllingDocuments() const
++  IsControllingClients() const
+   {
+-    return mActiveWorker && mControlledDocumentsCounter;
++    return mActiveWorker && mControlledClientsCounter;
+   }
+ 
+   void
+   Clear();
+ 
+   void
+   TryToActivateAsync();
+ 
+diff --git a/dom/workers/ServiceWorkerUnregisterJob.cpp b/dom/workers/ServiceWorkerUnregisterJob.cpp
+--- a/dom/workers/ServiceWorkerUnregisterJob.cpp
++++ b/dom/workers/ServiceWorkerUnregisterJob.cpp
+@@ -133,17 +133,17 @@ ServiceWorkerUnregisterJob::Unregister()
+   // "Set registration's uninstalling flag."
+   registration->mPendingUninstall = true;
+ 
+   // "Resolve promise with true"
+   mResult = true;
+   InvokeResultCallbacks(NS_OK);
+ 
+   // "If no service worker client is using registration..."
+-  if (!registration->IsControllingDocuments() && registration->IsIdle()) {
++  if (!registration->IsControllingClients() && registration->IsIdle()) {
+     // "Invoke [[Clear Registration]]..."
+     swm->RemoveRegistration(registration);
+   }
+ 
+   Finish(NS_OK);
+ }
+ 
+ } // namespace workers

+ 478 - 0
frg/work-js/mozilla-release/patches/1425975-07no08-59a1.patch

@@ -0,0 +1,478 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1515172221 18000
+# Node ID 841e106b28101d1762d8c41740f433d19e7def92
+# Parent  679687cc3e08a1afd166dc8083f6d0e6c982a662
+Bug 1425975 P7 Use the mControlledClients list to drive controller start and stop logic. r=asuth
+
+diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp
+--- a/docshell/base/nsDocShell.cpp
++++ b/docshell/base/nsDocShell.cpp
+@@ -148,16 +148,17 @@
+ #include "nsIStructuredCloneContainer.h"
+ #include "nsISupportsPrimitives.h"
+ #ifdef MOZ_PLACES
+ #include "nsIFaviconService.h"
+ #include "mozIPlacesPendingOperation.h"
+ #include "mozIAsyncFavicons.h"
+ #endif
+ #include "nsINetworkPredictor.h"
++#include "nsIServiceWorkerManager.h"
+ 
+ // Editor-related
+ #include "nsIEditingSession.h"
+ 
+ #include "nsPIDOMWindow.h"
+ #include "nsGlobalWindow.h"
+ #include "nsPIWindowRoot.h"
+ #include "nsICachingChannel.h"
+@@ -3442,23 +3443,37 @@ nsDocShell::MaybeCreateInitialClientSour
+     return;
+   }
+ 
+   Maybe<ServiceWorkerDescriptor> controller(parentInner->GetController());
+   if (controller.isNothing()) {
+     return;
+   }
+ 
++  nsCOMPtr<nsIServiceWorkerManager> swm = mozilla::services::GetServiceWorkerManager();
++  if (!swm) {
++    return;
++  }
++
+   // If the parent is controlled then propagate that controller to the
+   // initial about:blank client as well.  This will set the controller
+   // in the ClientManagerService in the parent.
+-  RefPtr<ClientHandle> handle =
+-    ClientManager::CreateHandle(mInitialClientSource->Info(),
+-                                parentInner->EventTargetFor(TaskCategory::Other));
+-  handle->Control(controller.ref());
++  //
++  // Note: If the registration is missing from the SWM we avoid setting
++  //       the controller on the client.  We can do this synchronously
++  //       for now since SWM is in the child process.  In the future
++  //       when SWM is in the parent process we will probably have to
++  //       always set the initial client source and then somehow clear
++  //       it if we find the registration is acutally gone.  Its also
++  //       possible this race only occurs in cases where the resulting
++  //       window is no longer exposed.  For example, in theory the SW
++  //       should not go away if our parent window is controlled.
++  if (!swm->StartControlling(mInitialClientSource->Info(), controller.ref())) {
++    return;
++  }
+ 
+   // Also mark the ClientSource as controlled directly in case script
+   // immediately accesses navigator.serviceWorker.controller.
+   mInitialClientSource->SetController(controller.ref());
+ }
+ 
+ Maybe<ClientInfo>
+ nsDocShell::GetInitialClientInfo() const
+diff --git a/dom/interfaces/base/nsIServiceWorkerManager.idl b/dom/interfaces/base/nsIServiceWorkerManager.idl
+--- a/dom/interfaces/base/nsIServiceWorkerManager.idl
++++ b/dom/interfaces/base/nsIServiceWorkerManager.idl
+@@ -10,16 +10,27 @@ interface mozIDOMWindow;
+ interface nsPIDOMWindowInner;
+ interface mozIDOMWindowProxy;
+ interface nsIArray;
+ interface nsIDocument;
+ interface nsIInterceptedChannel;
+ interface nsIPrincipal;
+ interface nsIRunnable;
+ interface nsIURI;
++%{C++
++namespace mozilla {
++namespace dom {
++class ClientInfo;
++class ServiceWorkerDescriptor;
++} // namespace dom
++} // namespace mozilla
++%}
++
++[ref] native const_ClientInfoRef(const mozilla::dom::ClientInfo);
++[ref] native const_ServiceWorkerDescriptorRef(const mozilla::dom::ServiceWorkerDescriptor);
+ 
+ [scriptable, uuid(52ee2c9d-ee87-4caf-9588-23ae77ff8798)]
+ interface nsIServiceWorkerUnregisterCallback : nsISupports
+ {
+   // aState is true if the unregistration succeded.
+   // It's false if this ServiceWorkerRegistration doesn't exist.
+   void unregisterSucceeded(in bool aState);
+   void unregisterFailed();
+@@ -145,16 +156,19 @@ interface nsIServiceWorkerManager : nsIS
+   /**
+    * Call this to request that document `aDoc` be controlled by a ServiceWorker
+    * if a registration exists for it's scope.
+    *
+    * This MUST only be called once per document!
+    */
+   [notxpcom,nostdcall] void MaybeStartControlling(in nsIDocument aDoc);
+ 
++  [notxpcom, nostdcall] bool StartControlling(in const_ClientInfoRef aClientInfo,
++                                              in const_ServiceWorkerDescriptorRef aServiceWorker);
++
+   /**
+    * Documents that have called MaybeStartControlling() should call this when
+    * they are destroyed. This function may be called multiple times, and is
+    * idempotent.
+    */
+   [notxpcom,nostdcall] void MaybeStopControlling(in nsIDocument aDoc);
+ 
+   /*
+diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp
+--- a/dom/workers/ServiceWorkerManager.cpp
++++ b/dom/workers/ServiceWorkerManager.cpp
+@@ -308,49 +308,83 @@ ServiceWorkerManager::Init(ServiceWorker
+   if (!actor) {
+     MaybeStartShutdown();
+     return;
+   }
+ 
+   mActor = static_cast<ServiceWorkerManagerChild*>(actor);
+ }
+ 
+-void
++RefPtr<GenericPromise>
+ ServiceWorkerManager::StartControllingClient(const ClientInfo& aClientInfo,
+                                              ServiceWorkerRegistrationInfo* aRegistrationInfo)
+ {
+-  RefPtr<ClientHandle> clientHandle =
+-    ClientManager::CreateHandle(aClientInfo,
+-                                SystemGroup::EventTargetFor(TaskCategory::Other));
++  MOZ_DIAGNOSTIC_ASSERT(aRegistrationInfo->GetActive());
++
++  RefPtr<GenericPromise> ref;
++
++  const ServiceWorkerDescriptor& active =
++    aRegistrationInfo->GetActive()->Descriptor();
+ 
+   auto entry = mControlledClients.LookupForAdd(aClientInfo.Id());
+   if (entry) {
++    RefPtr<ServiceWorkerRegistrationInfo> old =
++      entry.Data()->mRegistrationInfo.forget();
++
++    ref = Move(entry.Data()->mClientHandle->Control(active));
+     entry.Data()->mRegistrationInfo = aRegistrationInfo;
+-  } else {
+-    entry.OrInsert([&] {
+-      return new ControlledClientData(clientHandle, aRegistrationInfo);
++
++    if (old != aRegistrationInfo) {
++      StopControllingRegistration(old);
++      aRegistrationInfo->StartControllingClient();
++    }
++
++    Telemetry::Accumulate(Telemetry::SERVICE_WORKER_CONTROLLED_DOCUMENTS, 1);
++
++    return Move(ref);
++  }
++
++  RefPtr<ClientHandle> clientHandle =
++    ClientManager::CreateHandle(aClientInfo,
++                                SystemGroup::EventTargetFor(TaskCategory::Other));
++
++  ref = Move(clientHandle->Control(active));
++
++  aRegistrationInfo->StartControllingClient();
++
++  entry.OrInsert([&] {
++    return new ControlledClientData(clientHandle, aRegistrationInfo);
++  });
++
++  RefPtr<ServiceWorkerManager> self(this);
++  clientHandle->OnDetach()->Then(
++    SystemGroup::EventTargetFor(TaskCategory::Other), __func__,
++    [self = Move(self), aClientInfo] {
++      self->StopControllingClient(aClientInfo);
+     });
+ 
+-    RefPtr<ServiceWorkerManager> self(this);
+-    clientHandle->OnDetach()->Then(
+-      SystemGroup::EventTargetFor(TaskCategory::Other), __func__,
+-      [self = Move(self), aClientInfo] {
+-        self->StopControllingClient(aClientInfo);
+-      });
+-  }
++  Telemetry::Accumulate(Telemetry::SERVICE_WORKER_CONTROLLED_DOCUMENTS, 1);
++
++  return Move(ref);
+ }
+ 
+ void
+ ServiceWorkerManager::StopControllingClient(const ClientInfo& aClientInfo)
+ {
+   auto entry = mControlledClients.Lookup(aClientInfo.Id());
+   if (!entry) {
+     return;
+   }
++
++  RefPtr<ServiceWorkerRegistrationInfo> reg =
++    entry.Data()->mRegistrationInfo.forget();
++
+   entry.Remove();
++
++  StopControllingRegistration(reg);
+ }
+ 
+ void
+ ServiceWorkerManager::MaybeStartShutdown()
+ {
+   MOZ_ASSERT(NS_IsMainThread());
+ 
+   if (mShuttingDown) {
+@@ -2304,28 +2338,37 @@ ServiceWorkerManager::RemoveScopeAndRegi
+     return;
+   }
+ 
+   if (auto entry = data->mUpdateTimers.Lookup(aRegistration->mScope)) {
+     entry.Data()->Cancel();
+     entry.Remove();
+   }
+ 
+-  // Verify there are no controlled documents for the purged registration.
+-#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+-  for (auto iter = swm->mControlledDocuments.Iter(); !iter.Done(); iter.Next()) {
+-    ServiceWorkerRegistrationInfo* reg = iter.UserData();
++  // Verify there are no controlled clients for the purged registration.
++  for (auto iter = swm->mControlledClients.Iter(); !iter.Done(); iter.Next()) {
++    auto& reg = iter.UserData()->mRegistrationInfo;
+     if (reg->mScope.Equals(aRegistration->mScope)) {
+       MOZ_DIAGNOSTIC_ASSERT(false,
+-                            "controlled document when removing registration");
++                            "controlled client when removing registration");
+       iter.Remove();
+       break;
+     }
+   }
+-#endif
++
++  // Registration lifecycle is managed via mControlledClients now.  Do not
++  // assert on on mControlledDocuments as races may cause this to still be
++  // set when the registration is destroyed.
++  for (auto iter = swm->mControlledDocuments.Iter(); !iter.Done(); iter.Next()) {
++    ServiceWorkerRegistrationInfo* reg = iter.UserData();
++    if (reg->mScope.Equals(aRegistration->mScope)) {
++      iter.Remove();
++      break;
++    }
++  }
+ 
+   RefPtr<ServiceWorkerRegistrationInfo> info;
+   data->mInfos.Remove(aRegistration->mScope, getter_AddRefs(info));
+   data->mOrderedScopes.RemoveElement(aRegistration->mScope);
+   swm->NotifyListenersOnUnregister(info);
+ 
+   swm->MaybeRemoveRegistrationInfo(scopeKey);
+   swm->NotifyServiceWorkerRegistrationRemoved(aRegistration);
+@@ -2344,35 +2387,53 @@ ServiceWorkerManager::MaybeRemoveRegistr
+ 
+ void
+ ServiceWorkerManager::MaybeStartControlling(nsIDocument* aDoc)
+ {
+   AssertIsOnMainThread();
+   MOZ_ASSERT(aDoc);
+   RefPtr<ServiceWorkerRegistrationInfo> registration =
+     GetServiceWorkerRegistrationInfo(aDoc);
+-  if (registration) {
++  if (registration && registration->GetActive() &&
++      aDoc->GetSandboxFlags() == 0) {
+     MOZ_ASSERT(!mControlledDocuments.Contains(aDoc));
+     StartControllingADocument(registration, aDoc);
+   }
+ }
+ 
++bool
++ServiceWorkerManager::StartControlling(const ClientInfo& aClientInfo,
++                                       const ServiceWorkerDescriptor& aServiceWorker)
++{
++  AssertIsOnMainThread();
++
++  nsCOMPtr<nsIPrincipal> principal =
++    PrincipalInfoToPrincipal(aServiceWorker.PrincipalInfo());
++  NS_ENSURE_TRUE(principal, false);
++
++  nsCOMPtr<nsIURI> scope;
++  nsresult rv =
++    NS_NewURI(getter_AddRefs(scope), aServiceWorker.Scope(), nullptr, nullptr);
++  NS_ENSURE_SUCCESS(rv, false);
++
++  RefPtr<ServiceWorkerRegistrationInfo> registration =
++    GetServiceWorkerRegistrationInfo(principal, scope);
++  NS_ENSURE_TRUE(registration, false);
++
++  StartControllingClient(aClientInfo, registration);
++
++  return true;
++}
++
+ void
+ ServiceWorkerManager::MaybeStopControlling(nsIDocument* aDoc)
+ {
+   AssertIsOnMainThread();
+   MOZ_ASSERT(aDoc);
+-  RefPtr<ServiceWorkerRegistrationInfo> registration;
+-  mControlledDocuments.Remove(aDoc, getter_AddRefs(registration));
+-  // A document which was uncontrolled does not maintain that state itself, so
+-  // it will always call MaybeStopControlling() even if there isn't an
+-  // associated registration. So this check is required.
+-  if (registration) {
+-    StopControllingRegistration(registration);
+-  }
++  mControlledDocuments.Remove(aDoc);
+ }
+ 
+ void
+ ServiceWorkerManager::MaybeCheckNavigationUpdate(nsIDocument* aDoc)
+ {
+   AssertIsOnMainThread();
+   MOZ_ASSERT(aDoc);
+   // We perform these success path navigation update steps when the
+@@ -2386,60 +2447,24 @@ ServiceWorkerManager::MaybeCheckNavigati
+   //    algorithm.
+   RefPtr<ServiceWorkerRegistrationInfo> registration;
+   mControlledDocuments.Get(aDoc, getter_AddRefs(registration));
+   if (registration) {
+     registration->MaybeScheduleUpdate();
+   }
+ }
+ 
+-RefPtr<GenericPromise>
++void
+ ServiceWorkerManager::StartControllingADocument(ServiceWorkerRegistrationInfo* aRegistration,
+                                                 nsIDocument* aDoc)
+ {
+   MOZ_ASSERT(aRegistration);
+   MOZ_ASSERT(aDoc);
+ 
+-#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+-  auto storageAllowed = nsContentUtils::StorageAllowedForDocument(aDoc);
+-  MOZ_DIAGNOSTIC_ASSERT(storageAllowed == nsContentUtils::StorageAccess::eAllow);
+-#endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
+-
+-  RefPtr<GenericPromise> ref;
+-
+-  ServiceWorkerInfo* activeWorker = aRegistration->GetActive();
+-  if (NS_WARN_IF(!activeWorker)) {
+-    ref = GenericPromise::CreateAndReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+-                                          __func__);
+-    return ref.forget();
+-  }
+-
+-  Maybe<ClientInfo> clientInfo = aDoc->GetClientInfo();
+-  if (NS_WARN_IF(clientInfo.isNothing())) {
+-    ref = GenericPromise::CreateAndReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+-                                          __func__);
+-    return ref.forget();
+-  }
+-
+-  aRegistration->StartControllingClient();
+   mControlledDocuments.Put(aDoc, aRegistration);
+-
+-  StartControllingClient(clientInfo.ref(), aRegistration);
+-
+-  // Mark the document's ClientSource as controlled using the ClientHandle
+-  // interface.  While we could get at the ClientSource directly from the
+-  // document here, our goal is to move ServiceWorkerManager to a separate
+-  // process.  Using the ClientHandle supports this remote operation.
+-  RefPtr<ClientHandle> clientHandle =
+-    ClientManager::CreateHandle(clientInfo.ref(),
+-                                SystemGroup::EventTargetFor(TaskCategory::Other));
+-  ref = Move(clientHandle->Control(activeWorker->Descriptor()));
+-
+-  Telemetry::Accumulate(Telemetry::SERVICE_WORKER_CONTROLLED_DOCUMENTS, 1);
+-  return Move(ref);
+ }
+ 
+ void
+ ServiceWorkerManager::StopControllingRegistration(ServiceWorkerRegistrationInfo* aRegistration)
+ {
+   aRegistration->StopControllingClient();
+   if (aRegistration->IsControllingClients() || !aRegistration->IsIdle()) {
+     return;
+@@ -2766,20 +2791,17 @@ ServiceWorkerManager::DispatchFetchEvent
+         //       initial about:blank global.  See bug 1419620 and the spec
+         //       issue here: https://github.com/w3c/ServiceWorker/issues/1232
+       }
+ 
+       if (clientInfo.isSome()) {
+         // First, attempt to mark the reserved client controlled directly.  This
+         // will update the controlled status in the ClientManagerService in the
+         // parent.  It will also eventually propagate back to the ClientSource.
+-        RefPtr<ClientHandle> clientHandle =
+-          ClientManager::CreateHandle(clientInfo.ref(),
+-                                      SystemGroup::EventTargetFor(TaskCategory::Other));
+-        clientHandle->Control(serviceWorker->Descriptor());
++        StartControllingClient(clientInfo.ref(), registration);
+       }
+ 
+       // But we also note the reserved state on the LoadInfo.  This allows the
+       // ClientSource to be updated immediately after the nsIChannel starts.
+       // This is necessary to have the correct controller in place for immediate
+       // follow-on requests.
+       loadInfo->SetController(serviceWorker->Descriptor());
+     }
+@@ -3240,21 +3262,18 @@ ServiceWorkerManager::MaybeClaimClient(n
+                         getter_AddRefs(controllingRegistration));
+ 
+   if (aWorkerRegistration != matchingRegistration ||
+       aWorkerRegistration == controllingRegistration) {
+     ref = GenericPromise::CreateAndResolve(true, __func__);
+     return ref.forget();
+   }
+ 
+-  if (controllingRegistration) {
+-    StopControllingRegistration(controllingRegistration);
+-  }
+-
+-  ref = StartControllingADocument(aWorkerRegistration, aDocument);
++  StartControllingADocument(aWorkerRegistration, aDocument);
++  ref = StartControllingClient(clientInfo.ref(), aWorkerRegistration);
+   return ref.forget();
+ }
+ 
+ already_AddRefed<GenericPromise>
+ ServiceWorkerManager::MaybeClaimClient(nsIDocument* aDoc,
+                                        const ServiceWorkerDescriptor& aServiceWorker)
+ {
+   RefPtr<GenericPromise> ref;
+diff --git a/dom/workers/ServiceWorkerManager.h b/dom/workers/ServiceWorkerManager.h
+--- a/dom/workers/ServiceWorkerManager.h
++++ b/dom/workers/ServiceWorkerManager.h
+@@ -339,17 +339,17 @@ public:
+ 
+ private:
+   ServiceWorkerManager();
+   ~ServiceWorkerManager();
+ 
+   void
+   Init(ServiceWorkerRegistrar* aRegistrar);
+ 
+-  void
++  RefPtr<GenericPromise>
+   StartControllingClient(const ClientInfo& aClientInfo,
+                          ServiceWorkerRegistrationInfo* aRegistrationInfo);
+ 
+   void
+   StopControllingClient(const ClientInfo& aClientInfo);
+ 
+   void
+   MaybeStartShutdown();
+@@ -393,17 +393,17 @@ private:
+                                             WhichServiceWorker aWhichOne);
+   void
+   InvalidateServiceWorkerRegistrationWorker(ServiceWorkerRegistrationInfo* aRegistration,
+                                             WhichServiceWorker aWhichOnes);
+ 
+   void
+   NotifyServiceWorkerRegistrationRemoved(ServiceWorkerRegistrationInfo* aRegistration);
+ 
+-  RefPtr<GenericPromise>
++  void
+   StartControllingADocument(ServiceWorkerRegistrationInfo* aRegistration,
+                             nsIDocument* aDoc);
+ 
+   void
+   StopControllingRegistration(ServiceWorkerRegistrationInfo* aRegistration);
+ 
+   already_AddRefed<ServiceWorkerRegistrationInfo>
+   GetServiceWorkerRegistrationInfo(nsPIDOMWindowInner* aWindow);

+ 95 - 0
frg/work-js/mozilla-release/patches/1425975-09-59a1.patch

@@ -0,0 +1,95 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1515172221 18000
+# Node ID 8449669c2adc5012c05ea09d279d4b57e6df5096
+# Parent  2c6d9ef596243039d0e8b94f210ef85ae231902f
+Bug 1425975 P9 Refactor MaybeCheckNavigationUpdate() to take a ClientInfo instead of a document. r=asuth
+
+diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp
+--- a/dom/base/nsDocument.cpp
++++ b/dom/base/nsDocument.cpp
+@@ -5430,17 +5430,20 @@ nsDocument::DispatchContentLoadedEvents(
+                                         NS_LITERAL_STRING("MozApplicationManifest"),
+                                         true, true);
+   }
+ 
+   if (mMaybeServiceWorkerControlled) {
+     using mozilla::dom::workers::ServiceWorkerManager;
+     RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+     if (swm) {
+-      swm->MaybeCheckNavigationUpdate(this);
++      Maybe<ClientInfo> clientInfo = GetClientInfo();
++      if (clientInfo.isSome()) {
++        swm->MaybeCheckNavigationUpdate(clientInfo.ref());
++      }
+     }
+   }
+ 
+   UnblockOnload(true);
+ }
+ 
+ void
+ nsDocument::EndLoad()
+diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp
+--- a/dom/workers/ServiceWorkerManager.cpp
++++ b/dom/workers/ServiceWorkerManager.cpp
+@@ -2427,33 +2427,31 @@ void
+ ServiceWorkerManager::MaybeStopControlling(nsIDocument* aDoc)
+ {
+   AssertIsOnMainThread();
+   MOZ_ASSERT(aDoc);
+   mControlledDocuments.Remove(aDoc);
+ }
+ 
+ void
+-ServiceWorkerManager::MaybeCheckNavigationUpdate(nsIDocument* aDoc)
++ServiceWorkerManager::MaybeCheckNavigationUpdate(const ClientInfo& aClientInfo)
+ {
+   AssertIsOnMainThread();
+-  MOZ_ASSERT(aDoc);
+   // We perform these success path navigation update steps when the
+   // document tells us its more or less done loading.  This avoids
+   // slowing down page load and also lets pages consistently get
+   // updatefound events when they fire.
+   //
+   // 9.8.20 If respondWithEntered is false, then:
+   // 9.8.22 Else: (respondWith was entered and succeeded)
+   //    If request is a non-subresource request, then: Invoke Soft Update
+   //    algorithm.
+-  RefPtr<ServiceWorkerRegistrationInfo> registration;
+-  mControlledDocuments.Get(aDoc, getter_AddRefs(registration));
+-  if (registration) {
+-    registration->MaybeScheduleUpdate();
++  ControlledClientData* data = mControlledClients.Get(aClientInfo.Id());
++  if (data && data->mRegistrationInfo) {
++    data->mRegistrationInfo->MaybeScheduleUpdate();
+   }
+ }
+ 
+ void
+ ServiceWorkerManager::StartControllingADocument(ServiceWorkerRegistrationInfo* aRegistration,
+                                                 nsIDocument* aDoc)
+ {
+   MOZ_ASSERT(aRegistration);
+diff --git a/dom/workers/ServiceWorkerManager.h b/dom/workers/ServiceWorkerManager.h
+--- a/dom/workers/ServiceWorkerManager.h
++++ b/dom/workers/ServiceWorkerManager.h
+@@ -315,17 +315,17 @@ public:
+   AddRegistrationEventListener(const nsAString& aScope,
+                                ServiceWorkerRegistrationListener* aListener);
+ 
+   NS_IMETHOD
+   RemoveRegistrationEventListener(const nsAString& aScope,
+                                   ServiceWorkerRegistrationListener* aListener);
+ 
+   void
+-  MaybeCheckNavigationUpdate(nsIDocument* aDoc);
++  MaybeCheckNavigationUpdate(const ClientInfo& aClientInfo);
+ 
+   nsresult
+   SendPushEvent(const nsACString& aOriginAttributes,
+                 const nsACString& aScope,
+                 const nsAString& aMessageId,
+                 const Maybe<nsTArray<uint8_t>>& aData);
+ 
+   nsresult

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

@@ -0,0 +1,84 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1515172221 18000
+# Node ID 8e4ffe879be7ec092b58657f8b06fe78b96c4437
+# Parent  a3165eddf7570d539c943884057c570d272fb8d6
+Bug 1425975 P10 Fix the test_skip_waiting.html mochitest to properly wait for active worker state. r=asuth
+
+diff --git a/dom/workers/test/serviceworkers/skip_waiting_scope/index.html b/dom/workers/test/serviceworkers/skip_waiting_scope/index.html
+--- a/dom/workers/test/serviceworkers/skip_waiting_scope/index.html
++++ b/dom/workers/test/serviceworkers/skip_waiting_scope/index.html
+@@ -14,20 +14,16 @@
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+ <script class="testbody" type="text/javascript">
+ 
+   if (!parent) {
+     info("skip_waiting_scope/index.html shouldn't be launched directly!");
+   }
+ 
+-  navigator.serviceWorker.ready.then(function() {
+-    parent.postMessage("READY", "*");
+-  });
+-
+   navigator.serviceWorker.oncontrollerchange = function() {
+     parent.postMessage({
+       event: "controllerchange",
+       controllerScriptURL: navigator.serviceWorker.controller &&
+                            navigator.serviceWorker.controller.scriptURL
+     }, "*");
+   }
+ 
+diff --git a/dom/workers/test/serviceworkers/test_skip_waiting.html b/dom/workers/test/serviceworkers/test_skip_waiting.html
+--- a/dom/workers/test/serviceworkers/test_skip_waiting.html
++++ b/dom/workers/test/serviceworkers/test_skip_waiting.html
+@@ -8,45 +8,36 @@
+   <title>Bug 1131352 - Add ServiceWorkerGlobalScope skipWaiting()</title>
+   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ </head>
+ <body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
++<script src="utils.js"></script>
+ <script class="testbody" type="text/javascript">
+   var registration, iframe, content;
+ 
+   function start() {
+     return navigator.serviceWorker.register("worker.js",
+                                             {scope: "./skip_waiting_scope/"});
+   }
+ 
+-  function waitForActivated(swr) {
++  async function waitForActivated(swr) {
+     registration = swr;
+-    var promise = new Promise(function(resolve, reject) {
+-      window.onmessage = function(e) {
+-        if (e.data === "READY") {
+-          ok(true, "Active worker is activated now");
+-          resolve();
+-        } else {
+-          ok(false, "Wrong value. Somenting went wrong");
+-          resolve();
+-        }
+-      }
+-    });
++    await waitForState(registration.installing, "activated")
+ 
+     iframe = document.createElement("iframe");
+     iframe.setAttribute("src", "skip_waiting_scope/index.html");
+ 
+     content = document.getElementById("content");
+     content.appendChild(iframe);
+ 
+-    return promise;
++    await new Promise(resolve => iframe.onload = resolve);
+   }
+ 
+   function checkWhetherItSkippedWaiting() {
+     var promise = new Promise(function(resolve, reject) {
+       window.onmessage = function (evt) {
+         if (evt.data.event === "controllerchange") {
+           ok(evt.data.controllerScriptURL.match("skip_waiting_installed_worker"),
+              "The controller changed after skiping the waiting step");

+ 77 - 0
frg/work-js/mozilla-release/patches/1425975-11-59a1.patch

@@ -0,0 +1,77 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1515172222 18000
+# Node ID 86acf7e4d09daf2b392eba2784bc92591cab00d5
+# Parent  cda047412b4371d43f44df0bd1dc6a6ffce8e7d9
+Bug 1425975 P11 Fix test_workerupdatefound.html not to frame loading against SW activation and updatefound events. r=asuth
+
+diff --git a/dom/workers/test/serviceworkers/test_workerupdatefoundevent.html b/dom/workers/test/serviceworkers/test_workerupdatefoundevent.html
+--- a/dom/workers/test/serviceworkers/test_workerupdatefoundevent.html
++++ b/dom/workers/test/serviceworkers/test_workerupdatefoundevent.html
+@@ -8,24 +8,25 @@
+   <title>Bug 1131327 - Test ServiceWorkerRegistration.onupdatefound on ServiceWorker</title>
+   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ </head>
+ <body>
+ <p id="display"></p>
+ <div id="content"></div>
+ <pre id="test"></pre>
++<script src="utils.js"></script>
+ <script class="testbody" type="text/javascript">
+   var registration;
+   var promise;
+ 
+-  function start() {
+-    return navigator.serviceWorker.register("worker_updatefoundevent.js",
+-                                            { scope: "./updatefoundevent.html" })
+-      .then((swr) => registration = swr);
++  async function start() {
++    registration = await navigator.serviceWorker.register("worker_updatefoundevent.js",
++                                                          { scope: "./updatefoundevent.html" })
++    await waitForState(registration.installing, 'activated');
+   }
+ 
+   function startWaitForUpdateFound() {
+     registration.onupdatefound = function(e) {
+     }
+ 
+     promise = new Promise(function(resolve, reject) {
+       window.onmessage = function(e) {
+diff --git a/dom/workers/test/serviceworkers/worker_updatefoundevent.js b/dom/workers/test/serviceworkers/worker_updatefoundevent.js
+--- a/dom/workers/test/serviceworkers/worker_updatefoundevent.js
++++ b/dom/workers/test/serviceworkers/worker_updatefoundevent.js
+@@ -1,23 +1,18 @@
+ /**
+  * Any copyright is dedicated to the Public Domain.
+  * http://creativecommons.org/publicdomain/zero/1.0/
+  */
+ 
+-onactivate = function(e) {
+-  e.waitUntil(new Promise(function(resolve, reject) {
+-    registration.onupdatefound = function(e) {
+-      clients.matchAll().then(function(clients) {
+-        if (!clients.length) {
+-          reject("No clients found");
+-        }
++registration.onupdatefound = function(e) {
++  clients.matchAll().then(function(clients) {
++    if (!clients.length) {
++      reject("No clients found");
++    }
+ 
+-        if (registration.scope.match(/updatefoundevent\.html$/)) {
+-          clients[0].postMessage("finish");
+-          resolve();
+-        } else {
+-          dump("Scope did not match");
+-        }
+-      }, reject);
++    if (registration.scope.match(/updatefoundevent\.html$/)) {
++      clients[0].postMessage("finish");
++    } else {
++      dump("Scope did not match");
+     }
+-  }));
++  });
+ }

+ 52 - 0
frg/work-js/mozilla-release/patches/1425975-12-59a1.patch

@@ -0,0 +1,52 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1515172222 18000
+# Node ID d8406786b4192ef8fbbd4b56549f99ed02fbf040
+# Parent  553d59d90721656958bd8bfde4af36f898d8f8e7
+Bug 1425975 P12 Don't mark an initial about:blank client as controlled if its sandboxed. r=asuth
+
+diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp
+--- a/docshell/base/nsDocShell.cpp
++++ b/docshell/base/nsDocShell.cpp
+@@ -3396,17 +3396,17 @@ nsDocShell::MaybeCreateInitialClientSour
+   // when DoChannelLoad() is called before CreateAboutBlankContentViewer.
+   if (mInitialClientSource) {
+     return;
+   }
+ 
+   // Don't pre-allocate the client when we are sandboxed.  The inherited
+   // principal does not take sandboxing into account.
+   // TODO: Refactor sandboxing principal code out so we can use it here.
+-  if (!aPrincipal && (mSandboxFlags & SANDBOXED_ORIGIN)) {
++  if (!aPrincipal && mSandboxFlags) {
+     return;
+   }
+ 
+   nsIPrincipal* principal = aPrincipal ? aPrincipal
+                                        : GetInheritedPrincipal(false);
+ 
+   // Sometimes there is no principal available when we are called from
+   // CreateAboutBlankContentViewer.  For example, sometimes the principal
+@@ -3438,18 +3438,21 @@ nsDocShell::MaybeCreateInitialClientSour
+   nsCOMPtr<nsIDocShell> parent = GetParentDocshell();
+   nsPIDOMWindowOuter* parentOuter = parent ? parent->GetWindow() : nullptr;
+   nsPIDOMWindowInner* parentInner =
+     parentOuter ? parentOuter->GetCurrentInnerWindow() : nullptr;
+   if (!parentInner) {
+     return;
+   }
+ 
++  // We're done if there is no parent controller.  Also, don't inherit
++  // the controller if we're sandboxed.  This matches our behavior in
++  // ShouldPrepareForIntercept(),
+   Maybe<ServiceWorkerDescriptor> controller(parentInner->GetController());
+-  if (controller.isNothing()) {
++  if (controller.isNothing() || mSandboxFlags) {
+     return;
+   }
+ 
+   nsCOMPtr<nsIServiceWorkerManager> swm = mozilla::services::GetServiceWorkerManager();
+   if (!swm) {
+     return;
+   }
+ 

+ 155 - 0
frg/work-js/mozilla-release/patches/1425975-13-59a1.patch

@@ -0,0 +1,155 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1515172222 18000
+# Node ID 42f1a8bfebf4c0ec070634195ec0c3faa4368a58
+# Parent  1570383720ba3821e1de1d1ff2f0ad6d68a902e9
+Bug 1425975 P13 Check for a different final document principal and reset the ClientSource when it happens. r=asuth
+
+diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp
+--- a/dom/base/nsGlobalWindow.cpp
++++ b/dom/base/nsGlobalWindow.cpp
+@@ -3483,41 +3483,74 @@ nsGlobalWindow::EnsureClientSource()
+   // In this case we want to inherit this placeholder Client here.
+   if (!mClientSource) {
+     mClientSource = Move(initialClientSource);
+     if (mClientSource) {
+       newClientSource = true;
+     }
+   }
+  
++  // Verify the final ClientSource principal matches the final document
++  // principal.  The ClientChannelHelper handles things like network
++  // redirects, but there are other ways the document principal can change.
++  // For example, if something sets the nsIChannel.owner property, then
++  // the final channel principal can be anything.  Unfortunately there is
++  // no good way to detect this until after the channel completes loading.
++  //
++  // For now we handle this just by reseting the ClientSource.  This will
++  // result in a new ClientSource with the correct principal being created.
++  // To APIs like ServiceWorker and Clients API it will look like there was
++  // an initial content page created that was then immediately replaced.
++  // This is pretty close to what we are actually doing.
++  if (mClientSource) {
++    nsCOMPtr<nsIPrincipal> clientPrincipal(mClientSource->Info().GetPrincipal());
++    if (!clientPrincipal || !clientPrincipal->Equals(mDoc->NodePrincipal())) {
++      mClientSource.reset();
++    }
++  }
++
+   // If we don't have a reserved client or an initial client, then create
+   // one now.  This can happen in certain cases where we avoid preallocating
+   // the client in the docshell.  This mainly occurs in situations where
+   // the principal is not clearly inherited from the parent; e.g. sandboxed
+   // iframes, window.open(), etc.
++  //
++  // We also do this late ClientSource creation if the final document ended
++  // up with a different principal.
++  //
+   // TODO: We may not be marking initial about:blank documents created
+   //       this way as controlled by a service worker properly.  The
+   //       controller should be coming from the same place as the inheritted
+   //       principal.  We do this in docshell, but as mentioned we aren't
+   //       smart enough to handle all cases yet.  For example, a
+   //       window.open() with new URL should inherit the controller from
+   //       the opener, but we probably don't handle that yet.
+   if (!mClientSource) {
+     mClientSource = ClientManager::CreateSource(ClientType::Window,
+                                                 EventTargetFor(TaskCategory::Other),
+                                                 mDoc->NodePrincipal());
+     MOZ_DIAGNOSTIC_ASSERT(mClientSource);
+     newClientSource = true;
++
++    // Note, we don't apply the loadinfo controller below if we create
++    // the ClientSource here.
+   }
+ 
+   // The load may have started controlling the Client as well.  If
+   // so, mark it as controlled immediately here.  The actor may
+   // or may not have been notified by the parent side about being
+   // controlled yet.
+-  if (loadInfo) {
++  //
++  // Note: We should be careful not to control a client that was created late.
++  //       These clients were not seen by the ServiceWorkerManager when it
++  //       marked the LoadInfo controlled and it won't know about them.  Its
++  //       also possible we are creating the client late due to the final
++  //       principal changing and these clients should definitely not be
++  //       controlled by a service worker with a different principal.
++  else if (loadInfo) {
+     const Maybe<ServiceWorkerDescriptor> controller = loadInfo->GetController();
+     if (controller.isSome()) {
+       mClientSource->SetController(controller.ref());
+     }
+ 
+     // We also have to handle the case where te initial about:blank is
+     // controlled due to inheritting the service worker from its parent,
+     // but the actual nsIChannel load is not covered by any service worker.
+diff --git a/dom/clients/manager/ClientInfo.cpp b/dom/clients/manager/ClientInfo.cpp
+--- a/dom/clients/manager/ClientInfo.cpp
++++ b/dom/clients/manager/ClientInfo.cpp
+@@ -2,21 +2,23 @@
+ /* 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 "ClientInfo.h"
+ 
+ #include "mozilla/dom/ClientIPCTypes.h"
++#include "mozilla/ipc/BackgroundUtils.h"
+ 
+ namespace mozilla {
+ namespace dom {
+ 
+ using mozilla::ipc::PrincipalInfo;
++using mozilla::ipc::PrincipalInfoToPrincipal;
+ 
+ ClientInfo::ClientInfo(const nsID& aId,
+                        ClientType aType,
+                        const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
+                        const TimeStamp& aCreationTime)
+   : mData(MakeUnique<IPCClientInfo>(aId, aType, aPrincipalInfo, aCreationTime,
+                                     EmptyCString(),
+                                     mozilla::dom::FrameType::None))
+@@ -133,10 +135,18 @@ ClientInfo::IsPrivateBrowsing() const
+     default:
+     {
+       // clients should never be expanded principals
+       MOZ_CRASH("unexpected principal type!");
+     }
+   }
+ }
+ 
++nsCOMPtr<nsIPrincipal>
++ClientInfo::GetPrincipal() const
++{
++  MOZ_ASSERT(NS_IsMainThread());
++  nsCOMPtr<nsIPrincipal> ref = PrincipalInfoToPrincipal(PrincipalInfo());
++  return Move(ref);
++}
++
+ } // namespace dom
+ } // namespace mozilla
+diff --git a/dom/clients/manager/ClientInfo.h b/dom/clients/manager/ClientInfo.h
+--- a/dom/clients/manager/ClientInfo.h
++++ b/dom/clients/manager/ClientInfo.h
+@@ -90,14 +90,19 @@ public:
+ 
+   // Convert to the ipdl generated type.
+   const IPCClientInfo&
+   ToIPC() const;
+ 
+   // Determine if the client is in private browsing mode.
+   bool
+   IsPrivateBrowsing() const;
++
++  // Get a main-thread nsIPrincipal for the client.  This may return nullptr
++  // if the PrincipalInfo() fails to deserialize for some reason.
++  nsCOMPtr<nsIPrincipal>
++  GetPrincipal() const;
+ };
+ 
+ } // namespace dom
+ } // namespace mozilla
+ 
+ #endif // _mozilla_dom_ClientInfo_h

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

@@ -0,0 +1,109 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1515172222 18000
+# Node ID e7b92985e941f7f1b9b562fba75b0b764b1ce6a9
+# Parent  9a14e1e8544c7bb1d640516454a4518e0311af5e
+Bug 1425975 P14 Assert that storage is allowed when a ClientSource is both execution ready and controlled. r=asuth
+
+diff --git a/dom/clients/manager/ClientSource.cpp b/dom/clients/manager/ClientSource.cpp
+--- a/dom/clients/manager/ClientSource.cpp
++++ b/dom/clients/manager/ClientSource.cpp
+@@ -201,16 +201,24 @@ ClientSource::WorkerExecutionReady(Worke
+ {
+   MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate);
+   aWorkerPrivate->AssertIsOnWorkerThread();
+ 
+   if (IsShutdown()) {
+     return;
+   }
+ 
++  // A client without access to storage should never be controlled by
++  // a service worker.  Check this here in case we were controlled before
++  // execution ready.  We can't reliably determine what our storage policy
++  // is before execution ready, unfortunately.
++  if (mController.isSome()) {
++    MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate->IsStorageAllowed());
++  }
++
+   // Its safe to store the WorkerPrivate* here because the ClientSource
+   // is explicitly destroyed by WorkerPrivate before exiting its run loop.
+   MOZ_DIAGNOSTIC_ASSERT(mOwner.is<Nothing>());
+   mOwner = AsVariant(aWorkerPrivate);
+ 
+   ClientSourceExecutionReadyArgs args(
+     aWorkerPrivate->GetLocationInfo().mHref,
+     FrameType::None);
+@@ -230,16 +238,25 @@ ClientSource::WindowExecutionReady(nsPID
+     return NS_OK;
+   }
+ 
+   nsIDocument* doc = aInnerWindow->GetExtantDoc();
+   if (NS_WARN_IF(!doc)) {
+     return NS_ERROR_UNEXPECTED;
+   }
+ 
++  // A client without access to storage should never be controlled by
++  // a service worker.  Check this here in case we were controlled before
++  // execution ready.  We can't reliably determine what our storage policy
++  // is before execution ready, unfortunately.
++  if (mController.isSome()) {
++    MOZ_DIAGNOSTIC_ASSERT(nsContentUtils::StorageAllowedForWindow(aInnerWindow) ==
++                          nsContentUtils::StorageAccess::eAllow);
++  }
++
+   // Don't use nsAutoCString here since IPC requires a full nsCString anyway.
+   nsCString spec;
+ 
+   nsIURI* uri = doc->GetOriginalURI();
+   if (uri) {
+     nsresult rv = uri->GetSpec(spec);
+     if (NS_WARN_IF(NS_FAILED(rv))) {
+       return rv;
+@@ -285,16 +302,20 @@ ClientSource::DocShellExecutionReady(nsI
+     return NS_OK;
+   }
+ 
+   nsPIDOMWindowOuter* outer = aDocShell->GetWindow();
+   if (NS_WARN_IF(!outer)) {
+     return NS_ERROR_UNEXPECTED;
+   }
+ 
++  // Note: We don't assert storage access for a controlled client.  If
++  // the about:blank actually gets used then WindowExecutionReady() will
++  // get called which asserts storage access.
++
+   // TODO: dedupe this with WindowExecutionReady
+   FrameType frameType = FrameType::Top_level;
+   if (!outer->IsTopLevelWindow()) {
+     frameType = FrameType::Nested;
+   } else if(outer->HadOriginalOpener()) {
+     frameType = FrameType::Auxiliary;
+   }
+ 
+@@ -355,16 +376,26 @@ ClientSource::SetController(const Servic
+ {
+   NS_ASSERT_OWNINGTHREAD(ClientSource);
+ 
+   // A client in private browsing mode should never be controlled by
+   // a service worker.  The principal origin attributes should guarantee
+   // this invariant.
+   MOZ_DIAGNOSTIC_ASSERT(!mClientInfo.IsPrivateBrowsing());
+ 
++  // A client without access to storage should never be controlled a
++  // a service worker.  If we are already execution ready with a real
++  // window or worker, then verify assert the storage policy is correct.
++  if (GetInnerWindow()) {
++    MOZ_DIAGNOSTIC_ASSERT(nsContentUtils::StorageAllowedForWindow(GetInnerWindow()) ==
++                          nsContentUtils::StorageAccess::eAllow);
++  } else if (GetWorkerPrivate()) {
++    MOZ_DIAGNOSTIC_ASSERT(GetWorkerPrivate()->IsStorageAllowed());
++  }
++
+   if (mController.isSome() && mController.ref() == aServiceWorker) {
+     return;
+   }
+ 
+   mController.reset();
+   mController.emplace(aServiceWorker);
+ 
+   RefPtr<ServiceWorkerContainer> swc;

+ 182 - 0
frg/work-js/mozilla-release/patches/1425975-15-59a1.patch

@@ -0,0 +1,182 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1515172222 18000
+# Node ID 93e07d0261cb5b1025545263c22ac024ffa911c9
+# Parent  fe7a7e5af90168b677fca294e4f4dcf40009ed68
+Bug 1425975 P15 Add session lifetime policy checks to test_third_party_iframe.html. r=asuth
+
+diff --git a/dom/workers/test/serviceworkers/test_third_party_iframes.html b/dom/workers/test/serviceworkers/test_third_party_iframes.html
+--- a/dom/workers/test/serviceworkers/test_third_party_iframes.html
++++ b/dom/workers/test/serviceworkers/test_third_party_iframes.html
+@@ -77,19 +77,20 @@ function runTest(aExpectedResponses) {
+       ok(false, "Expected " + expected.status + " got " + status);
+     }
+     responsesIndex++;
+   };
+ }
+ 
+ // Verify that we can register and intercept a 3rd party iframe with
+ // the given cookie policy.
+-function testShouldIntercept(policy, done) {
++function testShouldIntercept(behavior, lifetime, done) {
+   SpecialPowers.pushPrefEnv({"set": [
+-      ["network.cookie.cookieBehavior", policy]
++      ["network.cookie.cookieBehavior", behavior],
++      ["network.cookie.lifetimePolicy", lifetime],
+   ]}, function() {
+     runTest([{
+       status: "ok"
+     }, {
+       status: "registrationdone",
+       next: function() {
+         iframe.addEventListener("load", testIframeLoaded);
+         iframe.src = origin + basePath + "iframe1.html";
+@@ -116,19 +117,20 @@ function testShouldIntercept(policy, don
+         done();
+       }
+     }]);
+   });
+ }
+ 
+ // Verify that we cannot register a service worker in a 3rd party
+ // iframe with the given cookie policy.
+-function testShouldNotRegister(policy, done) {
++function testShouldNotRegister(behavior, lifetime, done) {
+   SpecialPowers.pushPrefEnv({"set": [
+-      ["network.cookie.cookieBehavior", policy]
++      ["network.cookie.cookieBehavior", behavior],
++      ["network.cookie.lifetimePolicy", lifetime],
+   ]}, function() {
+     runTest([{
+       status: "registrationfailed",
+       next: function() {
+         iframe.addEventListener("load", testIframeLoaded);
+         iframe.src = origin + basePath + "iframe1.html";
+       }
+     }, {
+@@ -147,28 +149,30 @@ function testShouldNotRegister(policy, d
+       }
+     }]);
+   });
+ }
+ 
+ // Verify that if a service worker is already registered a 3rd
+ // party iframe will still not be intercepted with the given cookie
+ // policy.
+-function testShouldNotIntercept(policy, done) {
++function testShouldNotIntercept(behavior, lifetime, done) {
+   SpecialPowers.pushPrefEnv({"set": [
+-      ["network.cookie.cookieBehavior", COOKIE_BEHAVIOR_ACCEPT]
++    ["network.cookie.cookieBehavior", BEHAVIOR_ACCEPT],
++    ["network.cookie.lifetimePolicy", LIFETIME_EXPIRE],
+   ]}, function() {
+     runTest([{
+       status: "ok"
+     }, {
+       status: "registrationdone",
+       next: function() {
+         iframe.addEventListener("load", testIframeLoaded);
+         SpecialPowers.pushPrefEnv({"set": [
+-            ["network.cookie.cookieBehavior", policy],
++            ["network.cookie.cookieBehavior", behavior],
++            ["network.cookie.lifetimePolicy", lifetime],
+           ]}, function() {
+             iframe.src = origin + basePath + "iframe1.html";
+           });
+       }
+     }, {
+       status: "networkresponse",
+     }, {
+       status: "worker-networkresponse",
+@@ -181,17 +185,18 @@ function testShouldNotIntercept(policy, 
+         iframe.src = thirdPartyOrigin + basePath + "unregister.html";
+       }
+     }, {
+       status: "uncontrolled",
+     }, {
+       status: "getregistrationfailed",
+       next: function() {
+         SpecialPowers.pushPrefEnv({"set": [
+-            ["network.cookie.cookieBehavior", COOKIE_BEHAVIOR_ACCEPT],
++            ["network.cookie.cookieBehavior", BEHAVIOR_ACCEPT],
++            ["network.cookie.lifetimePolicy", LIFETIME_EXPIRE],
+           ]}, function() {
+             iframe.src = thirdPartyOrigin + basePath + "unregister.html";
+           });
+       }
+     }, {
+       status: "controlled",
+     }, {
+       status: "unregistrationdone",
+@@ -199,41 +204,59 @@ function testShouldNotIntercept(policy, 
+         window.onmessage = null;
+         ok(true, "Test finished successfully");
+         done();
+       }
+     }]);
+   });
+ }
+ 
+-const COOKIE_BEHAVIOR_ACCEPT        = 0;
+-const COOKIE_BEHAVIOR_REJECTFOREIGN = 1;
+-const COOKIE_BEHAVIOR_REJECT        = 2;
+-const COOKIE_BEHAVIOR_LIMITFOREIGN  = 3;
++const BEHAVIOR_ACCEPT        = 0;
++const BEHAVIOR_REJECTFOREIGN = 1;
++const BEHAVIOR_REJECT        = 2;
++const BEHAVIOR_LIMITFOREIGN  = 3;
++
++const LIFETIME_EXPIRE        = 0;
++const LIFETIME_SESSION       = 2;
+ 
+ let steps = [() => {
+   SpecialPowers.pushPrefEnv({"set": [
+     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+     ["dom.serviceWorkers.enabled", true],
+     ["dom.serviceWorkers.testing.enabled", true],
+     ["browser.dom.window.dump.enabled", true],
+-    ["network.cookie.cookieBehavior", COOKIE_BEHAVIOR_ACCEPT]
++    ["network.cookie.cookieBehavior", BEHAVIOR_ACCEPT],
++    ["network.cookie.lifetimePolicy", LIFETIME_EXPIRE],
+   ]}, next);
+ }, () => {
+-  testShouldIntercept(COOKIE_BEHAVIOR_ACCEPT, next);
++  testShouldIntercept(BEHAVIOR_ACCEPT, LIFETIME_EXPIRE, next);
++}, () => {
++  testShouldNotRegister(BEHAVIOR_REJECTFOREIGN, LIFETIME_EXPIRE, next);
++}, () => {
++  testShouldNotIntercept(BEHAVIOR_REJECTFOREIGN, LIFETIME_EXPIRE, next);
+ }, () => {
+-  testShouldNotRegister(COOKIE_BEHAVIOR_REJECTFOREIGN, next);
++  testShouldNotRegister(BEHAVIOR_REJECT, LIFETIME_EXPIRE, next);
++}, () => {
++  testShouldNotIntercept(BEHAVIOR_REJECT, LIFETIME_EXPIRE, next);
+ }, () => {
+-  testShouldNotIntercept(COOKIE_BEHAVIOR_REJECTFOREIGN, next);
++  testShouldNotRegister(BEHAVIOR_LIMITFOREIGN, LIFETIME_EXPIRE, next);
++}, () => {
++  testShouldNotIntercept(BEHAVIOR_LIMITFOREIGN, LIFETIME_EXPIRE, next);
+ }, () => {
+-  testShouldNotRegister(COOKIE_BEHAVIOR_REJECT, next);
++  testShouldNotIntercept(BEHAVIOR_ACCEPT, LIFETIME_SESSION, next);
+ }, () => {
+-  testShouldNotIntercept(COOKIE_BEHAVIOR_REJECT, next);
++  testShouldNotRegister(BEHAVIOR_REJECTFOREIGN, LIFETIME_SESSION, next);
++}, () => {
++  testShouldNotIntercept(BEHAVIOR_REJECTFOREIGN, LIFETIME_SESSION, next);
+ }, () => {
+-  testShouldNotRegister(COOKIE_BEHAVIOR_LIMITFOREIGN, next);
++  testShouldNotRegister(BEHAVIOR_REJECT, LIFETIME_SESSION, next);
++}, () => {
++  testShouldNotIntercept(BEHAVIOR_REJECT, LIFETIME_SESSION, next);
+ }, () => {
+-  testShouldNotIntercept(COOKIE_BEHAVIOR_LIMITFOREIGN, next);
++  testShouldNotRegister(BEHAVIOR_LIMITFOREIGN, LIFETIME_SESSION, next);
++}, () => {
++  testShouldNotIntercept(BEHAVIOR_LIMITFOREIGN, LIFETIME_SESSION, next);
+ }];
+ 
+ </script>
+ </pre>
+ </body>
+ </html>

+ 248 - 0
frg/work-js/mozilla-release/patches/1425975-16-59a1.patch

@@ -0,0 +1,248 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1515172222 18000
+# Node ID 5b1109ca1c1f887a793748f1e115152a42122151
+# Parent  54b5f539a81dc4b0824b1b2ab4539fedf06766c3
+Bug 1425975 P16 Make nsDocShell check for session cookie lifetime policy before allowing service worker intercept. r=asuth
+
+diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp
+--- a/docshell/base/nsDocShell.cpp
++++ b/docshell/base/nsDocShell.cpp
+@@ -3438,21 +3438,20 @@ nsDocShell::MaybeCreateInitialClientSour
+   nsCOMPtr<nsIDocShell> parent = GetParentDocshell();
+   nsPIDOMWindowOuter* parentOuter = parent ? parent->GetWindow() : nullptr;
+   nsPIDOMWindowInner* parentInner =
+     parentOuter ? parentOuter->GetCurrentInnerWindow() : nullptr;
+   if (!parentInner) {
+     return;
+   }
+ 
+-  // We're done if there is no parent controller.  Also, don't inherit
+-  // the controller if we're sandboxed.  This matches our behavior in
+-  // ShouldPrepareForIntercept(),
++  // We're done if there is no parent controller or if this docshell
++  // is not permitted to control for some reason.
+   Maybe<ServiceWorkerDescriptor> controller(parentInner->GetController());
+-  if (controller.isNothing() || mSandboxFlags) {
++  if (controller.isNothing() || !ServiceWorkerAllowedToControlWindow(nullptr)) {
+     return;
+   }
+ 
+   nsCOMPtr<nsIServiceWorkerManager> swm = mozilla::services::GetServiceWorkerManager();
+   if (!swm) {
+     return;
+   }
+ 
+@@ -14888,16 +14887,64 @@ nsDocShell::CanSetOriginAttributes()
+         return false;
+       }
+     }
+   }
+ 
+   return true;
+ }
+ 
++bool
++nsDocShell::ServiceWorkerAllowedToControlWindow(nsIURI* aURI)
++{
++  // NOTE: Ideally this method would call one of the
++  //       nsContentUtils::StorageAllowed*() methods to determine if the
++  //       interception is allowed.  Unfortunately we cannot safely do this
++  //       before the first window loads in the child process because the
++  //       permission manager might not have all its data yet.  Therefore,
++  //       we use this somewhat lame alternate implementation here.  Once
++  //       interception is moved to the parent process we should switch
++  //       to calling nsContentUtils::StorageAllowed*().  See bug 1428130.
++
++  if (UsePrivateBrowsing() || mSandboxFlags) {
++    return false;
++  }
++
++  uint32_t cookieBehavior = nsContentUtils::CookiesBehavior();
++  uint32_t lifetimePolicy = nsContentUtils::CookiesLifetimePolicy();
++  if (cookieBehavior == nsICookieService::BEHAVIOR_REJECT ||
++      lifetimePolicy == nsICookieService::ACCEPT_SESSION) {
++    return false;
++  }
++
++  if (!aURI || cookieBehavior == nsICookieService::BEHAVIOR_ACCEPT) {
++    return true;
++  }
++
++  nsCOMPtr<nsIDocShellTreeItem> parent;
++  GetSameTypeParent(getter_AddRefs(parent));
++  nsCOMPtr<nsPIDOMWindowOuter> parentWindow = parent ? parent->GetWindow()
++                                                     : nullptr;
++  if (parentWindow) {
++    nsresult rv = NS_OK;
++    nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
++      do_GetService(THIRDPARTYUTIL_CONTRACTID, &rv);
++    if (thirdPartyUtil) {
++      bool isThirdPartyURI = true;
++      rv = thirdPartyUtil->IsThirdPartyWindow(parentWindow, aURI,
++                                              &isThirdPartyURI);
++      if (NS_SUCCEEDED(rv) && isThirdPartyURI) {
++        return false;
++      }
++    }
++  }
++
++  return true;
++}
++
+ nsresult
+ nsDocShell::SetOriginAttributes(const OriginAttributes& aAttrs)
+ {
+   if (!CanSetOriginAttributes()) {
+     return NS_ERROR_FAILURE;
+   }
+ 
+   AssertOriginAttributesMatchPrivateBrowsing();
+@@ -15090,73 +15137,49 @@ nsDocShell::MaybeNotifyKeywordSearchLoad
+ #endif
+ }
+ 
+ NS_IMETHODIMP
+ nsDocShell::ShouldPrepareForIntercept(nsIURI* aURI, bool aIsNonSubresourceRequest,
+                                       bool* aShouldIntercept)
+ {
+   *aShouldIntercept = false;
+-  // No in private browsing
+-  if (UsePrivateBrowsing()) {
+-    return NS_OK;
+-  }
+-
+-  if (mSandboxFlags) {
+-    // If we're sandboxed, don't intercept.
+-    return NS_OK;
+-  }
+-
+-  uint32_t cookieBehavior = nsContentUtils::CookiesBehavior();
+-  if (cookieBehavior == nsICookieService::BEHAVIOR_REJECT) {
+-    // If cookies are disabled, don't intercept.
+-    return NS_OK;
+-  }
+-
+-  RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+-  if (!swm) {
+-    return NS_OK;
+-  }
+-
++
++  // For subresource requests we base our decision solely on the client's
++  // controller value.  Any settings that would have blocked service worker
++  // access should have been set before the initial navigation created the
++  // window.
+   if (!aIsNonSubresourceRequest) {
+     nsCOMPtr<nsIDocument> doc = GetDocument();
+     if (!doc) {
+       return NS_ERROR_NOT_AVAILABLE;
+     }
+ 
+     ErrorResult rv;
+     *aShouldIntercept = doc->GetController().isSome();
+     if (NS_WARN_IF(rv.Failed())) {
+       return rv.StealNSResult();
+     }
+ 
+     return NS_OK;
+   }
+ 
+-  // If the user has set a cookie policy that restricts cookies, then
+-  // avoid intercepting 3rd party iframes.
+-  if (cookieBehavior != nsICookieService::BEHAVIOR_ACCEPT) {
+-    nsCOMPtr<nsIDocShellTreeItem> parent;
+-    GetSameTypeParent(getter_AddRefs(parent));
+-    nsCOMPtr<nsPIDOMWindowOuter> parentWindow = parent ? parent->GetWindow()
+-                                                       : nullptr;
+-    if (parentWindow) {
+-      nsresult rv = NS_OK;
+-      nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
+-        do_GetService(THIRDPARTYUTIL_CONTRACTID, &rv);
+-      NS_ENSURE_SUCCESS(rv, rv);
+-
+-      bool isThirdPartyURI = true;
+-      rv = thirdPartyUtil->IsThirdPartyWindow(parentWindow, aURI, &isThirdPartyURI);
+-      if (NS_SUCCEEDED(rv) && isThirdPartyURI) {
+-        return NS_OK;
+-      }
+-    }
+-  }
+-
++  // For navigations, first check to see if we are allowed to control a
++  // window with the given URL.
++  if (!ServiceWorkerAllowedToControlWindow(aURI)) {
++    return NS_OK;
++  }
++
++  RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
++  if (!swm) {
++    return NS_OK;
++  }
++
++  // We're allowed to control a window, so check with the ServiceWorkerManager
++  // for a matching service worker.
+   nsCOMPtr<nsIPrincipal> principal =
+     BasePrincipal::CreateCodebasePrincipal(aURI, mOriginAttributes);
+   *aShouldIntercept = swm->IsAvailable(principal, aURI);
+   return NS_OK;
+ }
+ 
+ NS_IMETHODIMP
+ nsDocShell::ChannelIntercepted(nsIInterceptedChannel* aChannel)
+diff --git a/docshell/base/nsDocShell.h b/docshell/base/nsDocShell.h
+--- a/docshell/base/nsDocShell.h
++++ b/docshell/base/nsDocShell.h
+@@ -357,16 +357,27 @@ public:
+   void SetAncestorOuterWindowIDs(nsTArray<uint64_t>&& aAncestorOuterWindowIDs)
+   {
+     mAncestorOuterWindowIDs = mozilla::Move(aAncestorOuterWindowIDs);
+   }
+ 
+ private:
+   bool CanSetOriginAttributes();
+ 
++  // Determine if a service worker is allowed to control a window in this
++  // docshell with the given URL.  If there are any reasons it should not,
++  // this will return false.  If true is returned then the window *may* be
++  // controlled.  The caller must still consult either the parent controller
++  // or the ServiceWorkerManager to determine if a service worker should
++  // actually control the window.
++  //
++  // A nullptr URL is considered to be an about:blank window and will not
++  // trigger 3rd party iframe checks.
++  bool ServiceWorkerAllowedToControlWindow(nsIURI* aURI);
++
+ public:
+   const mozilla::OriginAttributes&
+   GetOriginAttributes()
+   {
+     return mOriginAttributes;
+   }
+ 
+   nsresult SetOriginAttributes(const mozilla::OriginAttributes& aAttrs);
+diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h
+--- a/dom/base/nsContentUtils.h
++++ b/dom/base/nsContentUtils.h
+@@ -2978,16 +2978,21 @@ public:
+ 
+   static bool IsNonSubresourceRequest(nsIChannel* aChannel);
+ 
+   static uint32_t CookiesBehavior()
+   {
+     return sCookiesBehavior;
+   }
+ 
++  static uint32_t CookiesLifetimePolicy()
++  {
++    return sCookiesLifetimePolicy;
++  }
++
+   // The order of these entries matters, as we use std::min for total ordering
+   // of permissions. Private Browsing is considered to be more limiting
+   // then session scoping
+   enum class StorageAccess {
+     // Don't allow access to the storage
+     eDeny = 0,
+     // Allow access to the storage, but only if it is secure to do so in a
+     // private browsing context.

+ 134 - 0
frg/work-js/mozilla-release/patches/1425975-17-59a1.patch

@@ -0,0 +1,134 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1515172223 18000
+# Node ID 14123832068511d74fc6e766feee1c5e77e28ebd
+# Parent  21a9dce1e778a7af71c4dac83c20a6b2d23b0d8e
+Bug 1425975 P17 Make web extension tests wait for service worker to activate to avoid races. r=kmag
+
+diff --git a/toolkit/components/extensions/test/mochitest/head.js b/toolkit/components/extensions/test/mochitest/head.js
+--- a/toolkit/components/extensions/test/mochitest/head.js
++++ b/toolkit/components/extensions/test/mochitest/head.js
+@@ -51,16 +51,31 @@ let Assert = {
+ function waitForLoad(win) {
+   return new Promise(resolve => {
+     win.addEventListener("load", function() {
+       resolve();
+     }, {capture: true, once: true});
+   });
+ }
+ 
++/* exported waitForState */
++
++function waitForState(sw, state) {
++  return new Promise(resolve => {
++    if (sw.state === state) {
++      return resolve();
++    }
++    sw.addEventListener("statechange", function onStateChange() {
++      if (sw.state === state) {
++        sw.removeEventListener("statechange", onStateChange);
++        resolve();
++      }
++    });
++  });
++}
+ /* exported loadChromeScript */
+ function loadChromeScript(fn) {
+   let wrapper = `
+ const {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
+ (${fn.toString()})();`;
+ 
+   return SpecialPowers.loadChromeScript(new Function(wrapper));
+ }
+diff --git a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_background_events.html b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_background_events.html
+--- a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_background_events.html
++++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_background_events.html
+@@ -55,16 +55,17 @@ add_task(async function test_webRequest_
+           {urls: ["https://example.com/*"]}
+         );
+       }
+     },
+   });
+ 
+   await extension.startup();
+   let registration = await navigator.serviceWorker.register("webrequest_worker.js", {scope: "."});
++  await waitForState(registration.installing, "activated");
+   await extension.awaitMessage("done");
+   await registration.unregister();
+   await extension.unload();
+ });
+ 
+ add_task(async function test_webRequest_background_events() {
+   let extension = ExtensionTestUtils.loadExtension({
+     manifest: {
+diff --git a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_filter.html b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_filter.html
+--- a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_filter.html
++++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_filter.html
+@@ -85,21 +85,23 @@ add_task(async function test_webRequest_
+ 
+   // We should not get events for a new window load.
+   let newWindow = window.open("file_image_good.png", "_blank", "width=100,height=100");
+   await waitForLoad(newWindow);
+   newWindow.close();
+ 
+   // We should not get background events.
+   let registration = await navigator.serviceWorker.register("webrequest_worker.js?test0", {scope: "."});
++  await waitForState(registration.installing, "activated");
+ 
+   // We should get events for the reload.
+   testWindow.location = "file_image_bad.png";
+   await extension.awaitMessage("done");
+ 
++  testWindow.location = "about:blank";
+   await registration.unregister();
+   await extension.unload();
+ });
+ 
+ add_task(async function test_webRequest_filter_tab() {
+   await SpecialPowers.pushPrefEnv({
+     set: [["dom.serviceWorkers.testing.enabled", true]],
+   });
+@@ -133,21 +135,23 @@ add_task(async function test_webRequest_
+     // We should not get events for a new window load.
+     let newWindow = window.open(img, "_blank", "width=100,height=100");
+     await waitForLoad(newWindow);
+     newWindow.close();
+   }
+ 
+   // We should not get background events.
+   let registration = await navigator.serviceWorker.register("webrequest_worker.js?test1", {scope: "."});
++  await waitForState(registration.installing, "activated");
+ 
+   // We should get events for the reload.
+   testWindow.location = img;
+   await extension.awaitMessage("done");
+ 
++  testWindow.location = "about:blank";
+   await registration.unregister();
+   await extension.unload();
+ });
+ 
+ 
+ add_task(async function test_webRequest_filter_background() {
+   await SpecialPowers.pushPrefEnv({
+     set: [["dom.serviceWorkers.testing.enabled", true]],
+@@ -181,17 +185,19 @@ add_task(async function test_webRequest_
+   extension.sendMessage("set-expected", {expect, origin: location.href});
+   await extension.awaitMessage("continue");
+ 
+   // We should not get events for a window.
+   testWindow.location = "file_image_bad.png";
+ 
+   // We should get events for the background page.
+   let registration = await navigator.serviceWorker.register(SimpleTest.getTestFileURL("webrequest_worker.js?test2"), {scope: "."});
++  await waitForState(registration.installing, "activated");
+   await extension.awaitMessage("done");
++  testWindow.location = "about:blank";
+   await registration.unregister();
+ 
+   await extension.unload();
+ });
+ 
+ add_task(async function teardown() {
+   testWindow.close();
+ });

+ 33 - 0
frg/work-js/mozilla-release/patches/1425975-18-59a1.patch

@@ -0,0 +1,33 @@
+# HG changeset patch
+# User Sebastian Hengst <archaeopteryx@coole-files.de>
+# Date 1515193852 -7200
+# Node ID b84fe2ad1ca27fc30c2e3f609b8f766185652560
+# Parent  a130c2c1923221429ac686e1a577b438cfb5a2aa
+Bug 1425975 - Fix merge bustage. r=bustage-fix a=bustage-fix
+
+diff --git a/docshell/base/nsDocShell.h.1425975-18.later b/docshell/base/nsDocShell.h.1425975-18.later
+new file mode 100644
+--- /dev/null
++++ b/docshell/base/nsDocShell.h.1425975-18.later
+@@ -0,0 +1,21 @@
++--- nsDocShell.h
+++++ nsDocShell.h
++@@ -347,18 +347,16 @@ public:
++    *
++    * This method steals the data from the passed-in array.
++    */
++   void SetAncestorOuterWindowIDs(nsTArray<uint64_t>&& aAncestorOuterWindowIDs)
++   {
++     mAncestorOuterWindowIDs = mozilla::Move(aAncestorOuterWindowIDs);
++   }
++ 
++-  bool CanSetOriginAttributes();
++-
++   // Determine if a service worker is allowed to control a window in this
++   // docshell with the given URL.  If there are any reasons it should not,
++   // this will return false.  If true is returned then the window *may* be
++   // controlled.  The caller must still consult either the parent controller
++   // or the ServiceWorkerManager to determine if a service worker should
++   // actually control the window.
++   //
++   // A nullptr URL is considered to be an about:blank window and will not

+ 66 - 0
frg/work-js/mozilla-release/patches/1425975-19-59a1.patch

@@ -0,0 +1,66 @@
+# HG changeset patch
+# User Sebastian Hengst <archaeopteryx@coole-files.de>
+# Date 1515196306 -7200
+# Node ID 9099a6ed993f0113c47c0d9e800bf0ff6e1a1dc1
+# Parent  ac43086e18c70dfce9591a4f06ecfa00ae429e8e
+Bug 1425975 - Follow-up: Move method to correct position. r=merge-fix a=merge-fix
+
+diff --git a/docshell/base/nsDocShell.h b/docshell/base/nsDocShell.h
+--- a/docshell/base/nsDocShell.h
++++ b/docshell/base/nsDocShell.h
+@@ -357,27 +357,16 @@ public:
+   void SetAncestorOuterWindowIDs(nsTArray<uint64_t>&& aAncestorOuterWindowIDs)
+   {
+     mAncestorOuterWindowIDs = mozilla::Move(aAncestorOuterWindowIDs);
+   }
+ 
+ private:
+   bool CanSetOriginAttributes();
+ 
+-  // Determine if a service worker is allowed to control a window in this
+-  // docshell with the given URL.  If there are any reasons it should not,
+-  // this will return false.  If true is returned then the window *may* be
+-  // controlled.  The caller must still consult either the parent controller
+-  // or the ServiceWorkerManager to determine if a service worker should
+-  // actually control the window.
+-  //
+-  // A nullptr URL is considered to be an about:blank window and will not
+-  // trigger 3rd party iframe checks.
+-  bool ServiceWorkerAllowedToControlWindow(nsIURI* aURI);
+-
+ public:
+   const mozilla::OriginAttributes&
+   GetOriginAttributes()
+   {
+     return mOriginAttributes;
+   }
+ 
+   nsresult SetOriginAttributes(const mozilla::OriginAttributes& aAttrs);
+@@ -845,16 +834,27 @@ protected:
+   // the principal is not provided we will attempt to inherit it when we
+   // are sure it will match what the real about:blank window principal
+   // would have been.  There are some corner cases where we cannot easily
+   // determine the correct principal and will not create the ClientSource.
+   // In these cases the initial about:blank will appear to not exist until
+   // its real document and window are created.
+   void MaybeCreateInitialClientSource(nsIPrincipal* aPrincipal = nullptr);
+ 
++  // Determine if a service worker is allowed to control a window in this
++  // docshell with the given URL.  If there are any reasons it should not,
++  // this will return false.  If true is returned then the window *may* be
++  // controlled.  The caller must still consult either the parent controller
++  // or the ServiceWorkerManager to determine if a service worker should
++  // actually control the window.
++  //
++  // A nullptr URL is considered to be an about:blank window and will not
++  // trigger 3rd party iframe checks.
++  bool ServiceWorkerAllowedToControlWindow(nsIURI* aURI);
++
+   // Return the ClientInfo for the initial about:blank window, if it exists
+   // or we have speculatively created a ClientSource in
+   // MaybeCreateInitialClientSource().  This can return a ClientInfo object
+   // even if GetExtantDoc() returns nullptr.
+   mozilla::Maybe<mozilla::dom::ClientInfo> GetInitialClientInfo() const;
+ 
+ protected:
+   nsresult GetCurScrollPos(int32_t aScrollOrientation, int32_t* aCurPos);

+ 51 - 0
frg/work-js/mozilla-release/patches/1425993-59a1.patch

@@ -0,0 +1,51 @@
+# HG changeset patch
+# User Ryan VanderMeulen <ryanvm@gmail.com>
+# Date 1515000949 18000
+# Node ID 4e60eefd78cc59b2ee913f4727e3cec2b3d2974e
+# Parent  34d0c3908556ece16a660a388ff1bf07c3311ff4
+Bug 1425993 - Make security violation events Nightly-only and update test expectations accordingly. r=smaug
+
+diff --git a/dom/tests/mochitest/general/test_interfaces.js b/dom/tests/mochitest/general/test_interfaces.js
+--- a/dom/tests/mochitest/general/test_interfaces.js
++++ b/dom/tests/mochitest/general/test_interfaces.js
+@@ -810,17 +810,17 @@ var interfaceNamesInGlobalScope =
+     "Screen",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+     "ScreenOrientation",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+     "ScriptProcessorNode",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+     "ScrollAreaEvent",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+-    "SecurityPolicyViolationEvent",
++    {name: "SecurityPolicyViolationEvent", release: false},
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+     "Selection",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+     "ServiceWorker",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+     "ServiceWorkerContainer",
+ // IMPORTANT: Do not change this list without review from a DOM peer!
+     "ServiceWorkerRegistration",
+diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js
+--- a/modules/libpref/init/all.js
++++ b/modules/libpref/init/all.js
+@@ -2627,17 +2627,17 @@ pref("security.checkloaduri", true);
+ pref("security.xpconnect.plugin.unrestricted", true);
+ // security-sensitive dialogs should delay button enabling. In milliseconds.
+ pref("security.dialog_enable_delay", 1000);
+ pref("security.notification_enable_delay", 500);
+ 
+ pref("security.csp.enable", true);
+ pref("security.csp.experimentalEnabled", false);
+ pref("security.csp.enableStrictDynamic", true);
+-#ifdef EARLY_BETA_OR_EARLIER
++#ifdef NIGHTLY_BUILD
+ pref("security.csp.enable_violation_events", true);
+ #else
+ pref("security.csp.enable_violation_events", false);
+ #endif
+ 
+ // Default Content Security Policy to apply to signed contents.
+ pref("security.signed_content.CSP.default", "script-src 'self'; style-src 'self'");
+ 

+ 51 - 0
frg/work-js/mozilla-release/patches/1426250-1-59a1.patch

@@ -0,0 +1,51 @@
+# HG changeset patch
+# User Gijs Kruitbosch <gijskruitbosch@gmail.com>
+# Date 1515187842 0
+# Node ID 462b35675a119a778f35aed836aa88b606f0babd
+# Parent  4613d73eede2733fa52f109cde033a0da87ad8ba
+Bug 1426250 - allow EventUtils to dispatch drag/mouse events through the DOM only, r=enndeakin
+
+MozReview-Commit-ID: HmdRdTQ05pm
+
+diff --git a/testing/mochitest/tests/SimpleTest/EventUtils.js b/testing/mochitest/tests/SimpleTest/EventUtils.js
+--- a/testing/mochitest/tests/SimpleTest/EventUtils.js
++++ b/testing/mochitest/tests/SimpleTest/EventUtils.js
+@@ -206,16 +206,20 @@ function sendDragEvent(aEvent, aTarget, 
+   var relatedTargetArg = aEvent.relatedTarget || null;
+   var dataTransfer     = aEvent.dataTransfer  || null;
+ 
+   event.initDragEvent(typeArg, canBubbleArg, cancelableArg, viewArg, detailArg,
+                       screenXArg, screenYArg, clientXArg, clientYArg,
+                       ctrlKeyArg, altKeyArg, shiftKeyArg, metaKeyArg,
+                       buttonArg, relatedTargetArg, dataTransfer);
+ 
++  if (aEvent._domDispatchOnly) {
++    return aTarget.dispatchEvent(event);
++  }
++
+   var utils = _getDOMWindowUtils(aWindow);
+   return utils.dispatchDOMEventViaPresShell(aTarget, event, true);
+ }
+ 
+ /**
+  * Send the char aChar to the focused element.  This method handles casing of
+  * chars (sends the right charcode, and sends a shift key for uppercase chars).
+  * No other modifiers are handled at this point.
+@@ -2129,16 +2133,17 @@ function createDragEventObject(aType, aD
+   // nsContentUtils::SetDataTransferInEvent for actual impl).
+   dataTransfer.dropEffect = aDataTransfer.dropEffect;
+ 
+   return Object.assign({
+     type: aType,
+     screenX: destScreenX, screenY: destScreenY,
+     clientX: destClientX, clientY: destClientY,
+     dataTransfer: dataTransfer,
++    _domDispatchOnly: aDragEvent._domDispatchOnly,
+   }, aDragEvent);
+ }
+ 
+ /**
+  * Emulate a event sequence of dragstart, dragenter, and dragover.
+  *
+  * @param aSrcElement   The element to use to start the drag.
+  * @param aDestElement  The element to fire the dragover, dragenter events

+ 44 - 0
frg/work-js/mozilla-release/patches/1426250-2-59a1.patch

@@ -0,0 +1,44 @@
+# HG changeset patch
+# User Gijs Kruitbosch <gijskruitbosch@gmail.com>
+# Date 1515187913 0
+# Node ID 5551470e71c346c115b9aef08b7a75e9d8607e8e
+# Parent  04c9c98098ccf6b9a4b88095c25dca253e1f5d47
+Bug 1426250 - allow changing log preference at runtime for CUI.jsm, r=jaws
+
+MozReview-Commit-ID: 9kYBC27wowg
+
+diff --git a/browser/components/customizableui/CustomizableUI.jsm b/browser/components/customizableui/CustomizableUI.jsm
+--- a/browser/components/customizableui/CustomizableUI.jsm
++++ b/browser/components/customizableui/CustomizableUI.jsm
+@@ -159,22 +159,29 @@ var gUIStateBeforeReset = {
+   drawInTitlebar: null,
+   currentTheme: null,
+   uiDensity: null,
+   autoTouchMode: null,
+ };
+ 
+ var gDefaultPanelPlacements = null;
+ 
++XPCOMUtils.defineLazyPreferenceGetter(this, "gDebuggingEnabled", kPrefCustomizationDebug, false,
++  (pref, oldVal, newVal) => {
++    if (typeof log != "undefined") {
++      log.maxLogLevel = newVal ? "all" : "log";
++    }
++  }
++);
++
+ XPCOMUtils.defineLazyGetter(this, "log", () => {
+   let scope = {};
+   ChromeUtils.import("resource://gre/modules/Console.jsm", scope);
+-  let debug = Services.prefs.getBoolPref(kPrefCustomizationDebug, false);
+   let consoleOptions = {
+-    maxLogLevel: debug ? "all" : "log",
++    maxLogLevel: gDebuggingEnabled ? "all" : "log",
+     prefix: "CustomizableUI",
+   };
+   return new scope.ConsoleAPI(consoleOptions);
+ });
+ 
+ var CustomizableUIInternal = {
+   initialize() {
+     log.debug("Initializing");

+ 406 - 0
frg/work-js/mozilla-release/patches/1426250-3-59a1.patch

@@ -0,0 +1,406 @@
+# HG changeset patch
+# User Gijs Kruitbosch <gijskruitbosch@gmail.com>
+# Date 1515188127 0
+# Node ID 21ce1e95d697cbc1bdaa45d124e55642ff5b2fd3
+# Parent  6f29e2a7b9a2d50ef649139ae1d8bd3223768bd0
+Bug 1426250 - make tests actually pass valid mouse coordinates when testing Customize Mode, r=jaws
+
+MozReview-Commit-ID: IglKedBqrWQ
+
+diff --git a/browser/components/customizableui/CustomizeMode.jsm b/browser/components/customizableui/CustomizeMode.jsm
+--- a/browser/components/customizableui/CustomizeMode.jsm
++++ b/browser/components/customizableui/CustomizeMode.jsm
+@@ -2301,20 +2301,16 @@ CustomizeMode.prototype = {
+       aElement = aElement.parentNode;
+     }
+ 
+     return null;
+   },
+ 
+   _getDragOverNode(aEvent, aAreaElement, aAreaType, aDraggedItemId) {
+     let expectedParent = aAreaElement.customizationTarget || aAreaElement;
+-    // Our tests are stupid. Cope:
+-    if (!aEvent.clientX && !aEvent.clientY) {
+-      return aEvent.target;
+-    }
+     // Offset the drag event's position with the offset to the center of
+     // the thing we're dragging
+     let dragX = aEvent.clientX - this._dragOffset.x;
+     let dragY = aEvent.clientY - this._dragOffset.y;
+ 
+     // Ensure this is within the container
+     let boundsContainer = expectedParent;
+     // NB: because the panel UI itself is inside a scrolling container, we need
+diff --git a/browser/components/customizableui/test/browser_876926_customize_mode_wrapping.js b/browser/components/customizableui/test/browser_876926_customize_mode_wrapping.js
+--- a/browser/components/customizableui/test/browser_876926_customize_mode_wrapping.js
++++ b/browser/components/customizableui/test/browser_876926_customize_mode_wrapping.js
+@@ -10,53 +10,60 @@ const kPanel = CustomizableUI.AREA_PANEL
+ const kToolbar = CustomizableUI.AREA_NAVBAR;
+ const kVisiblePalette = "customization-palette";
+ const kPlaceholderClass = "panel-customization-placeholder";
+ 
+ function checkWrapper(id) {
+   is(document.querySelectorAll("#wrapper-" + id).length, 1, "There should be exactly 1 wrapper for " + id + " in the customizing window.");
+ }
+ 
+-async function ensureVisibleIfInPalette(node) {
+-    if (node.parentNode.parentNode == gNavToolbox.palette) {
+-      node.scrollIntoView();
+-      window.QueryInterface(Ci.nsIInterfaceRequestor);
+-      let dwu = window.getInterface(Ci.nsIDOMWindowUtils);
+-      await BrowserTestUtils.waitForCondition(() => {
+-        let nodeBounds = dwu.getBoundsWithoutFlushing(node);
+-        let paletteBounds = dwu.getBoundsWithoutFlushing(gNavToolbox.palette);
+-        return nodeBounds.top >= paletteBounds.top && nodeBounds.bottom <= paletteBounds.bottom;
+-      });
++async function ensureVisible(node) {
++  let isInPalette = node.parentNode.parentNode == gNavToolbox.palette;
++  if (isInPalette) {
++    node.scrollIntoView();
++  }
++  window.QueryInterface(Ci.nsIInterfaceRequestor);
++  let dwu = window.getInterface(Ci.nsIDOMWindowUtils);
++  await BrowserTestUtils.waitForCondition(() => {
++    let nodeBounds = dwu.getBoundsWithoutFlushing(node);
++    if (isInPalette) {
++      let paletteBounds = dwu.getBoundsWithoutFlushing(gNavToolbox.palette);
++      if (!(nodeBounds.top >= paletteBounds.top && nodeBounds.bottom <= paletteBounds.bottom)) {
++        return false;
++      }
+     }
++    return nodeBounds.height && nodeBounds.width;
++  });
+ }
+ 
+ var move = {
+   "drag": async function(id, target) {
+     let targetNode = document.getElementById(target);
+     if (targetNode.customizationTarget) {
+       targetNode = targetNode.customizationTarget;
+     }
+     let nodeToMove = document.getElementById(id);
+-    await ensureVisibleIfInPalette(nodeToMove);
+-    simulateItemDrag(nodeToMove, targetNode);
++    await ensureVisible(nodeToMove);
++
++    simulateItemDrag(nodeToMove, targetNode, "end");
+   },
+   "dragToItem": async function(id, target) {
+     let targetNode = document.getElementById(target);
+     if (targetNode.customizationTarget) {
+       targetNode = targetNode.customizationTarget;
+     }
+     let items = targetNode.querySelectorAll("toolbarpaletteitem:not(." + kPlaceholderClass + ")");
+     if (target == kPanel) {
+       targetNode = items[items.length - 1];
+     } else {
+       targetNode = items[0];
+     }
+     let nodeToMove = document.getElementById(id);
+-    await ensureVisibleIfInPalette(nodeToMove);
+-    simulateItemDrag(nodeToMove, targetNode);
++    await ensureVisible(nodeToMove);
++    simulateItemDrag(nodeToMove, targetNode, "start");
+   },
+   "API": function(id, target) {
+     if (target == kVisiblePalette) {
+       return CustomizableUI.removeWidgetFromArea(id);
+     }
+     return CustomizableUI.addWidgetToArea(id, target, null);
+   }
+ };
+@@ -146,16 +153,21 @@ async function checkPanel(id, method) {
+ }
+ 
+ async function checkPalette(id, method) {
+   // Move back to palette:
+   await move[method](id, kVisiblePalette);
+   ok(CustomizableUI.inDefaultState, "Should end in default state");
+   let visibleChildren = gCustomizeMode.visiblePalette.children;
+   let expectedChild = method == "dragToItem" ? visibleChildren[0] : visibleChildren[visibleChildren.length - 1];
++  // Items dragged to the end of the palette should be the final item. That they're the penultimate
++  // item when dragged is tracked in bug 1395950. Once that's fixed, this hack can be removed.
++  if (method == "drag") {
++    expectedChild = expectedChild.previousElementSibling;
++  }
+   is(expectedChild.firstChild.id, id, "Widget " + id + " was moved using " + method + " and should now be wrapped in palette in customizing window.");
+   if (id == kXULWidgetId) {
+     ok(otherWin.gNavToolbox.palette.querySelector("#" + id), "Widget " + id + " should be in invisible palette in other window.");
+   }
+   checkWrapper(id);
+ }
+ 
+ // This test needs a XUL button that's in the palette by default. No such
+diff --git a/browser/components/customizableui/test/browser_878452_drag_to_panel.js b/browser/components/customizableui/test/browser_878452_drag_to_panel.js
+--- a/browser/components/customizableui/test/browser_878452_drag_to_panel.js
++++ b/browser/components/customizableui/test/browser_878452_drag_to_panel.js
+@@ -10,17 +10,17 @@ add_task(async function() {
+   await startCustomizing();
+   let btn = document.getElementById("feed-button");
+   let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
+ 
+   let lastButtonIndex = placements.length - 1;
+   let lastButton = placements[lastButtonIndex];
+   let placementsAfterInsert = placements.slice(0, lastButtonIndex).concat(["feed-button", lastButton]);
+   let lastButtonNode = document.getElementById(lastButton);
+-  simulateItemDrag(btn, lastButtonNode);
++  simulateItemDrag(btn, lastButtonNode, "start");
+   assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterInsert);
+   ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
+   let palette = document.getElementById("customization-palette");
+   simulateItemDrag(btn, palette);
+   ok(CustomizableUI.inDefaultState, "Should be in default state again.");
+ });
+ 
+ // Dragging an item from the palette to the panel itself should also work.
+@@ -55,12 +55,13 @@ add_task(async function() {
+   simulateItemDrag(btn, panel);
+   assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterAppend);
+   ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
+   let palette = document.getElementById("customization-palette");
+   simulateItemDrag(btn, palette);
+   assertAreaPlacements(panel.id, []);
+ });
+ 
+-add_task(async function asyncCleanup() {
++registerCleanupFunction(async function asyncCleanup() {
++  CustomizableUI.destroyWidget("cui-panel-item-to-drag-to");
+   await endCustomizing();
+   await resetCustomization();
+ });
+diff --git a/browser/components/customizableui/test/browser_918049_skipintoolbarset_dnd.js b/browser/components/customizableui/test/browser_918049_skipintoolbarset_dnd.js
+--- a/browser/components/customizableui/test/browser_918049_skipintoolbarset_dnd.js
++++ b/browser/components/customizableui/test/browser_918049_skipintoolbarset_dnd.js
+@@ -13,23 +13,24 @@ add_task(async function() {
+   skippedItem = document.createElement("toolbarbutton");
+   skippedItem.id = "test-skipintoolbarset-item";
+   skippedItem.setAttribute("label", "Test");
+   skippedItem.setAttribute("skipintoolbarset", "true");
+   skippedItem.setAttribute("removable", "true");
+   navbar.customizationTarget.appendChild(skippedItem);
+   let downloadsButton = document.getElementById("downloads-button");
+   await startCustomizing();
++  await waitForElementShown(skippedItem);
+   ok(CustomizableUI.inDefaultState, "Should still be in default state");
+-  simulateItemDrag(skippedItem, downloadsButton);
++  simulateItemDrag(skippedItem, downloadsButton, "start");
+   ok(CustomizableUI.inDefaultState, "Should still be in default state");
+   let skippedItemWrapper = skippedItem.parentNode;
+   is(skippedItemWrapper.nextSibling && skippedItemWrapper.nextSibling.id,
+      downloadsButton.parentNode.id, "Should be next to downloads button");
+-  simulateItemDrag(downloadsButton, skippedItem);
++  simulateItemDrag(downloadsButton, skippedItem, "start");
+   let downloadWrapper = downloadsButton.parentNode;
+   is(downloadWrapper.nextSibling && downloadWrapper.nextSibling.id,
+      skippedItem.parentNode.id, "Should be next to skipintoolbarset item");
+   ok(CustomizableUI.inDefaultState, "Should still be in default state");
+ });
+ 
+ add_task(async function asyncCleanup() {
+   await endCustomizing();
+diff --git a/browser/components/customizableui/test/browser_968565_insert_before_hidden_items.js b/browser/components/customizableui/test/browser_968565_insert_before_hidden_items.js
+--- a/browser/components/customizableui/test/browser_968565_insert_before_hidden_items.js
++++ b/browser/components/customizableui/test/browser_968565_insert_before_hidden_items.js
+@@ -40,17 +40,17 @@ add_task(async function() {
+   // Make sure we have some hidden items at the end of the nav-bar.
+   navbar.insertItem(hidden1.id);
+   navbar.insertItem(hidden2.id);
+ 
+   // Drag an item and drop it onto the nav-bar customization target, but
+   // not over a particular item.
+   await startCustomizing();
+   let downloadsButton = document.getElementById("downloads-button");
+-  simulateItemDrag(downloadsButton, navbar.customizationTarget);
++  simulateItemDrag(downloadsButton, navbar.customizationTarget, "end");
+ 
+   await endCustomizing();
+ 
+   is(downloadsButton.previousSibling.id, lastVisible.id,
+      "The downloads button should be placed after the last visible item.");
+ 
+   await resetCustomization();
+ });
+diff --git a/browser/components/customizableui/test/browser_newtab_button_customizemode.js.1426250-3.later b/browser/components/customizableui/test/browser_newtab_button_customizemode.js.1426250-3.later
+new file mode 100644
+--- /dev/null
++++ b/browser/components/customizableui/test/browser_newtab_button_customizemode.js.1426250-3.later
+@@ -0,0 +1,79 @@
++--- browser_newtab_button_customizemode.js
+++++ browser_newtab_button_customizemode.js
++@@ -27,46 +27,49 @@ function assertNewTabButton(which) {
++   }
++ }
++ 
++ /**
++  * Add and remove items *after* the new tab button in customize mode.
++  */
++ add_task(async function addremove_after_newtab_customizemode() {
++   await startCustomizing();
++-  simulateItemDrag(document.getElementById("home-button"),
++-                   kGlobalNewTabButton.parentNode.nextElementSibling);
+++  await waitForElementShown(kGlobalNewTabButton);
+++  simulateItemDrag(document.getElementById("home-button"), kGlobalNewTabButton, "end");
++   ok(gBrowser.tabContainer.hasAttribute("hasadjacentnewtabbutton"),
++     "tabs should have the adjacent newtab attribute");
++   await endCustomizing();
++   assertNewTabButton("inner");
++ 
++   await startCustomizing();
++-  simulateItemDrag(document.getElementById("home-button"),
++-    document.getElementById("stop-reload-button").parentNode.nextElementSibling);
+++  let dropTarget = document.getElementById("stop-reload-button");
+++  await waitForElementShown(dropTarget);
+++  simulateItemDrag(document.getElementById("home-button"), dropTarget, "end");
++   ok(gBrowser.tabContainer.hasAttribute("hasadjacentnewtabbutton"),
++     "tabs should still have the adjacent newtab attribute");
++   await endCustomizing();
++   assertNewTabButton("inner");
++   ok(CustomizableUI.inDefaultState, "Should be in default state");
++ });
++ 
++ /**
++  * Add and remove items *before* the new tab button in customize mode.
++  */
++ add_task(async function addremove_before_newtab_customizemode() {
++   await startCustomizing();
++-  simulateItemDrag(document.getElementById("home-button"), kGlobalNewTabButton);
+++  await waitForElementShown(kGlobalNewTabButton);
+++  simulateItemDrag(document.getElementById("home-button"), kGlobalNewTabButton, "start");
++   ok(!gBrowser.tabContainer.hasAttribute("hasadjacentnewtabbutton"),
++     "tabs should no longer have the adjacent newtab attribute");
++   await endCustomizing();
++   assertNewTabButton("global");
++   await startCustomizing();
++-  simulateItemDrag(document.getElementById("home-button"),
++-    document.getElementById("stop-reload-button").parentNode.nextElementSibling);
+++  let dropTarget = document.getElementById("stop-reload-button");
+++  await waitForElementShown(dropTarget);
+++  simulateItemDrag(document.getElementById("home-button"), dropTarget, "end");
++   ok(gBrowser.tabContainer.hasAttribute("hasadjacentnewtabbutton"),
++     "tabs should have the adjacent newtab attribute again");
++   await endCustomizing();
++   assertNewTabButton("inner");
++   ok(CustomizableUI.inDefaultState, "Should be in default state");
++ });
++ 
++ /**
++@@ -105,17 +108,18 @@ add_task(async function addremove_before
++   ok(CustomizableUI.inDefaultState, "Should be in default state");
++ });
++ 
++ /**
++   * Reset to defaults in customize mode to see if that doesn't break things.
++   */
++ add_task(async function reset_before_newtab_customizemode() {
++   await startCustomizing();
++-  simulateItemDrag(document.getElementById("home-button"), kGlobalNewTabButton);
+++  await waitForElementShown(kGlobalNewTabButton);
+++  simulateItemDrag(document.getElementById("home-button"), kGlobalNewTabButton, "start");
++   ok(!gBrowser.tabContainer.hasAttribute("hasadjacentnewtabbutton"),
++     "tabs should no longer have the adjacent newtab attribute");
++   await endCustomizing();
++   assertNewTabButton("global");
++   await startCustomizing();
++   await gCustomizeMode.reset();
++   ok(gBrowser.tabContainer.hasAttribute("hasadjacentnewtabbutton"),
++     "tabs should have the adjacent newtab attribute again");
+diff --git a/browser/components/customizableui/test/browser_remove_customized_specials.js.1426250-3.later b/browser/components/customizableui/test/browser_remove_customized_specials.js.1426250-3.later
+new file mode 100644
+--- /dev/null
++++ b/browser/components/customizableui/test/browser_remove_customized_specials.js.1426250-3.later
+@@ -0,0 +1,25 @@
++--- browser_remove_customized_specials.js
+++++ browser_remove_customized_specials.js
++@@ -10,19 +10,20 @@ add_task(async function() {
++   await startCustomizing();
++   CustomizableUI.addWidgetToArea("spring", "nav-bar", 5);
++   await gCustomizeMode.reset();
++   let springs = document.querySelectorAll("#nav-bar toolbarspring");
++   let lastSpring = springs[springs.length - 1];
++   let expectedPlacements = CustomizableUI.getWidgetIdsInArea("nav-bar");
++   info("Placements before drag: " + expectedPlacements.join(","));
++   let lastItem = document.getElementById(expectedPlacements[expectedPlacements.length - 1]);
++-  simulateItemDrag(lastSpring, lastItem);
+++  await waitForElementShown(lastItem);
+++  simulateItemDrag(lastSpring, lastItem, "end");
++   expectedPlacements.splice(expectedPlacements.indexOf(lastSpring.id), 1);
++-  expectedPlacements.splice(expectedPlacements.length - 1, 0, lastSpring.id);
+++  expectedPlacements.push(lastSpring.id);
++   let actualPlacements = CustomizableUI.getWidgetIdsInArea("nav-bar");
++   // Log these separately because Assert.deepEqual truncates the stringified versions...
++   info("Actual placements: " + actualPlacements.join(","));
++   info("Expected placements: " + expectedPlacements.join(","));
++   Assert.deepEqual(expectedPlacements, actualPlacements, "Should be able to move spring");
++   await gCustomizeMode.reset();
++   await endCustomizing();
++ });
+diff --git a/browser/components/customizableui/test/head.js b/browser/components/customizableui/test/head.js
+--- a/browser/components/customizableui/test/head.js
++++ b/browser/components/customizableui/test/head.js
+@@ -190,18 +190,32 @@ function todoAssertAreaPlacements(areaId
+   todo(isPassing, "The area placements for " + areaId +
+                   " should equal the expected placements.");
+ }
+ 
+ function getAreaWidgetIds(areaId) {
+   return CustomizableUI.getWidgetIdsInArea(areaId);
+ }
+ 
+-function simulateItemDrag(aToDrag, aTarget) {
+-  synthesizeDrop(aToDrag.parentNode, aTarget);
++function simulateItemDrag(aToDrag, aTarget, aEvent = {}) {
++  let ev = aEvent;
++  if (ev == "end" || ev == "start") {
++    let win = aTarget.ownerGlobal;
++    win.QueryInterface(Ci.nsIInterfaceRequestor);
++    const dwu = win.getInterface(Ci.nsIDOMWindowUtils);
++    let bounds = dwu.getBoundsWithoutFlushing(aTarget);
++    if (ev == "end") {
++      ev = {clientX: bounds.right - 2, clientY: bounds.bottom - 2};
++    } else {
++      ev = {clientX: bounds.left + 2, clientY: bounds.top + 2};
++    }
++  }
++  ev._domDispatchOnly = true;
++  synthesizeDrop(aToDrag.parentNode, aTarget, null, null,
++                 aToDrag.ownerGlobal, aTarget.ownerGlobal, ev);
+ }
+ 
+ function endCustomizing(aWindow = window) {
+   if (aWindow.document.documentElement.getAttribute("customizing") != "true") {
+     return true;
+   }
+   return new Promise(resolve => {
+     function onCustomizationEnds() {
+diff --git a/browser/components/customizableui/test/head.js.1426250-3.later b/browser/components/customizableui/test/head.js.1426250-3.later
+new file mode 100644
+--- /dev/null
++++ b/browser/components/customizableui/test/head.js.1426250-3.later
+@@ -0,0 +1,27 @@
++--- head.js
+++++ head.js
++@@ -492,17 +506,21 @@ function checkContextMenu(aContextMenu, 
++       is(menuItemDisabled, !aExpectedEntries[i][1], "disabled state for " + selector);
++     } catch (e) {
++       ok(false, "Exception when checking context menu: " + e);
++     }
++   }
++ }
++ 
++ function waitForOverflowButtonShown(win = window) {
+++  let ov = win.document.getElementById("nav-bar-overflow-button");
+++  let icon = win.document.getAnonymousElementByAttribute(ov, "class", "toolbarbutton-icon");
+++  return waitForElementShown(icon);
+++}
+++function waitForElementShown(element) {
+++  let win = element.ownerGlobal;
++   let dwu = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
++   return BrowserTestUtils.waitForCondition(() => {
++     info("Waiting for overflow button to have non-0 size");
++-    let ov = win.document.getElementById("nav-bar-overflow-button");
++-    let icon = win.document.getAnonymousElementByAttribute(ov, "class", "toolbarbutton-icon");
++-    let bounds = dwu.getBoundsWithoutFlushing(icon);
+++    let bounds = dwu.getBoundsWithoutFlushing(element);
++     return bounds.width > 0 && bounds.height > 0;
++   });
++ }

+ 151 - 0
frg/work-js/mozilla-release/patches/1426253-1-59a1.patch

@@ -0,0 +1,151 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1513785198 18000
+# Node ID c7fb331fc914d60e9847e1e6bb69468355d0bb93
+# Parent  b8380e17e90aa8c45d3c0cc76429aa1d0a585d8b
+Bug 1426253 P1 Expose nsIDocument GetClientInfo(), GetClientState(), and GetController(). r=asuth
+
+diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp
+--- a/dom/base/nsDocument.cpp
++++ b/dom/base/nsDocument.cpp
+@@ -209,29 +209,32 @@
+ 
+ #include "imgILoader.h"
+ #include "imgRequestProxy.h"
+ #include "nsWrapperCacheInlines.h"
+ #include "nsSandboxFlags.h"
+ #include "mozilla/dom/AnimatableBinding.h"
+ #include "mozilla/dom/AnonymousContent.h"
+ #include "mozilla/dom/BindingUtils.h"
++#include "mozilla/dom/ClientInfo.h"
++#include "mozilla/dom/ClientState.h"
+ #include "mozilla/dom/DocumentFragment.h"
+ #include "mozilla/dom/DocumentTimeline.h"
+ #include "mozilla/dom/Event.h"
+ #include "mozilla/dom/HTMLBodyElement.h"
+ #include "mozilla/dom/HTMLInputElement.h"
+ #include "mozilla/dom/ImageTracker.h"
+ #include "mozilla/dom/MediaQueryList.h"
+ #include "mozilla/dom/NodeFilterBinding.h"
+ #include "mozilla/OwningNonNull.h"
+ #include "mozilla/dom/TabChild.h"
+ #include "mozilla/dom/WebComponentsBinding.h"
+ #include "mozilla/dom/CustomElementRegistryBinding.h"
+ #include "mozilla/dom/CustomElementRegistry.h"
++#include "mozilla/dom/ServiceWorkerDescriptor.h"
+ #include "mozilla/dom/TimeoutManager.h"
+ #include "mozilla/ExtensionPolicyService.h"
+ #include "nsFrame.h"
+ #include "nsDOMCaretPosition.h"
+ #include "nsViewportInfo.h"
+ #include "mozilla/StaticPtr.h"
+ #include "nsITextControlElement.h"
+ #include "nsIDOMNSEditableElement.h"
+@@ -5664,16 +5667,46 @@ nsIDocument::GetAnonRootIfInAnonymousCon
+       return child->IsElement() ? child->AsElement() : nullptr;
+     }
+     child = parent;
+     parent = child->GetParentNode();
+   }
+   return nullptr;
+ }
+ 
++Maybe<ClientInfo>
++nsIDocument::GetClientInfo() const
++{
++  nsPIDOMWindowInner* inner = GetInnerWindow();
++  if (inner) {
++    return Move(inner->GetClientInfo());
++  }
++  return Move(Maybe<ClientInfo>());
++}
++
++Maybe<ClientState>
++nsIDocument::GetClientState() const
++{
++  nsPIDOMWindowInner* inner = GetInnerWindow();
++  if (inner) {
++    return Move(inner->GetClientState());
++  }
++  return Move(Maybe<ClientState>());
++}
++
++Maybe<ServiceWorkerDescriptor>
++nsIDocument::GetController() const
++{
++  nsPIDOMWindowInner* inner = GetInnerWindow();
++  if (inner) {
++    return Move(inner->GetController());
++  }
++  return Move(Maybe<ServiceWorkerDescriptor>());
++}
++
+ //
+ // nsIDOMDocument interface
+ //
+ DocumentType*
+ nsIDocument::GetDoctype() const
+ {
+   for (nsIContent* child = GetFirstChild();
+        child;
+diff --git a/dom/base/nsIDocument.h b/dom/base/nsIDocument.h
+--- a/dom/base/nsIDocument.h
++++ b/dom/base/nsIDocument.h
+@@ -124,16 +124,18 @@ class ImageLoader;
+ class Rule;
+ } // namespace css
+ 
+ namespace dom {
+ class Animation;
+ class AnonymousContent;
+ class Attr;
+ class BoxObject;
++class ClientInfo;
++class ClientState;
+ class CDATASection;
+ class Comment;
+ struct CustomElementDefinition;
+ class DocGroup;
+ class DocumentFragment;
+ class DocumentTimeline;
+ class DocumentType;
+ class DOMImplementation;
+@@ -157,16 +159,17 @@ class NodeFilter;
+ class NodeIterator;
+ enum class OrientationType : uint8_t;
+ class ProcessingInstruction;
+ class Promise;
+ class ResizeObserver;
+ class ResizeObserverController;
+ class ScriptLoader;
+ class Selection;
++class ServiceWorkerDescriptor;
+ class StyleSheetList;
+ class SVGDocument;
+ class SVGSVGElement;
+ class Touch;
+ class TouchList;
+ class TreeWalker;
+ class XPathEvaluator;
+ class XPathExpression;
+@@ -1084,16 +1087,20 @@ public:
+    */
+   virtual void ScheduleSVGForPresAttrEvaluation(nsSVGElement* aSVG) = 0;
+   // Unschedule an element scheduled by ScheduleFrameRequestCallback (e.g. for when it is destroyed)
+   virtual void UnscheduleSVGForPresAttrEvaluation(nsSVGElement* aSVG) = 0;
+ 
+   // Resolve all SVG pres attrs scheduled in ScheduleSVGForPresAttrEvaluation
+   virtual void ResolveScheduledSVGPresAttrs() = 0;
+ 
++  mozilla::Maybe<mozilla::dom::ClientInfo> GetClientInfo() const;
++  mozilla::Maybe<mozilla::dom::ClientState> GetClientState() const;
++  mozilla::Maybe<mozilla::dom::ServiceWorkerDescriptor> GetController() const;
++
+ protected:
+   virtual Element *GetRootElementInternal() const = 0;
+ 
+   void SetPageUnloadingEventTimeStamp()
+   {
+     MOZ_ASSERT(!mPageUnloadingEventTimeStamp);
+     mPageUnloadingEventTimeStamp = mozilla::TimeStamp::NowLoRes();
+   }

+ 155 - 0
frg/work-js/mozilla-release/patches/1426253-2-59a1.patch

@@ -0,0 +1,155 @@
+# HG changeset patch
+# User Ben Kelly <ben@wanderview.com>
+# Date 1513785198 18000
+# Node ID 5b676a0c9383eaa52ea203500daada8e3051d367
+# Parent  970726601fc228517f31e3c2422ca8b9c36f1d14
+Bug 1426253 P2 Use nsIDocument::GetClientInfo() where possible. r=asuth
+
+diff --git a/dom/clients/manager/ClientOpenWindowUtils.cpp b/dom/clients/manager/ClientOpenWindowUtils.cpp
+--- a/dom/clients/manager/ClientOpenWindowUtils.cpp
++++ b/dom/clients/manager/ClientOpenWindowUtils.cpp
+@@ -75,25 +75,18 @@ public:
+     nsresult rv = securityManager->CheckSameOriginURI(doc->GetOriginalURI(),
+                                                       mBaseURI, false);
+     if (NS_FAILED(rv)) {
+       mPromise->Resolve(NS_OK, __func__);
+       mPromise = nullptr;
+       return NS_OK;
+     }
+ 
+-    nsPIDOMWindowInner* innerWindow = doc->GetInnerWindow();
+-    if (NS_WARN_IF(!innerWindow)) {
+-      mPromise->Reject(NS_ERROR_FAILURE, __func__);
+-      mPromise = nullptr;
+-      return NS_OK;
+-    }
+-
+-    Maybe<ClientInfo> info = innerWindow->GetClientInfo();
+-    Maybe<ClientState> state = innerWindow->GetClientState();
++    Maybe<ClientInfo> info(doc->GetClientInfo());
++    Maybe<ClientState> state(doc->GetClientState());
+ 
+     if (NS_WARN_IF(info.isNothing() || state.isNothing())) {
+       mPromise->Reject(NS_ERROR_FAILURE, __func__);
+       mPromise = nullptr;
+       return NS_OK;
+     }
+ 
+     mPromise->Resolve(ClientInfoAndState(info.ref().ToIPC(), state.ref().ToIPC()),
+diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp
+--- a/dom/workers/ServiceWorkerManager.cpp
++++ b/dom/workers/ServiceWorkerManager.cpp
+@@ -2366,25 +2366,22 @@ ServiceWorkerManager::StartControllingAD
+   aRegistration->StartControllingADocument();
+   mControlledDocuments.Put(aDoc, aRegistration);
+ 
+   // Mark the document's ClientSource as controlled using the ClientHandle
+   // interface.  While we could get at the ClientSource directly from the
+   // document here, our goal is to move ServiceWorkerManager to a separate
+   // process.  Using the ClientHandle supports this remote operation.
+   ServiceWorkerInfo* activeWorker = aRegistration->GetActive();
+-  nsPIDOMWindowInner* innerWindow = aDoc->GetInnerWindow();
+-  if (activeWorker && innerWindow) {
+-    Maybe<ClientInfo> clientInfo = innerWindow->GetClientInfo();
+-    if (clientInfo.isSome()) {
+-      RefPtr<ClientHandle> clientHandle =
+-        ClientManager::CreateHandle(clientInfo.ref(),
+-                                    SystemGroup::EventTargetFor(TaskCategory::Other));
+-      ref = Move(clientHandle->Control(activeWorker->Descriptor()));
+-    }
++  Maybe<ClientInfo> clientInfo = aDoc->GetClientInfo();
++  if (activeWorker && clientInfo.isSome()) {
++    RefPtr<ClientHandle> clientHandle =
++      ClientManager::CreateHandle(clientInfo.ref(),
++                                  SystemGroup::EventTargetFor(TaskCategory::Other));
++    ref = Move(clientHandle->Control(activeWorker->Descriptor()));
+   }
+ 
+   Telemetry::Accumulate(Telemetry::SERVICE_WORKER_CONTROLLED_DOCUMENTS, 1);
+   return Move(ref);
+ }
+ 
+ void
+ ServiceWorkerManager::StopControllingADocument(ServiceWorkerRegistrationInfo* aRegistration)
+@@ -3267,45 +3264,41 @@ ServiceWorkerManager::SetSkipWaitingFlag
+ void
+ ServiceWorkerManager::UpdateClientControllers(ServiceWorkerRegistrationInfo* aRegistration)
+ {
+   AssertIsOnMainThread();
+ 
+   RefPtr<ServiceWorkerInfo> activeWorker = aRegistration->GetActive();
+   MOZ_DIAGNOSTIC_ASSERT(activeWorker);
+ 
+-  AutoTArray<nsCOMPtr<nsPIDOMWindowInner>, 16> innerWindows;
++  AutoTArray<nsCOMPtr<nsIDocument>, 16> docList;
+   for (auto iter = mControlledDocuments.Iter(); !iter.Done(); iter.Next()) {
+     if (iter.UserData() != aRegistration) {
+       continue;
+     }
+ 
+     nsCOMPtr<nsIDocument> doc = do_QueryInterface(iter.Key());
+     if (NS_WARN_IF(!doc)) {
+       continue;
+     }
+ 
+-    nsPIDOMWindowInner* innerWindow = doc->GetInnerWindow();
+-    if (NS_WARN_IF(!innerWindow)) {
+-      continue;
+-    }
+-
+-    innerWindows.AppendElement(innerWindow);
++    docList.AppendElement(doc.forget());
+   }
+ 
+   // Fire event after iterating mControlledDocuments is done to prevent
+   // modification by reentering from the event handlers during iteration.
+-  for (auto& innerWindow : innerWindows) {
+-    Maybe<ClientInfo> clientInfo = innerWindow->GetClientInfo();
+-    if (clientInfo.isSome()) {
+-      RefPtr<ClientHandle> clientHandle =
+-        ClientManager::CreateHandle(clientInfo.ref(),
+-                                    innerWindow->EventTargetFor(TaskCategory::Other));
+-      clientHandle->Control(activeWorker->Descriptor());
++  for (auto& doc : docList) {
++    Maybe<ClientInfo> clientInfo = doc->GetClientInfo();
++    if (clientInfo.isNothing()) {
++      continue;
+     }
++    RefPtr<ClientHandle> clientHandle =
++      ClientManager::CreateHandle(clientInfo.ref(),
++                                  SystemGroup::EventTargetFor(TaskCategory::Other));
++    clientHandle->Control(activeWorker->Descriptor());
+   }
+ }
+ 
+ already_AddRefed<ServiceWorkerRegistrationInfo>
+ ServiceWorkerManager::GetRegistration(nsIPrincipal* aPrincipal,
+                                       const nsACString& aScope) const
+ {
+   MOZ_ASSERT(aPrincipal);
+diff --git a/netwerk/base/LoadInfo.cpp b/netwerk/base/LoadInfo.cpp
+--- a/netwerk/base/LoadInfo.cpp
++++ b/netwerk/base/LoadInfo.cpp
+@@ -107,21 +107,17 @@ LoadInfo::LoadInfo(nsIPrincipal* aLoadin
+       (mSecurityFlags & nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL);
+     mSecurityFlags &= ~nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
+   }
+ 
+   if (aLoadingContext) {
+     // Ensure that all network requests for a window client have the ClientInfo
+     // properly set.
+     // TODO: The ClientInfo is not set properly for worker initiated requests yet.
+-    nsCOMPtr<nsPIDOMWindowInner> contextInner =
+-      aLoadingContext->OwnerDoc()->GetInnerWindow();
+-    if (contextInner) {
+-      mClientInfo = contextInner->GetClientInfo();
+-    }
++    mClientInfo = aLoadingContext->OwnerDoc()->GetClientInfo();
+ 
+     nsCOMPtr<nsPIDOMWindowOuter> contextOuter = aLoadingContext->OwnerDoc()->GetWindow();
+     if (contextOuter) {
+       ComputeIsThirdPartyContext(contextOuter);
+       mOuterWindowID = contextOuter->WindowID();
+       nsCOMPtr<nsPIDOMWindowOuter> parent = contextOuter->GetScriptableParent();
+       mParentOuterWindowID = parent ? parent->WindowID() : mOuterWindowID;
+       mTopOuterWindowID = FindTopOuterWindowID(contextOuter);

Some files were not shown because too many files changed in this diff