Merge inbound to mozilla-central. a=merge
authorBrindusan Cristian <cbrindusan@mozilla.com>
Mon, 22 Oct 2018 00:39:42 +0300
changeset 500779 b2fa4b07f6f7e489047808bd0238301ea943b4b3
parent 500770 c43c91d2b97b1d1b230efdabce8b67d52e5cda27 (current diff)
parent 500778 bacc9594a048c0cde34745393361c55bcc159f39 (diff)
child 500780 a4333f1bf9796e90f3f8a6b719af437608222abf
child 500783 50a3c9a567e3f8f6285ba6bb0c0b7e5e6a4358e9
push id1864
push userffxbld-merge
push dateMon, 03 Dec 2018 15:51:40 +0000
treeherdermozilla-release@f040763d99ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone64.0a1
first release with
nightly linux32
b2fa4b07f6f7 / 64.0a1 / 20181021220134 / files
nightly linux64
b2fa4b07f6f7 / 64.0a1 / 20181021220134 / files
nightly mac
b2fa4b07f6f7 / 64.0a1 / 20181021220134 / files
nightly win32
b2fa4b07f6f7 / 64.0a1 / 20181021220134 / files
nightly win64
b2fa4b07f6f7 / 64.0a1 / 20181021220134 / 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/devtools/client/debugger/new/test/mochitest/examples/doc_rr_basic.html
+++ b/devtools/client/debugger/new/test/mochitest/examples/doc_rr_basic.html
@@ -1,11 +1,11 @@
 <html lang="en" dir="ltr">
 <body>
