Merge inbound to mozilla-central. a=merge
authorMargareta Eliza Balazs <ebalazs@mozilla.com>
Mon, 27 Aug 2018 12:40:58 +0300
changeset 491193 768eef11f5ff119fc3b63221e326db16986900cb
parent 491192 c6a80a1c10a1c72f525ecc1bd94b72b5a66d9887 (current diff)
parent 491191 c09a146819d3a65127cee966b326efa9ee8ea4aa (diff)
child 491194 4084eef2130fb02b6aa4d197b6058b9f8c3acf7d
child 491205 68454690d5b06835f541ef2477a5844e875be3c2
child 491227 18c3eb3a78e5f24625bf5a5e13953c7ec977e10b
push id1815
push userffxbld-merge
push dateMon, 15 Oct 2018 10:40:45 +0000
treeherdermozilla-release@18d4c09e9378 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone63.0a1
first release with
nightly linux32
768eef11f5ff / 63.0a1 / 20180827100129 / files
nightly linux64
768eef11f5ff / 63.0a1 / 20180827100129 / files
nightly mac
768eef11f5ff / 63.0a1 / 20180827100129 / files
nightly win32
768eef11f5ff / 63.0a1 / 20180827100129 / files
nightly win64
768eef11f5ff / 63.0a1 / 20180827100129 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
--- a/browser/actors/NetErrorChild.jsm
+++ b/browser/actors/NetErrorChild.jsm
@@ -37,16 +37,17 @@ const SEC_ERROR_EXPIRED_ISSUER_CERTIFICA
 const SEC_ERROR_CA_CERT_INVALID                    = SEC_ERROR_BASE + 36;
 const SEC_ERROR_OCSP_FUTURE_RESPONSE               = SEC_ERROR_BASE + 131;
 const SEC_ERROR_OCSP_OLD_RESPONSE                  = SEC_ERROR_BASE + 132;
 const SEC_ERROR_REUSED_ISSUER_AND_SERIAL           = SEC_ERROR_BASE + 138;
 const SEC_ERROR_OCSP_INVALID_SIGNING_CERT          = SEC_ERROR_BASE + 144;
 const SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED  = SEC_ERROR_BASE + 176;
 const MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE = MOZILLA_PKIX_ERROR_BASE + 5;
 const MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE = MOZILLA_PKIX_ERROR_BASE + 6;
+const MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED = MOZILLA_PKIX_ERROR_BASE + 13;
 const MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT          = MOZILLA_PKIX_ERROR_BASE + 14;
 const MOZILLA_PKIX_ERROR_MITM_DETECTED             = MOZILLA_PKIX_ERROR_BASE + 15;
 
 
 const SSL_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SSL_ERROR_BASE;
 const SSL_ERROR_BAD_CERT_DOMAIN = SSL_ERROR_BASE + 12;
 const SSL_ERROR_SSL_DISABLED  = SSL_ERROR_BASE + 20;
 const SSL_ERROR_SSL2_DISABLED  = SSL_ERROR_BASE + 14;
