Merge mozilla-inbound to mozilla-central. a=merge
authorDaniel Varga <dvarga@mozilla.com>
Wed, 30 Jan 2019 23:54:54 +0200
changeset 513994 0a71d114c5e2
parent 513983 73a91e84dbec (current diff)
parent 513993 fb947a64a259 (diff)
child 514027 0f6d55fd51c4
child 514060 f9ee4fc73332
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone67.0a1
first release with
nightly linux32
0a71d114c5e2 / 67.0a1 / 20190130215539 / files
nightly linux64
0a71d114c5e2 / 67.0a1 / 20190130215539 / files
nightly mac
0a71d114c5e2 / 67.0a1 / 20190130215539 / files
nightly win32
0a71d114c5e2 / 67.0a1 / 20190130215539 / files
nightly win64
0a71d114c5e2 / 67.0a1 / 20190130215539 / 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 mozilla-inbound to mozilla-central. a=merge
--- a/accessible/ipc/win/ProxyAccessible.cpp
+++ b/accessible/ipc/win/ProxyAccessible.cpp
@@ -347,37 +347,37 @@ void ProxyAccessible::Attributes(nsTArra
   ConvertBSTRAttributesToArray(
       nsDependentString((wchar_t*)attrs, attrsWrap.length()), aAttrs);
 }
 
 nsTArray<ProxyAccessible*> ProxyAccessible::RelationByType(
     RelationType aType) const {
   RefPtr<IAccessible2_2> acc = QueryInterface<IAccessible2_2>(this);
   if (!acc) {
-    nsTArray<ProxyAccessible*>();
+    return nsTArray<ProxyAccessible*>();
   }
 
   _bstr_t relationType;
   for (uint32_t idx = 0; idx < ArrayLength(sRelationTypePairs); idx++) {
     if (aType == sRelationTypePairs[idx].first) {
       relationType = sRelationTypePairs[idx].second;
       break;
     }
   }
 
   if (!relationType) {
-    nsTArray<ProxyAccessible*>();
+    return nsTArray<ProxyAccessible*>();
   }
 
   IUnknown** targets;
   long nTargets = 0;
   HRESULT hr =
       acc->get_relationTargetsOfType(relationType, 0, &targets, &nTargets);
   if (FAILED(hr)) {
-    nsTArray<ProxyAccessible*>();
+    return nsTArray<ProxyAccessible*>();
   }
 
   nsTArray<ProxyAccessible*> proxies;
   for (long idx = 0; idx < nTargets; idx++) {
     IUnknown* target = targets[idx];
     proxies.AppendElement(GetProxyFor(Document(), target));
     target->Release();
   }
--- a/devtools/server/actors/root.js
+++ b/devtools/server/actors/root.js
@@ -116,21 +116,16 @@ RootActor.prototype = {
 
   traits: {
     sources: true,
     networkMonitor: true,
     // Whether the storage inspector actor to inspect cookies, etc.
     storageInspector: true,
     // Whether storage inspector is read only
     storageInspectorReadOnly: true,
-    // Whether conditional breakpoints are supported
-    conditionalBreakpoints: true,
-    // Whether the server supports full source actors (breakpoints on
-    // eval scripts, etc)
-    debuggerSourceActors: true,
     // Whether the server can return wasm binary source
     wasmBinarySource: true,
     bulk: true,
     // Whether the director scripts are supported
     directorScripts: true,
     // Whether the debugger server supports
     // blackboxing (not supported in Fever Dream yet)
     noBlackBoxing: false,
--- a/devtools/shared/client/breakpoint-client.js
+++ b/devtools/shared/client/breakpoint-client.js
@@ -52,78 +52,39 @@ BreakpointClient.prototype = {
   /**
    * Remove the breakpoint from the server.
    */
   remove: DebuggerClient.requester({
     type: "delete",
   }),
 
   /**
-   * Determines if this breakpoint has a condition
-   */
-  hasCondition: function() {
-    const root = this._client.mainRoot;
-    // XXX bug 990137: We will remove support for client-side handling of
-    // conditional breakpoints
-    if (root.traits.conditionalBreakpoints) {
-      return "condition" in this;
-    }
-    return "conditionalExpression" in this;
-  },
-
-  /**
-   * Get the condition of this breakpoint. Currently we have to
-   * support locally emulated conditional breakpoints until the
-   * debugger servers are updated (see bug 990137). We used a
-   * different property when moving it server-side to ensure that we
-   * are testing the right code.
-   */
-  getCondition: function() {
-    const root = this._client.mainRoot;
-    if (root.traits.conditionalBreakpoints) {
-      return this.condition;
-    }
-    return this.conditionalExpression;
-  },
-
-  /**
    * Set the condition of this breakpoint
    */
   setCondition: function(gThreadClient, condition) {
-    const root = this._client.mainRoot;
     const deferred = promise.defer();
 
-    if (root.traits.conditionalBreakpoints) {
-      const info = {
-        line: this.location.line,
-        column: this.location.column,
-        condition: condition,
-      };
+    const info = {
+      line: this.location.line,
+      column: this.location.column,
+      condition: condition,
+    };
 
-      // Remove the current breakpoint and add a new one with the
-      // condition.
-      this.remove(response => {
-        if (response && response.error) {
-          deferred.reject(response);
-          return;
-        }
+    // Remove the current breakpoint and add a new one with the
+    // condition.
+    this.remove(response => {
+      if (response && response.error) {
+        deferred.reject(response);
+        return;
+      }
 
-        deferred.resolve(this.source.setBreakpoint(info).then(([, newBreakpoint]) => {
-          return newBreakpoint;
-        }));
-      });
-    } else {
-      // The property shouldn't even exist if the condition is blank
-      if (condition === "") {
-        delete this.conditionalExpression;
-      } else {
-        this.conditionalExpression = condition;
-      }
-      deferred.resolve(this);
-    }
+      deferred.resolve(this.source.setBreakpoint(info).then(([, newBreakpoint]) => {
+        return newBreakpoint;
+      }));
+    });
 
     return deferred.promise;
   },
 };
 
 eventSource(BreakpointClient.prototype);
 
 module.exports = BreakpointClient;
--- a/devtools/shared/client/source-client.js
+++ b/devtools/shared/client/source-client.js
@@ -176,48 +176,40 @@ SourceClient.prototype = {
    *
    * @param object location
    *        The location and condition of the breakpoint in
    *        the form of { line[, column, condition] }.
    */
   setBreakpoint: function({ line, column, condition, noSliding }) {
     // A helper function that sets the breakpoint.
     const doSetBreakpoint = callback => {
-      const root = this._client.mainRoot;
       const location = {
         line,
         column,
       };
 
       const packet = {
         to: this.actor,
         type: "setBreakpoint",
         location,
         condition,
         noSliding,
       };
 
-      // Backwards compatibility: send the breakpoint request to the
-      // thread if the server doesn't support Debugger.Source actors.
-      if (!root.traits.debuggerSourceActors) {
-        packet.to = this._activeThread.actor;
-        packet.location.url = this.url;
-      }
-
       return this._client.request(packet).then(response => {
         // Ignoring errors, since the user may be setting a breakpoint in a
         // dead script that will reappear on a page reload.
         let bpClient;
         if (response.actor) {
           bpClient = new BreakpointClient(
             this._client,
             this,
             response.actor,
             location,
-            root.traits.conditionalBreakpoints ? condition : undefined
+            condition
           );
         }
         if (callback) {
           callback();
         }
         return [response, bpClient];
       });
     };
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -6980,16 +6980,23 @@ bool nsContentUtils::IsJavascriptMIMETyp
                                   nullptr};
 
   for (uint32_t i = 0; jsTypes[i]; ++i) {
     if (aMIMEType.LowerCaseEqualsASCII(jsTypes[i])) {
       return true;
     }
   }
 
+#ifdef JS_BUILD_BINAST
+  if (nsJSUtils::BinASTEncodingEnabled() &&
+      aMIMEType.LowerCaseEqualsASCII(APPLICATION_JAVASCRIPT_BINAST)) {
+    return true;
+  }
+#endif
+
   return false;
 }
 
 nsresult nsContentUtils::GenerateUUIDInPlace(nsID& aUUID) {
   MOZ_ASSERT(sUUIDGenerator);
 
   nsresult rv = sUUIDGenerator->GenerateUUIDInPlace(&aUUID);
   if (NS_WARN_IF(NS_FAILED(rv))) {
--- a/dom/base/nsJSUtils.h
+++ b/dom/base/nsJSUtils.h
@@ -10,16 +10,17 @@
 /**
  * This is not a generated file. It contains common utility functions
  * invoked from the JavaScript code generated from IDL interfaces.
  * The goal of the utility functions is to cut down on the size of
  * the generated code itself.
  */
 
 #include "mozilla/Assertions.h"
+#include "mozilla/StaticPrefs.h"
 
 #include "GeckoProfiler.h"
 #include "jsapi.h"
 #include "jsfriendapi.h"
 #include "js/Conversions.h"
 #include "js/SourceText.h"
 #include "js/StableStringChars.h"
 #include "nsString.h"
@@ -190,16 +191,25 @@ class nsJSUtils {
     //
     // The value returned in the mutable handle argument is part of the
     // compartment given as argument to the ExecutionContext constructor. If the
     // caller is in a different compartment, then the out-param value should be
     // wrapped by calling |JS_WrapValue|.
     MOZ_MUST_USE nsresult ExecScript(JS::MutableHandle<JS::Value> aRetValue);
   };
 
+  static bool BinASTEncodingEnabled()
+  {
+#ifdef JS_BUILD_BINAST
+    return mozilla::StaticPrefs::dom_script_loader_binast_encoding_enabled();
+#else
+    return false;
+#endif
+  }
+
   static nsresult CompileModule(JSContext* aCx,
                                 JS::SourceText<char16_t>& aSrcBuf,
                                 JS::Handle<JSObject*> aEvaluationGlobal,
                                 JS::CompileOptions& aCompileOptions,
                                 JS::MutableHandle<JSObject*> aModule);
 
   static nsresult InitModuleSourceElement(JSContext* aCx,
                                           JS::Handle<JSObject*> aModule,
--- a/dom/script/ScriptLoadHandler.cpp
+++ b/dom/script/ScriptLoadHandler.cpp
@@ -296,17 +296,17 @@ nsresult ScriptLoadHandler::EnsureKnownD
     if (altDataType.Equals(nsContentUtils::JSBytecodeMimeType())) {
       mRequest->SetBytecode();
       TRACE_FOR_TEST(mRequest->Element(), "scriptloader_load_bytecode");
       return NS_OK;
     }
     MOZ_ASSERT(altDataType.IsEmpty());
   }
 
-  if (ScriptLoader::BinASTEncodingEnabled()) {
+  if (nsJSUtils::BinASTEncodingEnabled()) {
     nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(req);
     if (httpChannel) {
       nsAutoCString mimeType;
       httpChannel->GetContentType(mimeType);
       if (mimeType.LowerCaseEqualsASCII(APPLICATION_JAVASCRIPT_BINAST)) {
         if (mRequest->ShouldAcceptBinASTEncoding()) {
           mRequest->SetBinASTSource();
           TRACE_FOR_TEST(mRequest->Element(), "scriptloader_load_source");
--- a/dom/script/ScriptLoader.cpp
+++ b/dom/script/ScriptLoader.cpp
@@ -1300,17 +1300,17 @@ nsresult ScriptLoader::StartLoad(ScriptL
       }
     }
   }
 
   nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
   if (httpChannel) {
     // HTTP content negotation has little value in this context.
     nsAutoCString acceptTypes("*/*");
-    if (BinASTEncodingEnabled() && aRequest->ShouldAcceptBinASTEncoding()) {
+    if (nsJSUtils::BinASTEncodingEnabled() && aRequest->ShouldAcceptBinASTEncoding()) {
       acceptTypes = APPLICATION_JAVASCRIPT_BINAST ", */*";
     }
     rv = httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
                                        acceptTypes, false);
     MOZ_ASSERT(NS_SUCCEEDED(rv));
 
     rv = httpChannel->SetReferrerWithPolicy(aRequest->mReferrer,
                                             aRequest->ReferrerPolicy());
--- a/dom/script/ScriptLoader.h
+++ b/dom/script/ScriptLoader.h
@@ -396,24 +396,16 @@ class ScriptLoader final : public nsISup
    * Abort the current stream, and re-start with a new load request from scratch
    * without requesting any alternate data. Returns NS_BINDING_RETARGETED on
    * success, as this error code is used to abort the input stream.
    */
   nsresult RestartLoad(ScriptLoadRequest* aRequest);
 
   void HandleLoadError(ScriptLoadRequest* aRequest, nsresult aResult);
 
-  static bool BinASTEncodingEnabled() {
-#ifdef JS_BUILD_BINAST
-    return StaticPrefs::dom_script_loader_binast_encoding_enabled();
-#else
-    return false;
-#endif
-  }
-
   /**
    * Process any pending requests asynchronously (i.e. off an event) if there
    * are any. Note that this is a no-op if there aren't any currently pending
    * requests.
    *
    * This function is virtual to allow cross-library calls to SetEnabled()
    */
   virtual void ProcessPendingRequestsAsync();
--- a/js/src/gc/GC.cpp
+++ b/js/src/gc/GC.cpp
@@ -5408,27 +5408,19 @@ static void SweepMisc(GCParallelTask* ta
     r->sweepObjectRealm();
     r->sweepRegExps();
   }
 }
 
 static void SweepCompressionTasks(GCParallelTask* task) {
   JSRuntime* runtime = task->runtime();
 
+  // Attach finished compression tasks.
   AutoLockHelperThreadState lock;
-
-  // Attach finished compression tasks.
-  auto& finished = HelperThreadState().compressionFinishedList(lock);
-  for (size_t i = 0; i < finished.length(); i++) {
-    if (finished[i]->runtimeMatches(runtime)) {
-      UniquePtr<SourceCompressionTask> compressionTask(std::move(finished[i]));
-      HelperThreadState().remove(finished, &i);
-      compressionTask->complete();
-    }
-  }
+  AttachFinishedCompressions(runtime, lock);
 
   // Sweep pending tasks that are holding onto should-be-dead ScriptSources.
   auto& pending = HelperThreadState().compressionPendingList(lock);
   for (size_t i = 0; i < pending.length(); i++) {
     if (pending[i]->shouldCancel()) {
       HelperThreadState().remove(pending, &i);
     }
   }
--- a/js/src/jsapi-tests/testScriptSourceCompression.cpp
+++ b/js/src/jsapi-tests/testScriptSourceCompression.cpp
@@ -13,25 +13,25 @@
 #include <stddef.h>   // size_t
 #include <stdint.h>   // uint32_t
 
 #include "jsapi.h"  // JS_FlattenString, JS_GC, JS_Get{Latin1,TwoByte}FlatStringChars, JS_GetStringLength, JS_ValueToFunction
 
 #include "js/CompilationAndEvaluation.h"  // JS::Evaluate
 #include "js/CompileOptions.h"            // JS::CompileOptions
 #include "js/Conversions.h"               // JS::ToString
-#include "js/GCAPI.h"                     // JS_GC
 #include "js/MemoryFunctions.h"           // JS_malloc
 #include "js/RootingAPI.h"                // JS::MutableHandle, JS::Rooted
 #include "js/SourceText.h"                // JS::SourceOwnership, JS::SourceText
 #include "js/UniquePtr.h"                 // js::UniquePtr
 #include "js/Utility.h"                   // JS::FreePolicy
 #include "js/Value.h"  // JS::NullValue, JS::ObjectValue, JS::Value
 #include "jsapi-tests/tests.h"
 #include "vm/Compression.h"  // js::Compressor::CHUNK_SIZE
+#include "vm/HelperThreads.h"  // js::RunPendingSourceCompressions
 #include "vm/JSFunction.h"   // JSFunction::getOrCreateScript
 #include "vm/JSScript.h"  // JSScript, js::ScriptSource::MinimumCompressibleLength
 
 using mozilla::ArrayLength;
 
 struct JSContext;
 class JSString;
 
@@ -94,20 +94,17 @@ static JSFunction* EvaluateChars(JSConte
   return JS_ValueToFunction(cx, rval);
 }
 
 static void CompressSourceSync(JS::Handle<JSFunction*> fun, JSContext* cx) {
   JS::Rooted<JSScript*> script(cx, JSFunction::getOrCreateScript(cx, fun));
   MOZ_RELEASE_ASSERT(script);
   MOZ_RELEASE_ASSERT(script->scriptSource()->hasSourceText());
 
-  // Hoodoo voodoo that presently compresses |fun|'s source.
-  // XXX Be a mensch: replace this with targeted actions to do this!
-  JS_GC(cx);
-  JS_GC(cx);
+  js::RunPendingSourceCompressions(cx->runtime());
 
   // XXX Temporarily don't assert this because not all builds guarantee it.
   //     We test behavior that is *affected in its implementation* by this
   //     condition, but is the same whether or not this assertion actually
   //     holds.  We'll figure out how to guarantee it in a followup change.
   // MOZ_RELEASE_ASSERT(script->scriptSource()->hasCompressedSource());
 }
 
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -2259,16 +2259,44 @@ void js::CancelOffThreadCompressions(JSR
     HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER);
   }
 
   // Clean up finished tasks.
   ClearCompressionTaskList(HelperThreadState().compressionFinishedList(lock),
                            runtime);
 }
 
+void js::AttachFinishedCompressions(JSRuntime* runtime, AutoLockHelperThreadState& lock) {
+  auto& finished = HelperThreadState().compressionFinishedList(lock);
+  for (size_t i = 0; i < finished.length(); i++) {
+    if (finished[i]->runtimeMatches(runtime)) {
+      UniquePtr<SourceCompressionTask> compressionTask(std::move(finished[i]));
+      HelperThreadState().remove(finished, &i);
+      compressionTask->complete();
+    }
+  }
+}
+
+void js::RunPendingSourceCompressions(JSRuntime* runtime) {
+  AutoLockHelperThreadState lock;
+
+  if (!HelperThreadState().threads) {
+    return;
+  }
+
+  HelperThreadState().startHandlingCompressionTasks(lock);
+
+  // Wait for all in-process compression tasks to complete.
+  while (!HelperThreadState().compressionWorklist(lock).empty()) {
+    HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER);
+  }
+
+  AttachFinishedCompressions(runtime, lock);
+}
+
 void PromiseHelperTask::executeAndResolveAndDestroy(JSContext* cx) {
   execute();
   run(cx, JS::Dispatchable::NotShuttingDown);
 }
 
 bool js::StartOffThreadPromiseHelperTask(JSContext* cx,
                                          UniquePtr<PromiseHelperTask> task) {
   // Execute synchronously if there are no helper threads.
--- a/js/src/vm/HelperThreads.h
+++ b/js/src/vm/HelperThreads.h
@@ -657,16 +657,22 @@ struct AutoEnqueuePendingParseTasksAfter
 // Enqueue a compression job to be processed if there's a major GC.
 bool EnqueueOffThreadCompression(JSContext* cx,
                                  UniquePtr<SourceCompressionTask> task);
 
 // Cancel all scheduled, in progress, or finished compression tasks for
 // runtime.
 void CancelOffThreadCompressions(JSRuntime* runtime);
 
+void AttachFinishedCompressions(JSRuntime* runtime,
+                                AutoLockHelperThreadState& lock);
+
+// Run all pending source compression tasks synchronously, for testing purposes
+void RunPendingSourceCompressions(JSRuntime* runtime);
+
 class MOZ_RAII AutoLockHelperThreadState : public LockGuard<Mutex> {
   using Base = LockGuard<Mutex>;
 
   MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 
  public:
   explicit AutoLockHelperThreadState(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM)
       : Base(HelperThreadState().helperLock) {
--- a/parser/html/nsHtml5StreamParser.cpp
+++ b/parser/html/nsHtml5StreamParser.cpp
@@ -31,18 +31,20 @@
 #include "nsIWyciwygChannel.h"
 #include "nsIThreadRetargetableRequest.h"
 #include "nsPrintfCString.h"
 #include "nsNetUtil.h"
 #include "nsUdetXPCOMWrapper.h"
 #include "nsXULAppAPI.h"
 #include "mozilla/SchedulerGroup.h"
 #include "nsJSEnvironment.h"
+#include "mozilla/dom/Document.h"
 
 using namespace mozilla;
+using namespace mozilla::dom;
 
 /*
  * Note that nsHtml5StreamParser implements cycle collecting AddRef and
  * Release. Therefore, nsHtml5StreamParser must never be refcounted from
  * the parser thread!
  *
  * To work around this limitation, runnables posted by the main thread to the
  * parser thread hold their reference to the stream parser in an
@@ -110,16 +112,30 @@ class nsHtml5ExecutorFlusher : public Ru
  private:
   RefPtr<nsHtml5TreeOpExecutor> mExecutor;
 
  public:
   explicit nsHtml5ExecutorFlusher(nsHtml5TreeOpExecutor* aExecutor)
       : Runnable("nsHtml5ExecutorFlusher"), mExecutor(aExecutor) {}
   NS_IMETHOD Run() override {
     if (!mExecutor->isInList()) {
+      Document* doc = mExecutor->GetDocument();
+      if (XRE_IsContentProcess() &&
+          nsContentUtils::
+              HighPriorityEventPendingForTopLevelDocumentBeforeContentfulPaint(
+                  doc)) {
+        // Possible early paint pending, reuse the runnable and try to
+        // call RunFlushLoop later.
+        nsCOMPtr<nsIRunnable> flusher = this;
+        if (NS_SUCCEEDED(
+                doc->Dispatch(TaskCategory::Network, flusher.forget()))) {
+          PROFILER_ADD_MARKER("HighPrio blocking parser flushing(1)", DOM);
+          return NS_OK;
+        }
+      }
       mExecutor->RunFlushLoop();
     }
     return NS_OK;
   }
 };
 
 class nsHtml5LoadFlusher : public Runnable {
  private:
--- a/parser/html/nsHtml5TreeOpExecutor.cpp
+++ b/parser/html/nsHtml5TreeOpExecutor.cpp
@@ -46,16 +46,30 @@ NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHER
 class nsHtml5ExecutorReflusher : public Runnable {
  private:
   RefPtr<nsHtml5TreeOpExecutor> mExecutor;
 
  public:
   explicit nsHtml5ExecutorReflusher(nsHtml5TreeOpExecutor* aExecutor)
       : mozilla::Runnable("nsHtml5ExecutorReflusher"), mExecutor(aExecutor) {}
   NS_IMETHOD Run() override {
+    Document* doc = mExecutor->GetDocument();
+    if (XRE_IsContentProcess() &&
+        nsContentUtils::
+            HighPriorityEventPendingForTopLevelDocumentBeforeContentfulPaint(
+                doc)) {
+      // Possible early paint pending, reuse the runnable and try to
+      // call RunFlushLoop later.
+      nsCOMPtr<nsIRunnable> flusher = this;
+      if (NS_SUCCEEDED(
+              doc->Dispatch(TaskCategory::Network, flusher.forget()))) {
+        PROFILER_ADD_MARKER("HighPrio blocking parser flushing(2)", DOM);
+        return NS_OK;
+      }
+    }
     mExecutor->RunFlushLoop();
     return NS_OK;
   }
 };
 
 class MOZ_RAII nsHtml5AutoFlush final {
  private:
   RefPtr<nsHtml5TreeOpExecutor> mExecutor;
--- a/testing/geckodriver/doc/Capabilities.md
+++ b/testing/geckodriver/doc/Capabilities.md
@@ -5,51 +5,57 @@ geckodriver has a few capabilities that 
 
 
 `moz:firefoxOptions`
 --------------------
 
 A dictionary used to define options which control how Firefox gets
 started and run. It may contain any of the following fields:
 
+<style type="text/css">
+  table { width: 100%; margin-bottom: 2em; }
+  table, th, td { border: solid gray 1px; }
+  td, th { padding: 5px 10px; }
+</style>
+
 <table>
  <thead>
   <tr>
    <th>Name
    <th>Type
    <th>Description
   </tr>
  </thead>
 
  <tr id=capability-binary>
   <td><code>binary</code>
-  <td>string
+  <td align="center">string
   <td>Absolute path of the Firefox binary,
    e.g. <code>/usr/bin/firefox</code>
    or <code>/Applications/Firefox.app/Contents/MacOS/firefox</code>,
    to select which custom browser binary to use.
    If left undefined geckodriver will attempt
    to deduce the default location of Firefox
    on the current system.
  </tr>
 
  <tr id=capability-args>
   <td><code>args</code>
-  <td>array&nbsp;of&nbsp;strings
+  <td align="center">array&nbsp;of&nbsp;strings
   <td><p>Command line arguments to pass to the Firefox binary.
    These must include the leading dash (<code>-</code>) where required,
    e.g. <code>["-devtools"]</code>.
 
    <p>To have geckodriver pick up an existing profile on the filesystem,
     you may pass <code>["-profile", "/path/to/profile"]</code>.
  </tr>
 
  <tr id=capability-profile>
   <td><code>profile</code>
-  <td>string
+  <td align="center">string
   <td><p>Base64-encoded ZIP of a profile directory to use for the Firefox instance.
    This may be used to e.g. install extensions or custom certificates,
    but for setting custom preferences
    we recommend using the <a href=#capability-prefs><code>prefs</code></a> entry
    instead of passing a profile.
 
    <p>Profiles are created in the system’s temporary folder.
     This is also where the encoded profile is extracted
@@ -62,26 +68,26 @@ started and run. It may contain any of t
 
    <p>To have geckodriver pick up an <em>existing profile</em> on the filesystem,
     please set the <a href=#capability-args><code>args</code></a> field
     to <code>{"args": ["-profile", "/path/to/your/profile"]}</code>.
  </tr>
 
  <tr id=capability-log>
   <td><code>log</code>
-  <td><a href=#log-object><code>log</code></a>&nbsp;object
+  <td align="center"><a href=#log-object><code>log</code></a>&nbsp;object
   <td>To increase the logging verbosity of geckodriver and Firefox,
    you may pass a <a href=#log-object><code>log</code> object</a>
    that may look like <code>{"log": {"level": "trace"}}</code>
    to include all trace-level logs and above.
  </tr>
 
  <tr id=capability-prefs>
   <td><code>prefs</code>
-  <td><a href=#prefs-object><code>prefs</code></a>&nbsp;object
+  <td align="center"><a href=#prefs-object><code>prefs</code></a>&nbsp;object
   <td>Map of preference name to preference value, which can be a
    string, a boolean or an integer.
  </tr>
 </table>
 
 
 `moz:useNonSpecCompliantPointerOrigin`
 --------------------------------------
@@ -136,17 +142,17 @@ it will be removed once the interactabil
    <th>Name
    <th>Type
    <th>Description
   </tr>
  </thead>
 
  <tr>
   <td><code>level</code>
-  <td>string
+  <td align="center">string
   <td>Set the level of verbosity of geckodriver and Firefox.
    Available levels are <code>trace</code>,
    <code>debug</code>, <code>config</code>,
    <code>info</code>, <code>warn</code>,
    <code>error</code>, and <code>fatal</code>.
    If left undefined the default is <code>info</code>.
    The value is treated case-insensitively.
  </tr>
@@ -162,17 +168,17 @@ it will be removed once the interactabil
    <th>Name
    <th>Type
    <th>Description
   </tr>
  </thead>
 
  <tr>
   <td><var>preference name</var>
-  <td>string, number, boolean
+  <td align="center">string, number, boolean
   <td>One entry per preference to override.
  </tr>
 </table>
 
 
 Capabilities example
 ====================
 
--- a/testing/geckodriver/doc/Support.md
+++ b/testing/geckodriver/doc/Support.md
@@ -1,34 +1,39 @@
 Supported platforms
 ===================
 
 The following table shows a mapping between [geckodriver releases],
-supported versions of Firefox, and required Selenium version:
+and required versions of Selenium and Firefox:
 
 <style type="text/css">
   table { width: 100%; margin-bottom: 2em; }
   table, th, td { border: solid gray 1px; }
-  td { padding: 5px 10px; text-align: center; }
+  td, th { padding: 5px 10px; text-align: center; }
 </style>
 
 <table>
  <thead>
   <tr>
     <th rowspan="2">geckodriver
     <th rowspan="2">Selenium
-    <th colspan="2">Supported versions of Firefox
+    <th colspan="2">Firefox
   </tr>
   <tr>
     <th>min
     <th>max
   </tr>
  </thead>
 
  <tr>
+  <td>0.24.0
+  <td>≥ 3.11 (3.14 Python)
+  <td>57
+  <td>n/a
+ <tr>
   <td>0.23.0
   <td>≥ 3.11 (3.14 Python)
   <td>57
   <td>n/a
  <tr>
   <td>0.22.0
   <td>≥ 3.11 (3.14 Python)
   <td>57