Merge inbound to mozilla-central. a=merge
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