@@ -141,16 +142,21 @@ class NetErrorChild extends ActorChild {
           msg1 += gPipNSSBundle.GetStringFromName("certErrorTrust_SignatureAlgorithmDisabled") + "\n";
           break;
         case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
           msg1 += gPipNSSBundle.GetStringFromName("certErrorTrust_ExpiredIssuer") + "\n";
           break;
         case MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT:
           msg1 += gPipNSSBundle.GetStringFromName("certErrorTrust_SelfSigned") + "\n";
           break;
+        // This error code currently only exists for the Symantec distrust, we may need to adjust
+        // it to fit other distrusts later.
+        case MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED:
+          msg1 += gPipNSSBundle.formatStringFromName("certErrorTrust_Symantec", [hostString], 1) + "\n";
+          break;
         default:
           msg1 += gPipNSSBundle.GetStringFromName("certErrorTrust_Untrusted") + "\n";
       }
     }
 
     technicalInfo.appendChild(doc.createTextNode(msg1));
 
     if (input.data.isDomainMismatch) {
@@ -360,16 +366,36 @@ class NetErrorChild extends ActorChild {
         }
         if (est) {
           // eslint-disable-next-line no-unsanitized/property
           est.innerHTML = errWhatToDoTitle.innerHTML;
         }
         updateContainerPosition();
         break;
 
+      // This error code currently only exists for the Symantec distrust
+      // in Firefox 63, so we add copy explaining that to the user.
+      // In case of future distrusts of that scale we might need to add
+      // additional parameters that allow us to identify the affected party
+      // without replicating the complex logic from certverifier code.
+      case MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED:
+        let description = gPipNSSBundle.formatStringFromName(
+          "certErrorSymantecDistrustDescription", [doc.location.hostname], 1);
+        let descriptionContainer = doc.getElementById("errorShortDescText2");
+        descriptionContainer.textContent = description;
+
+        let adminDescription = doc.createElement("p");
+        adminDescription.textContent =
+          gPipNSSBundle.GetStringFromName("certErrorSymantecDistrustAdministrator");
+        descriptionContainer.append(adminDescription);
+
+        learnMoreLink.href = baseURL + "symantec-warning";
+
+        updateContainerPosition();
+        break;
       case MOZILLA_PKIX_ERROR_MITM_DETECTED:
       case MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT:
         learnMoreLink.href = baseURL + "security-error";
         break;
 
       // In case the certificate expired we make sure the system clock
       // matches the remote-settings service (blocklist via Kinto) ping time
       // and is not before the build date.
--- a/browser/base/content/aboutNetError.xhtml
+++ b/browser/base/content/aboutNetError.xhtml
@@ -109,16 +109,21 @@
 
         <!-- LONG CONTENT (the section most likely to require scrolling) -->
         <div id="errorLongContent">
 
           <!-- Short Description -->
           <div id="errorShortDesc">
             <p id="errorShortDescText" />
           </div>
+
+          <div id="errorShortDesc2">
+              <p id="errorShortDescText2" />
+          </div>
+
           <p id="badStsCertExplanation" hidden="true">&certerror.whatShouldIDo.badStsCertExplanation;</p>
 
           <div id="wrongSystemTimePanel">
             &certerror.wrongSystemTime2;
           </div>
 
           <div id="wrongSystemTimeWithoutReferencePanel">
             &certerror.wrongSystemTimeWithoutReference;
--- a/devtools/client/inspector/markup/views/element-editor.js
+++ b/devtools/client/inspector/markup/views/element-editor.js
@@ -697,21 +697,23 @@ ElementEditor.prototype = {
    */
   onDisplayBadgeClick: function(event) {
     event.stopPropagation();
 
     const target = event.target;
 
     if (Services.prefs.getBoolPref("devtools.inspector.flexboxHighlighter.enabled") &&
         (target.dataset.display === "flex" || target.dataset.display === "inline-flex")) {
+      this._displayBadge.classList.add("active");
       this.highlighters.toggleFlexboxHighlighter(this.inspector.selection.nodeFront,
         "markup");
     }
 
     if (target.dataset.display === "grid" || target.dataset.display === "inline-grid") {
+      this._displayBadge.classList.add("active");
       this.highlighters.toggleGridHighlighter(this.inspector.selection.nodeFront,
         "markup");
     }
   },
 
   onCustomBadgeClick: function() {
     const { url, line } = this.node.customElementLocation;
     this.markup.toolbox.viewSourceInDebugger(url, line, "show_custom_element");
--- a/devtools/client/themes/markup.css
+++ b/devtools/client/themes/markup.css
@@ -1,31 +1,37 @@
 /* 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/. */
 
 :root {
   --markup-badge-active-background-color: var(--blue-50);
-  --markup-badge-background-color: var(--grey-20);
+  --markup-badge-active-border-color: #FFFFFFB3;
+  --markup-badge-background-color: white;
   --markup-badge-border-color: #CACAD1;
-  --markup-badge-color: var(--grey-90);
+  --markup-badge-color: var(--grey-60);
   --markup-badge-hover-background-color: #DFDFE8;
+  --markup-badge-interactive-background-color: var(--grey-20);
+  --markup-badge-interactive-color: var(--grey-90);
   --markup-hidden-attr-name-color: #BA89B8;
   --markup-hidden-attr-value-color: #5C6D87;
   --markup-hidden-punctuation-color: #909090;
   --markup-hidden-tag-color: #97A4B3;
   --markup-outline: var(--theme-splitter-color);
 }
 
 .theme-dark:root {
   --markup-badge-active-background-color: var(--blue-60);
-  --markup-badge-background-color: var(--grey-70);
+  --markup-badge-active-border-color: #FFF6;
+  --markup-badge-background-color: var(--grey-80);
   --markup-badge-border-color: var(--grey-50);
-  --markup-badge-color: var(--grey-30);
+  --markup-badge-color: var(--grey-40);
   --markup-badge-hover-background-color: var(--grey-80);
+  --markup-badge-interactive-background-color: var(--grey-70);
+  --markup-badge-interactive-color: var(--grey-30);
   --markup-hidden-attr-name-color: #B07EB3;
   --markup-hidden-attr-value-color: #9893A3;
   --markup-hidden-punctuation-color: #909090;
   --markup-hidden-tag-color: #AFB5BF;
   --markup-outline: var(--theme-selection-background);
 }
 
 * {
@@ -224,28 +230,31 @@ ul.children + .tag-line::before {
 }
 
 .child.collapsed .close {
   display: inline;
 }
 
 .expandable.collapsed .close::before {
   /* Display an ellipsis character in collapsed nodes that can be expanded. */
-  content: "\2026";
+  content: "";
+  background-color: var(--markup-badge-interactive-background-color);
+  background-image: url(chrome://devtools/skin/images/more.svg);
+  background-repeat: no-repeat;
+  background-position: center;
+  border: 1px solid var(--markup-badge-border-color);
+  fill: var(--markup-badge-interactive-color);
+  vertical-align: middle;
   display: inline-block;
   width: 12px;
   height: 8px;
   margin: 0 2px;
   line-height: 3px;
-  color: var(--theme-body-color-inactive);
   border-radius: 3px;
-  border-style: solid;
-  border-width: 1px;
-  text-align: center;
-  vertical-align: middle;
+  -moz-context-properties: fill;
 }
 
 /* Hide HTML void elements (img, hr, br, …) closing tag when the element is not
  * expanded (it can be if it has pseudo-elements attached) */
 .child.collapsed > .tag-line .void-element .close {
   display: none;
 }
 
@@ -408,35 +417,38 @@ ul.children + .tag-line::before {
 }
 
 @media (min-resolution: 1.1dppx) {
   .markup-badge {
     font-size: 9px;
   }
 }
 
-.markup-badge.active {
-  background-color: var(--markup-badge-active-background-color);
-  border-color: var(--theme-selection-color);
-  color: var(--theme-selection-color);
-}
-
+/* Markup badges that are interactive/clickable */
 .markup-badge[data-custom],
 .markup-badge[data-display="flex"].interactive,
 .markup-badge[data-display="grid"],
 .markup-badge[data-display="inline-flex"],
 .markup-badge[data-display="inline-grid"],
 .markup-badge[data-event] {
+  background-color: var(--markup-badge-interactive-background-color);
+  color: var(--markup-badge-interactive-color);
   cursor: pointer;
 }
 
-.markup-badge[data-display="flex"].interactive:focus,
-.markup-badge[data-display="flex"].interactive:hover,
-.markup-badge[data-display="grid"]:focus,
-.markup-badge[data-display="grid"]:hover,
-.markup-badge[data-display="inline-flex"].interactive:focus,
-.markup-badge[data-display="inline-flex"].interactive:hover,
-.markup-badge[data-display="inline-grid"]:focus,
-.markup-badge[data-display="inline-grid"]:hover,
+.markup-badge[data-display="flex"]:not(.active).interactive:focus,
+.markup-badge[data-display="flex"]:not(.active).interactive:hover,
+.markup-badge[data-display="grid"]:not(.active):focus,
+.markup-badge[data-display="grid"]:not(.active):hover,
+.markup-badge[data-display="inline-flex"]:not(.active).interactive:focus,
+.markup-badge[data-display="inline-flex"]:not(.active).interactive:hover,
+.markup-badge[data-display="inline-grid"]:not(.active):focus,
+.markup-badge[data-display="inline-grid"]:not(.active):hover,
 .markup-badge[data-event]:focus,
 .markup-badge[data-event]:hover {
   background-color: var(--markup-badge-hover-background-color);
 }
+
+.markup-badge.active {
+  background-color: var(--markup-badge-active-background-color);
+  border-color: var(--markup-badge-active-border-color);
+  color: var(--theme-selection-color);
+}
--- a/devtools/server/actors/promises.js
+++ b/devtools/server/actors/promises.js
@@ -60,18 +60,18 @@ var PromisesActor = protocol.ActorClassW
     this.dbg.addDebuggees();
 
     this._navigationLifetimePool = this._createActorPool();
     this.conn.addActorPool(this._navigationLifetimePool);
 
     this._newPromises = [];
     this._promisesSettled = [];
 
-    this.dbg.findScripts().forEach(s => {
-      this.parentActor.sources.createSourceActors(s.source);
+    this.dbg.findSources().forEach(source => {
+      this.parentActor.sources.createSourceActors(source);
     });
 
     this.dbg.onNewScript = s => {
       this.parentActor.sources.createSourceActors(s.source);
     };
 
     EventEmitter.on(this.parentActor, "window-ready", this._onWindowReady);
 
--- a/devtools/server/actors/replay/debugger.js
+++ b/devtools/server/actors/replay/debugger.js
@@ -165,21 +165,33 @@ ReplayDebugger.prototype = {
     return this._sendRequest({ type: "findConsoleMessages" });
   },
 
   /////////////////////////////////////////////////////////
   // ScriptSource methods
   /////////////////////////////////////////////////////////
 
   _getSource(id) {
-    if (!this._scriptSources[id]) {
-      const data = this._sendRequest({ type: "getSource", id });
-      this._scriptSources[id] = new ReplayDebuggerScriptSource(this, data);
+    const source = this._scriptSources[id];
+    if (source) {
+      return source;
     }
-    return this._scriptSources[id];
+    return this._addSource(this._sendRequest({ type: "getSource", id }));
+  },
+
+  _addSource(data) {
+    if (!this._scriptSources[data.id]) {
+      this._scriptSources[data.id] = new ReplayDebuggerScriptSource(this, data);
+    }
+    return this._scriptSources[data.id];
+  },
+
+  findSources() {
+    const data = this._sendRequest({ type: "findSources" });
+    return data.map(source => this._addSource(source));
   },
 
   /////////////////////////////////////////////////////////
   // Object methods
   /////////////////////////////////////////////////////////
 
   _getObject(id) {
     if (id && !this._objects[id]) {
--- a/devtools/server/actors/replay/replay.js
+++ b/devtools/server/actors/replay/replay.js
@@ -460,16 +460,32 @@ function getScriptData(id) {
     lineCount: script.lineCount,
     sourceStart: script.sourceStart,
     sourceLength: script.sourceLength,
     displayName: script.displayName,
     url: script.url,
   };
 }
 
+function getSourceData(id) {
+  const source = gScriptSources.getObject(id);
+  const introductionScript = gScripts.getId(source.introductionScript);
+  return {
+    id: id,
+    text: source.text,
+    url: source.url,
+    displayURL: source.displayURL,
+    elementAttributeName: source.elementAttributeName,
+    introductionScript,
+    introductionOffset: introductionScript ? source.introductionOffset : undefined,
+    introductionType: source.introductionType,
+    sourceMapURL: source.sourceMapURL,
+  };
+}
+
 function forwardToScript(name) {
   return request => gScripts.getObject(request.id)[name](request.value);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 // Handlers
 ///////////////////////////////////////////////////////////////////////////////
 
@@ -490,30 +506,26 @@ const gRequestHandlers = {
   getNewScript(request) {
     return getScriptData(gScripts.lastId());
   },
 
   getContent(request) {
     return RecordReplayControl.getContent(request.url);
   },
 
+  findSources(request) {
+    const sources = [];
+    gScriptSources.forEach((id) => {
+      sources.push(getSourceData(id));
+    });
+    return sources;
+  },
+
   getSource(request) {
-    const source = gScriptSources.getObject(request.id);
-    const introductionScript = gScripts.getId(source.introductionScript);
-    return {
-      id: request.id,
-      text: source.text,
-      url: source.url,
-      displayURL: source.displayURL,
-      elementAttributeName: source.elementAttributeName,
-      introductionScript,
-      introductionOffset: introductionScript ? source.introductionOffset : undefined,
-      introductionType: source.introductionType,
-      sourceMapURL: source.sourceMapURL,
-    };
+    return getSourceData(request.id);
   },
 
   getObject(request) {
     const object = gPausedObjects.getObject(request.id);
     if (object instanceof Debugger.Object) {
       return {
         id: request.id,
         kind: "Object",
--- a/devtools/server/actors/thread.js
+++ b/devtools/server/actors/thread.js
@@ -1106,32 +1106,22 @@ const ThreadActor = ActorClassWithSpec(t
       } else if (actor.destroy) {
         actor.destroy();
       }
     }
     return res ? res : {};
   },
 
   /**
-   * Get the script and source lists from the debugger.
+   * Get the source lists from the debugger.
    */
   _discoverSources: function() {
-    // Only get one script per Debugger.Source.
-    const sourcesToScripts = new Map();
-    const scripts = this.dbg.findScripts();
-
-    for (let i = 0, len = scripts.length; i < len; i++) {
-      const s = scripts[i];
-      if (s.source) {
-        sourcesToScripts.set(s.source, s);
-      }
-    }
-
-    return Promise.all([...sourcesToScripts.values()].map(script => {
-      return this.sources.createSourceActors(script.source);
+    const sources = this.dbg.findSources();
+    return Promise.all(sources.map(source => {
+      return this.sources.createSourceActors(source);
     }));
   },
 
   onSources: function(request) {
     return this._discoverSources().then(() => {
       // No need to flush the new source packets here, as we are sending the
       // list of sources out immediately and we don't need to invoke the
       // overhead of an RDP packet for every source right now. Let the default
--- a/js/src/doc/Debugger/Debugger.md
+++ b/js/src/doc/Debugger/Debugger.md
@@ -352,36 +352,20 @@ other kinds of objects.
     debuggee globals, if a debuggee global is otherwise unreachable, it may
     be dropped at any moment from the array this method returns.
 
 `getNewestFrame()`
 :   Return a [`Debugger.Frame`][frame] instance referring to the youngest
     [visible frame][vf] currently on the calling thread's stack, or `null`
     if there are no visible frames on the stack.
 
-<code>findSources([<i>query</i>]) <i>(not yet implemented)</i></code>
-:   Return an array of all [`Debugger.Source`][source] instances matching
-    <i>query</i>. Each source appears only once in the array. <i>Query</i>
-    is an object whose properties restrict which sources are returned; a
-    source must meet all the criteria given by <i>query</i> to be returned.
-    If <i>query</i> is omitted, we return all sources of all debuggee
+<code>findSources()</code>
+:   Return an array of all [`Debugger.Source`][source] instances of all debuggee
     scripts.
 
-    <i>Query</i> may have the following properties:
-
-    `url`
-    :   The source's `url` property must be equal to this value.
-
-    `global`
-    :   The source must have been evaluated in the scope of the given global
-        object. If this property's value is a [`Debugger.Object`][object] instance
-        belonging to this `Debugger` instance, then its referent is used. If the
-        object is not a global object, then the global in whose scope it was
-        allocated is used.
-
     Note that the result may include sources that can no longer ever be
     used by the debuggee: say, eval code that has finished running, or
     source for unreachable functions. Whether such sources appear can be
     affected by the garbage collector's behavior, so this function's result
     is not entirely deterministic.
 
 <code>findScripts([<i>query</i>])</code>
 :   Return an array of [`Debugger.Script`][script] instances for all debuggee scripts
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findSources-01.js
@@ -0,0 +1,4 @@
+// In a debugger with no debuggees, findSources should return no scripts.
+
+const dbg = new Debugger;
+assertEq(dbg.findSources().length, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findSources-02.js
@@ -0,0 +1,15 @@
+// In a debugger with scripts, findSources finds the script source.
+
+const g = newGlobal();
+// Declare a function in order to keep the script source alive across GC.
+g.evaluate(`function fa() {}`, { fileName: "a.js" });
+g.evaluate(`function fb() {}`, { fileName: "b.js" });
+g.evaluate(`function fc() {}`, { fileName: "c.js" });
+
+const dbg = new Debugger();
+const gw = dbg.addDebuggee(g);
+
+const sources = dbg.findSources();
+assertEq(sources.filter(s => s.url == "a.js").length, 1);
+assertEq(sources.filter(s => s.url == "b.js").length, 1);
+assertEq(sources.filter(s => s.url == "c.js").length, 1);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findSources-03.js
@@ -0,0 +1,19 @@
+// In a debugger with multiple debuggees, findSources finds script sources across all debuggees.
+
+const g1 = newGlobal();
+const g2 = newGlobal();
+// Declare a function in order to keep the script source alive across GC.
+g1.evaluate(`function fa() {}`, { fileName: "a.js" });
+g1.evaluate(`function fb() {}`, { fileName: "b.js" });
+g2.evaluate(`function fc() {}`, { fileName: "c.js" });
+g2.evaluate(`function fd() {}`, { fileName: "d.js" });
+
+const dbg = new Debugger();
+const g1w = dbg.addDebuggee(g1);
+const g2w = dbg.addDebuggee(g2);
+
+const sources = dbg.findSources();
+assertEq(dbg.findSources().filter(s => s.url == "a.js").length, 1);
+assertEq(dbg.findSources().filter(s => s.url == "b.js").length, 1);
+assertEq(dbg.findSources().filter(s => s.url == "c.js").length, 1);
+assertEq(dbg.findSources().filter(s => s.url == "d.js").length, 1);
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -4183,41 +4183,94 @@ Debugger::removeDebuggeeGlobal(FreeOp* f
         global->realm()->updateDebuggerObservesBinarySource();
         global->realm()->updateDebuggerObservesCoverage();
     }
 }
 
 
 static inline DebuggerSourceReferent GetSourceReferent(JSObject* obj);
 
+class MOZ_STACK_CLASS Debugger::QueryBase
+{
+  protected:
+    QueryBase(JSContext* cx, Debugger* dbg)
+      : cx(cx),
+        debugger(dbg),
+        iterMarker(&cx->runtime()->gc),
+        realms(cx->zone()),
+        oom(false)
+    {}
+
+    // The context in which we should do our work.
+    JSContext* cx;
+
+    // The debugger for which we conduct queries.
+    Debugger* debugger;
+
+    // Require the set of realms to stay fixed while this query is alive.
+    gc::AutoEnterIteration iterMarker;
+
+    using RealmSet = HashSet<Realm*, DefaultHasher<Realm*>, ZoneAllocPolicy>;
+
+    // A script must be in one of these realms to match the query.
+    RealmSet realms;
+
+    // Indicates whether OOM has occurred while matching.
+    bool oom;
+
+    bool addRealm(Realm* realm) {
+        return realms.put(realm);
+    }
+
+    // Arrange for this query to match only scripts that run in |global|.
+    bool matchSingleGlobal(GlobalObject* global) {
+        MOZ_ASSERT(realms.count() == 0);
+        if (!addRealm(global->realm())) {
+            ReportOutOfMemory(cx);
+            return false;
+        }
+        return true;
+    }
+
+    // Arrange for this ScriptQuery to match all scripts running in debuggee
+    // globals.
+    bool matchAllDebuggeeGlobals() {
+        MOZ_ASSERT(realms.count() == 0);
+        // Build our realm set from the debugger's set of debuggee globals.
+        for (WeakGlobalObjectSet::Range r = debugger->debuggees.all(); !r.empty(); r.popFront()) {
+            if (!addRealm(r.front()->realm())) {
+                ReportOutOfMemory(cx);
+                return false;
+            }
+        }
+        return true;
+    }
+};
+
 /*
  * A class for parsing 'findScripts' query arguments and searching for
  * scripts that match the criteria they represent.
  */
-class MOZ_STACK_CLASS Debugger::ScriptQuery
+class MOZ_STACK_CLASS Debugger::ScriptQuery : public Debugger::QueryBase
 {
   public:
     /* Construct a ScriptQuery to use matching scripts for |dbg|. */
-    ScriptQuery(JSContext* cx, Debugger* dbg):
-        cx(cx),
-        debugger(dbg),
-        iterMarker(&cx->runtime()->gc),
-        realms(cx->zone()),
+    ScriptQuery(JSContext* cx, Debugger* dbg)
+      : QueryBase(cx, dbg),
         url(cx),
         displayURLString(cx),
         hasSource(false),
         source(cx, AsVariant(static_cast<ScriptSourceObject*>(nullptr))),
         hasLine(false),
         line(0),
         innermost(false),
         innermostForRealm(cx->zone()),
         scriptVector(cx, ScriptVector(cx)),
         lazyScriptVector(cx, LazyScriptVector(cx)),
-        wasmInstanceVector(cx, WasmInstanceObjectVector(cx)),
-        oom(false)
+        wasmInstanceVector(cx, WasmInstanceObjectVector(cx))
     {}
 
     /*
      * Parse the query object |query|, and prepare to match only the scripts
      * it specifies.
      */
     bool parseQuery(HandleObject query) {
         // Check for a 'global' property, which limits the results to those
@@ -4431,30 +4484,16 @@ class MOZ_STACK_CLASS Debugger::ScriptQu
         return lazyScriptVector;
     }
 
     Handle<WasmInstanceObjectVector> foundWasmInstances() const {
         return wasmInstanceVector;
     }
 
   private:
-    /* The context in which we should do our work. */
-    JSContext* cx;
-
-    /* The debugger for which we conduct queries. */
-    Debugger* debugger;
-
-    /* Require the set of realms to stay fixed while the ScriptQuery is alive. */
-    gc::AutoEnterIteration iterMarker;
-
-    using RealmSet = HashSet<Realm*, DefaultHasher<Realm*>, ZoneAllocPolicy>;
-
-    /* A script must be in one of these realms to match the query. */
-    RealmSet realms;
-
     /* If this is a string, matching scripts have urls equal to it. */
     RootedValue url;
 
     /* url as a C string. */
     JSAutoByteString urlCString;
 
     /* If this is a string, matching scripts' sources have displayURLs equal to
      * it. */
@@ -4494,49 +4533,16 @@ class MOZ_STACK_CLASS Debugger::ScriptQu
     Rooted<ScriptVector> scriptVector;
     Rooted<LazyScriptVector> lazyScriptVector;
 
     /*
      * Like above, but for wasm modules.
      */
     Rooted<WasmInstanceObjectVector> wasmInstanceVector;
 
-    /* Indicates whether OOM has occurred while matching. */
-    bool oom;
-
-    bool addRealm(Realm* realm) {
-        return realms.put(realm);
-    }
-
-    /* Arrange for this ScriptQuery to match only scripts that run in |global|. */
-    bool matchSingleGlobal(GlobalObject* global) {
-        MOZ_ASSERT(realms.count() == 0);
-        if (!addRealm(global->realm())) {
-            ReportOutOfMemory(cx);
-            return false;
-        }
-        return true;
-    }
-
-    /*
-     * Arrange for this ScriptQuery to match all scripts running in debuggee
-     * globals.
-     */
-    bool matchAllDebuggeeGlobals() {
-        MOZ_ASSERT(realms.count() == 0);
-        // Build our realm set from the debugger's set of debuggee globals.
-        for (WeakGlobalObjectSet::Range r = debugger->debuggees.all(); !r.empty(); r.popFront()) {
-            if (!addRealm(r.front()->realm())) {
-                ReportOutOfMemory(cx);
-                return false;
-            }
-        }
-        return true;
-    }
-
     /*
      * Given that parseQuery or omittedQuery has been called, prepare to match
      * scripts. Set urlCString and displayURLChars as appropriate.
      */
     bool prepareQuery() {
         // Compute urlCString and displayURLChars, if a url or displayURL was
         // given respectively.
         if (url.isString()) {
@@ -4761,16 +4767,170 @@ Debugger::findScripts(JSContext* cx, uns
         result->setDenseElement(wasmStart + i, ObjectValue(*scriptObject));
     }
 
     args.rval().setObject(*result);
     return true;
 }
 
 /*
+ * A class for searching sources for 'findSources'.
+ */
+class MOZ_STACK_CLASS Debugger::SourceQuery : public Debugger::QueryBase
+{
+  public:
+    using SourceSet = JS::GCHashSet<JSObject*,
+                                    js::MovableCellHasher<JSObject*>,
+                                    ZoneAllocPolicy>;
+
+    SourceQuery(JSContext* cx, Debugger* dbg)
+      : QueryBase(cx, dbg),
+        sources(cx, SourceSet(cx->zone()))
+    {}
+
+    bool findSources() {
+        if (!matchAllDebuggeeGlobals())
+            return false;
+
+        Realm* singletonRealm = nullptr;
+        if (realms.count() == 1)
+            singletonRealm = realms.all().front();
+
+        // Search each realm for debuggee scripts.
+        MOZ_ASSERT(sources.empty());
+        oom = false;
+        IterateScripts(cx, singletonRealm, this, considerScript);
+        IterateLazyScripts(cx, singletonRealm, this, considerLazyScript);
+        if (oom) {
+            ReportOutOfMemory(cx);
+            return false;
+        }
+
+        // TODO: Until such time that wasm modules are real ES6 modules,
+        // unconditionally consider all wasm toplevel instance scripts.
+        for (WeakGlobalObjectSet::Range r = debugger->allDebuggees(); !r.empty(); r.popFront()) {
+            for (wasm::Instance* instance : r.front()->realm()->wasm.instances()) {
+                consider(instance->object());
+                if (oom) {
+                    ReportOutOfMemory(cx);
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    Handle<SourceSet> foundSources() const {
+        return sources;
+    }
+
+  private:
+    Rooted<SourceSet> sources;
+
+    static void considerScript(JSRuntime* rt, void* data, JSScript* script,
+                               const JS::AutoRequireNoGC& nogc) {
+        SourceQuery* self = static_cast<SourceQuery*>(data);
+        self->consider(script, nogc);
+    }
+
+    static void considerLazyScript(JSRuntime* rt, void* data, LazyScript* lazyScript,
+                                   const JS::AutoRequireNoGC& nogc) {
+        SourceQuery* self = static_cast<SourceQuery*>(data);
+        self->consider(lazyScript, nogc);
+    }
+
+    void consider(JSScript* script, const JS::AutoRequireNoGC& nogc) {
+        if (oom || script->selfHosted())
+            return;
+        Realm* realm = script->realm();
+        if (!realms.has(realm))
+            return;
+
+        if (!script->sourceObject())
+            return;
+
+        ScriptSourceObject* source =
+            &UncheckedUnwrap(script->sourceObject())->as<ScriptSourceObject>();
+        if (!sources.put(source))
+            oom = true;
+    }
+
+    void consider(LazyScript* lazyScript, const JS::AutoRequireNoGC& nogc) {
+        if (oom)
+            return;
+        Realm* realm = lazyScript->realm();
+        if (!realms.has(realm))
+            return;
+
+        // If the script is already delazified, it should already be handled.
+        if (lazyScript->maybeScript())
+            return;
+
+        ScriptSourceObject* source = &lazyScript->sourceObject();
+        if (!sources.put(source))
+            oom = true;
+    }
+
+    void consider(WasmInstanceObject* instanceObject) {
+        if (oom)
+            return;
+
+        if (!sources.put(instanceObject))
+            oom = true;
+    }
+};
+
+static inline DebuggerSourceReferent
+AsSourceReferent(JSObject* obj)
+{
+    if (obj->is<ScriptSourceObject>()) {
+        return AsVariant(&obj->as<ScriptSourceObject>());
+    }
+    return AsVariant(&obj->as<WasmInstanceObject>());
+}
+
+/* static */ bool
+Debugger::findSources(JSContext* cx, unsigned argc, Value* vp)
+{
+    THIS_DEBUGGER(cx, argc, vp, "findSources", args, dbg);
+
+    if (gc::GCRuntime::temporaryAbortIfWasmGc(cx)) {
+        JS_ReportErrorASCII(cx, "API temporarily unavailable under wasm gc");
+        return false;
+    }
+
+    SourceQuery query(cx, dbg);
+    if (!query.findSources())
+        return false;
+
+    Handle<SourceQuery::SourceSet> sources(query.foundSources());
+
+    size_t resultLength = sources.count();
+    RootedArrayObject result(cx, NewDenseFullyAllocatedArray(cx, resultLength));
+    if (!result)
+        return false;
+
+    result->ensureDenseInitializedLength(cx, 0, resultLength);
+
+    size_t i = 0;
+    for (auto iter = sources.get().iter(); !iter.done(); iter.next()) {
+        Rooted<DebuggerSourceReferent> sourceReferent(cx, AsSourceReferent(iter.get()));
+        RootedObject sourceObject(cx, dbg->wrapVariantReferent(cx, sourceReferent));
+        if (!sourceObject)
+            return false;
+        result->setDenseElement(i, ObjectValue(*sourceObject));
+        i++;
+    }
+
+    args.rval().setObject(*result);
+    return true;
+}
+
+/*
  * A class for parsing 'findObjects' query arguments and searching for objects
  * that match the criteria they represent.
  */
 class MOZ_STACK_CLASS Debugger::ObjectQuery
 {
   public:
     /* Construct an ObjectQuery to use matching scripts for |dbg|. */
     ObjectQuery(JSContext* cx, Debugger* dbg) :
@@ -5176,16 +5336,17 @@ const JSFunctionSpec Debugger::methods[]
     JS_FN("addAllGlobalsAsDebuggees", Debugger::addAllGlobalsAsDebuggees, 0, 0),
     JS_FN("removeDebuggee", Debugger::removeDebuggee, 1, 0),
     JS_FN("removeAllDebuggees", Debugger::removeAllDebuggees, 0, 0),
     JS_FN("hasDebuggee", Debugger::hasDebuggee, 1, 0),
     JS_FN("getDebuggees", Debugger::getDebuggees, 0, 0),
     JS_FN("getNewestFrame", Debugger::getNewestFrame, 0, 0),
     JS_FN("clearAllBreakpoints", Debugger::clearAllBreakpoints, 0, 0),
     JS_FN("findScripts", Debugger::findScripts, 1, 0),
+    JS_FN("findSources", Debugger::findSources, 1, 0),
     JS_FN("findObjects", Debugger::findObjects, 1, 0),
     JS_FN("findAllGlobals", Debugger::findAllGlobals, 0, 0),
     JS_FN("makeGlobalObjectReference", Debugger::makeGlobalObjectReference, 1, 0),
     JS_FN("adoptDebuggeeValue", Debugger::adoptDebuggeeValue, 1, 0),
     JS_FS_END
 };
 
 const JSFunctionSpec Debugger::static_methods[] {
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -561,17 +561,19 @@ class Debugger : private mozilla::Linked
      */
 #ifdef NIGHTLY_BUILD
     uint32_t traceLoggerLastDrainedSize;
     uint32_t traceLoggerLastDrainedIteration;
 #endif
     uint32_t traceLoggerScriptedCallsLastDrainedSize;
     uint32_t traceLoggerScriptedCallsLastDrainedIteration;
 
+    class QueryBase;
     class ScriptQuery;
+    class SourceQuery;
     class ObjectQuery;
 
     MOZ_MUST_USE bool addDebuggeeGlobal(JSContext* cx, Handle<GlobalObject*> obj);
     void removeDebuggeeGlobal(FreeOp* fop, GlobalObject* global,
                               WeakGlobalObjectSet::Enum* debugEnum);
 
     enum class CallUncaughtExceptionHook {
         No,
@@ -713,16 +715,17 @@ class Debugger : private mozilla::Linked
     static bool addAllGlobalsAsDebuggees(JSContext* cx, unsigned argc, Value* vp);
     static bool removeDebuggee(JSContext* cx, unsigned argc, Value* vp);
     static bool removeAllDebuggees(JSContext* cx, unsigned argc, Value* vp);
     static bool hasDebuggee(JSContext* cx, unsigned argc, Value* vp);
     static bool getDebuggees(JSContext* cx, unsigned argc, Value* vp);
     static bool getNewestFrame(JSContext* cx, unsigned argc, Value* vp);
     static bool clearAllBreakpoints(JSContext* cx, unsigned argc, Value* vp);
     static bool findScripts(JSContext* cx, unsigned argc, Value* vp);
+    static bool findSources(JSContext* cx, unsigned argc, Value* vp);
     static bool findObjects(JSContext* cx, unsigned argc, Value* vp);
     static bool findAllGlobals(JSContext* cx, unsigned argc, Value* vp);
     static bool makeGlobalObjectReference(JSContext* cx, unsigned argc, Value* vp);
     static bool setupTraceLoggerScriptCalls(JSContext* cx, unsigned argc, Value* vp);
     static bool drainTraceLoggerScriptCalls(JSContext* cx, unsigned argc, Value* vp);
     static bool startTraceLogger(JSContext* cx, unsigned argc, Value* vp);
     static bool endTraceLogger(JSContext* cx, unsigned argc, Value* vp);
     static bool isCompilableUnit(JSContext* cx, unsigned argc, Value* vp);
--- a/mobile/android/chrome/content/aboutCertError.xhtml
+++ b/mobile/android/chrome/content/aboutCertError.xhtml
@@ -111,18 +111,18 @@
       <!-- LONG CONTENT (the section most likely to require scrolling) -->
       <div id="errorLongContent">
         <div id="introContent">
           <p id="introContentP1">&certerror.introPara1;</p>
         </div>
 
         <div id="whatShouldIDoContent">
           <h2>&certerror.whatShouldIDo.heading;</h2>
-          <div id="whatShouldIDoContentText">
-            <p>&certerror.whatShouldIDo.content;</p>
+          <div>
+            <p id="whatShouldIDoContentText">&certerror.whatShouldIDo.content;</p>
             <button id="getMeOutOfHereButton">&certerror.getMeOutOfHere.label;</button>
           </div>
         </div>
 
         <!-- The following sections can be unhidden by default by setting the
              "browser.xul.error_pages.expert_bad_cert" pref to true -->
         <div id="technicalContent" collapsed="true">
           <h2 class="expander" onclick="toggle('technicalContent');" id="technicalContentHeading">&certerror.technical.heading;</h2>
--- a/mobile/android/chrome/content/content.js
+++ b/mobile/android/chrome/content/content.js
@@ -86,16 +86,17 @@ const SEC_ERROR_UNTRUSTED_CERT          
 const SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE         = SEC_ERROR_BASE + 30;
 const SEC_ERROR_CA_CERT_INVALID                    = SEC_ERROR_BASE + 36;
 const SEC_ERROR_OCSP_FUTURE_RESPONSE               = SEC_ERROR_BASE + 131;
 const SEC_ERROR_OCSP_OLD_RESPONSE                  = SEC_ERROR_BASE + 132;
 const SEC_ERROR_REUSED_ISSUER_AND_SERIAL           = SEC_ERROR_BASE + 138;
 const SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED  = SEC_ERROR_BASE + 176;
 const MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE = MOZILLA_PKIX_ERROR_BASE + 5;
 const MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE = MOZILLA_PKIX_ERROR_BASE + 6;
+const MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED = MOZILLA_PKIX_ERROR_BASE + 13;
 
 
 const SSL_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SSL_ERROR_BASE;
 const SSL_ERROR_SSL_DISABLED  = SSL_ERROR_BASE + 20;
 const SSL_ERROR_SSL2_DISABLED  = SSL_ERROR_BASE + 14;
 
 var AboutNetErrorListener = {
   init(chromeGlobal) {
@@ -198,16 +199,21 @@ var AboutCertErrorListener = {
           msg += gPipNSSBundle.GetStringFromName("certErrorTrust_Issuer") + "\n";
           break;
         case SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED:
           msg += gPipNSSBundle.GetStringFromName("certErrorTrust_SignatureAlgorithmDisabled") + "\n";
           break;
         case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
           msg += gPipNSSBundle.GetStringFromName("certErrorTrust_ExpiredIssuer") + "\n";
           break;
+        // This error code currently only exists for the Symantec distrust, we may need to adjust
+        // it to fit other distrusts later.
+        case MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED:
+          msg += gPipNSSBundle.formatStringFromName("certErrorTrust_Symantec", [hostString], 1) + "\n";
+          break;
         case SEC_ERROR_UNTRUSTED_CERT:
         default:
           msg += gPipNSSBundle.GetStringFromName("certErrorTrust_Untrusted") + "\n";
       }
     }
     if (sslStatus.isUntrusted && sslStatus.serverCert.isSelfSigned) {
       msg += gPipNSSBundle.GetStringFromName("certErrorTrust_SelfSigned") + "\n";
     }
@@ -229,16 +235,33 @@ var AboutCertErrorListener = {
     let technicalInfo = doc.getElementById("technicalContentText");
 
     let uri = Services.io.newURI(location);
     let hostString = uri.host;
     if (uri.port != 443 && uri.port != -1) {
       hostString += ":" + uri.port;
     }
 
+    // This error code currently only exists for the Symantec distrust
+    // in Firefox 63, so we add copy explaining that to the user.
+    // In case of future distrusts of that scale we might need to add
+    // additional parameters that allow us to identify the affected party
+    // without replicating the complex logic from certverifier code.
+    if (securityInfo.errorCode == MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED) {
+      let introContent = doc.getElementById("introContent");
+      let description = doc.createElement("p");
+      description.textContent = gPipNSSBundle.formatStringFromName(
+        "certErrorSymantecDistrustDescription", [hostString], 1);
+      introContent.append(description);
+
+      // The regular "what should I do" message does not make sense in this case.
+      doc.getElementById("whatShouldIDoContentText").textContent =
+        gPipNSSBundle.GetStringFromName("certErrorSymantecDistrustAdministrator");
+    }
+
     this._setTechDetailsMsgPart1(hostString, sslStatus, securityInfo, technicalInfo, doc);
 
     if (sslStatus.isDomainMismatch) {
       let subjectAltNamesList = sslStatus.serverCert.subjectAltNames;
       let subjectAltNames = subjectAltNamesList.split(",");
       let numSubjectAltNames = subjectAltNames.length;
       let msgPrefix = "";
       if (numSubjectAltNames != 0) {
--- a/security/manager/locales/en-US/chrome/pipnss/pipnss.properties
+++ b/security/manager/locales/en-US/chrome/pipnss/pipnss.properties
@@ -282,16 +282,18 @@ certErrorTrust_UnknownIssuer4=Someone co
 # LOCALIZATION NOTE (certErrorTrust_UnknownIssuer5): %1$S is replaced by the brand name, %2$S is replaced by host name.
 certErrorTrust_UnknownIssuer5=Websites prove their identity via security certificates. %1$S does not trust %2$S because its security certificate issuer is unknown, the certificate is self-signed, or the server is not sending the correct intermediate certificates.
 certErrorTrust_CaInvalid=The certificate is not trusted because it was issued by an invalid CA certificate.
 certErrorTrust_Issuer=The certificate is not trusted because the issuer certificate is not trusted.
 certErrorTrust_SignatureAlgorithmDisabled=The certificate is not trusted because it was signed using a signature algorithm that was disabled because that algorithm is not secure.
 certErrorTrust_ExpiredIssuer=The certificate is not trusted because the issuer certificate has expired.
 certErrorTrust_Untrusted=The certificate does not come from a trusted source.
 certErrorTrust_MitM=Your connection is being intercepted by a TLS proxy. Uninstall it if possible or configure your device to trust its root certificate.
+# LOCALIZATION NOTE (certErrorTrust_Symantec): %S is replaced by the domain for which the certificate is valid
+certErrorTrust_Symantec=The security certificate for %S is not trustworthy because the issuing organization failed to follow security practices. Certificates issued by Symantec, including the Thawte, GeoTrust, and RapidSSL brands, are not considered safe.
 
 certErrorMismatch=The certificate is not valid for the name %S.
 # LOCALIZATION NOTE (certErrorMismatch1, certErrorMismatchSinglePrefix1, certErrorMismatchMultiple1): %1$S is replaced by the brand name, %2$S is replaced by host name.
 certErrorMismatch1=Websites prove their identity via security certificates. %1$S does not trust %2$S because it uses a security certificate that is not valid for %2$S.
 # LOCALIZATION NOTE (certErrorMismatchSinglePrefix): %S is replaced by the domain for which the certificate is valid
 certErrorMismatchSinglePrefix=The certificate is only valid for %S.
 # LOCALIZATION NOTE (certErrorMismatchSinglePrefix1): %3$S is replaced by the domain for which the certificate is valid
 certErrorMismatchSinglePrefix1=Websites prove their identity via security certificates. %1$S does not trust %2$S because it uses a security certificate that is not valid for %2$S.
@@ -301,16 +303,20 @@ certErrorMismatchMultiple1=Websites prov
 # LOCALIZATION NOTE (certErrorExpiredNow): Do not translate %1$S (date+time of expired certificate) or %2$S (current date+time)
 certErrorExpiredNow=The certificate expired on %1$S. The current time is %2$S.
 certErrorExpiredNow1=Websites prove their identity via security certificates, which are valid for a set time period. The security certificate for %S appears to be expired.
 
 # LOCALIZATION NOTE (certErrorNotYetValidNow): Do not translate %1$S (date+time certificate will become valid) or %2$S (current date+time)
 certErrorNotYetValidNow=The certificate will not be valid until %1$S. The current time is %2$S.
 certErrorNotYetValidNow1=Websites prove their identity via security certificates, which are valid for a set time period. The security certificate for %S appears to be not yet valid.
 
+# LOCALIZATION NOTE (certErrorSymantecDistrustDescription): %S will be replaced by the domain for which the certificate is valid.
+certErrorSymantecDistrustDescription=Websites prove their identity via certificates, which are issued by certificate authorities. Most browsers will no longer trust Symantec, the certificate authority for %S.
+certErrorSymantecDistrustAdministrator=You may notify the website’s administrator about this problem.
+
 # LOCALIZATION NOTE (certErrorCodePrefix3): %S is replaced by the error code.
 certErrorCodePrefix3=Error code: %S
 
 P12DefaultNickname=Imported Certificate
 CertUnknown=Unknown
 CertNoEmailAddress=(no email address)
 CaCertExists=This certificate is already installed as a certificate authority.
 NotACACert=This is not a certificate authority certificate, so it can’t be imported into the certificate authority list.