blob: c31896dc17fd2725c11efd5bab7819e850d9ccb9 [file] [log] [blame]
/*
* Copyright (C) 2005-2025 Apple Inc. All rights reserved.
* Copyright (C) 2014 Google Inc. All rights reserved.
* Copyright (C) 2025-2026 Samuel Weinig <[email protected]>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include "RenderTheme.h"
#include "BorderShape.h"
#include "ButtonPart.h"
#include "CSSContrastColorResolver.h"
#include "CSSPropertyNames.h"
#include "CSSValueKeywords.h"
#include "ColorBlending.h"
#include "ColorLuminance.h"
#include "ColorSerialization.h"
#include "ColorWellPart.h"
#include "ContainerNodeInlines.h"
#include "DataListButtonElement.h"
#include "DeprecatedGlobalSettings.h"
#include "Document.h"
#include "FileList.h"
#include "FloatConversion.h"
#include "FloatRoundedRect.h"
#include "FocusController.h"
#include "FontSelector.h"
#include "FrameSelection.h"
#include "GraphicsContext.h"
#include "GraphicsTypes.h"
#include "HTMLAttachmentElement.h"
#include "HTMLButtonElement.h"
#include "HTMLDataListElement.h"
#include "HTMLInputElement.h"
#include "HTMLMeterElement.h"
#include "HTMLNames.h"
#include "HTMLOptionElement.h"
#include "HTMLProgressElement.h"
#include "HTMLSelectElement.h"
#include "HTMLTextAreaElement.h"
#include "ImageAdapter.h"
#include "ImageControlsButtonPart.h"
#include "InnerSpinButtonPart.h"
#include "LocalFrame.h"
#include "LocalizedStrings.h"
#include "MenuListButtonPart.h"
#include "MenuListPart.h"
#include "MeterPart.h"
#include "Page.h"
#include "PaintInfo.h"
#include "ProgressBarPart.h"
#include "RenderMeter.h"
#include "RenderElementInlines.h"
#include "RenderProgress.h"
#include "RenderStyle+GettersInlines.h"
#include "RenderStyle+SettersInlines.h"
#include "RenderView.h"
#include "SearchFieldCancelButtonPart.h"
#include "SearchFieldPart.h"
#include "SearchFieldResultsPart.h"
#include "SliderThumbElement.h"
#include "SliderThumbPart.h"
#include "SliderTrackPart.h"
#include "SpinButtonElement.h"
#include "StringTruncator.h"
#include "StyleComputedStyle+InitialInlines.h"
#include "StylePadding.h"
#include "StylePrimitiveNumericTypes+Evaluation.h"
#include "SwitchThumbPart.h"
#include "SwitchTrackPart.h"
#include "TextAreaPart.h"
#include "TextControlInnerElements.h"
#include "TextFieldPart.h"
#include "Theme.h"
#include "ToggleButtonPart.h"
#include "TypedElementDescendantIteratorInlines.h"
#include "UserAgentParts.h"
#include <wtf/FileSystem.h>
#include <wtf/Language.h>
#include <wtf/NeverDestroyed.h>
#if ENABLE(SERVICE_CONTROLS)
#include "ImageControlsMac.h"
#endif
namespace WebCore {
using namespace CSS::Literals;
using namespace HTMLNames;
RenderTheme::RenderTheme() = default;
RenderTheme::~RenderTheme() = default;
StyleAppearance RenderTheme::adjustAppearanceForElement(RenderStyle& style, const RenderStyle& parentStyle, const Element* element, StyleAppearance autoAppearance) const
{
if (!element) {
style.setUsedAppearance(StyleAppearance::None);
return StyleAppearance::None;
}
// Each user agent part for input type='color' controls
// should use StyleAppearance::None if their parent is
// using primitive appearance.
// FIXME: (rdar://148625484) If the parent devolves
// after the initial styles are applied, the children
// are not immediately updated to match.
if ((autoAppearance == StyleAppearance::ColorWellSwatch
|| autoAppearance == StyleAppearance::ColorWellSwatchOverlay
|| autoAppearance == StyleAppearance::ColorWellSwatchWrapper)
&& (parentStyle.usedAppearance() == StyleAppearance::None)) {
style.setUsedAppearance(StyleAppearance::None);
return StyleAppearance::None;
}
auto appearance = style.usedAppearance();
if (appearance == autoAppearance)
return appearance;
// Aliases of 'auto'.
// https://drafts.csswg.org/css-ui-4/#typedef-appearance-compat-auto
if (appearance == StyleAppearance::Auto
|| appearance == StyleAppearance::SearchField
|| appearance == StyleAppearance::TextArea
|| appearance == StyleAppearance::Checkbox
|| appearance == StyleAppearance::Radio
|| appearance == StyleAppearance::Listbox
|| appearance == StyleAppearance::Meter
|| appearance == StyleAppearance::ProgressBar
|| appearance == StyleAppearance::SquareButton
|| appearance == StyleAppearance::PushButton
|| appearance == StyleAppearance::SliderHorizontal
|| appearance == StyleAppearance::Menulist) {
style.setUsedAppearance(autoAppearance);
return autoAppearance;
}
// The following keywords should work well for some element types
// even if their default appearances are different from the keywords.
if (appearance == StyleAppearance::Button) {
if (autoAppearance == StyleAppearance::PushButton || autoAppearance == StyleAppearance::SquareButton)
return appearance;
style.setUsedAppearance(autoAppearance);
return autoAppearance;
}
if (appearance == StyleAppearance::MenulistButton) {
if (autoAppearance == StyleAppearance::Menulist)
return appearance;
style.setUsedAppearance(autoAppearance);
return autoAppearance;
}
auto* inputElement = dynamicDowncast<HTMLInputElement>(element);
if (appearance == StyleAppearance::TextField) {
if (inputElement && inputElement->isSearchField())
return appearance;
style.setUsedAppearance(autoAppearance);
return autoAppearance;
}
if (appearance == StyleAppearance::SliderVertical) {
if (inputElement && inputElement->isRangeControl())
return appearance;
style.setUsedAppearance(autoAppearance);
return autoAppearance;
}
#if ENABLE(APPLE_PAY)
// Only apply `appearance: -apple-pay-button` on buttons and non-form controls.
if (appearance == StyleAppearance::ApplePayButton) {
if (autoAppearance == StyleAppearance::Button)
return appearance;
if (!inputElement && autoAppearance == StyleAppearance::None)
return appearance;
style.setUsedAppearance(autoAppearance);
return autoAppearance;
}
#endif
return appearance;
}
static bool isAppearanceAllowedForAllElements(StyleAppearance appearance)
{
#if ENABLE(APPLE_PAY)
if (appearance == StyleAppearance::ApplePayButton)
return true;
#endif
UNUSED_PARAM(appearance);
return false;
}
static bool devolvableWidgetsEnabledAndSupported(const Element* element)
{
bool devolvableWidgetsEnabled = element->document().settings().devolvableWidgetsEnabled();
#if PLATFORM(COCOA)
return devolvableWidgetsEnabled && WTF::linkedOnOrAfterSDKWithBehavior(SDKAlignedBehavior::DevolvableWidgets);
#else
return devolvableWidgetsEnabled;
#endif
}
static bool shouldCheckLegacyStylesForNativeAppearance(const Element* element)
{
#if PLATFORM(MAC)
#if ENABLE(FORM_CONTROL_REFRESH)
return element && !element->document().settings().formControlRefreshEnabled();
#else
UNUSED_PARAM(element);
return true;
#endif
#else
UNUSED_PARAM(element);
return false;
#endif
}
bool RenderTheme::hasAppearanceForElementTypeFromUAStyle(const Element& element)
{
// NOTE: This is just a legacy hard-coded list of elements that have some appearance value in html.css
// FIXME: Remove when devolvable widgets are universally enabled.
const auto& localName = element.localName();
return localName == HTMLNames::inputTag
|| localName == HTMLNames::textareaTag
|| localName == HTMLNames::buttonTag
|| localName == HTMLNames::progressTag
|| localName == HTMLNames::selectTag
|| localName == HTMLNames::meterTag
|| (element.isInUserAgentShadowTree() && element.userAgentPart() == UserAgentParts::webkitListButton());
}
void RenderTheme::adjustStyle(RenderStyle& style, const RenderStyle& parentStyle, const Element* element)
{
auto autoAppearance = autoAppearanceForElement(style, element);
auto appearance = adjustAppearanceForElement(style, parentStyle, element, autoAppearance);
if (appearance == StyleAppearance::None || appearance == StyleAppearance::Base)
return;
// Force inline and table display styles to be inline-block (except for table- which is block)
if (style.display() == DisplayType::Inline || style.display() == DisplayType::InlineTable || style.display() == DisplayType::TableRowGroup
|| style.display() == DisplayType::TableHeaderGroup || style.display() == DisplayType::TableFooterGroup
|| style.display() == DisplayType::TableRow || style.display() == DisplayType::TableColumnGroup || style.display() == DisplayType::TableColumn
|| style.display() == DisplayType::TableCell || style.display() == DisplayType::TableCaption)
style.setEffectiveDisplay(DisplayType::InlineBlock);
else if (style.display() == DisplayType::ListItem || style.display() == DisplayType::Table)
style.setEffectiveDisplay(DisplayType::Block);
bool widgetMayDevolve = devolvableWidgetsEnabledAndSupported(element);
bool widgetHasNativeAppearanceDisabled = widgetMayDevolve && element->isDevolvableWidget() && style.nativeAppearanceDisabled() && !isAppearanceAllowedForAllElements(appearance);
bool hasAppearanceFromUAStyle = element && hasAppearanceForElementTypeFromUAStyle(*element);
if (!widgetMayDevolve || shouldCheckLegacyStylesForNativeAppearance(element))
widgetHasNativeAppearanceDisabled |= hasAppearanceFromUAStyle && isControlStyled(style);
if (widgetHasNativeAppearanceDisabled) {
switch (appearance) {
case StyleAppearance::Menulist:
appearance = StyleAppearance::MenulistButton;
break;
case StyleAppearance::MenulistButton:
appearance = widgetMayDevolve ? StyleAppearance::MenulistButton : StyleAppearance::None;
break;
#if PLATFORM(IOS_FAMILY)
case StyleAppearance::ListButton:
#endif
default:
appearance = StyleAppearance::None;
break;
}
style.setUsedAppearance(appearance);
}
if (appearance == StyleAppearance::SearchField && searchFieldShouldAppearAsTextField(style, element->document().settings())) {
appearance = StyleAppearance::TextField;
style.setUsedAppearance(appearance);
}
if (!isAppearanceAllowedForAllElements(appearance)
&& !hasAppearanceFromUAStyle
&& autoAppearance == StyleAppearance::None
&& !style.borderAndBackgroundEqual(RenderStyle::defaultStyleSingleton()))
style.setUsedAppearance(StyleAppearance::None);
if (!style.hasUsedAppearance())
return;
if (!supportsBoxShadow(style))
style.setBoxShadow(CSS::Keyword::None { });
switch (appearance) {
case StyleAppearance::Checkbox:
return adjustCheckboxStyle(style, element);
case StyleAppearance::Radio:
return adjustRadioStyle(style, element);
case StyleAppearance::ColorWell:
return adjustColorWellStyle(style, element);
case StyleAppearance::ColorWellSwatch:
return adjustColorWellSwatchStyle(style, element);
case StyleAppearance::ColorWellSwatchOverlay:
return adjustColorWellSwatchOverlayStyle(style, element);
case StyleAppearance::ColorWellSwatchWrapper:
return adjustColorWellSwatchWrapperStyle(style, element);
case StyleAppearance::PushButton:
case StyleAppearance::SquareButton:
case StyleAppearance::DefaultButton:
case StyleAppearance::Button:
return adjustButtonStyle(style, element);
case StyleAppearance::InnerSpinButton:
return adjustInnerSpinButtonStyle(style, element);
case StyleAppearance::TextField:
return adjustTextFieldStyle(style, element);
case StyleAppearance::TextArea:
return adjustTextAreaStyle(style, element);
case StyleAppearance::Menulist:
return adjustMenuListStyle(style, element);
case StyleAppearance::MenulistButton:
return adjustMenuListButtonStyle(style, element);
case StyleAppearance::SliderHorizontal:
case StyleAppearance::SliderVertical:
return adjustSliderTrackStyle(style, element);
case StyleAppearance::SliderThumbHorizontal:
case StyleAppearance::SliderThumbVertical:
return adjustSliderThumbStyle(style, element);
case StyleAppearance::SearchField:
return adjustSearchFieldStyle(style, element);
case StyleAppearance::SearchFieldCancelButton:
return adjustSearchFieldCancelButtonStyle(style, element);
case StyleAppearance::SearchFieldDecoration:
return adjustSearchFieldDecorationPartStyle(style, element);
case StyleAppearance::SearchFieldResultsDecoration:
return adjustSearchFieldResultsDecorationPartStyle(style, element);
case StyleAppearance::SearchFieldResultsButton:
return adjustSearchFieldResultsButtonStyle(style, element);
case StyleAppearance::Switch:
return adjustSwitchStyle(style, element);
case StyleAppearance::SwitchThumb:
case StyleAppearance::SwitchTrack:
return adjustSwitchThumbOrSwitchTrackStyle(style);
case StyleAppearance::ProgressBar:
return adjustProgressBarStyle(style, element);
case StyleAppearance::Meter:
return adjustMeterStyle(style, element);
#if ENABLE(SERVICE_CONTROLS)
case StyleAppearance::ImageControlsButton:
return adjustImageControlsButtonStyle(style, element);
#endif
#if ENABLE(APPLE_PAY)
case StyleAppearance::ApplePayButton:
return adjustApplePayButtonStyle(style, element);
#endif
case StyleAppearance::ListButton:
return adjustListButtonStyle(style, element);
default:
break;
}
}
StyleAppearance RenderTheme::autoAppearanceForElement(RenderStyle& style, const Element* elementPtr) const
{
if (!elementPtr)
return StyleAppearance::None;
Ref element = *elementPtr;
if (RefPtr input = dynamicDowncast<HTMLInputElement>(element)) {
if (input->isTextButton() || input->isUploadButton())
return StyleAppearance::Button;
if (input->isSwitch())
return StyleAppearance::Switch;
if (input->isCheckbox())
return StyleAppearance::Checkbox;
if (input->isRadioButton())
return StyleAppearance::Radio;
if (input->isSearchField())
return StyleAppearance::SearchField;
if (input->isDateField() || input->isDateTimeLocalField() || input->isMonthField() || input->isTimeField() || input->isWeekField()) {
#if PLATFORM(IOS_FAMILY)
return StyleAppearance::MenulistButton;
#else
return StyleAppearance::TextField;
#endif
}
if (input->isColorControl())
return StyleAppearance::ColorWell;
if (input->isRangeControl())
return style.writingMode().isHorizontal() ? StyleAppearance::SliderHorizontal : StyleAppearance::SliderVertical;
if (input->isTextField())
return StyleAppearance::TextField;
// <input type=hidden|image|file>
return StyleAppearance::None;
}
if (is<HTMLButtonElement>(element)) {
#if ENABLE(SERVICE_CONTROLS)
if (isImageControlsButton(element.get()))
return StyleAppearance::ImageControlsButton;
#endif
return StyleAppearance::Button;
}
if (RefPtr select = dynamicDowncast<HTMLSelectElement>(element)) {
#if PLATFORM(IOS_FAMILY)
return StyleAppearance::MenulistButton;
#else
return select->usesMenuList() ? StyleAppearance::Menulist : StyleAppearance::Listbox;
#endif
}
if (is<HTMLTextAreaElement>(element))
return StyleAppearance::TextArea;
if (is<HTMLMeterElement>(element))
return StyleAppearance::Meter;
if (is<HTMLProgressElement>(element))
return StyleAppearance::ProgressBar;
#if ENABLE(ATTACHMENT_ELEMENT)
if (is<HTMLAttachmentElement>(element))
return StyleAppearance::Attachment;
#endif
if (element->isInUserAgentShadowTree()) {
auto& part = element->userAgentPart();
if (RefPtr button = dynamicDowncast<DataListButtonElement>(element)) {
ASSERT(part == UserAgentParts::webkitListButton());
if (!button->canAdjustStyleForAppearance())
return StyleAppearance::None;
return StyleAppearance::ListButton;
}
if (part == UserAgentParts::webkitSearchCancelButton())
return StyleAppearance::SearchFieldCancelButton;
if (RefPtr button = dynamicDowncast<SearchFieldResultsButtonElement>(element)) {
if (!button->canAdjustStyleForAppearance())
return StyleAppearance::None;
if (part == UserAgentParts::webkitSearchDecoration())
return StyleAppearance::SearchFieldDecoration;
if (part == UserAgentParts::webkitSearchResultsDecoration())
return StyleAppearance::SearchFieldResultsDecoration;
if (part == UserAgentParts::webkitSearchResultsButton())
return StyleAppearance::SearchFieldResultsButton;
}
if (part == UserAgentParts::webkitSliderThumb())
return StyleAppearance::SliderThumbHorizontal;
if (part == UserAgentParts::webkitInnerSpinButton())
return StyleAppearance::InnerSpinButton;
if (part == UserAgentParts::internalColorSwatchOverlay())
return StyleAppearance::ColorWellSwatchOverlay;
if (part == UserAgentParts::webkitColorSwatch())
return StyleAppearance::ColorWellSwatch;
if (part == UserAgentParts::webkitColorSwatchWrapper())
return StyleAppearance::ColorWellSwatchWrapper;
if (part == UserAgentParts::thumb())
return StyleAppearance::SwitchThumb;
if (part == UserAgentParts::track())
return StyleAppearance::SwitchTrack;
}
return StyleAppearance::None;
}
#if ENABLE(APPLE_PAY)
static void updateApplePayButtonPartForRenderer(ApplePayButtonPart& applePayButtonPart, const RenderElement& renderer)
{
CheckedRef style = renderer.style();
auto platformLocale = [&] -> String {
auto locale = style->computedLocale();
if (locale.isAuto())
return defaultLanguage(ShouldMinimizeLanguages::No);
return Style::toPlatform(locale);
}();
applePayButtonPart.setButtonType(style->applePayButtonType());
applePayButtonPart.setButtonStyle(style->applePayButtonStyle());
applePayButtonPart.setLocale(WTF::move(platformLocale));
}
#endif
static void updateMeterPartForRenderer(MeterPart& meterPart, const RenderMeter& renderMeter)
{
Ref element = *renderMeter.meterElement();
MeterPart::GaugeRegion gaugeRegion;
switch (element->gaugeRegion()) {
case HTMLMeterElement::GaugeRegionOptimum:
gaugeRegion = MeterPart::GaugeRegion::Optimum;
break;
case HTMLMeterElement::GaugeRegionSuboptimal:
gaugeRegion = MeterPart::GaugeRegion::Suboptimal;
break;
case HTMLMeterElement::GaugeRegionEvenLessGood:
gaugeRegion = MeterPart::GaugeRegion::EvenLessGood;
break;
}
meterPart.setGaugeRegion(gaugeRegion);
meterPart.setValue(element->value());
meterPart.setMinimum(element->min());
meterPart.setMaximum(element->max());
}
static void updateProgressBarPartForRenderer(ProgressBarPart& progressBarPart, const RenderProgress& renderProgress)
{
progressBarPart.setPosition(renderProgress.position());
progressBarPart.setAnimationStartTime(renderProgress.animationStartTime().secondsSinceEpoch());
}
static void updateSliderTrackPartForRenderer(SliderTrackPart& sliderTrackPart, const RenderElement& renderer)
{
Ref input = downcast<HTMLInputElement>(*renderer.element());
ASSERT(input->isRangeControl());
IntSize thumbSize;
if (CheckedPtr thumbRenderer = input->sliderThumbElement()->renderer()) {
const auto& thumbStyle = thumbRenderer->style();
auto fixedWidth = thumbStyle.width().tryFixed();
auto fixedHeight = thumbStyle.height().tryFixed();
auto thumbWidth = fixedWidth ? static_cast<int>(fixedWidth->resolveZoom(thumbStyle.usedZoomForLength())) : 0;
auto thumbHeight = fixedHeight ? static_cast<int>(fixedHeight->resolveZoom(thumbStyle.usedZoomForLength())) : 0;
thumbSize = { thumbWidth, thumbHeight };
}
IntRect trackBounds;
if (CheckedPtr trackRenderer = input->sliderTrackElement()->renderer()) {
trackBounds = trackRenderer->absoluteBoundingBoxRectIgnoringTransforms();
// We can ignoring transforms because transform is handled by the graphics context.
auto sliderBounds = renderer.absoluteBoundingBoxRectIgnoringTransforms();
// Make position relative to the transformed ancestor element.
trackBounds.moveBy(-sliderBounds.location());
}
double minimum = input->minimum();
double maximum = input->maximum();
double thumbPosition = 0;
if (maximum > minimum)
thumbPosition = (input->valueAsNumber() - minimum) / (maximum - minimum);
Vector<double> tickRatios;
if (auto dataList = input->dataList()) {
for (Ref optionElement : dataList->suggestions()) {
auto optionValue = input->listOptionValueAsDouble(optionElement.get());
if (!optionValue)
continue;
double tickRatio = (*optionValue - minimum) / (maximum - minimum);
tickRatios.append(tickRatio);
}
}
sliderTrackPart.setThumbSize(thumbSize);
sliderTrackPart.setTrackBounds(trackBounds);
sliderTrackPart.setThumbPosition(thumbPosition);
sliderTrackPart.setTickRatios(WTF::move(tickRatios));
}
static void updateSwitchThumbPartForRenderer(SwitchThumbPart& switchThumbPart, const RenderElement& renderer)
{
Ref input = downcast<HTMLInputElement>(*renderer.protectedNode()->shadowHost());
ASSERT(input->isSwitch());
switchThumbPart.setIsOn(input->isSwitchVisuallyOn());
switchThumbPart.setProgress(input->switchAnimationVisuallyOnProgress());
}
static void updateSwitchTrackPartForRenderer(SwitchTrackPart& switchTrackPart, const RenderElement& renderer)
{
Ref input = downcast<HTMLInputElement>(*renderer.protectedNode()->shadowHost());
ASSERT(input->isSwitch());
switchTrackPart.setIsOn(input->isSwitchVisuallyOn());
switchTrackPart.setProgress(input->switchAnimationVisuallyOnProgress());
}
RefPtr<ControlPart> RenderTheme::createControlPart(const RenderElement& renderer) const
{
auto appearance = renderer.style().usedAppearance();
switch (appearance) {
case StyleAppearance::None:
case StyleAppearance::Auto:
case StyleAppearance::Base:
break;
case StyleAppearance::Checkbox:
case StyleAppearance::Radio:
return ToggleButtonPart::create(appearance);
case StyleAppearance::PushButton:
case StyleAppearance::SquareButton:
case StyleAppearance::Button:
case StyleAppearance::DefaultButton:
return ButtonPart::create(appearance);
case StyleAppearance::Menulist:
return MenuListPart::create();
case StyleAppearance::MenulistButton:
return MenuListButtonPart::create();
case StyleAppearance::Meter:
return MeterPart::create();
case StyleAppearance::ProgressBar:
return ProgressBarPart::create();
case StyleAppearance::SliderHorizontal:
case StyleAppearance::SliderVertical:
return SliderTrackPart::create(appearance);
case StyleAppearance::SearchField:
return SearchFieldPart::create();
#if ENABLE(APPLE_PAY)
case StyleAppearance::ApplePayButton:
return ApplePayButtonPart::create();
#endif
#if ENABLE(ATTACHMENT_ELEMENT)
case StyleAppearance::Attachment:
case StyleAppearance::BorderlessAttachment:
break;
#endif
case StyleAppearance::Listbox:
case StyleAppearance::TextArea:
return TextAreaPart::create(appearance);
case StyleAppearance::TextField:
return TextFieldPart::create();
case StyleAppearance::ColorWellSwatch:
case StyleAppearance::ColorWellSwatchOverlay:
case StyleAppearance::ColorWellSwatchWrapper:
break;
case StyleAppearance::ColorWell:
return ColorWellPart::create();
#if ENABLE(SERVICE_CONTROLS)
case StyleAppearance::ImageControlsButton:
return ImageControlsButtonPart::create();
#endif
case StyleAppearance::InnerSpinButton:
return InnerSpinButtonPart::create();
case StyleAppearance::ListButton:
break;
case StyleAppearance::SearchFieldDecoration:
break;
case StyleAppearance::SearchFieldResultsDecoration:
case StyleAppearance::SearchFieldResultsButton:
return SearchFieldResultsPart::create(appearance);
case StyleAppearance::SearchFieldCancelButton:
return SearchFieldCancelButtonPart::create();
case StyleAppearance::SliderThumbHorizontal:
case StyleAppearance::SliderThumbVertical:
return SliderThumbPart::create(appearance);
case StyleAppearance::Switch:
break;
case StyleAppearance::SwitchThumb:
return SwitchThumbPart::create();
case StyleAppearance::SwitchTrack:
return SwitchTrackPart::create();
}
ASSERT_NOT_REACHED();
return nullptr;
}
void RenderTheme::updateControlPartForRenderer(ControlPart& part, const RenderElement& renderer) const
{
if (auto* meterPart = dynamicDowncast<MeterPart>(part)) {
updateMeterPartForRenderer(*meterPart, downcast<RenderMeter>(renderer));
return;
}
if (auto* progressBarPart = dynamicDowncast<ProgressBarPart>(part)) {
updateProgressBarPartForRenderer(*progressBarPart, downcast<RenderProgress>(renderer));
return;
}
if (auto* sliderTrackPart = dynamicDowncast<SliderTrackPart>(part)) {
updateSliderTrackPartForRenderer(*sliderTrackPart, renderer);
return;
}
if (auto* switchThumbPart = dynamicDowncast<SwitchThumbPart>(part)) {
updateSwitchThumbPartForRenderer(*switchThumbPart, renderer);
return;
}
if (auto* switchTrackPart = dynamicDowncast<SwitchTrackPart>(part)) {
updateSwitchTrackPartForRenderer(*switchTrackPart, renderer);
return;
}
#if ENABLE(APPLE_PAY)
if (auto* applePayButtonPart = dynamicDowncast<ApplePayButtonPart>(part)) {
updateApplePayButtonPartForRenderer(*applePayButtonPart, renderer);
return;
}
#endif
}
OptionSet<ControlStyle::State> RenderTheme::extractControlStyleStatesForRendererInternal(const RenderElement& renderer) const
{
OptionSet<ControlStyle::State> states;
if (isHovered(renderer)) {
states.add(ControlStyle::State::Hovered);
if (isSpinUpButtonPartHovered(renderer))
states.add(ControlStyle::State::SpinUp);
}
if (isPressed(renderer)) {
states.add(ControlStyle::State::Pressed);
if (isSpinUpButtonPartPressed(renderer))
states.add(ControlStyle::State::SpinUp);
}
if (isFocused(renderer) && renderer.style().outlineStyle() == OutlineStyle::Auto)
states.add(ControlStyle::State::Focused);
if (isEnabled(renderer))
states.add(ControlStyle::State::Enabled);
if (isChecked(renderer))
states.add(ControlStyle::State::Checked);
if (isDefault(renderer))
states.add(ControlStyle::State::Default);
if (isWindowActive(renderer))
states.add(ControlStyle::State::WindowActive);
if (isIndeterminate(renderer))
states.add(ControlStyle::State::Indeterminate);
if (isPresenting(renderer))
states.add(ControlStyle::State::Presenting);
if (useFormSemanticContext())
states.add(ControlStyle::State::FormSemanticContext);
if (renderer.useDarkAppearance())
states.add(ControlStyle::State::DarkAppearance);
if (renderer.writingMode().isInlineFlipped())
states.add(ControlStyle::State::InlineFlippedWritingMode);
if (supportsLargeFormControls())
states.add(ControlStyle::State::LargeControls);
if (isReadOnlyControl(renderer))
states.add(ControlStyle::State::ReadOnly);
if (hasListButton(renderer)) {
states.add(ControlStyle::State::ListButton);
if (hasListButtonPressed(renderer))
states.add(ControlStyle::State::ListButtonPressed);
}
if (!renderer.writingMode().isHorizontal())
states.add(ControlStyle::State::VerticalWritingMode);
return states;
}
static const RenderElement* effectiveRendererForAppearance(const RenderObject& renderObject)
{
auto* renderer = dynamicDowncast<RenderElement>(renderObject);
if (!renderer) {
ASSERT_NOT_REACHED();
return { };
}
auto type = renderer->style().usedAppearance();
if (type == StyleAppearance::SearchFieldCancelButton
|| type == StyleAppearance::SwitchTrack
|| type == StyleAppearance::SwitchThumb) {
RefPtr element = renderer->element();
RefPtr<Node> input = element->shadowHost();
if (!input)
input = element;
return dynamicDowncast<RenderBox>(input->renderer());
}
return renderer;
}
OptionSet<ControlStyle::State> RenderTheme::extractControlStyleStatesForRenderer(const RenderObject& renderObject) const
{
if (CheckedPtr renderer = effectiveRendererForAppearance(renderObject))
return extractControlStyleStatesForRendererInternal(*renderer);
return { };
}
ControlStyle RenderTheme::extractControlStyleForRenderer(const RenderElement& renderObject) const
{
CheckedPtr renderer = effectiveRendererForAppearance(renderObject);
if (!renderer)
return { };
CheckedRef style = renderer->style();
return {
extractControlStyleStatesForRendererInternal(*renderer),
style->computedFontSize(),
style->usedZoom(),
style->usedAccentColor(renderObject.styleColorOptions()),
style->visitedDependentColorApplyingColorFilter(),
Style::evaluate<FloatBoxExtent>(style->usedBorderWidths().to<Style::LineWidthBox>(), Style::ZoomNeeded { })
};
}
bool RenderTheme::paint(const RenderBox& box, ControlPart& part, const PaintInfo& paintInfo, const LayoutRect& rect)
{
// If painting is disabled, but we aren't updating control tints, then just bail.
// If we are updating control tints, just schedule a repaint if the theme supports tinting
// for that control.
if (paintInfo.context().invalidatingControlTints()) {
if (controlSupportsTints(box))
box.repaint();
return false;
}
if (paintInfo.context().paintingDisabled())
return false;
updateControlPartForRenderer(part, box);
float deviceScaleFactor = box.protectedDocument()->deviceScaleFactor();
auto zoomedRect = snapRectToDevicePixels(rect, deviceScaleFactor);
auto borderShape = BorderShape::shapeForBorderRect(box.checkedStyle().get(), LayoutRect(zoomedRect));
auto controlStyle = extractControlStyleForRenderer(box);
auto& context = paintInfo.context();
context.drawControlPart(part, borderShape.deprecatedPixelSnappedRoundedRect(deviceScaleFactor), deviceScaleFactor, controlStyle);
return false;
}
bool RenderTheme::paint(const RenderBox& box, const PaintInfo& paintInfo, const LayoutRect& rect)
{
// If painting is disabled, but we aren't updating control tints, then just bail.
// If we are updating control tints, just schedule a repaint if the theme supports tinting
// for that control.
if (paintInfo.context().invalidatingControlTints()) {
if (controlSupportsTints(box))
box.repaint();
return false;
}
if (paintInfo.context().paintingDisabled())
return false;
auto appearance = box.style().usedAppearance();
if (!canPaint(paintInfo, box.settings(), appearance)) [[unlikely]]
return false;
float deviceScaleFactor = box.protectedDocument()->deviceScaleFactor();
FloatRect devicePixelSnappedRect = snapRectToDevicePixels(rect, deviceScaleFactor);
switch (appearance) {
case StyleAppearance::Checkbox:
return paintCheckbox(box, paintInfo, devicePixelSnappedRect);
case StyleAppearance::Radio:
return paintRadio(box, paintInfo, devicePixelSnappedRect);
case StyleAppearance::ColorWell:
return paintColorWell(box, paintInfo, devicePixelSnappedRect);
case StyleAppearance::ColorWellSwatch:
return paintColorWellSwatch(box, paintInfo, devicePixelSnappedRect);
case StyleAppearance::PushButton:
case StyleAppearance::SquareButton:
case StyleAppearance::DefaultButton:
case StyleAppearance::Button:
return paintButton(box, paintInfo, devicePixelSnappedRect);
case StyleAppearance::Menulist:
return paintMenuList(box, paintInfo, devicePixelSnappedRect);
case StyleAppearance::MenulistButton:
return paintMenuListButton(box, paintInfo, devicePixelSnappedRect);
case StyleAppearance::Meter:
return paintMeter(box, paintInfo, devicePixelSnappedRect);
case StyleAppearance::ProgressBar:
return paintProgressBar(box, paintInfo, devicePixelSnappedRect);
case StyleAppearance::SliderHorizontal:
case StyleAppearance::SliderVertical:
return paintSliderTrack(box, paintInfo, devicePixelSnappedRect);
case StyleAppearance::SliderThumbHorizontal:
case StyleAppearance::SliderThumbVertical:
return paintSliderThumb(box, paintInfo, devicePixelSnappedRect);
case StyleAppearance::TextField:
return paintTextField(box, paintInfo, devicePixelSnappedRect);
case StyleAppearance::TextArea:
return paintTextArea(box, paintInfo, devicePixelSnappedRect);
case StyleAppearance::Listbox:
return true;
case StyleAppearance::InnerSpinButton:
return paintInnerSpinButton(box, paintInfo, devicePixelSnappedRect);
case StyleAppearance::SearchField:
return paintSearchField(box, paintInfo, devicePixelSnappedRect);
case StyleAppearance::SearchFieldCancelButton:
return paintSearchFieldCancelButton(box, paintInfo, devicePixelSnappedRect);
case StyleAppearance::SearchFieldDecoration:
return paintSearchFieldDecorationPart(box, paintInfo, devicePixelSnappedRect);
case StyleAppearance::SearchFieldResultsDecoration:
return paintSearchFieldResultsDecorationPart(box, paintInfo, devicePixelSnappedRect);
case StyleAppearance::SearchFieldResultsButton:
return paintSearchFieldResultsButton(box, paintInfo, devicePixelSnappedRect);
case StyleAppearance::Switch:
return true;
case StyleAppearance::SwitchThumb:
return paintSwitchThumb(box, paintInfo, devicePixelSnappedRect);
case StyleAppearance::SwitchTrack:
return paintSwitchTrack(box, paintInfo, devicePixelSnappedRect);
#if ENABLE(SERVICE_CONTROLS)
case StyleAppearance::ImageControlsButton:
return paintImageControlsButton(box, paintInfo, snappedIntRect(rect));
#endif
case StyleAppearance::ListButton:
return paintListButton(box, paintInfo, devicePixelSnappedRect);
#if ENABLE(ATTACHMENT_ELEMENT)
case StyleAppearance::Attachment:
case StyleAppearance::BorderlessAttachment:
return paintAttachment(box, paintInfo, snappedIntRect(rect));
#endif
default:
break;
}
return true; // We don't support the appearance, so let the normal background/border paint.
}
bool RenderTheme::paintBorderOnly(const RenderBox& box, const PaintInfo& paintInfo)
{
if (paintInfo.context().paintingDisabled())
return false;
#if PLATFORM(IOS_FAMILY)
return box.style().usedAppearance() != StyleAppearance::None && box.style().usedAppearance() != StyleAppearance::Base;
#else
// Call the appropriate paint method based off the appearance value.
switch (box.style().usedAppearance()) {
case StyleAppearance::TextField:
case StyleAppearance::Listbox:
case StyleAppearance::TextArea:
case StyleAppearance::MenulistButton:
case StyleAppearance::SearchField:
return true;
case StyleAppearance::Checkbox:
case StyleAppearance::Radio:
case StyleAppearance::PushButton:
case StyleAppearance::SquareButton:
case StyleAppearance::ColorWell:
case StyleAppearance::DefaultButton:
case StyleAppearance::Button:
case StyleAppearance::Menulist:
case StyleAppearance::Meter:
case StyleAppearance::ProgressBar:
case StyleAppearance::SliderHorizontal:
case StyleAppearance::SliderVertical:
case StyleAppearance::SliderThumbHorizontal:
case StyleAppearance::SliderThumbVertical:
case StyleAppearance::SearchFieldCancelButton:
case StyleAppearance::SearchFieldDecoration:
case StyleAppearance::SearchFieldResultsDecoration:
case StyleAppearance::SearchFieldResultsButton:
#if ENABLE(SERVICE_CONTROLS)
case StyleAppearance::ImageControlsButton:
#endif
default:
break;
}
return false;
#endif
}
void RenderTheme::paintDecorations(const RenderBox& box, const PaintInfo& paintInfo, const LayoutRect& rect)
{
if (paintInfo.context().paintingDisabled())
return;
FloatRect devicePixelSnappedRect = snapRectToDevicePixels(rect, box.protectedDocument()->deviceScaleFactor());
// Call the appropriate paint method based off the appearance value.
switch (box.style().usedAppearance()) {
case StyleAppearance::MenulistButton:
paintMenuListButtonDecorations(box, paintInfo, devicePixelSnappedRect);
break;
case StyleAppearance::TextField:
paintTextFieldDecorations(box, paintInfo, devicePixelSnappedRect);
break;
case StyleAppearance::TextArea:
paintTextAreaDecorations(box, paintInfo, devicePixelSnappedRect);
break;
case StyleAppearance::ColorWell:
paintColorWellDecorations(box, paintInfo, devicePixelSnappedRect);
break;
case StyleAppearance::Menulist:
paintMenuListDecorations(box, paintInfo, devicePixelSnappedRect);
break;
case StyleAppearance::SliderThumbHorizontal:
case StyleAppearance::SearchField:
paintSearchFieldDecorations(box, paintInfo, devicePixelSnappedRect);
break;
case StyleAppearance::Meter:
case StyleAppearance::ProgressBar:
case StyleAppearance::SliderHorizontal:
case StyleAppearance::SliderVertical:
case StyleAppearance::Listbox:
case StyleAppearance::DefaultButton:
case StyleAppearance::SearchFieldCancelButton:
case StyleAppearance::SearchFieldDecoration:
case StyleAppearance::SearchFieldResultsDecoration:
case StyleAppearance::SearchFieldResultsButton:
#if ENABLE(SERVICE_CONTROLS)
case StyleAppearance::ImageControlsButton:
#endif
default:
break;
}
}
Color RenderTheme::activeSelectionBackgroundColor(OptionSet<StyleColorOptions> options) const
{
auto& cache = colorCache(options);
if (!cache.activeSelectionBackgroundColor.isValid())
cache.activeSelectionBackgroundColor = transformSelectionBackgroundColor(platformActiveSelectionBackgroundColor(options), options);
return cache.activeSelectionBackgroundColor;
}
Color RenderTheme::inactiveSelectionBackgroundColor(OptionSet<StyleColorOptions> options) const
{
auto& cache = colorCache(options);
if (!cache.inactiveSelectionBackgroundColor.isValid())
cache.inactiveSelectionBackgroundColor = transformSelectionBackgroundColor(platformInactiveSelectionBackgroundColor(options), options);
return cache.inactiveSelectionBackgroundColor;
}
Color RenderTheme::transformSelectionBackgroundColor(const Color& color, OptionSet<StyleColorOptions>) const
{
return blendWithWhite(color);
}
Color RenderTheme::activeSelectionForegroundColor(OptionSet<StyleColorOptions> options) const
{
auto& cache = colorCache(options);
if (!cache.activeSelectionForegroundColor.isValid() && supportsSelectionForegroundColors(options))
cache.activeSelectionForegroundColor = platformActiveSelectionForegroundColor(options);
return cache.activeSelectionForegroundColor;
}
Color RenderTheme::inactiveSelectionForegroundColor(OptionSet<StyleColorOptions> options) const
{
auto& cache = colorCache(options);
if (!cache.inactiveSelectionForegroundColor.isValid() && supportsSelectionForegroundColors(options))
cache.inactiveSelectionForegroundColor = platformInactiveSelectionForegroundColor(options);
return cache.inactiveSelectionForegroundColor;
}
Color RenderTheme::activeListBoxSelectionBackgroundColor(OptionSet<StyleColorOptions> options) const
{
auto& cache = colorCache(options);
if (!cache.activeListBoxSelectionBackgroundColor.isValid())
cache.activeListBoxSelectionBackgroundColor = platformActiveListBoxSelectionBackgroundColor(options);
return cache.activeListBoxSelectionBackgroundColor;
}
Color RenderTheme::inactiveListBoxSelectionBackgroundColor(OptionSet<StyleColorOptions> options) const
{
auto& cache = colorCache(options);
if (!cache.inactiveListBoxSelectionBackgroundColor.isValid())
cache.inactiveListBoxSelectionBackgroundColor = platformInactiveListBoxSelectionBackgroundColor(options);
return cache.inactiveListBoxSelectionBackgroundColor;
}
Color RenderTheme::activeListBoxSelectionForegroundColor(OptionSet<StyleColorOptions> options) const
{
auto& cache = colorCache(options);
if (!cache.activeListBoxSelectionForegroundColor.isValid() && supportsListBoxSelectionForegroundColors(options))
cache.activeListBoxSelectionForegroundColor = platformActiveListBoxSelectionForegroundColor(options);
return cache.activeListBoxSelectionForegroundColor;
}
Color RenderTheme::inactiveListBoxSelectionForegroundColor(OptionSet<StyleColorOptions> options) const
{
auto& cache = colorCache(options);
if (!cache.inactiveListBoxSelectionForegroundColor.isValid() && supportsListBoxSelectionForegroundColors(options))
cache.inactiveListBoxSelectionForegroundColor = platformInactiveListBoxSelectionForegroundColor(options);
return cache.inactiveListBoxSelectionForegroundColor;
}
Color RenderTheme::platformActiveSelectionBackgroundColor(OptionSet<StyleColorOptions>) const
{
// Use a blue color by default if the platform theme doesn't define anything.
return Color::blue;
}
Color RenderTheme::platformActiveSelectionForegroundColor(OptionSet<StyleColorOptions>) const
{
// Use a white color by default if the platform theme doesn't define anything.
return Color::white;
}
Color RenderTheme::platformInactiveSelectionBackgroundColor(OptionSet<StyleColorOptions>) const
{
// Use a grey color by default if the platform theme doesn't define anything.
// This color matches Firefox's inactive color.
return SRGBA<uint8_t> { 176, 176, 176 };
}
Color RenderTheme::platformInactiveSelectionForegroundColor(OptionSet<StyleColorOptions>) const
{
// Use a black color by default.
return Color::black;
}
Color RenderTheme::platformActiveListBoxSelectionBackgroundColor(OptionSet<StyleColorOptions> options) const
{
return platformActiveSelectionBackgroundColor(options);
}
Color RenderTheme::platformActiveListBoxSelectionForegroundColor(OptionSet<StyleColorOptions> options) const
{
return platformActiveSelectionForegroundColor(options);
}
Color RenderTheme::platformInactiveListBoxSelectionBackgroundColor(OptionSet<StyleColorOptions> options) const
{
return platformInactiveSelectionBackgroundColor(options);
}
Color RenderTheme::platformInactiveListBoxSelectionForegroundColor(OptionSet<StyleColorOptions> options) const
{
return platformInactiveSelectionForegroundColor(options);
}
int RenderTheme::baselinePosition(const RenderBox& box) const
{
return box.isHorizontalWritingMode() ? box.height() : LayoutUnit(box.width() / 2.0f);
}
bool RenderTheme::isControlContainer(StyleAppearance appearance) const
{
// There are more leaves than this, but we'll patch this function as we add support for
// more controls.
return appearance != StyleAppearance::Checkbox && appearance != StyleAppearance::Radio;
}
bool RenderTheme::isControlStyled(const RenderStyle& style) const
{
switch (style.usedAppearance()) {
case StyleAppearance::PushButton:
case StyleAppearance::SquareButton:
case StyleAppearance::ColorWell:
case StyleAppearance::DefaultButton:
case StyleAppearance::Button:
case StyleAppearance::Listbox:
case StyleAppearance::Menulist:
case StyleAppearance::ProgressBar:
case StyleAppearance::Meter:
// FIXME: SearchFieldPart should be included here when making search fields style-able.
case StyleAppearance::TextField:
case StyleAppearance::TextArea:
// Test the style to see if the UA border and background match.
return style.nativeAppearanceDisabled();
default:
return false;
}
}
bool RenderTheme::supportsFocusRing(const RenderElement&, const RenderStyle& style) const
{
return style.hasUsedAppearance()
&& style.usedAppearance() != StyleAppearance::TextField
&& style.usedAppearance() != StyleAppearance::TextArea
&& style.usedAppearance() != StyleAppearance::MenulistButton
&& style.usedAppearance() != StyleAppearance::Listbox;
}
bool RenderTheme::isWindowActive(const RenderElement& renderer) const
{
return renderer.page().focusController().isActive();
}
bool RenderTheme::isChecked(const RenderElement& renderer) const
{
RefPtr element = dynamicDowncast<HTMLInputElement>(renderer.element());
return element && element->matchesCheckedPseudoClass();
}
bool RenderTheme::isIndeterminate(const RenderElement& renderer) const
{
// This does not currently support multiple elements and therefore radio buttons are excluded.
// FIXME: However, what about <progress>?
RefPtr input = dynamicDowncast<HTMLInputElement>(renderer.element());
return input && input->isCheckbox() && input->matchesIndeterminatePseudoClass();
}
bool RenderTheme::isEnabled(const RenderElement& renderer) const
{
RefPtr element = renderer.element();
return element && !element->isDisabledFormControl();
}
bool RenderTheme::isFocused(const RenderElement& renderer) const
{
RefPtr element = renderer.element();
if (!element)
return false;
// FIXME: This should be part of RenderTheme::extractControlStyleForRenderer().
RefPtr delegate = element;
if (RefPtr sliderThumb = dynamicDowncast<SliderThumbElement>(element))
delegate = sliderThumb->hostInput();
Ref document = delegate->document();
RefPtr frame = document->frame();
return delegate == document->focusedElement() && frame && frame->checkedSelection()->isFocusedAndActive();
}
bool RenderTheme::isPressed(const RenderElement& renderer) const
{
RefPtr element = renderer.element();
return element && element->active();
}
bool RenderTheme::isSpinUpButtonPartPressed(const RenderElement& renderer) const
{
if (RefPtr spinButton = dynamicDowncast<SpinButtonElement>(renderer.element()))
return spinButton->active() && spinButton->upDownState() == SpinButtonElement::Up;
return false;
}
bool RenderTheme::isReadOnlyControl(const RenderElement& renderer) const
{
if (RefPtr element = renderer.element())
return is<HTMLFormControlElement>(*element) && !element->matchesReadWritePseudoClass();
return false;
}
bool RenderTheme::isHovered(const RenderElement& renderer) const
{
if (RefPtr spinButton = dynamicDowncast<SpinButtonElement>(renderer.element()))
return spinButton->hovered() && spinButton->upDownState() != SpinButtonElement::Indeterminate;
if (RefPtr element = renderer.element())
return element->hovered();
return false;
}
bool RenderTheme::isSpinUpButtonPartHovered(const RenderElement& renderer) const
{
if (RefPtr spinButton = dynamicDowncast<SpinButtonElement>(renderer.element()))
return spinButton->upDownState() == SpinButtonElement::Up;
return false;
}
bool RenderTheme::isPresenting(const RenderElement& renderer) const
{
RefPtr input = dynamicDowncast<HTMLInputElement>(renderer.element());
return input && input->isPresentingAttachedView();
}
bool RenderTheme::isDefault(const RenderElement& renderer) const
{
// A button should only have the default appearance if the page is active
if (!isWindowActive(renderer))
return false;
return renderer.style().usedAppearance() == StyleAppearance::DefaultButton;
}
bool RenderTheme::hasListButton(const RenderElement& renderer) const
{
RefPtr input = dynamicDowncast<HTMLInputElement>(renderer.generatingElement());
return input && input->hasDataList();
}
bool RenderTheme::hasListButtonPressed(const RenderElement& renderer) const
{
RefPtr input = dynamicDowncast<HTMLInputElement>(renderer.generatingElement());
if (!input)
return false;
RefPtr dataListButton = input->dataListButtonElement();
return dataListButton && dataListButton->active();
}
std::optional<FontCascadeDescription> RenderTheme::controlFont(StyleAppearance, const FontCascade&, float) const
{
return std::nullopt;
}
Style::PaddingBox RenderTheme::controlPadding(StyleAppearance appearance, const Style::PaddingBox& padding, float) const
{
switch (appearance) {
case StyleAppearance::Menulist:
case StyleAppearance::MenulistButton:
case StyleAppearance::Checkbox:
case StyleAppearance::Radio:
return Style::PaddingBox { 0_css_px };
default:
return padding;
}
}
Style::PreferredSizePair RenderTheme::controlSize(StyleAppearance, const FontCascade&, const Style::PreferredSizePair& zoomedSize, float) const
{
return zoomedSize;
}
Style::MinimumSizePair RenderTheme::minimumControlSize(StyleAppearance, const FontCascade&, const Style::MinimumSizePair&, float) const
{
return { 0_css_px, 0_css_px };
}
Style::MinimumSizePair RenderTheme::minimumControlSize(StyleAppearance appearance, const FontCascade& fontCascade, const Style::MinimumSizePair& minSize, const Style::PreferredSizePair& preferredSize, float zoom) const
{
auto minimumControlSize = this->minimumControlSize(appearance, fontCascade, minSize, zoom);
auto resultWidth = minimumControlSize.width();
auto resultHeight = minimumControlSize.height();
// Other StyleAppearance types are composed controls with shadow subtree.
if (appearance == StyleAppearance::Radio || appearance == StyleAppearance::Checkbox) {
if (minSize.width().isIntrinsicOrLegacyIntrinsicOrAuto())
resultWidth = preferredSize.width().asMinimumSize();
if (minSize.height().isIntrinsicOrLegacyIntrinsicOrAuto())
resultHeight = preferredSize.height().asMinimumSize();
}
return { WTF::move(resultWidth), WTF::move(resultHeight) };
}
Style::LineWidthBox RenderTheme::controlBorder(StyleAppearance appearance, const FontCascade&, const Style::LineWidthBox& zoomedBox, float, const Element*) const
{
switch (appearance) {
case StyleAppearance::PushButton:
case StyleAppearance::Menulist:
case StyleAppearance::SearchField:
case StyleAppearance::Checkbox:
case StyleAppearance::Radio:
return Style::LineWidthBox { 0_css_px };
default:
return zoomedBox;
}
}
// FIXME: iOS does not use this so arguably this should be better abstracted. Or maybe we should
// investigate if we can bring the various ports closer together.
void RenderTheme::adjustButtonOrCheckboxOrColorWellOrInnerSpinButtonOrRadioStyle(RenderStyle& style, const Element* element) const
{
auto appearance = style.usedAppearance();
CheckedRef fontCascade = style.fontCascade();
auto borderBox = controlBorder(appearance, fontCascade.get(), style.usedBorderWidths().to<Style::LineWidthBox>(), style.usedZoom(), element);
auto supportsVerticalWritingMode = [](StyleAppearance appearance) {
return appearance == StyleAppearance::Button
|| appearance == StyleAppearance::ColorWell
|| appearance == StyleAppearance::DefaultButton
|| appearance == StyleAppearance::SquareButton
|| appearance == StyleAppearance::PushButton;
};
// Transpose for vertical writing mode:
if (!style.writingMode().isHorizontal() && supportsVerticalWritingMode(appearance))
borderBox = Style::LineWidthBox { borderBox.left(), borderBox.top(), borderBox.right(), borderBox.bottom() };
if (Style::evaluate<float>(borderBox.top(), Style::ZoomNeeded { }) != Style::evaluate<int>(style.usedBorderTopWidth(), Style::ZoomNeeded { })) {
if (!borderBox.top().isZero())
style.setBorderTopWidth(Style::LineWidth { borderBox.top() });
else
style.resetBorderTop();
}
if (Style::evaluate<float>(borderBox.right(), Style::ZoomNeeded { }) != Style::evaluate<int>(style.usedBorderRightWidth(), Style::ZoomNeeded { })) {
if (!borderBox.right().isZero())
style.setBorderRightWidth(Style::LineWidth { borderBox.right() });
else
style.resetBorderRight();
}
if (Style::evaluate<float>(borderBox.bottom(), Style::ZoomNeeded { }) != Style::evaluate<int>(style.usedBorderBottomWidth(), Style::ZoomNeeded { })) {
if (!borderBox.bottom().isZero())
style.setBorderBottomWidth(Style::LineWidth { borderBox.bottom() });
else
style.resetBorderBottom();
}
if (Style::evaluate<float>(borderBox.left(), Style::ZoomNeeded { }) != Style::evaluate<int>(style.usedBorderLeftWidth(), Style::ZoomNeeded { })) {
if (!borderBox.left().isZero())
style.setBorderLeftWidth(Style::LineWidth { borderBox.left() });
else
style.resetBorderLeft();
}
// Padding
auto paddingBox = controlPadding(appearance, style.paddingBox(), style.usedZoom());
if (paddingBox != style.paddingBox())
style.setPaddingBox(WTF::move(paddingBox));
// Whitespace
if (controlRequiresPreWhiteSpace(appearance)) {
style.setWhiteSpaceCollapse(WhiteSpaceCollapse::Preserve);
style.setTextWrapMode(TextWrapMode::NoWrap);
}
// Width / Height
// The width and height here are affected by the zoom.
// FIXME: Check is flawed, since it doesn't take min-width/max-width into account.
auto zoomForDeterminingControlSize = Style::ZoomFactor { usedZoomForComputedStyle(style) };
auto controlSize = this->controlSize(appearance, fontCascade.get(), { style.width(), style.height() }, zoomForDeterminingControlSize.value);
if (controlSize.width() != style.width())
style.setWidth(Style::PreferredSize { controlSize.width() });
if (controlSize.height() != style.height())
style.setHeight(Style::PreferredSize { controlSize.height() });
// Min-Width / Min-Height
auto minimumControlSize = this->minimumControlSize(appearance, fontCascade.get(), { style.minWidth(), style.minHeight() }, { style.width(), style.height() }, zoomForDeterminingControlSize.value);
// FIXME: The min-width/min-heigh value should use `calc-size()` when supported to make non-specified overrides work.
auto usedZoomForLength = style.usedZoomForLength();
if (auto fixedOverrideMinWidth = minimumControlSize.width().tryFixed()) {
if (auto fixedOriginalMinWidth = style.minWidth().tryFixed()) {
if (fixedOverrideMinWidth->resolveZoom(usedZoomForLength) > fixedOriginalMinWidth->resolveZoom(usedZoomForLength))
style.setMinWidth(Style::MinimumSize(minimumControlSize.width()));
} else if (auto percentageOriginalMinWidth = style.minWidth().tryPercentage()) {
// FIXME: This really makes no sense but matches existing behavior. Should use a `calc(max(override, original))` here instead.
if (fixedOverrideMinWidth->resolveZoom(usedZoomForLength) > percentageOriginalMinWidth->value)
style.setMinWidth(Style::MinimumSize(minimumControlSize.width()));
} else if (fixedOverrideMinWidth->isPositive()) {
style.setMinWidth(Style::MinimumSize(minimumControlSize.width()));
}
} else if (auto percentageOverrideMinWidth = minimumControlSize.width().tryPercentage()) {
if (auto fixedOriginalMinWidth = style.minWidth().tryFixed()) {
// FIXME: This really makes no sense but matches existing behavior. Should use a `calc(max(override, original))` here instead.
if (percentageOverrideMinWidth->value > fixedOriginalMinWidth->resolveZoom(usedZoomForLength))
style.setMinWidth(Style::MinimumSize(minimumControlSize.width()));
} else if (auto percentageOriginalMinWidth = style.minWidth().tryPercentage()) {
if (percentageOverrideMinWidth->value > percentageOriginalMinWidth->value)
style.setMinWidth(Style::MinimumSize(minimumControlSize.width()));
} else if (percentageOverrideMinWidth->isPositive()) {
style.setMinWidth(Style::MinimumSize(minimumControlSize.width()));
}
}
if (auto fixedOverrideMinHeight = minimumControlSize.height().tryFixed()) {
if (auto fixedOriginalMinHeight = style.minHeight().tryFixed()) {
if (fixedOverrideMinHeight->resolveZoom(usedZoomForLength) > fixedOriginalMinHeight->resolveZoom(usedZoomForLength))
style.setMinHeight(Style::MinimumSize(minimumControlSize.height()));
} else if (auto percentageOriginalMinHeight = style.minHeight().tryPercentage()) {
// FIXME: This really makes no sense but matches existing behavior. Should use a `calc(max(override, original))` here instead.
if (fixedOverrideMinHeight->resolveZoom(usedZoomForLength) > percentageOriginalMinHeight->value)
style.setMinHeight(Style::MinimumSize(minimumControlSize.height()));
} else if (fixedOverrideMinHeight->isPositive()) {
style.setMinHeight(Style::MinimumSize(minimumControlSize.height()));
}
} else if (auto percentageOverrideMinHeight = minimumControlSize.height().tryPercentage()) {
if (auto fixedOriginalMinHeight = style.minHeight().tryFixed()) {
// FIXME: This really makes no sense but matches existing behavior. Should use a `calc(max(override, original))` here instead.
if (percentageOverrideMinHeight->value > fixedOriginalMinHeight->resolveZoom(usedZoomForLength))
style.setMinHeight(Style::MinimumSize(minimumControlSize.height()));
} else if (auto percentageOriginalMinHeight = style.minHeight().tryPercentage()) {
if (percentageOverrideMinHeight->value > percentageOriginalMinHeight->value)
style.setMinHeight(Style::MinimumSize(minimumControlSize.height()));
} else if (percentageOverrideMinHeight->isPositive()) {
style.setMinHeight(Style::MinimumSize(minimumControlSize.height()));
}
}
// Font
if (auto controlFont = this->controlFont(appearance, fontCascade.get(), style.usedZoom())) {
// If overriding the specified font with the theme font, also override the line height with the standard line height.
style.setLineHeight(Style::ComputedStyle::initialLineHeight());
style.setFontDescription(WTF::move(controlFont.value()));
}
// Special style that tells enabled default buttons in active windows to use the ActiveButtonText color.
// The active window part of the test has to be done at paint time since it's not triggered by a style change.
style.setInsideDefaultButton(appearance == StyleAppearance::DefaultButton && element && !element->isDisabledFormControl());
}
void RenderTheme::adjustCheckboxStyle(RenderStyle& style, const Element* element) const
{
adjustButtonOrCheckboxOrColorWellOrInnerSpinButtonOrRadioStyle(style, element);
}
void RenderTheme::adjustRadioStyle(RenderStyle& style, const Element* element) const
{
adjustButtonOrCheckboxOrColorWellOrInnerSpinButtonOrRadioStyle(style, element);
}
void RenderTheme::adjustColorWellStyle(RenderStyle& style, const Element* element) const
{
adjustButtonOrCheckboxOrColorWellOrInnerSpinButtonOrRadioStyle(style, element);
}
void RenderTheme::adjustButtonStyle(RenderStyle& style, const Element* element) const
{
adjustButtonOrCheckboxOrColorWellOrInnerSpinButtonOrRadioStyle(style, element);
}
void RenderTheme::adjustInnerSpinButtonStyle(RenderStyle& style, const Element* element) const
{
adjustButtonOrCheckboxOrColorWellOrInnerSpinButtonOrRadioStyle(style, element);
}
void RenderTheme::adjustMenuListStyle(RenderStyle& style, const Element*) const
{
style.setOverflowX(Overflow::Visible);
style.setOverflowY(Overflow::Visible);
}
void RenderTheme::adjustMeterStyle(RenderStyle& style, const Element*) const
{
style.setBoxShadow(CSS::Keyword::None { });
}
FloatSize RenderTheme::meterSizeForBounds(const RenderMeter&, const FloatRect& bounds) const
{
return bounds.size();
}
#if ENABLE(ATTACHMENT_ELEMENT)
String RenderTheme::attachmentStyleSheet() const
{
ASSERT(DeprecatedGlobalSettings::attachmentElementEnabled());
return "attachment { appearance: auto; }"_s;
}
#endif // ENABLE(ATTACHMENT_ELEMENT)
void RenderTheme::paintSliderTicks(const RenderElement& renderer, const PaintInfo& paintInfo, const FloatRect& rect)
{
RefPtr input = dynamicDowncast<HTMLInputElement>(renderer.element());
if (!input || !input->isRangeControl())
return;
auto dataList = input->dataList();
if (!dataList)
return;
double min = input->minimum();
double max = input->maximum();
CheckedRef style = renderer.style();
auto appearance = style->usedAppearance();
// We don't support ticks on alternate sliders like MediaVolumeSliders.
if (appearance != StyleAppearance::SliderHorizontal && appearance != StyleAppearance::SliderVertical)
return;
bool isHorizontal = appearance == StyleAppearance::SliderHorizontal;
IntSize thumbSize;
if (CheckedPtr thumbRenderer = input->sliderThumbElement()->renderer()) {
auto& thumbStyle = thumbRenderer->style();
int thumbWidth = 0;
auto usedZoom = thumbStyle.usedZoomForLength();
if (auto fixedWidth = thumbStyle.width().tryFixed())
thumbWidth = static_cast<int>(fixedWidth->resolveZoom(usedZoom));
int thumbHeight = 0;
if (auto fixedHeight = thumbStyle.height().tryFixed())
thumbHeight = static_cast<int>(fixedHeight->resolveZoom(usedZoom));
thumbSize.setWidth(isHorizontal ? thumbWidth : thumbHeight);
thumbSize.setHeight(isHorizontal ? thumbHeight : thumbWidth);
}
IntSize tickSize = sliderTickSize();
float zoomFactor = style->usedZoom();
FloatRect tickRect;
int tickRegionSideMargin = 0;
int tickRegionWidth = 0;
IntRect trackBounds;
// We can ignoring transforms because transform is handled by the graphics context.
if (CheckedPtr trackRenderer = input->sliderTrackElement()->renderer())
trackBounds = trackRenderer->absoluteBoundingBoxRectIgnoringTransforms();
IntRect sliderBounds = renderer.absoluteBoundingBoxRectIgnoringTransforms();
// Make position relative to the transformed ancestor element.
trackBounds.setX(trackBounds.x() - sliderBounds.x() + rect.x());
trackBounds.setY(trackBounds.y() - sliderBounds.y() + rect.y());
if (isHorizontal) {
tickRect.setWidth(floor(tickSize.width() * zoomFactor));
tickRect.setHeight(floor(tickSize.height() * zoomFactor));
tickRect.setY(floor(rect.y() + rect.height() / 2.0 + sliderTickOffsetFromTrackCenter() * zoomFactor));
tickRegionSideMargin = trackBounds.x() + (thumbSize.width() - tickSize.width() * zoomFactor) / 2.0;
tickRegionWidth = trackBounds.width() - thumbSize.width();
} else {
tickRect.setWidth(floor(tickSize.height() * zoomFactor));
tickRect.setHeight(floor(tickSize.width() * zoomFactor));
tickRect.setX(floor(rect.x() + rect.width() / 2.0 + sliderTickOffsetFromTrackCenter() * zoomFactor));
tickRegionSideMargin = trackBounds.y() + (thumbSize.width() - tickSize.width() * zoomFactor) / 2.0;
tickRegionWidth = trackBounds.height() - thumbSize.width();
}
GraphicsContextStateSaver stateSaver(paintInfo.context());
paintInfo.context().setFillColor(style->visitedDependentColorApplyingColorFilter());
bool isInlineFlipped = (!isHorizontal && renderer.writingMode().isHorizontal()) || renderer.writingMode().isInlineFlipped();
for (Ref optionElement : dataList->suggestions()) {
if (auto optionValue = input->listOptionValueAsDouble(optionElement.get())) {
double tickFraction = (*optionValue - min) / (max - min);
double tickRatio = isInlineFlipped ? 1.0 - tickFraction : tickFraction;
double tickPosition = round(tickRegionSideMargin + tickRegionWidth * tickRatio);
if (isHorizontal)
tickRect.setX(tickPosition);
else
tickRect.setY(tickPosition);
paintInfo.context().fillRect(tickRect);
}
}
}
void RenderTheme::paintPlatformResizer(const RenderLayerModelObject& renderer, GraphicsContext& context, const LayoutRect& resizerCornerRect)
{
RefPtr<Image> resizeCornerImage;
FloatSize cornerResizerSize;
Ref document = renderer.document();
if (document->deviceScaleFactor() >= 2) {
static NeverDestroyed<Image*> resizeCornerImageHiRes(&ImageAdapter::loadPlatformResource("textAreaResizeCorner@2x").leakRef());
resizeCornerImage = resizeCornerImageHiRes;
cornerResizerSize = resizeCornerImage->size();
cornerResizerSize.scale(0.5f);
} else {
static NeverDestroyed<Image*> resizeCornerImageLoRes(&ImageAdapter::loadPlatformResource("textAreaResizeCorner").leakRef());
resizeCornerImage = resizeCornerImageLoRes;
cornerResizerSize = resizeCornerImage->size();
}
if (renderer.shouldPlaceVerticalScrollbarOnLeft()) {
GraphicsContextStateSaver stateSaver(context);
context.translate(resizerCornerRect.x() + cornerResizerSize.width(), resizerCornerRect.y() + resizerCornerRect.height() - cornerResizerSize.height());
context.scale(FloatSize(-1.0, 1.0));
if (resizeCornerImage)
context.drawImage(*resizeCornerImage, FloatRect(FloatPoint(), cornerResizerSize));
return;
}
if (!resizeCornerImage)
return;
FloatRect imageRect = snapRectToDevicePixels(LayoutRect(resizerCornerRect.maxXMaxYCorner() - cornerResizerSize, cornerResizerSize), document->deviceScaleFactor());
context.drawImage(*resizeCornerImage, imageRect);
}
void RenderTheme::paintPlatformResizerFrame(const RenderLayerModelObject&, GraphicsContext& context, const LayoutRect& resizerAbsRect)
{
// Clipping will exclude the right and bottom edges of this frame.
GraphicsContextStateSaver stateSaver(context);
context.clip(resizerAbsRect);
LayoutRect largerCorner = resizerAbsRect;
largerCorner.setSize(LayoutSize(largerCorner.width() + 1_lu, largerCorner.height() + 1_lu));
context.setStrokeColor(SRGBA<uint8_t> { 217, 217, 217 });
context.setStrokeThickness(1.0f);
context.setFillColor(Color::transparentBlack);
context.drawRect(snappedIntRect(largerCorner));
}
bool RenderTheme::shouldHaveSpinButton(const HTMLInputElement& inputElement) const
{
return inputElement.isSteppable() && !inputElement.isRangeControl();
}
void RenderTheme::setColorWellSwatchBackground(HTMLElement& swatch, Color color)
{
if (!color.isOpaque())
color = blendSourceOver(Color::white, color);
swatch.setInlineStyleProperty(CSSPropertyBackgroundColor, serializationForHTML(color));
}
void RenderTheme::adjustSliderThumbStyle(RenderStyle& style, const Element* element) const
{
adjustSliderThumbSize(style, element);
}
void RenderTheme::adjustSwitchStyleDisplay(RenderStyle& style) const
{
// RenderTheme::adjustStyle() normalizes a bunch of display types to InlineBlock and Block.
switch (style.display()) {
case DisplayType::InlineBlock:
style.setEffectiveDisplay(DisplayType::InlineGrid);
break;
case DisplayType::Block:
style.setEffectiveDisplay(DisplayType::Grid);
break;
default:
break;
}
}
void RenderTheme::adjustSwitchStyle(RenderStyle& style, const Element*) const
{
// FIXME: This probably has the same flaw as
// RenderTheme::adjustButtonOrCheckboxOrColorWellOrInnerSpinButtonOrRadioStyle() by not taking
// min-width/min-height into account.
auto controlSize = this->controlSize(StyleAppearance::Switch, style.checkedFontCascade().get(), { style.logicalWidth(), style.logicalHeight() }, usedZoomForComputedStyle(style));
style.setLogicalWidth(Style::PreferredSize { controlSize.width() });
style.setLogicalHeight(Style::PreferredSize { controlSize.height() });
adjustSwitchStyleDisplay(style);
}
void RenderTheme::adjustSwitchThumbOrSwitchTrackStyle(RenderStyle& style) const
{
style.setGridItemRowStart(Style::GridPosition::Explicit { { 1 } });
style.setGridItemColumnStart(Style::GridPosition::Explicit { { 1 } });
}
Style::PaddingBox RenderTheme::popupInternalPaddingBox(const RenderStyle&) const
{
return Style::PaddingBox { 0_css_px };
}
void RenderTheme::purgeCaches()
{
m_colorCacheMap.clear();
}
void RenderTheme::platformColorsDidChange()
{
m_colorCacheMap.clear();
Page::updateStyleForAllPagesAfterGlobalChangeInEnvironment();
}
auto RenderTheme::colorCache(OptionSet<StyleColorOptions> options) const -> ColorCache&
{
auto optionsIgnoringVisitedLink = options;
optionsIgnoringVisitedLink.remove(StyleColorOptions::ForVisitedLink);
return m_colorCacheMap.ensure(optionsIgnoringVisitedLink.toRaw(), [] {
return ColorCache();
}).iterator->value;
}
static Color defaultLinkColor(bool useDarkAppearance)
{
return useDarkAppearance ? SRGBA<uint8_t> { 158, 158, 255 } : SRGBA<uint8_t> { 0, 0, 238 };
}
static Color defaultVisitedLinkColor(bool useDarkAppearance)
{
return useDarkAppearance ? SRGBA<uint8_t> { 208, 173, 240 } : SRGBA<uint8_t> { 85, 26, 139 };
}
Color RenderTheme::systemColor(CSSValueID cssValueId, OptionSet<StyleColorOptions> options) const
{
auto useDarkAppearance = options.contains(StyleColorOptions::UseDarkAppearance);
auto forVisitedLink = options.contains(StyleColorOptions::ForVisitedLink);
switch (cssValueId) {
// https://drafts.csswg.org/css-color-4/#valdef-system-color-canvas
// Background of application content or documents.
case CSSValueCanvas:
return Color::white;
// https://drafts.csswg.org/css-color-4/#valdef-system-color-canvastext
// Text in application content or documents.
case CSSValueCanvastext:
return Color::black;
// https://drafts.csswg.org/css-color-4/#valdef-system-color-linktext
// Text in non-active, non-visited links. For light backgrounds, traditionally blue.
case CSSValueLinktext:
return defaultLinkColor(useDarkAppearance);
// https://drafts.csswg.org/css-color-4/#valdef-system-color-visitedtext
// Text in visited links. For light backgrounds, traditionally purple.
case CSSValueVisitedtext:
return defaultVisitedLinkColor(useDarkAppearance);
// https://drafts.csswg.org/css-color-4/#valdef-system-color-activetext
// Text in active links. For light backgrounds, traditionally red.
case CSSValueActivetext:
case CSSValueWebkitActivelink: // Non-standard addition.
return useDarkAppearance ? SRGBA<uint8_t> { 255, 158, 158 } : Color::red;
// https://drafts.csswg.org/css-color-4/#valdef-system-color-buttonface
// The face background color for push buttons.
case CSSValueButtonface:
return Color::lightGray;
// https://drafts.csswg.org/css-color-4/#valdef-system-color-buttontext
// Text on push buttons.
case CSSValueButtontext:
return Color::black;
// https://drafts.csswg.org/css-color-4/#valdef-system-color-buttonborder
// The base border color for push buttons.
case CSSValueButtonborder:
return Color::white;
// https://drafts.csswg.org/css-color-4/#valdef-system-color-field
// Background of input fields.
case CSSValueField:
return Color::white;
// https://drafts.csswg.org/css-color-4/#valdef-system-color-fieldtext
// Text in input fields.
case CSSValueFieldtext:
return Color::black;
// https://drafts.csswg.org/css-color-4/#valdef-system-color-highlight
// Background of selected text, for example from ::selection.
case CSSValueHighlight:
return SRGBA<uint8_t> { 181, 213, 255 };
// https://drafts.csswg.org/css-color-4/#valdef-system-color-highlighttext
// Text of selected text.
case CSSValueHighlighttext:
return Color::black;
// https://drafts.csswg.org/css-color-4/#valdef-system-color-selecteditem
// Background of selected items, for example a selected checkbox.
case CSSValueSelecteditem:
return Color::lightGray;
// https://drafts.csswg.org/css-color-4/#valdef-system-color-selecteditemtext
// Text of selected items.
case CSSValueSelecteditemtext:
return Color::black;
// https://drafts.csswg.org/css-color-4/#valdef-system-color-mark
// Background of text that has been specially marked (such as by the HTML mark element).
case CSSValueMark:
return Color::yellow;
// https://drafts.csswg.org/css-color-4/#valdef-system-color-marktext
// Text that has been specially marked (such as by the HTML mark element).
case CSSValueMarktext:
return Color::black;
// https://drafts.csswg.org/css-color-4/#valdef-system-color-graytext
// Disabled text. (Often, but not necessarily, gray.)
case CSSValueGraytext:
return Color::darkGray;
// https://drafts.csswg.org/css-color-4/#valdef-system-color-accentcolor
// Background of accented user interface controls.
case CSSValueAccentcolor:
return SRGBA<uint8_t> { 0, 122, 255 };
// https://drafts.csswg.org/css-color-4/#valdef-system-color-accentcolortext
// Text of accented user interface controls.
case CSSValueAccentcolortext:
return Color::black;
// Non-standard addition.
case CSSValueActivebuttontext:
return Color::black;
// Non-standard addition.
case CSSValueText:
return Color::black;
// Non-standard addition.
case CSSValueWebkitLink: {
if (forVisitedLink)
return defaultVisitedLinkColor(useDarkAppearance);
return defaultLinkColor(useDarkAppearance);
}
// Deprecated system-colors:
// https://drafts.csswg.org/css-color-4/#deprecated-system-colors
// https://drafts.csswg.org/css-color-4/#activeborder
// DEPRECATED: Active window border.
case CSSValueActiveborder:
return systemColor(CSSValueButtonborder, options);
// https://drafts.csswg.org/css-color-4/#activecaption
// DEPRECATED: Active window caption.
case CSSValueActivecaption:
return systemColor(CSSValueCanvastext, options);
// https://drafts.csswg.org/css-color-4/#appworkspace
// DEPRECATED: Background color of multiple document interface.
case CSSValueAppworkspace:
return systemColor(CSSValueCanvas, options);
// https://drafts.csswg.org/css-color-4/#background
// DEPRECATED: Desktop background.
case CSSValueBackground:
return systemColor(CSSValueCanvas, options);
// https://drafts.csswg.org/css-color-4/#buttonhighlight
// DEPRECATED: The color of the border facing the light source for 3-D elements that
// appear 3-D due to one layer of surrounding border.
case CSSValueButtonhighlight:
return systemColor(CSSValueButtonface, options);
// https://drafts.csswg.org/css-color-4/#buttonshadow
// DEPRECATED: The color of the border away from the light source for 3-D elements that
// appear 3-D due to one layer of surrounding border.
case CSSValueButtonshadow:
return systemColor(CSSValueButtonface, options);
// https://drafts.csswg.org/css-color-4/#captiontext
// DEPRECATED: Text in caption, size box, and scrollbar arrow box.
case CSSValueCaptiontext:
return systemColor(CSSValueCanvastext, options);
// https://drafts.csswg.org/css-color-4/#inactiveborder
// DEPRECATED: Inactive window border.
case CSSValueInactiveborder:
return systemColor(CSSValueButtonborder, options);
// https://drafts.csswg.org/css-color-4/#inactivecaption
// DEPRECATED: Inactive window caption.
case CSSValueInactivecaption:
return systemColor(CSSValueCanvas, options);
// https://drafts.csswg.org/css-color-4/#inactivecaptiontext
// DEPRECATED: Color of text in an inactive caption.
case CSSValueInactivecaptiontext:
return systemColor(CSSValueGraytext, options);
// https://drafts.csswg.org/css-color-4/#infobackground
// DEPRECATED: Background color for tooltip controls.
case CSSValueInfobackground:
return systemColor(CSSValueCanvas, options);
// https://drafts.csswg.org/css-color-4/#infotext
// DEPRECATED: Text color for tooltip controls.
case CSSValueInfotext:
return systemColor(CSSValueCanvastext, options);
// https://drafts.csswg.org/css-color-4/#menu
// DEPRECATED: Menu background.
case CSSValueMenu:
return systemColor(CSSValueCanvas, options);
// https://drafts.csswg.org/css-color-4/#menutext
// DEPRECATED: Text in menus.
case CSSValueMenutext:
return systemColor(CSSValueCanvastext, options);
// https://drafts.csswg.org/css-color-4/#scrollbar
// DEPRECATED: Scroll bar gray area.
case CSSValueScrollbar:
return systemColor(CSSValueCanvas, options);
// https://drafts.csswg.org/css-color-4/#threeddarkshadow
// DEPRECATED: The color of the darker (generally outer) of the two borders away from
// thelight source for 3-D elements that appear 3-D due to two concentric layers of
// surrounding border.
case CSSValueThreeddarkshadow:
return systemColor(CSSValueButtonborder, options);
// https://drafts.csswg.org/css-color-4/#threedface
// DEPRECATED: The face background color for 3-D elements that appear 3-D due to two
// concentric layers of surrounding border
case CSSValueThreedface:
return systemColor(CSSValueButtonface, options);
// https://drafts.csswg.org/css-color-4/#threedhighlight
// DEPRECATED: The color of the lighter (generally outer) of the two borders facing
// the light source for 3-D elements that appear 3-D due to two concentric layers of
// surrounding border.
case CSSValueThreedhighlight:
return systemColor(CSSValueButtonborder, options);
// https://drafts.csswg.org/css-color-4/#threedlightshadow
// DEPRECATED: The color of the darker (generally inner) of the two borders facing
// the light source for 3-D elements that appear 3-D due to two concentric layers of
// surrounding border
case CSSValueThreedlightshadow:
return systemColor(CSSValueButtonborder, options);
// https://drafts.csswg.org/css-color-4/#threedshadow
// DEPRECATED: The color of the lighter (generally inner) of the two borders away
// from the light source for 3-D elements that appear 3-D due to two concentric layers
// of surrounding border.
case CSSValueThreedshadow:
return systemColor(CSSValueButtonborder, options);
// https://drafts.csswg.org/css-color-4/#window
// DEPRECATED: Window background.
case CSSValueWindow:
return systemColor(CSSValueCanvas, options);
// https://drafts.csswg.org/css-color-4/#windowframe
// DEPRECATED: Window frame.
case CSSValueWindowframe:
return systemColor(CSSValueButtonborder, options);
// https://drafts.csswg.org/css-color-4/#windowtext
// DEPRECATED: Text in windows.
case CSSValueWindowtext:
return systemColor(CSSValueCanvastext, options);
default:
return { };
}
}
Color RenderTheme::textSearchHighlightColor(OptionSet<StyleColorOptions> options) const
{
auto& cache = colorCache(options);
if (!cache.textSearchHighlightColor.isValid())
cache.textSearchHighlightColor = platformTextSearchHighlightColor(options);
return cache.textSearchHighlightColor;
}
Color RenderTheme::platformTextSearchHighlightColor(OptionSet<StyleColorOptions>) const
{
return Color::yellow;
}
Color RenderTheme::annotationHighlightBackgroundColor(OptionSet<StyleColorOptions> options) const
{
auto& cache = colorCache(options);
if (!cache.annotationHighlightBackgroundColor.isValid())
cache.annotationHighlightBackgroundColor = transformSelectionBackgroundColor(platformAnnotationHighlightBackgroundColor(options), options);
return cache.annotationHighlightBackgroundColor;
}
Color RenderTheme::platformAnnotationHighlightBackgroundColor(OptionSet<StyleColorOptions>) const
{
return Color::yellow;
}
Color RenderTheme::annotationHighlightForegroundColor(OptionSet<StyleColorOptions> options) const
{
auto& cache = colorCache(options);
if (!cache.annotationHighlightForegroundColor.isValid())
cache.annotationHighlightForegroundColor = resolve(CSS::ContrastColorResolver { annotationHighlightBackgroundColor(options) });
return cache.annotationHighlightForegroundColor;
}
Color RenderTheme::defaultButtonTextColor(OptionSet<StyleColorOptions> options) const
{
auto& cache = colorCache(options);
if (!cache.defaultButtonTextColor.isValid())
cache.defaultButtonTextColor = platformDefaultButtonTextColor(options);
return cache.defaultButtonTextColor;
}
Color RenderTheme::platformDefaultButtonTextColor(OptionSet<StyleColorOptions> options) const
{
return systemColor(CSSValueActivebuttontext, options);
}
#if ENABLE(TOUCH_EVENTS)
Color RenderTheme::tapHighlightColor()
{
return singleton().platformTapHighlightColor();
}
#endif
// Value chosen to return dark gray for both white on black and black on white.
constexpr float datePlaceholderColorLightnessAdjustmentFactor = 0.66f;
Color RenderTheme::datePlaceholderTextColor(const Color& textColor, const Color& backgroundColor) const
{
// FIXME: Consider using LCHA<float> rather than HSLA<float> for better perceptual results and to avoid clamping to sRGB gamut, which is what HSLA does.
auto hsla = textColor.toColorTypeLossy<HSLA<float>>().resolved();
if (textColor.luminance() < backgroundColor.luminance())
hsla.lightness += datePlaceholderColorLightnessAdjustmentFactor * (100.0f - hsla.lightness);
else
hsla.lightness *= datePlaceholderColorLightnessAdjustmentFactor;
// FIXME: Consider keeping color in LCHA (if that change is made) or converting back to the initial underlying color type to avoid unnecessarily clamping colors outside of sRGB.
return convertColor<SRGBA<float>>(hsla);
}
Color RenderTheme::spellingMarkerColor(OptionSet<StyleColorOptions> options) const
{
auto& cache = colorCache(options);
if (!cache.spellingMarkerColor.isValid())
cache.spellingMarkerColor = platformSpellingMarkerColor(options);
return cache.spellingMarkerColor;
}
Color RenderTheme::platformSpellingMarkerColor(OptionSet<StyleColorOptions>) const
{
return Color::red;
}
Color RenderTheme::dictationAlternativesMarkerColor(OptionSet<StyleColorOptions> options) const
{
auto& cache = colorCache(options);
if (!cache.dictationAlternativesMarkerColor.isValid())
cache.dictationAlternativesMarkerColor = platformDictationAlternativesMarkerColor(options);
return cache.dictationAlternativesMarkerColor;
}
Color RenderTheme::platformDictationAlternativesMarkerColor(OptionSet<StyleColorOptions>) const
{
return Color::green;
}
Color RenderTheme::autocorrectionReplacementMarkerColor(const RenderText& renderer) const
{
auto options = renderer.styleColorOptions();
auto& cache = colorCache(options);
if (!cache.autocorrectionReplacementMarkerColor.isValid())
cache.autocorrectionReplacementMarkerColor = platformAutocorrectionReplacementMarkerColor(options);
return cache.autocorrectionReplacementMarkerColor;
}
Color RenderTheme::platformAutocorrectionReplacementMarkerColor(OptionSet<StyleColorOptions>) const
{
return Color::green;
}
Color RenderTheme::grammarMarkerColor(OptionSet<StyleColorOptions> options) const
{
auto& cache = colorCache(options);
if (!cache.grammarMarkerColor.isValid())
cache.grammarMarkerColor = platformGrammarMarkerColor(options);
return cache.grammarMarkerColor;
}
Color RenderTheme::platformGrammarMarkerColor(OptionSet<StyleColorOptions>) const
{
return Color::green;
}
Color RenderTheme::documentMarkerLineColor(const RenderText& renderer, DocumentMarkerLineStyleMode mode) const
{
auto options = renderer.styleColorOptions();
switch (mode) {
case DocumentMarkerLineStyleMode::Spelling:
return spellingMarkerColor(options);
case DocumentMarkerLineStyleMode::DictationAlternatives:
case DocumentMarkerLineStyleMode::TextCheckingDictationPhraseWithAlternatives:
return dictationAlternativesMarkerColor(options);
case DocumentMarkerLineStyleMode::AutocorrectionReplacement:
return autocorrectionReplacementMarkerColor(renderer);
case DocumentMarkerLineStyleMode::Grammar:
return grammarMarkerColor(options);
}
ASSERT_NOT_REACHED();
return Color::transparentBlack;
}
Color RenderTheme::focusRingColor(OptionSet<StyleColorOptions> options) const
{
auto& cache = colorCache(options);
if (!cache.systemFocusRingColor.isValid())
cache.systemFocusRingColor = platformFocusRingColor(options);
return cache.systemFocusRingColor;
}
String RenderTheme::fileListDefaultLabel(bool multipleFilesAllowed) const
{
if (multipleFilesAllowed)
return fileButtonNoFilesSelectedLabel();
return fileButtonNoFileSelectedLabel();
}
String RenderTheme::fileListNameForWidth(const FileList* fileList, const FontCascade& font, int width, bool multipleFilesAllowed) const
{
if (width <= 0)
return String();
String string;
if (fileList->isEmpty())
string = fileListDefaultLabel(multipleFilesAllowed);
else if (fileList->length() == 1)
string = fileList->item(0)->name();
else
return StringTruncator::rightTruncate(multipleFileUploadText(fileList->length()), width, font);
return StringTruncator::centerTruncate(string, width, font);
}
#if USE(SYSTEM_PREVIEW)
void RenderTheme::paintSystemPreviewBadge(Image& image, const PaintInfo& paintInfo, const FloatRect& rect)
{
// The default implementation paints a small marker
// in the upper right corner, as long as the image is big enough.
UNUSED_PARAM(image);
auto& context = paintInfo.context();
GraphicsContextStateSaver stateSaver { context };
if (rect.width() < 32 || rect.height() < 32)
return;
auto markerRect = FloatRect {rect.x() + rect.width() - 24, rect.y() + 8, 16, 16 };
auto roundedMarkerRect = FloatRoundedRect { markerRect, CornerRadii { 8 } };
context.fillRoundedRect(roundedMarkerRect, Color::red);
}
#endif
#if ENABLE(TOUCH_EVENTS)
Color RenderTheme::platformTapHighlightColor() const
{
// This color is expected to be drawn on a semi-transparent overlay,
// making it more transparent than its alpha value indicates.
return Color::black.colorWithAlphaByte(102);
}
#endif
} // namespace WebCore