-<div id="maindiv">Hello World!</div>
+<div id="maindiv" style="padding-top:50px">Hello World!</div>
 </body>
 <script>
 const cpmm = SpecialPowers.Services.cpmm;
 function recordingFinished() {
   cpmm.sendAsyncMessage("RecordingFinished");
 }
 var number = 0;
 function f() {
--- a/devtools/client/debugger/new/test/mochitest/examples/doc_rr_continuous.html
+++ b/devtools/client/debugger/new/test/mochitest/examples/doc_rr_continuous.html
@@ -1,11 +1,11 @@
 <html lang="en" dir="ltr">
 <body>
-<div id="maindiv">Hello World!</div>
+<div id="maindiv" style="padding-top:50px">Hello World!</div>
 </body>
 <script>
 var number = 0;
 function f() {
   updateNumber();
   window.setTimeout(f, 1);
 }
 function updateNumber() {
--- a/devtools/client/debugger/new/test/mochitest/examples/doc_rr_error.html
+++ b/devtools/client/debugger/new/test/mochitest/examples/doc_rr_error.html
@@ -1,11 +1,11 @@
 <html lang="en" dir="ltr">
 <body>
-<div id="maindiv">Hello World!</div>
+<div id="maindiv" style="padding-top:50px">Hello World!</div>
 </body>
 <script>
 const cpmm = SpecialPowers.Services.cpmm;
 function recordingFinished() {
   cpmm.sendAsyncMessage("RecordingFinished");
 }
 var number = 0;
 function f() {
--- a/devtools/client/webconsole/components/MessageContainer.js
+++ b/devtools/client/webconsole/components/MessageContainer.js
@@ -23,17 +23,17 @@ const componentMap = new Map([
   ["NetworkEventMessage", require("./message-types/NetworkEventMessage")],
   ["PageError", require("./message-types/PageError")],
 ]);
 
 function isPaused({ getMessage, pausedExecutionPoint }) {
   const message = getMessage();
   return pausedExecutionPoint
   && message.executionPoint
-    && pausedExecutionPoint.checkpoint === message.executionPoint.checkpoint;
+    && pausedExecutionPoint.progress === message.executionPoint.progress;
 }
 
 class MessageContainer extends Component {
   static get propTypes() {
     return {
       messageId: PropTypes.string.isRequired,
       open: PropTypes.bool.isRequired,
       serviceContainer: PropTypes.object.isRequired,
--- a/devtools/server/actors/replay/graphics.js
+++ b/devtools/server/actors/replay/graphics.js
@@ -9,25 +9,127 @@
 // which are connected to the compositor in the UI process in the usual way.
 // We need to update the contents of the document to draw the raw graphics data
 // provided by the child process.
 
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
-function updateWindow(window, buffer, width, height, hadFailure) {
+const CC = Components.Constructor;
+
+// Create a sandbox with the resources we need. require() doesn't work here.
+const sandbox = Cu.Sandbox(CC("@mozilla.org/systemprincipal;1", "nsIPrincipal")());
+Cu.evalInSandbox(
+  "Components.utils.import('resource://gre/modules/jsdebugger.jsm');" +
+  "addDebuggerToGlobal(this);",
+  sandbox
+);
+const RecordReplayControl = sandbox.RecordReplayControl;
+
+// State for dragging the overlay.
+let dragx, dragy;
+
+// Windows in the middleman process are initially set up as about:blank pages.
+// This method fills them in with a canvas filling the tab, and an overlay that
+// can be displayed over that canvas.
+function setupContents(window) {
+  // The middlemanOverlay element is shown when the active child is replaying.
+  const overlay = window.middlemanOverlay = window.document.createElement("div");
+  overlay.style.position = "absolute";
+  overlay.style.border = "medium solid #000000";
+  overlay.style.backgroundColor = "#BBCCCC";
+
+  // The middlemanPosition element is contained in the overlay and shows the
+  // current position in the recording.
+  const position = window.middlemanPosition = window.document.createElement("div");
+  position.innerText = "";
+  position.style.textAlign = "center";
+  position.style.padding = "5px 5px 0px 5px";
+  overlay.appendChild(position);
+
+  // The middlemanProgressBar element is contained in the overlay and shows any
+  // progress made on the current operation.
+  const progressBar =
+    window.middlemanProgressBar = window.document.createElement("canvas");
+  progressBar.width = 100;
+  progressBar.height = 5;
+  progressBar.getContext("2d").fillStyle = "white";
+  progressBar.getContext("2d").fillRect(0, 0, 100, 5);
+  progressBar.style.padding = "5px 5px 5px 5px";
+
+  overlay.appendChild(progressBar);
+  window.document.body.prepend(overlay);
+
+  overlay.onmousedown = window.middlemanMouseDown = function(e) {
+    e.preventDefault();
+    dragx = e.clientX;
+    dragy = e.clientY;
+    window.document.onmouseup = window.middlemanMouseUp;
+    window.document.onmousemove = window.middlemanMouseMove;
+  };
+
+  window.middlemanMouseMove = function(e) {
+    // Compute the new position of the overlay per the current drag operation.
+    // Don't allow the overlay to be dragged outside the window.
+    e.preventDefault();
+    const canvas = window.middlemanCanvas;
+    let diffx = e.clientX - dragx;
+    let diffy = e.clientY - dragy;
+    const newTop = () => overlay.offsetTop + diffy;
+    if (newTop() < 0) {
+      diffy -= newTop();
+    }
+    const maxTop = canvas.height / window.devicePixelRatio;
+    if (newTop() + overlay.offsetHeight >= maxTop) {
+      diffy -= newTop() + overlay.offsetHeight - maxTop;
+    }
+    overlay.style.top = newTop() + "px";
+    const newLeft = () => overlay.offsetLeft + diffx;
+    if (newLeft() < 0) {
+      diffx -= newLeft();
+    }
+    const maxLeft = canvas.width / window.devicePixelRatio;
+    if (newLeft() + overlay.offsetWidth >= maxLeft) {
+      diffx -= newLeft() + overlay.offsetWidth - maxLeft;
+    }
+    overlay.style.left = (overlay.offsetLeft + diffx) + "px";
+    dragx += diffx;
+    dragy += diffy;
+  };
+
+  window.middlemanMouseUp = function(e) {
+    window.document.onmouseup = null;
+    window.document.onmousemove = null;
+  };
+
+  // The middlemanCanvas element fills the tab's contents.
+  const canvas = window.middlemanCanvas = window.document.createElement("canvas");
+  canvas.style.position = "absolute";
+  window.document.body.style.margin = "0px";
+  window.document.body.prepend(canvas);
+}
+
+function getOverlay(window) {
+  if (!window.middlemanOverlay) {
+    setupContents(window);
+  }
+  return window.middlemanOverlay;
+}
+
+function getCanvas(window) {
+  if (!window.middlemanCanvas) {
+    setupContents(window);
+  }
+  return window.middlemanCanvas;
+}
+
+function updateWindowCanvas(window, buffer, width, height, hadFailure) {
   // Make sure the window has a canvas filling the screen.
-  let canvas = window.middlemanCanvas;
-  if (!canvas) {
-    canvas = window.document.createElement("canvas");
-    window.document.body.style.margin = "0px";
-    window.document.body.insertBefore(canvas, window.document.body.firstChild);
-    window.middlemanCanvas = canvas;
-  }
+  const canvas = getCanvas(window);
 
   canvas.width = width;
   canvas.height = height;
 
   // If there is a scale for this window, then the graphics will already have
   // been scaled in the child process. To avoid scaling the graphics twice,
   // transform the canvas to undo the scaling.
   const scale = window.devicePixelRatio;
@@ -48,28 +150,56 @@ function updateWindow(window, buffer, wi
   if (hadFailure) {
     cx.fillStyle = "red";
     cx.font = "48px serif";
     cx.fillText("PAINT FAILURE", 10, 50);
   }
 
   // Make recording/replaying tabs easier to differentiate from other tabs.
   window.document.title = "RECORD/REPLAY";
+
+  updateWindowOverlay(window);
 }
 
 // Entry point for when we have some new graphics data from the child process
 // to draw.
 // eslint-disable-next-line no-unused-vars
-function Update(buffer, width, height, hadFailure) {
+function UpdateCanvas(buffer, width, height, hadFailure) {
   try {
     // Paint to all windows we can find. Hopefully there is only one.
     for (const window of Services.ww.getWindowEnumerator()) {
-      updateWindow(window, buffer, width, height, hadFailure);
+      updateWindowCanvas(window, buffer, width, height, hadFailure);
     }
   } catch (e) {
-    dump("Middleman Graphics Update Exception: " + e + "\n");
+    dump("Middleman Graphics UpdateCanvas Exception: " + e + "\n");
+  }
+}
+
+function updateWindowOverlay(window) {
+  const overlay = getOverlay(window);
+
+  const position = RecordReplayControl.recordingPosition();
+  if (position === undefined) {
+    overlay.style.visibility = "hidden";
+  } else {
+    overlay.style.visibility = "visible";
+    window.middlemanPosition.innerText = (Math.round(position * 10000) / 100) + "%";
+  }
+}
+
+// Entry point for when we need to update the overlay's contents or visibility.
+// eslint-disable-next-line no-unused-vars
+function UpdateOverlay() {
+  try {
+    // Paint to all windows we can find. Hopefully there is only one.
+    for (const window of Services.ww.getWindowEnumerator()) {
+      updateWindowOverlay(window);
+    }
+  } catch (e) {
+    dump("Middleman Graphics UpdateOverlay Exception: " + e + "\n");
   }
 }
 
 // eslint-disable-next-line no-unused-vars
 var EXPORTED_SYMBOLS = [
-  "Update",
+  "UpdateCanvas",
+  "UpdateOverlay",
 ];
--- a/devtools/server/actors/targets/browsing-context.js
+++ b/devtools/server/actors/targets/browsing-context.js
@@ -1178,31 +1178,40 @@ const browsingContextTargetPrototype = {
    * Prepare to enter a nested event loop by disabling debuggee events.
    */
   preNest() {
     if (!this.window) {
       // The browsing context is already closed.
       return;
     }
     const windowUtils = this.window.windowUtils;
-    windowUtils.suppressEventHandling(true);
+
+    // Events are not suppressed when running in the middleman, as we are in a
+    // different process from the debuggee and may want to process events in
+    // the middleman for e.g. the overlay drawn when rewinding.
+    if (Debugger.recordReplayProcessKind() != "Middleman") {
+      windowUtils.suppressEventHandling(true);
+    }
+
     windowUtils.suspendTimeouts();
   },
 
   /**
    * Prepare to exit a nested event loop by enabling debuggee events.
    */
   postNest(nestData) {
     if (!this.window) {
       // The browsing context is already closed.
       return;
     }
     const windowUtils = this.window.windowUtils;
     windowUtils.resumeTimeouts();
-    windowUtils.suppressEventHandling(false);
+    if (Debugger.recordReplayProcessKind() != "Middleman") {
+      windowUtils.suppressEventHandling(false);
+    }
   },
 
   _changeTopLevelDocument(window) {
     // Fake a will-navigate on the previous document
     // to let a chance to unregister it
     this._willNavigate(this.window, window.location.href, null, true);
 
     this._windowDestroyed(this.window, null, true);
--- a/toolkit/recordreplay/ipc/JSControl.cpp
+++ b/toolkit/recordreplay/ipc/JSControl.cpp
@@ -427,16 +427,31 @@ Middleman_HadRepaintFailure(JSContext* a
   CallArgs args = CallArgsFromVp(aArgc, aVp);
 
   parent::UpdateGraphicsInUIProcess(nullptr);
 
   args.rval().setUndefined();
   return true;
 }
 
+static bool
+Middleman_RecordingPosition(JSContext* aCx, unsigned aArgc, Value* aVp)
+{
+  CallArgs args = CallArgsFromVp(aArgc, aVp);
+
+  Maybe<double> recordingPosition = parent::GetRecordingPosition();
+
+  if (recordingPosition.isSome()) {
+    args.rval().setNumber(recordingPosition.ref());
+  } else {
+    args.rval().setUndefined();
+  }
+  return true;
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // Devtools Sandbox
 ///////////////////////////////////////////////////////////////////////////////
 
 static PersistentRootedObject* gDevtoolsSandbox;
 
 // URL of the root script that runs when recording/replaying.
 #define ReplayScriptURL "resource://devtools/server/actors/replay/replay.js"
@@ -907,16 +922,17 @@ static const JSFunctionSpec gMiddlemanMe
   JS_FN("timeWarp", Middleman_TimeWarp, 1, 0),
   JS_FN("pause", Middleman_Pause, 0, 0),
   JS_FN("sendRequest", Middleman_SendRequest, 1, 0),
   JS_FN("setBreakpoint", Middleman_SetBreakpoint, 2, 0),
   JS_FN("clearBreakpoint", Middleman_ClearBreakpoint, 1, 0),
   JS_FN("maybeSwitchToReplayingChild", Middleman_MaybeSwitchToReplayingChild, 0, 0),
   JS_FN("hadRepaint", Middleman_HadRepaint, 2, 0),
   JS_FN("hadRepaintFailure", Middleman_HadRepaintFailure, 0, 0),
+  JS_FN("recordingPosition", Middleman_RecordingPosition, 0, 0),
   JS_FS_END
 };
 
 static const JSFunctionSpec gRecordReplayMethods[] = {
   JS_FN("areThreadEventsDisallowed", RecordReplay_AreThreadEventsDisallowed, 0, 0),
   JS_FN("maybeDivergeFromRecording", RecordReplay_MaybeDivergeFromRecording, 0, 0),
   JS_FN("advanceProgressCounter", RecordReplay_AdvanceProgressCounter, 0, 0),
   JS_FN("positionHit", RecordReplay_PositionHit, 1, 0),
--- a/toolkit/recordreplay/ipc/ParentForwarding.cpp
+++ b/toolkit/recordreplay/ipc/ParentForwarding.cpp
@@ -87,16 +87,24 @@ HandleMessageInMiddleman(ipc::Side aSide
       type == dom::PContent::Msg_SaveRecording__ID ||
       // Teardown that should only happen in the middleman.
       type == dom::PContent::Msg_Shutdown__ID) {
     ipc::IProtocol::Result r = dom::ContentChild::GetSingleton()->PContentChild::OnMessageReceived(aMessage);
     MOZ_RELEASE_ASSERT(r == ipc::IProtocol::MsgProcessed);
     return true;
   }
 
+  // Send input events to the middleman when the active child is replaying,
+  // so that UI elements such as the replay overlay can be interacted with.
+  if (!ActiveChildIsRecording() && nsContentUtils::IsMessageInputEvent(aMessage)) {
+    ipc::IProtocol::Result r = dom::ContentChild::GetSingleton()->PContentChild::OnMessageReceived(aMessage);
+    MOZ_RELEASE_ASSERT(r == ipc::IProtocol::MsgProcessed);
+    return true;
+  }
+
   // The content process has its own compositor, so compositor messages from
   // the UI process should only be handled in the middleman.
   if (type >= layers::PCompositorBridge::PCompositorBridgeStart &&
       type <= layers::PCompositorBridge::PCompositorBridgeEnd) {
     layers::CompositorBridgeChild* compositorChild = layers::CompositorBridgeChild::Get();
     ipc::IProtocol::Result r = compositorChild->OnMessageReceived(aMessage);
     MOZ_RELEASE_ASSERT(r == ipc::IProtocol::MsgProcessed);
     return true;
--- a/toolkit/recordreplay/ipc/ParentGraphics.cpp
+++ b/toolkit/recordreplay/ipc/ParentGraphics.cpp
@@ -148,19 +148,16 @@ UpdateGraphicsInUIProcess(const PaintMes
   gLastExplicitPaint = nullptr;
   gLastCheckpoint = CheckpointId::Invalid;
 
   // Make sure there is a sandbox which is running the graphics JS module.
   if (!gGraphicsSandbox) {
     InitGraphicsSandbox();
   }
 
-  AutoSafeJSContext cx;
-  JSAutoRealm ar(cx, *gGraphicsSandbox);
-
   size_t width = gLastPaintWidth;
   size_t height = gLastPaintHeight;
   size_t stride = layers::ImageDataSerializer::ComputeRGBStride(gSurfaceFormat, width);
 
   // Make sure the width and height are appropriately sized.
   CheckedInt<size_t> scaledWidth = CheckedInt<size_t>(width) * 4;
   CheckedInt<size_t> scaledHeight = CheckedInt<size_t>(height) * stride;
   MOZ_RELEASE_ASSERT(scaledWidth.isValid() && scaledWidth.value() <= stride);
@@ -179,33 +176,53 @@ UpdateGraphicsInUIProcess(const PaintMes
     memory = gBufferMemory;
     for (size_t y = 0; y < height; y++) {
       char* src = (char*)gGraphicsMemory + y * stride;
       char* dst = (char*)gBufferMemory + y * width * 4;
       memcpy(dst, src, width * 4);
     }
   }
 
+  AutoSafeJSContext cx;
+  JSAutoRealm ar(cx, *gGraphicsSandbox);
+
   JSObject* bufferObject =
     JS_NewArrayBufferWithExternalContents(cx, width * height * 4, memory);
   MOZ_RELEASE_ASSERT(bufferObject);
 
   JS::AutoValueArray<4> args(cx);
   args[0].setObject(*bufferObject);
   args[1].setInt32(width);
   args[2].setInt32(height);
   args[3].setBoolean(hadFailure);
 
   // Call into the graphics module to update the canvas it manages.
   RootedValue rval(cx);
-  if (!JS_CallFunctionName(cx, *gGraphicsSandbox, "Update", args, &rval)) {
+  if (!JS_CallFunctionName(cx, *gGraphicsSandbox, "UpdateCanvas", args, &rval)) {
     MOZ_CRASH("UpdateGraphicsInUIProcess");
   }
 }
 
+void
+UpdateGraphicsOverlay()
+{
+  if (!gLastPaintWidth || !gLastPaintHeight) {
+    return;
+  }
+
+  AutoSafeJSContext cx;
+  JSAutoRealm ar(cx, *gGraphicsSandbox);
+
+  RootedValue rval(cx);
+  if (!JS_CallFunctionName(cx, *gGraphicsSandbox, "UpdateOverlay",
+                           JS::HandleValueArray::empty(), &rval)) {
+    MOZ_CRASH("UpdateGraphicsOverlay");
+  }
+}
+
 static void
 MaybeTriggerExplicitPaint()
 {
   if (gLastExplicitPaint && gLastExplicitPaint->mCheckpointId == gLastCheckpoint) {
     UpdateGraphicsInUIProcess(gLastExplicitPaint.get());
   }
 }
 
--- a/toolkit/recordreplay/ipc/ParentIPC.cpp
+++ b/toolkit/recordreplay/ipc/ParentIPC.cpp
@@ -278,17 +278,17 @@ public:
       MOZ_CRASH("Unexpected message");
     }
   }
 };
 
 bool
 ActiveChildIsRecording()
 {
-  return gActiveChild->IsRecording();
+  return gActiveChild && gActiveChild->IsRecording();
 }
 
 ChildProcessInfo*
 ActiveRecordingChild()
 {
   MOZ_RELEASE_ASSERT(ActiveChildIsRecording());
   return gActiveChild;
 }
@@ -606,16 +606,22 @@ SwitchActiveChild(ChildProcessInfo* aChi
   }
   aChild->SetRole(MakeUnique<ChildRoleActive>());
   if (oldActiveChild->IsRecording()) {
     oldActiveChild->SetRole(MakeUnique<ChildRoleInert>());
   } else {
     oldActiveChild->RecoverToCheckpoint(oldActiveChild->MostRecentSavedCheckpoint());
     oldActiveChild->SetRole(MakeUnique<ChildRoleStandby>());
   }
+
+  // The graphics overlay is affected when we switch between recording and
+  // replaying children.
+  if (aChild->IsRecording() != oldActiveChild->IsRecording()) {
+    UpdateGraphicsOverlay();
+  }
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 // Preferences
 ///////////////////////////////////////////////////////////////////////////////
 
 static bool gPreferencesLoaded;
 static bool gRewindingEnabled;
@@ -852,16 +858,29 @@ MaybeSwitchToReplayingChild()
     FlushRecording();
     size_t checkpoint = gActiveChild->RewindTargetCheckpoint();
     ChildProcessInfo* child =
       OtherReplayingChild(ReplayingChildResponsibleForSavingCheckpoint(checkpoint));
     SwitchActiveChild(child);
   }
 }
 
+Maybe<double>
+GetRecordingPosition()
+{
+  if (gActiveChild->IsRecording()) {
+    return Nothing();
+  }
+
+  // Get the fraction of the recording that the active child has reached so far.
+  double fraction = (gActiveChild->MostRecentCheckpoint() - CheckpointId::First)
+                  / (double) (gCheckpointTimes.length() - CheckpointId::First);
+  return Some(fraction);
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // Initialization
 ///////////////////////////////////////////////////////////////////////////////
 
 // Message loop processed on the main thread.
 static MessageLoop* gMainThreadMessageLoop;
 
 MessageLoop*
@@ -1163,16 +1182,20 @@ ResumeBeforeWaitingForIPDLReply()
 }
 
 static void
 RecvHitCheckpoint(const HitCheckpointMessage& aMsg)
 {
   UpdateCheckpointTimes(aMsg);
   MaybeUpdateGraphicsAtCheckpoint(aMsg.mCheckpointId);
 
+  if (!gActiveChild->IsRecording()) {
+    UpdateGraphicsOverlay();
+  }
+
   // Resume either forwards or backwards. Break the resume off into a separate
   // runnable, to avoid starving any code already on the stack and waiting for
   // the process to pause. Immediately resume if the main thread is blocked.
   if (MainThreadIsWaitingForIPDLReply()) {
     MOZ_RELEASE_ASSERT(gChildExecuteForward);
     Resume(true);
   } else if (!gResumeForwardOrBackward) {
     gResumeForwardOrBackward = true;
--- a/toolkit/recordreplay/ipc/ParentInternal.h
+++ b/toolkit/recordreplay/ipc/ParentInternal.h
@@ -71,30 +71,37 @@ void SendRequest(const js::CharBuffer& a
 // Set or clear a breakpoint in the child process.
 void SetBreakpoint(size_t aId, const js::BreakpointPosition& aPosition);
 
 // If possible, make sure the active child is replaying, and that requests
 // which might trigger an unhandled divergence can be processed (recording
 // children cannot process such requests).
 void MaybeSwitchToReplayingChild();
 
+// If the active child is replaying, get its fractional (range [0,1]) position
+// in the recording. If the active child is recording, return Nothing.
+Maybe<double> GetRecordingPosition();
+
 ///////////////////////////////////////////////////////////////////////////////
 // Graphics
 ///////////////////////////////////////////////////////////////////////////////
 
 extern void* gGraphicsMemory;
 
 void InitializeGraphicsMemory();
 void SendGraphicsMemoryToChild();
 
 // Update the graphics painted in the UI process, per painting data received
 // from a child process, or null if a repaint was triggered and failed due to
 // an unhandled recording divergence.
 void UpdateGraphicsInUIProcess(const PaintMessage* aMsg);
 
+// Update the overlay shown over the tab's graphics.
+void UpdateGraphicsOverlay();
+
 // If necessary, update graphics after the active child sends a paint message
 // or reaches a checkpoint.
 void MaybeUpdateGraphicsAtPaint(const PaintMessage& aMsg);
 void MaybeUpdateGraphicsAtCheckpoint(size_t aCheckpointId);
 
 // ID for the mach message sent from a child process to the middleman to
 // request a port for the graphics shmem.
 static const int32_t GraphicsHandshakeMessageId = 42;
--- a/widget/cocoa/nsNativeThemeCocoa.h
+++ b/widget/cocoa/nsNativeThemeCocoa.h
@@ -172,16 +172,17 @@ public:
   struct UnifiedToolbarParams {
     float unifiedHeight = 0.0f;
     bool isMain = false;
   };
 
   struct TextBoxParams {
     bool disabled = false;
     bool focused = false;
+    bool borderless = false;
   };
 
   struct SearchFieldParams {
     float verticalAlignFactor = 0.5f;
     bool insideToolbar = false;
     bool disabled = false;
     bool focused = false;
     bool rtl = false;
--- a/widget/cocoa/nsNativeThemeCocoa.mm
+++ b/widget/cocoa/nsNativeThemeCocoa.mm
@@ -2017,16 +2017,25 @@ void
 nsNativeThemeCocoa::DrawTextBox(CGContextRef cgContext, const HIRect& inBoxRect,
                                 TextBoxParams aParams)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
   CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
   CGContextFillRect(cgContext, inBoxRect);
 
+#if DRAW_IN_FRAME_DEBUG
+  CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
+  CGContextFillRect(cgContext, inBoxRect);
+#endif
+
+  if (aParams.borderless) {
+    return;
+  }
+
   HIThemeFrameDrawInfo fdi;
   fdi.version = 0;
   fdi.kind = kHIThemeFrameTextFieldSquare;
 
   // We don't ever set an inactive state for this because it doesn't
   // look right (see other apps).
   fdi.state = aParams.disabled ? kThemeStateUnavailable : kThemeStateActive;
   fdi.isFocused = aParams.focused;
@@ -2037,21 +2046,16 @@ nsNativeThemeCocoa::DrawTextBox(CGContex
   HIRect drawRect = inBoxRect;
   SInt32 frameOutset = 0;
   ::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset);
   drawRect.origin.x += frameOutset;
   drawRect.origin.y += frameOutset;
   drawRect.size.width -= frameOutset * 2;
   drawRect.size.height -= frameOutset * 2;
 
-#if DRAW_IN_FRAME_DEBUG
-  CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
-  CGContextFillRect(cgContext, inBoxRect);
-#endif
-
   HIThemeDrawFrame(&drawRect, &fdi, cgContext, HITHEME_ORIENTATION);
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 static const CellRenderSettings progressSettings[2][2] = {
   // Vertical progress bar.
   {
@@ -3329,51 +3333,54 @@ nsNativeThemeCocoa::ComputeWidgetInfo(ns
         [(ToolbarWindow*)win unifiedToolbarHeight] : nativeWidgetRect.Height();
       return Some(WidgetInfo::NativeTitlebar(
         UnifiedToolbarParams{unifiedToolbarHeight, isMain}));
     }
 
     case StyleAppearance::Statusbar:
       return Some(WidgetInfo::StatusBar(IsActive(aFrame, YES)));
 
-    case StyleAppearance::Menulist:
-    case StyleAppearance::MenulistTextfield: {
+    case StyleAppearance::Menulist: {
       ControlParams controlParams = ComputeControlParams(aFrame, eventState);
       controlParams.focused = controlParams.focused || IsFocused(aFrame);
       controlParams.pressed = IsOpenButton(aFrame);
       DropdownParams params;
       params.controlParams = controlParams;
       params.pullsDown = false;
-      params.editable = aWidgetType == StyleAppearance::MenulistTextfield;
+      params.editable = false;
       return Some(WidgetInfo::Dropdown(params));
     }
 
     case StyleAppearance::MenulistButton:
     case StyleAppearance::MozMenulistButton:
       return Some(WidgetInfo::Button(
         ButtonParams{ComputeControlParams(aFrame, eventState),
                      ButtonType::eArrowButton}));
 
     case StyleAppearance::Groupbox:
       return Some(WidgetInfo::GroupBox());
 
+    case StyleAppearance::MenulistTextfield:
     case StyleAppearance::Textfield:
     case StyleAppearance::NumberInput: {
       bool isFocused = eventState.HasState(NS_EVENT_STATE_FOCUS);
       // XUL textboxes set the native appearance on the containing box, while
       // concrete focus is set on the html:input element within it. We can
       // though, check the focused attribute of xul textboxes in this case.
       // On Mac, focus rings are always shown for textboxes, so we do not need
       // to check the window's focus ring state here
       if (aFrame->GetContent()->IsXULElement() && IsFocused(aFrame)) {
         isFocused = true;
       }
 
       bool isDisabled = IsDisabled(aFrame, eventState) || IsReadOnly(aFrame);
-      return Some(WidgetInfo::TextBox(TextBoxParams{isDisabled, isFocused}));
+      bool borderless =
+        (aWidgetType == StyleAppearance::MenulistTextfield && !isFocused);
+      return Some(WidgetInfo::TextBox(TextBoxParams{isDisabled, isFocused,
+                                                    borderless}));
     }
 
     case StyleAppearance::Searchfield:
       return Some(WidgetInfo::SearchField(
         ComputeSearchFieldParams(aFrame, eventState)));
 
     case StyleAppearance::Progressbar:
     {
@@ -4059,19 +4066,16 @@ nsNativeThemeCocoa::GetWidgetBorder(nsDe
     // be sure to handle StaticPrefs::layout_css_webkit_appearance_enabled.
     case StyleAppearance::Menulist:
     case StyleAppearance::MenulistButton:
     case StyleAppearance::MozMenulistButton:
       result = DirectionAwareMargin(kAquaDropdownBorder, aFrame);
       break;
 
     case StyleAppearance::MenulistTextfield:
-      result = DirectionAwareMargin(kAquaComboboxBorder, aFrame);
-      break;
-
     case StyleAppearance::NumberInput:
     case StyleAppearance::Textfield:
     {
       SInt32 frameOutset = 0;
       ::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset);
 
       SInt32 textPadding = 0;
       ::GetThemeMetric(kThemeMetricEditTextWhitespace, &textPadding);
@@ -4325,16 +4329,17 @@ nsNativeThemeCocoa::GetMinimumWidgetSize
     case StyleAppearance::MozMenulistButton:
     {
       SInt32 popupHeight = 0;
       ::GetThemeMetric(kThemeMetricPopupButtonHeight, &popupHeight);
       aResult->SizeTo(0, popupHeight);
       break;
     }
 
+    case StyleAppearance::MenulistTextfield:
     case StyleAppearance::NumberInput:
     case StyleAppearance::Textfield:
     case StyleAppearance::TextfieldMultiline:
     case StyleAppearance::Searchfield:
     {
       // at minimum, we should be tall enough for 9pt text.
       // I'm using hardcoded values here because the appearance manager
       // values for the frame size are incorrect.
@@ -4640,17 +4645,16 @@ nsNativeThemeCocoa::ThemeSupportsWidget(
   }
 
   switch (aWidgetType) {
     // Combobox dropdowns don't support native theming in vertical mode.
     case StyleAppearance::Menulist:
     case StyleAppearance::MenulistButton:
     case StyleAppearance::MozMenulistButton:
     case StyleAppearance::MenulistText:
-    case StyleAppearance::MenulistTextfield:
       if (aFrame && aFrame->GetWritingMode().IsVertical()) {
         return false;
       }
       MOZ_FALLTHROUGH;
 
     case StyleAppearance::Listbox:
 
     case StyleAppearance::Dialog:
@@ -4704,16 +4708,17 @@ nsNativeThemeCocoa::ThemeSupportsWidget(
     case StyleAppearance::Treetwisty:
     case StyleAppearance::Treetwistyopen:
     case StyleAppearance::Treeview:
     case StyleAppearance::Treeheader:
     case StyleAppearance::Treeheadercell:
     case StyleAppearance::Treeheadersortarrow:
     case StyleAppearance::Treeitem:
     case StyleAppearance::Treeline:
+    case StyleAppearance::MenulistTextfield:
     case StyleAppearance::MozMacSourceList:
     case StyleAppearance::MozMacSourceListSelection:
     case StyleAppearance::MozMacActiveSourceListSelection:
 
     case StyleAppearance::Range:
 
     case StyleAppearance::ScaleHorizontal:
     case StyleAppearance::ScalethumbHorizontal:
@@ -4798,17 +4803,16 @@ bool
 nsNativeThemeCocoa::ThemeDrawsFocusForWidget(WidgetType aWidgetType)
 {
   if (aWidgetType == StyleAppearance::MenulistButton &&
       StaticPrefs::layout_css_webkit_appearance_enabled()) {
     aWidgetType = StyleAppearance::Menulist;
   }
 
   if (aWidgetType == StyleAppearance::Menulist ||
-      aWidgetType == StyleAppearance::MenulistTextfield ||
       aWidgetType == StyleAppearance::Button ||
       aWidgetType == StyleAppearance::MozMacHelpButton ||
       aWidgetType == StyleAppearance::MozMacDisclosureButtonOpen ||
       aWidgetType == StyleAppearance::MozMacDisclosureButtonClosed ||
       aWidgetType == StyleAppearance::Radio ||
       aWidgetType == StyleAppearance::Range ||
       aWidgetType == StyleAppearance::Checkbox)
     return true;
@@ -4838,16 +4842,17 @@ nsNativeThemeCocoa::WidgetAppearanceDepe
     case StyleAppearance::Menuseparator:
     case StyleAppearance::Tooltip:
     case StyleAppearance::InnerSpinButton:
     case StyleAppearance::Spinner:
     case StyleAppearance::SpinnerUpbutton:
     case StyleAppearance::SpinnerDownbutton:
     case StyleAppearance::Separator:
     case StyleAppearance::Toolbox:
+    case StyleAppearance::MenulistTextfield:
     case StyleAppearance::NumberInput:
     case StyleAppearance::Textfield:
     case StyleAppearance::Treeview:
     case StyleAppearance::Treeline:
     case StyleAppearance::TextfieldMultiline:
     case StyleAppearance::Listbox:
     case StyleAppearance::Resizer:
       return false;
--- a/widget/gtk/WidgetStyleCache.cpp
+++ b/widget/gtk/WidgetStyleCache.cpp
@@ -773,16 +773,19 @@ CreateHeaderBar()
   CreateHeaderBarWidget(MOZ_GTK_HEADER_BAR);
   CreateHeaderBarWidget(MOZ_GTK_HEADER_BAR_MAXIMIZED);
   CreateHeaderBarButtons();
 }
 
 static GtkWidget*
 CreateWidget(WidgetNodeType aWidgetType)
 {
+  MOZ_ASSERT(aWidgetType != MOZ_GTK_DROPDOWN_ENTRY,
+             "Callers should be passing MOZ_GTK_ENTRY");
+
   switch (aWidgetType) {
     case MOZ_GTK_WINDOW:
       return CreateWindowWidget();
     case MOZ_GTK_WINDOW_CONTAINER:
       return CreateWindowContainerWidget();
     case MOZ_GTK_CHECKBUTTON_CONTAINER:
       return CreateCheckboxWidget();
     case MOZ_GTK_PROGRESSBAR:
@@ -1504,16 +1507,20 @@ ResetWidgetCache(void)
   /* Clear already freed arrays */
   mozilla::PodArrayZero(sWidgetStorage);
 }
 
 GtkStyleContext*
 GetStyleContext(WidgetNodeType aNodeType, GtkTextDirection aDirection,
                 GtkStateFlags aStateFlags, StyleFlags aFlags)
 {
+  if (aNodeType == MOZ_GTK_DROPDOWN_ENTRY) {
+    aNodeType = MOZ_GTK_ENTRY;
+  }
+
   GtkStyleContext* style;
   if (gtk_check_version(3, 20, 0) != nullptr) {
     style = GetWidgetStyleInternal(aNodeType);
   } else {
     style = GetCssNodeStyleInternal(aNodeType);
   }
   bool stateChanged = false;
   bool stateHasDirection = gtk_get_minor_version() >= 8;
--- a/widget/gtk/gtk3drawing.cpp
+++ b/widget/gtk/gtk3drawing.cpp
@@ -1242,17 +1242,18 @@ moz_gtk_vpaned_paint(cairo_t *cr, GdkRec
                       rect->x, rect->y, rect->width, rect->height);
     return MOZ_GTK_SUCCESS;
 }
 
 // See gtk_entry_draw() for reference.
 static gint
 moz_gtk_entry_paint(cairo_t *cr, GdkRectangle* rect,
                     GtkWidgetState* state,
-                    GtkStyleContext* style)
+                    GtkStyleContext* style,
+                    WidgetNodeType widget)
 {
     gint x = rect->x, y = rect->y, width = rect->width, height = rect->height;
     int draw_focus_outline_only = state->depressed; // StyleAppearance::FocusOutline
 
     if (draw_focus_outline_only) {
         // Inflate the given 'rect' with the focus outline size.
         gint h, v;
         moz_gtk_get_focus_outline_size(style, &h, &v);
@@ -1260,17 +1261,21 @@ moz_gtk_entry_paint(cairo_t *cr, GdkRect
         rect->width += 2 * h;
         rect->y -= v;
         rect->height += 2 * v;
         width = rect->width;
         height = rect->height;
     } else {
         gtk_render_background(style, cr, x, y, width, height);
     }
-    gtk_render_frame(style, cr, x, y, width, height);
+
+    // Paint the border, except for 'menulist-textfield' that isn't focused:
+    if (widget != MOZ_GTK_DROPDOWN_ENTRY || state->focused) {
+      gtk_render_frame(style, cr, x, y, width, height);
+    }
 
     return MOZ_GTK_SUCCESS;
 }
 
 static gint
 moz_gtk_text_view_paint(cairo_t *cr, GdkRectangle* aRect,
                         GtkWidgetState* state,
                         GtkTextDirection direction)
@@ -2410,18 +2415,19 @@ moz_gtk_get_widget_border(WidgetNodeType
             if (widget == MOZ_GTK_TOOLBAR_BUTTON)
                 gtk_style_context_restore(style);
 
             moz_gtk_add_style_border(style, left, top, right, bottom);
 
             return MOZ_GTK_SUCCESS;
         }
     case MOZ_GTK_ENTRY:
+    case MOZ_GTK_DROPDOWN_ENTRY:
         {
-            style = GetStyleContext(MOZ_GTK_ENTRY);
+            style = GetStyleContext(widget);
 
             // XXX: Subtract 1 pixel from the padding to account for the default
             // padding in forms.css. See bug 1187385.
             *left = *top = *right = *bottom = -1;
             moz_gtk_add_border_padding(style, left, top, right, bottom);
 
             return MOZ_GTK_SUCCESS;
         }
@@ -2444,19 +2450,16 @@ moz_gtk_get_widget_border(WidgetNodeType
                                                GetWidget(MOZ_GTK_TREE_HEADER_CELL)));
             style = GetStyleContext(MOZ_GTK_TREE_HEADER_CELL);
             moz_gtk_add_border_padding(style, left, top, right, bottom);
             return MOZ_GTK_SUCCESS;
         }
     case MOZ_GTK_TREE_HEADER_SORTARROW:
         w = GetWidget(MOZ_GTK_TREE_HEADER_SORTARROW);
         break;
-    case MOZ_GTK_DROPDOWN_ENTRY:
-        w = GetWidget(MOZ_GTK_COMBOBOX_ENTRY_TEXTAREA);
-        break;
     case MOZ_GTK_DROPDOWN_ARROW:
         w = GetWidget(MOZ_GTK_COMBOBOX_ENTRY_BUTTON);
         break;
     case MOZ_GTK_DROPDOWN:
         {
             /* We need to account for the arrow on the dropdown, so text
              * doesn't come too close to the arrow, or in some cases spill
              * into the arrow. */
@@ -3302,17 +3305,17 @@ moz_gtk_widget_paint(WidgetNodeType widg
                                          (widget == MOZ_GTK_SPINBUTTON_DOWN),
                                          state, direction);
         break;
     case MOZ_GTK_SPINBUTTON_ENTRY:
         {
             GtkStyleContext* style =
                 GetStyleContext(MOZ_GTK_SPINBUTTON_ENTRY, direction,
                                 GetStateFlagsFromGtkWidgetState(state));
-            gint ret = moz_gtk_entry_paint(cr, rect, state, style);
+            gint ret = moz_gtk_entry_paint(cr, rect, state, style, widget);
             return ret;
         }
         break;
     case MOZ_GTK_GRIPPER:
         return moz_gtk_gripper_paint(cr, rect, state,
                                      direction);
         break;
     case MOZ_GTK_TREEVIEW:
@@ -3329,42 +3332,34 @@ moz_gtk_widget_paint(WidgetNodeType widg
                                                     (GtkArrowType) flags,
                                                     direction);
         break;
     case MOZ_GTK_TREEVIEW_EXPANDER:
         return moz_gtk_treeview_expander_paint(cr, rect, state,
                                                (GtkExpanderStyle) flags, direction);
         break;
     case MOZ_GTK_ENTRY:
+    case MOZ_GTK_DROPDOWN_ENTRY:
         {
             GtkStyleContext* style =
-                GetStyleContext(MOZ_GTK_ENTRY, direction,
+                GetStyleContext(widget, direction,
                                 GetStateFlagsFromGtkWidgetState(state));
-            gint ret = moz_gtk_entry_paint(cr, rect, state, style);
+            gint ret = moz_gtk_entry_paint(cr, rect, state, style, widget);
             return ret;
         }
     case MOZ_GTK_TEXT_VIEW:
         return moz_gtk_text_view_paint(cr, rect, state, direction);
         break;
     case MOZ_GTK_DROPDOWN:
         return moz_gtk_combo_box_paint(cr, rect, state, direction);
         break;
     case MOZ_GTK_DROPDOWN_ARROW:
         return moz_gtk_combo_box_entry_button_paint(cr, rect,
                                                     state, flags, direction);
         break;
-    case MOZ_GTK_DROPDOWN_ENTRY:
-        {
-            GtkStyleContext* style =
-                GetStyleContext(MOZ_GTK_COMBOBOX_ENTRY_TEXTAREA, direction,
-                                GetStateFlagsFromGtkWidgetState(state));
-            gint ret = moz_gtk_entry_paint(cr, rect, state, style);
-            return ret;
-        }
-        break;
     case MOZ_GTK_CHECKBUTTON_CONTAINER:
     case MOZ_GTK_RADIOBUTTON_CONTAINER:
         return moz_gtk_container_paint(cr, rect, state, widget, direction);
         break;
     case MOZ_GTK_CHECKBUTTON_LABEL:
     case MOZ_GTK_RADIOBUTTON_LABEL:
         return moz_gtk_toggle_label_paint(cr, rect, state,
                                           (widget == MOZ_GTK_RADIOBUTTON_LABEL),
--- a/widget/gtk/nsNativeThemeGTK.cpp
+++ b/widget/gtk/nsNativeThemeGTK.cpp
@@ -1736,16 +1736,17 @@ nsNativeThemeGTK::GetMinimumWidgetSize(n
 
       LayoutDeviceIntMargin border;
       GetCachedWidgetBorder(aFrame, aWidgetType, GetTextDirection(aFrame), &border);
       aResult->width += border.left + border.right;
       aResult->height += border.top + border.bottom;
     }
     break;
 #ifdef MOZ_WIDGET_GTK
+  case StyleAppearance::MenulistTextfield:
   case StyleAppearance::NumberInput:
   case StyleAppearance::Textfield:
     {
       moz_gtk_get_entry_min_height(&aResult->height);
     }
     break;
 #endif
   case StyleAppearance::Separator:
@@ -1922,17 +1923,16 @@ nsNativeThemeGTK::ThemeSupportsWidget(ns
       StaticPrefs::layout_css_webkit_appearance_enabled()) {
     aWidgetType = StyleAppearance::Menulist;
   }
 
   switch (aWidgetType) {
   // Combobox dropdowns don't support native theming in vertical mode.
   case StyleAppearance::Menulist:
   case StyleAppearance::MenulistText:
-  case StyleAppearance::MenulistTextfield:
     if (aFrame && aFrame->GetWritingMode().IsVertical()) {
       return false;
     }
     MOZ_FALLTHROUGH;
 
   case StyleAppearance::Button:
   case StyleAppearance::ButtonFocus:
   case StyleAppearance::Radio:
@@ -1984,16 +1984,17 @@ nsNativeThemeGTK::ThemeSupportsWidget(ns
   case StyleAppearance::ScrollbarbuttonLeft:
   case StyleAppearance::ScrollbarbuttonRight:
   case StyleAppearance::ScrollbarHorizontal:
   case StyleAppearance::ScrollbarVertical:
   case StyleAppearance::ScrollbartrackHorizontal:
   case StyleAppearance::ScrollbartrackVertical:
   case StyleAppearance::ScrollbarthumbHorizontal:
   case StyleAppearance::ScrollbarthumbVertical:
+  case StyleAppearance::MenulistTextfield:
   case StyleAppearance::NumberInput:
   case StyleAppearance::Textfield:
   case StyleAppearance::TextfieldMultiline:
   case StyleAppearance::Range:
   case StyleAppearance::RangeThumb:
   case StyleAppearance::ScaleHorizontal:
   case StyleAppearance::ScalethumbHorizontal:
   case StyleAppearance::ScaleVertical:
--- a/widget/headless/HeadlessThemeGTK.cpp
+++ b/widget/headless/HeadlessThemeGTK.cpp
@@ -40,16 +40,17 @@ HeadlessThemeGTK::GetWidgetBorder(nsDevi
     case StyleAppearance::Button:
     case StyleAppearance::Toolbarbutton:
       result.top = 6;
       result.right = 7;
       result.bottom = 6;
       result.left = 7;
       break;
     case StyleAppearance::FocusOutline:
+    case StyleAppearance::MenulistTextfield:
     case StyleAppearance::NumberInput:
     case StyleAppearance::Textfield:
       result.top = 5;
       result.right = 7;
       result.bottom = 5;
       result.left = 7;
       break;
     case StyleAppearance::Statusbarpanel:
@@ -96,22 +97,16 @@ HeadlessThemeGTK::GetWidgetBorder(nsDevi
       break;
     case StyleAppearance::MenulistButton:
     case StyleAppearance::MozMenulistButton:
       result.top = 1;
       result.right = 1;
       result.bottom = 1;
       result.left = 0;
       break;
-    case StyleAppearance::MenulistTextfield:
-      result.top = 1;
-      result.right = 0;
-      result.bottom = 1;
-      result.left = 1;
-      break;
     case StyleAppearance::Menuitem:
     case StyleAppearance::Checkmenuitem:
     case StyleAppearance::Radiomenuitem:
       if (IsRegularMenuItem(aFrame)) {
         break;
       }
       result.top = 3;
       result.right = 5;
@@ -246,16 +241,17 @@ HeadlessThemeGTK::GetMinimumWidgetSize(n
       aResult->height = 16;
       *aIsOverridable = false;
       break;
     case StyleAppearance::InnerSpinButton:
     case StyleAppearance::Spinner:
       aResult->width = 14;
       aResult->height = 26;
       break;
+    case StyleAppearance::MenulistTextfield:
     case StyleAppearance::NumberInput:
     case StyleAppearance::Textfield:
       aResult->width = 0;
       aResult->height = 12;
       break;
     case StyleAppearance::ScrollbarHorizontal:
       aResult->width = 31;
       aResult->height = 10;
--- a/widget/nsNativeTheme.cpp
+++ b/widget/nsNativeTheme.cpp
@@ -100,17 +100,18 @@ nsNativeTheme::GetContentState(nsIFrame*
       flags |= NS_EVENT_STATE_FOCUS;
   }
 
   // On Windows and Mac, only draw focus rings if they should be shown. This
   // means that focus rings are only shown once the keyboard has been used to
   // focus something in the window.
 #if defined(XP_MACOSX)
   // Mac always draws focus rings for textboxes and lists.
-  if (aWidgetType == StyleAppearance::NumberInput ||
+  if (aWidgetType == StyleAppearance::MenulistTextfield ||
+      aWidgetType == StyleAppearance::NumberInput ||
       aWidgetType == StyleAppearance::Textfield ||
       aWidgetType == StyleAppearance::TextfieldMultiline ||
       aWidgetType == StyleAppearance::Searchfield ||
       aWidgetType == StyleAppearance::Listbox) {
     return flags;
   }
 #endif
 #if defined(XP_WIN)
@@ -342,16 +343,17 @@ nsNativeTheme::IsWidgetStyled(nsPresCont
       nsNumberControlFrame::GetNumberControlFrameForSpinButton(aFrame);
     if (numberControlFrame) {
       return !numberControlFrame->ShouldUseNativeStyleForSpinner();
     }
   }
 
   return (aWidgetType == StyleAppearance::NumberInput ||
           aWidgetType == StyleAppearance::Button ||
+          aWidgetType == StyleAppearance::MenulistTextfield ||
           aWidgetType == StyleAppearance::Textfield ||
           aWidgetType == StyleAppearance::TextfieldMultiline ||
           aWidgetType == StyleAppearance::Listbox ||
           aWidgetType == StyleAppearance::Menulist ||
           (aWidgetType == StyleAppearance::MenulistButton &&
            StaticPrefs::layout_css_webkit_appearance_enabled())) &&
          aFrame->GetContent()->IsHTMLElement() &&
          aPresContext->HasAuthorSpecifiedRules(aFrame,
--- a/widget/windows/nsNativeThemeWin.cpp
+++ b/widget/windows/nsNativeThemeWin.cpp
@@ -725,16 +725,17 @@ nsresult nsNativeThemeWin::GetCachedMini
 mozilla::Maybe<nsUXThemeClass> nsNativeThemeWin::GetThemeClass(WidgetType aWidgetType)
 {
   switch (aWidgetType) {
     case StyleAppearance::Button:
     case StyleAppearance::Radio:
     case StyleAppearance::Checkbox:
     case StyleAppearance::Groupbox:
       return Some(eUXButton);
+    case StyleAppearance::MenulistTextfield:
     case StyleAppearance::NumberInput:
     case StyleAppearance::Textfield:
     case StyleAppearance::TextfieldMultiline:
     case StyleAppearance::FocusOutline:
       return Some(eUXEdit);
     case StyleAppearance::Tooltip:
       return Some(eUXTooltip);
     case StyleAppearance::Toolbox:
@@ -946,16 +947,17 @@ nsNativeThemeWin::GetThemePartAndState(n
     }
     case StyleAppearance::Groupbox: {
       aPart = BP_GROUPBOX;
       aState = TS_NORMAL;
       // Since we don't support groupbox disabled and GBS_DISABLED looks the
       // same as GBS_NORMAL don't bother supporting GBS_DISABLED.
       return NS_OK;
     }
+    case StyleAppearance::MenulistTextfield:
     case StyleAppearance::NumberInput:
     case StyleAppearance::Textfield:
     case StyleAppearance::TextfieldMultiline: {
       EventStates eventState = GetContentState(aFrame, aWidgetType);
 
       /* Note: the NOSCROLL type has a rounded corner in each corner.  The more
        * specific HSCROLL, VSCROLL, HVSCROLL types have side and/or top/bottom
        * edges rendered as straight horizontal lines with sharp corners to
@@ -1864,21 +1866,26 @@ RENDER_AGAIN:
   // The following widgets need to be RTL-aware
   else if (aWidgetType == StyleAppearance::Resizer ||
            aWidgetType == StyleAppearance::MenulistButton ||
            aWidgetType == StyleAppearance::MozMenulistButton)
   {
     DrawThemeBGRTLAware(theme, hdc, part, state,
                         &widgetRect, &clipRect, IsFrameRTL(aFrame));
   }
-  else if (aWidgetType == StyleAppearance::NumberInput ||
+  else if (aWidgetType == StyleAppearance::MenulistTextfield ||
+           aWidgetType == StyleAppearance::NumberInput ||
            aWidgetType == StyleAppearance::Textfield ||
            aWidgetType == StyleAppearance::TextfieldMultiline) {
-    DrawThemeBackground(theme, hdc, part, state, &widgetRect, &clipRect);
-     if (state == TFS_EDITBORDER_DISABLED) {
+    // Paint the border, except for 'menulist-textfield' that isn't focused:
+    if (aWidgetType != StyleAppearance::MenulistTextfield ||
+        state == TFS_EDITBORDER_FOCUSED) {
+      DrawThemeBackground(theme, hdc, part, state, &widgetRect, &clipRect);
+    }
+    if (state == TFS_EDITBORDER_DISABLED) {
       InflateRect(&widgetRect, -1, -1);
       ::FillRect(hdc, &widgetRect, reinterpret_cast<HBRUSH>(COLOR_BTNFACE+1));
     }
   }
   else if (aWidgetType == StyleAppearance::Progressbar ||
            aWidgetType == StyleAppearance::ProgressbarVertical) {
     // DrawThemeBackground renders each corner with a solid white pixel.
     // Restore these pixels to the underlying color. Tracks are rendered
@@ -2094,17 +2101,18 @@ nsNativeThemeWin::GetWidgetBorder(nsDevi
     if (IsLeftToSelectedTab(aFrame))
       // Remove the right edge, since we won't be drawing it.
       result.right = 0;
     else if (IsRightToSelectedTab(aFrame))
       // Remove the left edge, since we won't be drawing it.
       result.left = 0;
   }
 
-  if (aFrame && (aWidgetType == StyleAppearance::NumberInput ||
+  if (aFrame && (aWidgetType == StyleAppearance::MenulistTextfield ||
+                 aWidgetType == StyleAppearance::NumberInput ||
                  aWidgetType == StyleAppearance::Textfield ||
                  aWidgetType == StyleAppearance::TextfieldMultiline)) {
     nsIContent* content = aFrame->GetContent();
     if (content && content->IsHTMLElement()) {
       // We need to pad textfields by 1 pixel, since the caret will draw
       // flush against the edge by default if we don't.
       result.top++;
       result.left++;
@@ -2194,34 +2202,36 @@ nsNativeThemeWin::GetWidgetPadding(nsDev
     SIZE popupSize;
     GetThemePartSize(theme, nullptr, MENU_POPUPBORDERS, /* state */ 0, nullptr, TS_TRUE, &popupSize);
     aResult->top = aResult->bottom = popupSize.cy;
     aResult->left = aResult->right = popupSize.cx;
     ScaleForFrameDPI(aResult, aFrame);
     return ok;
   }
 
-  if (aWidgetType == StyleAppearance::NumberInput ||
+  if (aWidgetType == StyleAppearance::MenulistTextfield ||
+      aWidgetType == StyleAppearance::NumberInput ||
       aWidgetType == StyleAppearance::Textfield ||
       aWidgetType == StyleAppearance::TextfieldMultiline ||
       aWidgetType == StyleAppearance::Menulist)
   {
     // If we have author-specified padding for these elements, don't do the
     // fixups below.
     if (aFrame->PresContext()->HasAuthorSpecifiedRules(aFrame, NS_AUTHOR_SPECIFIED_PADDING))
       return false;
   }
 
   /* textfields need extra pixels on all sides, otherwise they wrap their
    * content too tightly.  The actual border is drawn 1px inside the specified
    * rectangle, so Gecko will end up making the contents look too small.
    * Instead, we add 2px padding for the contents and fix this. (Used to be 1px
    * added, see bug 430212)
    */
-  if (aWidgetType == StyleAppearance::NumberInput ||
+  if (aWidgetType == StyleAppearance::MenulistTextfield ||
+      aWidgetType == StyleAppearance::NumberInput ||
       aWidgetType == StyleAppearance::Textfield ||
       aWidgetType == StyleAppearance::TextfieldMultiline) {
     aResult->top = aResult->bottom = 2;
     aResult->left = aResult->right = 2;
     ScaleForFrameDPI(aResult, aFrame);
     return ok;
   } else if (IsHTMLContent(aFrame) && aWidgetType == StyleAppearance::Menulist) {
     /* For content menulist controls, we need an extra pixel so that we have
@@ -2353,16 +2363,17 @@ nsNativeThemeWin::GetMinimumWidgetSize(n
   if (!theme) {
     rv = ClassicGetMinimumWidgetSize(aFrame, aWidgetType, aResult, aIsOverridable);
     ScaleForFrameDPI(aResult, aFrame);
     return rv;
   }
 
   switch (aWidgetType) {
     case StyleAppearance::Groupbox:
+    case StyleAppearance::MenulistTextfield:
     case StyleAppearance::NumberInput:
     case StyleAppearance::Textfield:
     case StyleAppearance::Toolbox:
     case StyleAppearance::MozWinMediaToolbox:
     case StyleAppearance::MozWinCommunicationsToolbox:
     case StyleAppearance::MozWinBrowsertabbarToolbox:
     case StyleAppearance::Toolbar:
     case StyleAppearance::Statusbar:
@@ -3807,18 +3818,22 @@ RENDER_AGAIN:
     }
     // Draw controls with 2px 3D inset border
     case StyleAppearance::NumberInput:
     case StyleAppearance::Textfield:
     case StyleAppearance::TextfieldMultiline:
     case StyleAppearance::Listbox:
     case StyleAppearance::Menulist:
     case StyleAppearance::MenulistTextfield: {
-      // Draw inset edge
-      ::DrawEdge(hdc, &widgetRect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
+      // Paint the border, except for 'menulist-textfield' that isn't focused:
+      if (aWidgetType != StyleAppearance::MenulistTextfield || focused) {
+        // Draw inset edge
+        ::DrawEdge(hdc, &widgetRect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
+      }
+
       EventStates eventState = GetContentState(aFrame, aWidgetType);
 
       // Fill in background
       if (IsDisabled(aFrame, eventState) ||
           (aFrame->GetContent()->IsXULElement() &&
            IsReadOnly(aFrame)))
         ::FillRect(hdc, &widgetRect, (HBRUSH) (COLOR_BTNFACE+1));
       else