зеркало из https://github.com/mozilla/gecko-dev.git
1560 строки
49 KiB
C++
1560 строки
49 KiB
C++
/* -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "mozilla/ArrayUtils.h"
|
|
|
|
#include "nscore.h"
|
|
|
|
#include "nsXPLookAndFeel.h"
|
|
#include "nsLookAndFeel.h"
|
|
#include "HeadlessLookAndFeel.h"
|
|
#include "RemoteLookAndFeel.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsCRT.h"
|
|
#include "nsFont.h"
|
|
#include "nsIFrame.h"
|
|
#include "nsIXULRuntime.h"
|
|
#include "Theme.h"
|
|
#include "SurfaceCacheUtils.h"
|
|
#include "mozilla/dom/ContentParent.h"
|
|
#include "mozilla/dom/ContentChild.h"
|
|
#include "mozilla/Preferences.h"
|
|
#include "mozilla/Services.h"
|
|
#include "mozilla/ServoStyleSet.h"
|
|
#include "mozilla/ServoCSSParser.h"
|
|
#include "mozilla/StaticPrefs_browser.h"
|
|
#include "mozilla/StaticPrefs_editor.h"
|
|
#include "mozilla/StaticPrefs_layout.h"
|
|
#include "mozilla/StaticPrefs_ui.h"
|
|
#include "mozilla/StaticPrefs_widget.h"
|
|
#include "mozilla/dom/Document.h"
|
|
#include "mozilla/PreferenceSheet.h"
|
|
#include "mozilla/gfx/2D.h"
|
|
#include "mozilla/widget/WidgetMessageUtils.h"
|
|
#include "mozilla/RelativeLuminanceUtils.h"
|
|
#include "mozilla/Telemetry.h"
|
|
#include "mozilla/TelemetryScalarEnums.h"
|
|
|
|
#include "gfxPlatform.h"
|
|
#include "gfxFont.h"
|
|
|
|
#include "qcms.h"
|
|
|
|
#include <bitset>
|
|
|
|
using namespace mozilla;
|
|
|
|
using IntID = mozilla::LookAndFeel::IntID;
|
|
using FloatID = mozilla::LookAndFeel::FloatID;
|
|
using ColorID = mozilla::LookAndFeel::ColorID;
|
|
using FontID = mozilla::LookAndFeel::FontID;
|
|
|
|
template <typename Index, typename Value, Index kEnd>
|
|
class EnumeratedCache {
|
|
mozilla::EnumeratedArray<Index, kEnd, Value> mEntries;
|
|
std::bitset<size_t(kEnd)> mValidity;
|
|
|
|
public:
|
|
constexpr EnumeratedCache() = default;
|
|
|
|
bool IsValid(Index aIndex) const { return mValidity[size_t(aIndex)]; }
|
|
|
|
const Value* Get(Index aIndex) const {
|
|
return IsValid(aIndex) ? &mEntries[aIndex] : nullptr;
|
|
}
|
|
|
|
void Insert(Index aIndex, Value aValue) {
|
|
mValidity[size_t(aIndex)] = true;
|
|
mEntries[aIndex] = aValue;
|
|
}
|
|
|
|
void Remove(Index aIndex) {
|
|
mValidity[size_t(aIndex)] = false;
|
|
mEntries[aIndex] = Value();
|
|
}
|
|
|
|
void Clear() {
|
|
mValidity.reset();
|
|
for (auto& entry : mEntries) {
|
|
entry = Value();
|
|
}
|
|
}
|
|
};
|
|
|
|
using ColorCache = EnumeratedCache<ColorID, Maybe<nscolor>, ColorID::End>;
|
|
|
|
struct ColorCaches {
|
|
using UseStandins = LookAndFeel::UseStandins;
|
|
|
|
ColorCache mCaches[2][2];
|
|
|
|
constexpr ColorCaches() = default;
|
|
|
|
ColorCache& Get(ColorScheme aScheme, UseStandins aUseStandins) {
|
|
return mCaches[aScheme == ColorScheme::Dark]
|
|
[aUseStandins == UseStandins::Yes];
|
|
}
|
|
|
|
void Clear() {
|
|
for (auto& c : mCaches) {
|
|
for (auto& cache : c) {
|
|
cache.Clear();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
static ColorCaches sColorCaches;
|
|
|
|
static EnumeratedCache<FloatID, Maybe<float>, FloatID::End> sFloatCache;
|
|
static EnumeratedCache<IntID, Maybe<int32_t>, IntID::End> sIntCache;
|
|
static EnumeratedCache<FontID, widget::LookAndFeelFont, FontID::End> sFontCache;
|
|
|
|
// To make one of these prefs toggleable from a reftest add a user
|
|
// pref in testing/profiles/reftest/user.js. For example, to make
|
|
// ui.useAccessibilityTheme toggleable, add:
|
|
//
|
|
// user_pref("ui.useAccessibilityTheme", 0);
|
|
//
|
|
// This needs to be of the same length and in the same order as
|
|
// LookAndFeel::IntID values.
|
|
static const char sIntPrefs[][45] = {
|
|
"ui.caretBlinkTime",
|
|
"ui.caretBlinkCount",
|
|
"ui.caretWidth",
|
|
"ui.caretVisibleWithSelection",
|
|
"ui.selectTextfieldsOnKeyFocus",
|
|
"ui.submenuDelay",
|
|
"ui.menusCanOverlapOSBar",
|
|
"ui.useOverlayScrollbars",
|
|
"ui.allowOverlayScrollbarsOverlap",
|
|
"ui.skipNavigatingDisabledMenuItem",
|
|
"ui.dragThresholdX",
|
|
"ui.dragThresholdY",
|
|
"ui.useAccessibilityTheme",
|
|
"ui.scrollArrowStyle",
|
|
"ui.scrollButtonLeftMouseButtonAction",
|
|
"ui.scrollButtonMiddleMouseButtonAction",
|
|
"ui.scrollButtonRightMouseButtonAction",
|
|
"ui.treeOpenDelay",
|
|
"ui.treeCloseDelay",
|
|
"ui.treeLazyScrollDelay",
|
|
"ui.treeScrollDelay",
|
|
"ui.treeScrollLinesMax",
|
|
"accessibility.tabfocus", // Weird one...
|
|
"ui.chosenMenuItemsShouldBlink",
|
|
"ui.windowsAccentColorInTitlebar",
|
|
"ui.windowsDefaultTheme",
|
|
"ui.dwmCompositor",
|
|
"ui.windowsClassic",
|
|
"ui.windowsGlass",
|
|
"ui.macGraphiteTheme",
|
|
"ui.macBigSurTheme",
|
|
"ui.macRTL",
|
|
"ui.alertNotificationOrigin",
|
|
"ui.scrollToClick",
|
|
"ui.IMERawInputUnderlineStyle",
|
|
"ui.IMESelectedRawTextUnderlineStyle",
|
|
"ui.IMEConvertedTextUnderlineStyle",
|
|
"ui.IMESelectedConvertedTextUnderlineStyle",
|
|
"ui.SpellCheckerUnderlineStyle",
|
|
"ui.menuBarDrag",
|
|
"ui.scrollbarButtonAutoRepeatBehavior",
|
|
"ui.tooltipDelay",
|
|
"ui.swipeAnimationEnabled",
|
|
"ui.scrollbarDisplayOnMouseMove",
|
|
"ui.scrollbarFadeBeginDelay",
|
|
"ui.scrollbarFadeDuration",
|
|
"ui.contextMenuOffsetVertical",
|
|
"ui.contextMenuOffsetHorizontal",
|
|
"ui.GtkCSDAvailable",
|
|
"ui.GtkCSDMinimizeButton",
|
|
"ui.GtkCSDMaximizeButton",
|
|
"ui.GtkCSDCloseButton",
|
|
"ui.GtkCSDMinimizeButtonPosition",
|
|
"ui.GtkCSDMaximizeButtonPosition",
|
|
"ui.GtkCSDCloseButtonPosition",
|
|
"ui.GtkCSDReversedPlacement",
|
|
"ui.systemUsesDarkTheme",
|
|
"ui.prefersReducedMotion",
|
|
"ui.primaryPointerCapabilities",
|
|
"ui.allPointerCapabilities",
|
|
"ui.systemVerticalScrollbarWidth",
|
|
"ui.systemHorizontalScrollbarHeight",
|
|
"ui.touchDeviceSupportPresent",
|
|
"ui.titlebarRadius",
|
|
"ui.GtkMenuRadius",
|
|
"ui.dynamicRange",
|
|
"ui.videoDynamicRange",
|
|
"ui.panelAnimations",
|
|
};
|
|
|
|
static_assert(ArrayLength(sIntPrefs) == size_t(LookAndFeel::IntID::End),
|
|
"Should have a pref for each int value");
|
|
|
|
// This array MUST be kept in the same order as the float id list in
|
|
// LookAndFeel.h
|
|
// clang-format off
|
|
static const char sFloatPrefs[][37] = {
|
|
"ui.IMEUnderlineRelativeSize",
|
|
"ui.SpellCheckerUnderlineRelativeSize",
|
|
"ui.caretAspectRatio",
|
|
"ui.textScaleFactor",
|
|
"ui.cursorScale",
|
|
};
|
|
// clang-format on
|
|
|
|
static_assert(ArrayLength(sFloatPrefs) == size_t(LookAndFeel::FloatID::End),
|
|
"Should have a pref for each float value");
|
|
|
|
// This array MUST be kept in the same order as the color list in
|
|
// specified/color.rs
|
|
static const char sColorPrefs[][41] = {
|
|
"ui.activeborder",
|
|
"ui.activecaption",
|
|
"ui.appworkspace",
|
|
"ui.background",
|
|
"ui.buttonface",
|
|
"ui.buttonhighlight",
|
|
"ui.buttonshadow",
|
|
"ui.buttontext",
|
|
"ui.buttonborder",
|
|
"ui.captiontext",
|
|
"ui.-moz-field",
|
|
"ui.-moz-disabledfield",
|
|
"ui.-moz-fieldtext",
|
|
"ui.mark",
|
|
"ui.marktext",
|
|
"ui.-moz-comboboxtext",
|
|
"ui.-moz-combobox",
|
|
"ui.graytext",
|
|
"ui.highlight",
|
|
"ui.highlighttext",
|
|
"ui.inactiveborder",
|
|
"ui.inactivecaption",
|
|
"ui.inactivecaptiontext",
|
|
"ui.infobackground",
|
|
"ui.infotext",
|
|
"ui.menu",
|
|
"ui.menutext",
|
|
"ui.scrollbar",
|
|
"ui.threeddarkshadow",
|
|
"ui.threedface",
|
|
"ui.threedhighlight",
|
|
"ui.threedlightshadow",
|
|
"ui.threedshadow",
|
|
"ui.window",
|
|
"ui.windowframe",
|
|
"ui.windowtext",
|
|
"ui.-moz-buttondefault",
|
|
"ui.-moz-default-color",
|
|
"ui.-moz-default-background-color",
|
|
"ui.-moz-dialog",
|
|
"ui.-moz-dialogtext",
|
|
"ui.-moz-dragtargetzone",
|
|
"ui.-moz-cellhighlight",
|
|
"ui.-moz_cellhighlighttext",
|
|
"ui.selecteditem",
|
|
"ui.selecteditemtext",
|
|
"ui.-moz-buttonhoverface",
|
|
"ui.-moz_buttonhovertext",
|
|
"ui.-moz_menuhover",
|
|
"ui.-moz_menuhoverdisabled",
|
|
"ui.-moz_menuhovertext",
|
|
"ui.-moz_menubartext",
|
|
"ui.-moz_menubarhovertext",
|
|
"ui.-moz_eventreerow",
|
|
"ui.-moz_oddtreerow",
|
|
"ui.-moz-buttonactivetext",
|
|
"ui.-moz-buttonactiveface",
|
|
"ui.-moz-buttondisabledface",
|
|
"ui.-moz_mac_chrome_active",
|
|
"ui.-moz_mac_chrome_inactive",
|
|
"ui.-moz-mac-defaultbuttontext",
|
|
"ui.-moz-mac-focusring",
|
|
"ui.-moz-mac-menuselect",
|
|
"ui.-moz-mac-menushadow",
|
|
"ui.-moz-mac-menutextdisable",
|
|
"ui.-moz-mac-menutextselect",
|
|
"ui.-moz_mac_disabledtoolbartext",
|
|
"ui.-moz-mac-secondaryhighlight",
|
|
"ui.-moz-mac-menupopup",
|
|
"ui.-moz-mac-menuitem",
|
|
"ui.-moz-mac-active-menuitem",
|
|
"ui.-moz-mac-source-list",
|
|
"ui.-moz-mac-source-list-selection",
|
|
"ui.-moz-mac-active-source-list-selection",
|
|
"ui.-moz-mac-tooltip",
|
|
"ui.accentcolor",
|
|
"ui.accentcolortext",
|
|
"ui.-moz-autofill-background",
|
|
"ui.-moz-win-mediatext",
|
|
"ui.-moz-win-communicationstext",
|
|
"ui.-moz-nativehyperlinktext",
|
|
"ui.-moz-nativevisitedhyperlinktext",
|
|
"ui.-moz-hyperlinktext",
|
|
"ui.-moz-activehyperlinktext",
|
|
"ui.-moz-visitedhyperlinktext",
|
|
"ui.-moz-colheadertext",
|
|
"ui.-moz-colheaderhovertext",
|
|
"ui.textSelectDisabledBackground",
|
|
"ui.textSelectAttentionBackground",
|
|
"ui.textSelectAttentionForeground",
|
|
"ui.textHighlightBackground",
|
|
"ui.textHighlightForeground",
|
|
"ui.IMERawInputBackground",
|
|
"ui.IMERawInputForeground",
|
|
"ui.IMERawInputUnderline",
|
|
"ui.IMESelectedRawTextBackground",
|
|
"ui.IMESelectedRawTextForeground",
|
|
"ui.IMESelectedRawTextUnderline",
|
|
"ui.IMEConvertedTextBackground",
|
|
"ui.IMEConvertedTextForeground",
|
|
"ui.IMEConvertedTextUnderline",
|
|
"ui.IMESelectedConvertedTextBackground",
|
|
"ui.IMESelectedConvertedTextForeground",
|
|
"ui.IMESelectedConvertedTextUnderline",
|
|
"ui.SpellCheckerUnderline",
|
|
"ui.themedScrollbar",
|
|
"ui.themedScrollbarInactive",
|
|
"ui.themedScrollbarThumb",
|
|
"ui.themedScrollbarThumbHover",
|
|
"ui.themedScrollbarThumbActive",
|
|
"ui.themedScrollbarThumbInactive",
|
|
};
|
|
|
|
static_assert(ArrayLength(sColorPrefs) == size_t(LookAndFeel::ColorID::End),
|
|
"Should have a pref for each color value");
|
|
|
|
// This array MUST be kept in the same order as the SystemFont enum.
|
|
static const char sFontPrefs[][41] = {
|
|
"ui.font.caption",
|
|
"ui.font.icon",
|
|
"ui.font.menu",
|
|
"ui.font.message-box",
|
|
"ui.font.small-caption",
|
|
"ui.font.status-bar",
|
|
"ui.font.-moz-pull-down-menu",
|
|
"ui.font.-moz-button",
|
|
"ui.font.-moz-list",
|
|
"ui.font.-moz-field",
|
|
};
|
|
|
|
static_assert(ArrayLength(sFontPrefs) == size_t(LookAndFeel::FontID::End),
|
|
"Should have a pref for each font value");
|
|
|
|
const char* nsXPLookAndFeel::GetColorPrefName(ColorID aId) {
|
|
return sColorPrefs[size_t(aId)];
|
|
}
|
|
|
|
bool nsXPLookAndFeel::sInitialized = false;
|
|
|
|
nsXPLookAndFeel* nsXPLookAndFeel::sInstance = nullptr;
|
|
bool nsXPLookAndFeel::sShutdown = false;
|
|
|
|
auto LookAndFeel::SystemZoomSettings() -> ZoomSettings {
|
|
ZoomSettings settings;
|
|
switch (StaticPrefs::browser_display_os_zoom_behavior()) {
|
|
case 0:
|
|
default:
|
|
break;
|
|
case 1:
|
|
settings.mFullZoom = GetTextScaleFactor();
|
|
break;
|
|
case 2:
|
|
settings.mTextZoom = GetTextScaleFactor();
|
|
break;
|
|
}
|
|
return settings;
|
|
}
|
|
|
|
// static
|
|
nsXPLookAndFeel* nsXPLookAndFeel::GetInstance() {
|
|
if (sInstance) {
|
|
return sInstance;
|
|
}
|
|
|
|
NS_ENSURE_TRUE(!sShutdown, nullptr);
|
|
|
|
// If we're in a content process, then the parent process will have supplied
|
|
// us with an initial FullLookAndFeel object.
|
|
// We grab this data from the ContentChild,
|
|
// where it's been temporarily stashed, and initialize our new LookAndFeel
|
|
// object with it.
|
|
|
|
FullLookAndFeel* lnf = nullptr;
|
|
|
|
if (auto* cc = mozilla::dom::ContentChild::GetSingleton()) {
|
|
lnf = &cc->BorrowLookAndFeelData();
|
|
}
|
|
|
|
if (lnf) {
|
|
sInstance = new widget::RemoteLookAndFeel(std::move(*lnf));
|
|
} else if (gfxPlatform::IsHeadless()) {
|
|
sInstance = new widget::HeadlessLookAndFeel();
|
|
} else {
|
|
sInstance = new nsLookAndFeel();
|
|
}
|
|
|
|
// This is only ever used once during initialization, and can be cleared now.
|
|
if (lnf) {
|
|
*lnf = {};
|
|
}
|
|
|
|
widget::Theme::Init();
|
|
return sInstance;
|
|
}
|
|
|
|
// static
|
|
void nsXPLookAndFeel::Shutdown() {
|
|
if (sShutdown) {
|
|
return;
|
|
}
|
|
|
|
sShutdown = true;
|
|
delete sInstance;
|
|
sInstance = nullptr;
|
|
|
|
// This keeps strings alive, so need to clear to make leak checking happy.
|
|
sFontCache.Clear();
|
|
|
|
widget::Theme::Shutdown();
|
|
}
|
|
|
|
static void IntPrefChanged(const nsACString& aPref) {
|
|
// Most Int prefs can't change our system colors or fonts, but
|
|
// ui.systemUsesDarkTheme can, since it affects the effective color-scheme
|
|
// (affecting system colors).
|
|
auto changeKind = aPref.EqualsLiteral("ui.systemUsesDarkTheme")
|
|
? widget::ThemeChangeKind::Style
|
|
: widget::ThemeChangeKind::MediaQueriesOnly;
|
|
LookAndFeel::NotifyChangedAllWindows(changeKind);
|
|
}
|
|
|
|
static void FloatPrefChanged(const nsACString& aPref) {
|
|
// Most float prefs can't change our system colors or fonts, but
|
|
// textScaleFactor affects layout.
|
|
auto changeKind = aPref.EqualsLiteral("ui.textScaleFactor")
|
|
? widget::ThemeChangeKind::StyleAndLayout
|
|
: widget::ThemeChangeKind::MediaQueriesOnly;
|
|
LookAndFeel::NotifyChangedAllWindows(changeKind);
|
|
}
|
|
|
|
static void ColorPrefChanged() {
|
|
// Color prefs affect style, because they by definition change system colors.
|
|
LookAndFeel::NotifyChangedAllWindows(widget::ThemeChangeKind::Style);
|
|
}
|
|
|
|
static void FontPrefChanged() {
|
|
// Color prefs affect style, because they by definition change system fonts.
|
|
LookAndFeel::NotifyChangedAllWindows(widget::ThemeChangeKind::Style);
|
|
}
|
|
|
|
// static
|
|
void nsXPLookAndFeel::OnPrefChanged(const char* aPref, void* aClosure) {
|
|
nsDependentCString prefName(aPref);
|
|
for (const char* pref : sIntPrefs) {
|
|
if (prefName.Equals(pref)) {
|
|
IntPrefChanged(prefName);
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (const char* pref : sFloatPrefs) {
|
|
if (prefName.Equals(pref)) {
|
|
FloatPrefChanged(prefName);
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (const char* pref : sColorPrefs) {
|
|
// We use StringBeginsWith to handle .dark prefs too.
|
|
if (StringBeginsWith(prefName, nsDependentCString(pref))) {
|
|
ColorPrefChanged();
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (const char* pref : sFontPrefs) {
|
|
if (StringBeginsWith(prefName, nsDependentCString(pref))) {
|
|
FontPrefChanged();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool LookAndFeel::WindowsNonNativeMenusEnabled() {
|
|
switch (StaticPrefs::browser_display_windows_non_native_menus()) {
|
|
case 0:
|
|
return false;
|
|
case 1:
|
|
return true;
|
|
default:
|
|
#ifdef XP_WIN
|
|
return IsWin10OrLater();
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static constexpr struct {
|
|
nsLiteralCString mName;
|
|
widget::ThemeChangeKind mChangeKind =
|
|
widget::ThemeChangeKind::MediaQueriesOnly;
|
|
} kMediaQueryPrefs[] = {
|
|
{"browser.display.windows.non_native_menus"_ns},
|
|
// Affects whether standins are used for the accent color.
|
|
{"widget.non-native-theme.use-theme-accent"_ns,
|
|
widget::ThemeChangeKind::Style},
|
|
// These two affect system colors on Windows.
|
|
{"widget.windows.uwp-system-colors.enabled"_ns,
|
|
widget::ThemeChangeKind::Style},
|
|
// These two affect system colors on Windows.
|
|
{"widget.windows.uwp-system-colors.highlight-accent"_ns,
|
|
widget::ThemeChangeKind::Style},
|
|
// Affects env().
|
|
{"layout.css.prefers-color-scheme.content-override"_ns,
|
|
widget::ThemeChangeKind::Style},
|
|
// Affects media queries and scrollbar sizes, so gotta relayout.
|
|
{"widget.gtk.overlay-scrollbars.enabled"_ns,
|
|
widget::ThemeChangeKind::StyleAndLayout},
|
|
// Affects zoom settings which includes text and full zoom.
|
|
{"browser.display.os-zoom-behavior"_ns,
|
|
widget::ThemeChangeKind::StyleAndLayout},
|
|
// This affects not only the media query, but also the native theme, so we
|
|
// need to re-layout.
|
|
{"browser.theme.toolbar-theme"_ns, widget::ThemeChangeKind::AllBits},
|
|
{"browser.theme.content-theme"_ns},
|
|
{"layout.css.moz-box-flexbox-emulation.enabled"_ns},
|
|
{"mathml.legacy_maction_and_semantics_implementations.disabled"_ns},
|
|
{"mathml.ms_lquote_rquote_attributes.disabled"_ns},
|
|
};
|
|
|
|
// Read values from the user's preferences.
|
|
// This is done once at startup, but since the user's preferences
|
|
// haven't actually been read yet at that time, we also have to
|
|
// set a callback to inform us of changes to each pref.
|
|
void nsXPLookAndFeel::Init() {
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
// Say we're already initialized, and take the chance that it might fail;
|
|
// protects against some other process writing to our static variables.
|
|
sInitialized = true;
|
|
|
|
RecomputeColorSchemes();
|
|
|
|
// XXX If we could reorganize the pref names, we should separate the branch
|
|
// for each types. Then, we could reduce the unnecessary loop from
|
|
// nsXPLookAndFeel::OnPrefChanged().
|
|
Preferences::RegisterPrefixCallback(OnPrefChanged, "ui.");
|
|
// We really do just want the accessibility.tabfocus pref, not other prefs
|
|
// that start with that string.
|
|
Preferences::RegisterCallback(OnPrefChanged, "accessibility.tabfocus");
|
|
|
|
for (auto& pref : kMediaQueryPrefs) {
|
|
Preferences::RegisterCallback(
|
|
[](const char*, void* aChangeKind) {
|
|
auto changeKind =
|
|
widget::ThemeChangeKind(reinterpret_cast<uintptr_t>(aChangeKind));
|
|
LookAndFeel::NotifyChangedAllWindows(changeKind);
|
|
},
|
|
pref.mName, reinterpret_cast<void*>(uintptr_t(pref.mChangeKind)));
|
|
}
|
|
}
|
|
|
|
nsXPLookAndFeel::~nsXPLookAndFeel() {
|
|
NS_ASSERTION(sInstance == this,
|
|
"This destroying instance isn't the singleton instance");
|
|
sInstance = nullptr;
|
|
}
|
|
|
|
static bool IsSpecialColor(LookAndFeel::ColorID aID, nscolor aColor) {
|
|
using ColorID = LookAndFeel::ColorID;
|
|
|
|
if (aColor == NS_SAME_AS_FOREGROUND_COLOR) {
|
|
return true;
|
|
}
|
|
|
|
switch (aID) {
|
|
case ColorID::IMESelectedRawTextBackground:
|
|
case ColorID::IMESelectedConvertedTextBackground:
|
|
case ColorID::IMERawInputBackground:
|
|
case ColorID::IMEConvertedTextBackground:
|
|
case ColorID::IMESelectedRawTextForeground:
|
|
case ColorID::IMESelectedConvertedTextForeground:
|
|
case ColorID::IMERawInputForeground:
|
|
case ColorID::IMEConvertedTextForeground:
|
|
case ColorID::IMERawInputUnderline:
|
|
case ColorID::IMEConvertedTextUnderline:
|
|
case ColorID::IMESelectedRawTextUnderline:
|
|
case ColorID::IMESelectedConvertedTextUnderline:
|
|
case ColorID::SpellCheckerUnderline:
|
|
return NS_IS_SELECTION_SPECIAL_COLOR(aColor);
|
|
default:
|
|
break;
|
|
}
|
|
/*
|
|
* In GetColor(), every color that is not a special color is color
|
|
* corrected. Use false to make other colors color corrected.
|
|
*/
|
|
return false;
|
|
}
|
|
|
|
nscolor nsXPLookAndFeel::GetStandinForNativeColor(ColorID aID,
|
|
ColorScheme aScheme) {
|
|
if (aScheme == ColorScheme::Dark) {
|
|
if (auto color = GenericDarkColor(aID)) {
|
|
return *color;
|
|
}
|
|
}
|
|
|
|
// The stand-in colors are taken from what the non-native theme needs (for
|
|
// field/button colors), the Windows 7 Aero theme except Mac-specific colors
|
|
// which are taken from Mac OS 10.7.
|
|
|
|
#define COLOR(name_, r, g, b) \
|
|
case ColorID::name_: \
|
|
return NS_RGB(r, g, b);
|
|
|
|
#define COLORA(name_, r, g, b, a) \
|
|
case ColorID::name_: \
|
|
return NS_RGBA(r, g, b, a);
|
|
|
|
switch (aID) {
|
|
// These are here for the purposes of headless mode.
|
|
case ColorID::IMESelectedRawTextBackground:
|
|
case ColorID::IMESelectedConvertedTextBackground:
|
|
case ColorID::IMERawInputBackground:
|
|
case ColorID::IMEConvertedTextBackground:
|
|
return NS_TRANSPARENT;
|
|
case ColorID::IMESelectedRawTextForeground:
|
|
case ColorID::IMESelectedConvertedTextForeground:
|
|
case ColorID::IMERawInputForeground:
|
|
case ColorID::IMEConvertedTextForeground:
|
|
return NS_SAME_AS_FOREGROUND_COLOR;
|
|
case ColorID::IMERawInputUnderline:
|
|
case ColorID::IMEConvertedTextUnderline:
|
|
return NS_40PERCENT_FOREGROUND_COLOR;
|
|
case ColorID::Accentcolor:
|
|
return widget::sDefaultAccent.ToABGR();
|
|
case ColorID::Accentcolortext:
|
|
return widget::sDefaultAccentText.ToABGR();
|
|
COLOR(SpellCheckerUnderline, 0xff, 0x00, 0x00)
|
|
COLOR(TextSelectDisabledBackground, 0xaa, 0xaa, 0xaa)
|
|
|
|
// CSS 2 colors:
|
|
COLOR(Activeborder, 0xB4, 0xB4, 0xB4)
|
|
COLOR(Activecaption, 0x99, 0xB4, 0xD1)
|
|
COLOR(Appworkspace, 0xAB, 0xAB, 0xAB)
|
|
COLOR(Background, 0x00, 0x00, 0x00)
|
|
COLOR(Buttonhighlight, 0xFF, 0xFF, 0xFF)
|
|
COLOR(Buttonshadow, 0xA0, 0xA0, 0xA0)
|
|
|
|
// Buttons and comboboxes should be kept in sync since they are drawn with
|
|
// the same colors by the non-native theme.
|
|
COLOR(Buttonface, 0xe9, 0xe9, 0xed)
|
|
COLORA(MozButtondisabledface, 0xe9, 0xe9, 0xed, 128)
|
|
|
|
COLOR(MozCombobox, 0xe9, 0xe9, 0xed)
|
|
|
|
COLOR(Buttontext, 0x00, 0x00, 0x00)
|
|
COLOR(MozComboboxtext, 0x00, 0x00, 0x00)
|
|
|
|
COLOR(Captiontext, 0x00, 0x00, 0x00)
|
|
COLOR(Graytext, 0x6D, 0x6D, 0x6D)
|
|
COLOR(Highlight, 0x33, 0x99, 0xFF)
|
|
COLOR(Highlighttext, 0xFF, 0xFF, 0xFF)
|
|
COLOR(Inactiveborder, 0xF4, 0xF7, 0xFC)
|
|
COLOR(Inactivecaption, 0xBF, 0xCD, 0xDB)
|
|
COLOR(Inactivecaptiontext, 0x43, 0x4E, 0x54)
|
|
COLOR(Infobackground, 0xFF, 0xFF, 0xE1)
|
|
COLOR(Infotext, 0x00, 0x00, 0x00)
|
|
COLOR(Menu, 0xF0, 0xF0, 0xF0)
|
|
COLOR(Menutext, 0x00, 0x00, 0x00)
|
|
COLOR(Scrollbar, 0xC8, 0xC8, 0xC8)
|
|
COLOR(Threeddarkshadow, 0x69, 0x69, 0x69)
|
|
COLOR(Threedface, 0xF0, 0xF0, 0xF0)
|
|
COLOR(Threedhighlight, 0xFF, 0xFF, 0xFF)
|
|
COLOR(Threedlightshadow, 0xE3, 0xE3, 0xE3)
|
|
COLOR(Threedshadow, 0xA0, 0xA0, 0xA0)
|
|
COLOR(Buttonborder, 0xE3, 0xE3, 0xE3)
|
|
COLOR(Mark, 0xFF, 0xFF, 0x00)
|
|
COLOR(Marktext, 0x00, 0x00, 0x00)
|
|
COLOR(Window, 0xFF, 0xFF, 0xFF)
|
|
COLOR(Windowframe, 0x64, 0x64, 0x64)
|
|
COLOR(Windowtext, 0x00, 0x00, 0x00)
|
|
COLOR(MozButtondefault, 0x69, 0x69, 0x69)
|
|
COLOR(Field, 0xFF, 0xFF, 0xFF)
|
|
COLORA(MozDisabledfield, 0xFF, 0xFF, 0xFF, 128)
|
|
COLOR(Fieldtext, 0x00, 0x00, 0x00)
|
|
COLOR(MozDialog, 0xF0, 0xF0, 0xF0)
|
|
COLOR(MozDialogtext, 0x00, 0x00, 0x00)
|
|
COLOR(MozColheadertext, 0x00, 0x00, 0x00)
|
|
COLOR(MozColheaderhovertext, 0x00, 0x00, 0x00)
|
|
COLOR(MozDragtargetzone, 0xFF, 0xFF, 0xFF)
|
|
COLOR(MozCellhighlight, 0xF0, 0xF0, 0xF0)
|
|
COLOR(MozCellhighlighttext, 0x00, 0x00, 0x00)
|
|
COLOR(Selecteditem, 0x33, 0x99, 0xFF)
|
|
COLOR(Selecteditemtext, 0xFF, 0xFF, 0xFF)
|
|
COLOR(MozButtonhoverface, 0xd0, 0xd0, 0xd7)
|
|
COLOR(MozButtonhovertext, 0x00, 0x00, 0x00)
|
|
COLOR(MozButtonactiveface, 0xb1, 0xb1, 0xb9)
|
|
COLOR(MozButtonactivetext, 0x00, 0x00, 0x00)
|
|
COLOR(MozMenuhover, 0x33, 0x99, 0xFF)
|
|
COLOR(MozMenuhovertext, 0x00, 0x00, 0x00)
|
|
COLOR(MozMenubartext, 0x00, 0x00, 0x00)
|
|
COLOR(MozMenubarhovertext, 0x00, 0x00, 0x00)
|
|
COLOR(MozMenuhoverdisabled, 0xF0, 0xF0, 0xF0)
|
|
COLOR(MozEventreerow, 0xFF, 0xFF, 0xFF)
|
|
COLOR(MozOddtreerow, 0xFF, 0xFF, 0xFF)
|
|
COLOR(MozMacChromeActive, 0xB2, 0xB2, 0xB2)
|
|
COLOR(MozMacChromeInactive, 0xE1, 0xE1, 0xE1)
|
|
COLOR(MozMacFocusring, 0x60, 0x9D, 0xD7)
|
|
COLOR(MozMacMenuselect, 0x38, 0x75, 0xD7)
|
|
COLOR(MozMacMenushadow, 0xA3, 0xA3, 0xA3)
|
|
COLOR(MozMacMenutextdisable, 0x88, 0x88, 0x88)
|
|
COLOR(MozMacMenutextselect, 0xFF, 0xFF, 0xFF)
|
|
COLOR(MozMacDisabledtoolbartext, 0x3F, 0x3F, 0x3F)
|
|
COLOR(MozMacSecondaryhighlight, 0xD4, 0xD4, 0xD4)
|
|
COLOR(MozMacMenupopup, 0xe6, 0xe6, 0xe6)
|
|
COLOR(MozMacMenuitem, 0xe6, 0xe6, 0xe6)
|
|
COLOR(MozMacActiveMenuitem, 0x0a, 0x64, 0xdc)
|
|
COLOR(MozMacSourceList, 0xf7, 0xf7, 0xf7)
|
|
COLOR(MozMacSourceListSelection, 0xc8, 0xc8, 0xc8)
|
|
COLOR(MozMacActiveSourceListSelection, 0x0a, 0x64, 0xdc)
|
|
COLOR(MozMacTooltip, 0xf7, 0xf7, 0xf7)
|
|
// Seems to be the default color (hardcoded because of bug 1065998)
|
|
COLOR(MozWinMediatext, 0xFF, 0xFF, 0xFF)
|
|
COLOR(MozWinCommunicationstext, 0xFF, 0xFF, 0xFF)
|
|
COLOR(MozNativehyperlinktext, 0x00, 0x66, 0xCC)
|
|
COLOR(MozNativevisitedhyperlinktext, 0x55, 0x1A, 0x8B)
|
|
default:
|
|
break;
|
|
}
|
|
return NS_RGB(0xFF, 0xFF, 0xFF);
|
|
}
|
|
|
|
#undef COLOR
|
|
#undef COLORA
|
|
|
|
// Taken from in-content/common.inc.css's dark theme.
|
|
Maybe<nscolor> nsXPLookAndFeel::GenericDarkColor(ColorID aID) {
|
|
nscolor color = NS_RGB(0, 0, 0);
|
|
static constexpr nscolor kWindowBackground = NS_RGB(28, 27, 34);
|
|
static constexpr nscolor kWindowText = NS_RGB(251, 251, 254);
|
|
switch (aID) {
|
|
case ColorID::Window: // --in-content-page-background
|
|
case ColorID::Background:
|
|
color = kWindowBackground;
|
|
break;
|
|
|
|
case ColorID::Menu:
|
|
color = NS_RGB(0x2b, 0x2a, 0x33);
|
|
break;
|
|
|
|
case ColorID::MozMenuhovertext:
|
|
case ColorID::MozMenubarhovertext:
|
|
case ColorID::Menutext:
|
|
color = NS_RGB(0xfb, 0xfb, 0xfe);
|
|
break;
|
|
|
|
case ColorID::MozMenuhover:
|
|
color = NS_RGB(0x52, 0x52, 0x5e);
|
|
break;
|
|
|
|
case ColorID::MozMenuhoverdisabled:
|
|
color = NS_RGB(0x3a, 0x39, 0x44);
|
|
break;
|
|
|
|
case ColorID::MozOddtreerow:
|
|
case ColorID::MozDialog: // --in-content-box-background
|
|
color = NS_RGB(35, 34, 43);
|
|
break;
|
|
case ColorID::Windowtext: // --in-content-page-color
|
|
case ColorID::MozDialogtext:
|
|
case ColorID::Fieldtext:
|
|
case ColorID::Buttontext: // --in-content-button-text-color (via
|
|
// --in-content-page-color)
|
|
case ColorID::MozComboboxtext:
|
|
case ColorID::MozButtonhovertext:
|
|
case ColorID::MozButtonactivetext:
|
|
color = kWindowText;
|
|
break;
|
|
case ColorID::Buttonshadow:
|
|
case ColorID::Threedshadow:
|
|
case ColorID::Threedlightshadow:
|
|
case ColorID::Buttonborder: // --in-content-box-border-color computed
|
|
// with kWindowText above
|
|
// kWindowBackground.
|
|
case ColorID::Graytext: // opacity: 0.4 of kWindowText blended over the
|
|
// "Window" background color, which happens to be
|
|
// the same :-)
|
|
color = NS_ComposeColors(kWindowBackground, NS_RGBA(251, 251, 254, 102));
|
|
break;
|
|
case ColorID::MozCellhighlight:
|
|
case ColorID::Selecteditem: // --in-content-primary-button-background /
|
|
// --in-content-item-selected
|
|
color = NS_RGB(0, 221, 255);
|
|
break;
|
|
case ColorID::Field:
|
|
case ColorID::Buttonface: // --in-content-button-background
|
|
case ColorID::Threedface:
|
|
case ColorID::MozCombobox:
|
|
case ColorID::MozCellhighlighttext:
|
|
case ColorID::Selecteditemtext: // --in-content-primary-button-text-color /
|
|
// --in-content-item-selected-text
|
|
color = NS_RGB(43, 42, 51);
|
|
break;
|
|
case ColorID::Threeddarkshadow: // Same as Threedlightshadow but with the
|
|
// background.
|
|
case ColorID::MozDisabledfield: // opacity: 0.4 of the face above blended
|
|
// over the "Window" background color.
|
|
case ColorID::MozButtondisabledface:
|
|
color = NS_ComposeColors(kWindowBackground, NS_RGBA(43, 42, 51, 102));
|
|
break;
|
|
case ColorID::MozButtonhoverface: // --in-content-button-background-hover
|
|
color = NS_RGB(82, 82, 94);
|
|
break;
|
|
case ColorID::MozButtonactiveface: // --in-content-button-background-active
|
|
color = NS_RGB(91, 91, 102);
|
|
break;
|
|
case ColorID::Highlight:
|
|
color = NS_RGBA(0, 221, 255, 78);
|
|
break;
|
|
case ColorID::Highlighttext:
|
|
color = NS_SAME_AS_FOREGROUND_COLOR;
|
|
break;
|
|
case ColorID::MozNativehyperlinktext:
|
|
// If you change this color, you probably also want to change the default
|
|
// value of browser.anchor_color.dark.
|
|
color = NS_RGB(0x8c, 0x8c, 0xff);
|
|
break;
|
|
case ColorID::MozNativevisitedhyperlinktext:
|
|
// If you change this color, you probably also want to change the default
|
|
// value of browser.visited_color.dark.
|
|
color = NS_RGB(0xff, 0xad, 0xff);
|
|
break;
|
|
case ColorID::SpellCheckerUnderline:
|
|
// This is the default for active links in dark mode as well
|
|
// (browser.active_color.dark). See bug 1755564 for some analysis and
|
|
// other options too.
|
|
color = NS_RGB(0xff, 0x66, 0x66);
|
|
break;
|
|
default:
|
|
return Nothing();
|
|
}
|
|
return Some(color);
|
|
}
|
|
|
|
// Uncomment the #define below if you want to debug system color use in a skin
|
|
// that uses them. When set, it will make all system color pairs that are
|
|
// appropriate for foreground/background pairing the same. This means if the
|
|
// skin is using system colors correctly you will not be able to see *any* text.
|
|
//
|
|
// #define DEBUG_SYSTEM_COLOR_USE
|
|
|
|
#ifdef DEBUG_SYSTEM_COLOR_USE
|
|
static nsresult SystemColorUseDebuggingColor(LookAndFeel::ColorID aID,
|
|
nscolor& aResult) {
|
|
using ColorID = LookAndFeel::ColorID;
|
|
|
|
switch (aID) {
|
|
// css2 http://www.w3.org/TR/REC-CSS2/ui.html#system-colors
|
|
case ColorID::Activecaption:
|
|
// active window caption background
|
|
case ColorID::Captiontext:
|
|
// text in active window caption
|
|
aResult = NS_RGB(0xff, 0x00, 0x00);
|
|
break;
|
|
|
|
case ColorID::Highlight:
|
|
// background of selected item
|
|
case ColorID::Highlighttext:
|
|
// text of selected item
|
|
aResult = NS_RGB(0xff, 0xff, 0x00);
|
|
break;
|
|
|
|
case ColorID::Inactivecaption:
|
|
// inactive window caption
|
|
case ColorID::Inactivecaptiontext:
|
|
// text in inactive window caption
|
|
aResult = NS_RGB(0x66, 0x66, 0x00);
|
|
break;
|
|
|
|
case ColorID::Infobackground:
|
|
// tooltip background color
|
|
case ColorID::Infotext:
|
|
// tooltip text color
|
|
aResult = NS_RGB(0x00, 0xff, 0x00);
|
|
break;
|
|
|
|
case ColorID::Menu:
|
|
// menu background
|
|
case ColorID::Menutext:
|
|
// menu text
|
|
aResult = NS_RGB(0x00, 0xff, 0xff);
|
|
break;
|
|
|
|
case ColorID::Threedface:
|
|
case ColorID::Buttonface:
|
|
// 3-D face color
|
|
case ColorID::Buttontext:
|
|
// text on push buttons
|
|
aResult = NS_RGB(0x00, 0x66, 0x66);
|
|
break;
|
|
|
|
case ColorID::Window:
|
|
case ColorID::Windowtext:
|
|
aResult = NS_RGB(0x00, 0x00, 0xff);
|
|
break;
|
|
|
|
// from the CSS3 working draft (not yet finalized)
|
|
// http://www.w3.org/tr/2000/wd-css3-userint-20000216.html#color
|
|
|
|
case ColorID::Field:
|
|
case ColorID::Fieldtext:
|
|
aResult = NS_RGB(0xff, 0x00, 0xff);
|
|
break;
|
|
|
|
case ColorID::MozDialog:
|
|
case ColorID::MozDialogtext:
|
|
aResult = NS_RGB(0x66, 0x00, 0x66);
|
|
break;
|
|
|
|
default:
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
#endif
|
|
|
|
static nsresult GetPrefColor(const char* aPref, nscolor& aResult) {
|
|
nsAutoCString colorStr;
|
|
MOZ_TRY(Preferences::GetCString(aPref, colorStr));
|
|
if (!ServoCSSParser::ComputeColor(nullptr, NS_RGB(0, 0, 0), colorStr,
|
|
&aResult)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
static nsresult GetColorFromPref(LookAndFeel::ColorID aID, ColorScheme aScheme,
|
|
nscolor& aResult) {
|
|
const char* prefName = sColorPrefs[size_t(aID)];
|
|
if (aScheme == ColorScheme::Dark) {
|
|
nsAutoCString darkPrefName(prefName);
|
|
darkPrefName.Append(".dark");
|
|
if (NS_SUCCEEDED(GetPrefColor(darkPrefName.get(), aResult))) {
|
|
return NS_OK;
|
|
}
|
|
}
|
|
return GetPrefColor(prefName, aResult);
|
|
}
|
|
|
|
// All these routines will return NS_OK if they have a value,
|
|
// in which case the nsLookAndFeel should use that value;
|
|
// otherwise we'll return NS_ERROR_NOT_AVAILABLE, in which case, the
|
|
// platform-specific nsLookAndFeel should use its own values instead.
|
|
nsresult nsXPLookAndFeel::GetColorValue(ColorID aID, ColorScheme aScheme,
|
|
UseStandins aUseStandins,
|
|
nscolor& aResult) {
|
|
if (!sInitialized) {
|
|
Init();
|
|
}
|
|
|
|
#ifdef DEBUG_SYSTEM_COLOR_USE
|
|
if (NS_SUCCEEDED(SystemColorUseDebuggingColor(aID, aResult))) {
|
|
return NS_OK;
|
|
}
|
|
#endif
|
|
|
|
auto& cache = sColorCaches.Get(aScheme, aUseStandins);
|
|
if (const auto* cached = cache.Get(aID)) {
|
|
if (cached->isNothing()) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
aResult = cached->value();
|
|
return NS_OK;
|
|
}
|
|
|
|
// NOTE: Servo holds a lock and the main thread is paused, so writing to the
|
|
// global cache here is fine.
|
|
auto result = GetUncachedColor(aID, aScheme, aUseStandins);
|
|
cache.Insert(aID, result);
|
|
if (!result) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
aResult = *result;
|
|
return NS_OK;
|
|
}
|
|
|
|
Maybe<nscolor> nsXPLookAndFeel::GetUncachedColor(ColorID aID,
|
|
ColorScheme aScheme,
|
|
UseStandins aUseStandins) {
|
|
if (aUseStandins == UseStandins::Yes) {
|
|
return Some(GetStandinForNativeColor(aID, aScheme));
|
|
}
|
|
nscolor r;
|
|
if (NS_SUCCEEDED(GetColorFromPref(aID, aScheme, r))) {
|
|
return Some(r);
|
|
}
|
|
if (NS_SUCCEEDED(NativeGetColor(aID, aScheme, r))) {
|
|
if (gfxPlatform::GetCMSMode() == CMSMode::All && !IsSpecialColor(aID, r)) {
|
|
qcms_transform* transform = gfxPlatform::GetCMSInverseRGBTransform();
|
|
if (transform) {
|
|
uint8_t color[4];
|
|
color[0] = NS_GET_R(r);
|
|
color[1] = NS_GET_G(r);
|
|
color[2] = NS_GET_B(r);
|
|
color[3] = NS_GET_A(r);
|
|
qcms_transform_data(transform, color, color, 1);
|
|
r = NS_RGBA(color[0], color[1], color[2], color[3]);
|
|
}
|
|
}
|
|
|
|
return Some(r);
|
|
}
|
|
return Nothing();
|
|
}
|
|
|
|
nsresult nsXPLookAndFeel::GetIntValue(IntID aID, int32_t& aResult) {
|
|
if (!sInitialized) {
|
|
Init();
|
|
}
|
|
|
|
if (const auto* cached = sIntCache.Get(aID)) {
|
|
if (cached->isNothing()) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
aResult = cached->value();
|
|
return NS_OK;
|
|
}
|
|
|
|
if (NS_SUCCEEDED(Preferences::GetInt(sIntPrefs[size_t(aID)], &aResult))) {
|
|
sIntCache.Insert(aID, Some(aResult));
|
|
return NS_OK;
|
|
}
|
|
|
|
if (NS_FAILED(NativeGetInt(aID, aResult))) {
|
|
sIntCache.Insert(aID, Nothing());
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
sIntCache.Insert(aID, Some(aResult));
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsXPLookAndFeel::GetFloatValue(FloatID aID, float& aResult) {
|
|
if (!sInitialized) {
|
|
Init();
|
|
}
|
|
|
|
if (const auto* cached = sFloatCache.Get(aID)) {
|
|
if (cached->isNothing()) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
aResult = cached->value();
|
|
return NS_OK;
|
|
}
|
|
|
|
int32_t pref = 0;
|
|
if (NS_SUCCEEDED(Preferences::GetInt(sFloatPrefs[size_t(aID)], &pref))) {
|
|
aResult = float(pref) / 100.0f;
|
|
sFloatCache.Insert(aID, Some(aResult));
|
|
return NS_OK;
|
|
}
|
|
|
|
if (NS_FAILED(NativeGetFloat(aID, aResult))) {
|
|
sFloatCache.Insert(aID, Nothing());
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
sFloatCache.Insert(aID, Some(aResult));
|
|
return NS_OK;
|
|
}
|
|
|
|
bool nsXPLookAndFeel::LookAndFeelFontToStyle(const LookAndFeelFont& aFont,
|
|
nsString& aName,
|
|
gfxFontStyle& aStyle) {
|
|
if (!aFont.haveFont()) {
|
|
return false;
|
|
}
|
|
aName = aFont.name();
|
|
aStyle = gfxFontStyle();
|
|
aStyle.size = aFont.size();
|
|
aStyle.weight = FontWeight::FromInt(aFont.weight());
|
|
aStyle.style =
|
|
aFont.italic() ? FontSlantStyle::ITALIC : FontSlantStyle::NORMAL;
|
|
aStyle.systemFont = true;
|
|
return true;
|
|
}
|
|
|
|
widget::LookAndFeelFont nsXPLookAndFeel::StyleToLookAndFeelFont(
|
|
const nsAString& aName, const gfxFontStyle& aStyle) {
|
|
LookAndFeelFont font;
|
|
font.haveFont() = true;
|
|
font.name() = aName;
|
|
font.size() = aStyle.size;
|
|
font.weight() = aStyle.weight.ToFloat();
|
|
font.italic() = aStyle.style.IsItalic();
|
|
MOZ_ASSERT(aStyle.style.IsNormal() || aStyle.style.IsItalic(),
|
|
"Cannot handle oblique font style");
|
|
#ifdef DEBUG
|
|
{
|
|
// Assert that all the remaining font style properties have their
|
|
// default values.
|
|
gfxFontStyle candidate = aStyle;
|
|
gfxFontStyle defaults{};
|
|
candidate.size = defaults.size;
|
|
candidate.weight = defaults.weight;
|
|
candidate.style = defaults.style;
|
|
MOZ_ASSERT(candidate.Equals(defaults),
|
|
"Some font style properties not supported");
|
|
}
|
|
#endif
|
|
return font;
|
|
}
|
|
|
|
bool nsXPLookAndFeel::GetFontValue(FontID aID, nsString& aName,
|
|
gfxFontStyle& aStyle) {
|
|
if (const LookAndFeelFont* cached = sFontCache.Get(aID)) {
|
|
return LookAndFeelFontToStyle(*cached, aName, aStyle);
|
|
}
|
|
|
|
LookAndFeelFont font;
|
|
auto GetFontsFromPrefs = [&]() -> bool {
|
|
nsDependentCString pref(sFontPrefs[size_t(aID)]);
|
|
if (NS_FAILED(Preferences::GetString(pref.get(), aName))) {
|
|
return false;
|
|
}
|
|
font.haveFont() = true;
|
|
font.name() = aName;
|
|
font.size() = Preferences::GetFloat(nsAutoCString(pref + ".size"_ns).get());
|
|
// This is written this way rather than using the fallback so that an empty
|
|
// pref (such like the one about:config creates) doesn't cause system fonts
|
|
// to have zero-size.
|
|
if (font.size() < 1.0f) {
|
|
font.size() = StyleFONT_MEDIUM_PX;
|
|
}
|
|
font.weight() = Preferences::GetFloat(
|
|
nsAutoCString(pref + ".weight"_ns).get(), FontWeight::NORMAL.ToFloat());
|
|
font.italic() =
|
|
Preferences::GetBool(nsAutoCString(pref + ".italic"_ns).get());
|
|
return true;
|
|
};
|
|
|
|
if (GetFontsFromPrefs()) {
|
|
LookAndFeelFontToStyle(font, aName, aStyle);
|
|
} else if (NativeGetFont(aID, aName, aStyle)) {
|
|
font = StyleToLookAndFeelFont(aName, aStyle);
|
|
} else {
|
|
MOZ_ASSERT(!font.haveFont());
|
|
}
|
|
bool success = font.haveFont();
|
|
sFontCache.Insert(aID, std::move(font));
|
|
return success;
|
|
}
|
|
|
|
void nsXPLookAndFeel::RefreshImpl() {
|
|
// Wipe out our caches.
|
|
sColorCaches.Clear();
|
|
sFontCache.Clear();
|
|
sFloatCache.Clear();
|
|
sIntCache.Clear();
|
|
RecomputeColorSchemes();
|
|
|
|
// Clear any cached FullLookAndFeel data, which is now invalid.
|
|
if (XRE_IsParentProcess()) {
|
|
widget::RemoteLookAndFeel::ClearCachedData();
|
|
}
|
|
}
|
|
|
|
static bool sRecordedLookAndFeelTelemetry = false;
|
|
|
|
void nsXPLookAndFeel::RecordTelemetry() {
|
|
if (!XRE_IsParentProcess()) {
|
|
return;
|
|
}
|
|
|
|
if (sRecordedLookAndFeelTelemetry) {
|
|
return;
|
|
}
|
|
|
|
sRecordedLookAndFeelTelemetry = true;
|
|
|
|
int32_t i;
|
|
Telemetry::ScalarSet(
|
|
Telemetry::ScalarID::WIDGET_DARK_MODE,
|
|
NS_SUCCEEDED(GetIntValue(IntID::SystemUsesDarkTheme, i)) && i != 0);
|
|
|
|
RecordLookAndFeelSpecificTelemetry();
|
|
}
|
|
|
|
namespace mozilla {
|
|
|
|
static widget::ThemeChangeKind sGlobalThemeChangeKind{0};
|
|
|
|
void LookAndFeel::NotifyChangedAllWindows(widget::ThemeChangeKind aKind) {
|
|
sGlobalThemeChanged = true;
|
|
sGlobalThemeChangeKind |= aKind;
|
|
|
|
if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) {
|
|
const char16_t kind[] = {char16_t(aKind), 0};
|
|
obs->NotifyObservers(nullptr, "internal-look-and-feel-changed", kind);
|
|
}
|
|
}
|
|
|
|
void LookAndFeel::DoHandleGlobalThemeChange() {
|
|
MOZ_ASSERT(sGlobalThemeChanged);
|
|
sGlobalThemeChanged = false;
|
|
auto kind = std::exchange(sGlobalThemeChangeKind, widget::ThemeChangeKind(0));
|
|
|
|
// Tell the theme that it changed, so it can flush any handles to stale theme
|
|
// data.
|
|
//
|
|
// We can use the *DoNotUseDirectly functions directly here, because we want
|
|
// to notify all possible themes in a given process (but just once).
|
|
if (XRE_IsParentProcess() ||
|
|
!StaticPrefs::widget_non_native_theme_enabled()) {
|
|
if (nsCOMPtr<nsITheme> theme = do_GetNativeThemeDoNotUseDirectly()) {
|
|
theme->ThemeChanged();
|
|
}
|
|
}
|
|
if (nsCOMPtr<nsITheme> theme = do_GetBasicNativeThemeDoNotUseDirectly()) {
|
|
theme->ThemeChanged();
|
|
}
|
|
|
|
// Clear all cached LookAndFeel colors.
|
|
LookAndFeel::Refresh();
|
|
|
|
// Reset default background and foreground colors for the document since they
|
|
// may be using system colors.
|
|
PreferenceSheet::Refresh();
|
|
|
|
// Vector images (SVG) may be using theme colors so we discard all cached
|
|
// surfaces. (We could add a vector image only version of DiscardAll, but
|
|
// in bug 940625 we decided theme changes are rare enough not to bother.)
|
|
image::SurfaceCacheUtils::DiscardAll();
|
|
|
|
if (XRE_IsParentProcess()) {
|
|
dom::ContentParent::BroadcastThemeUpdate(kind);
|
|
}
|
|
|
|
nsContentUtils::AddScriptRunner(
|
|
NS_NewRunnableFunction("HandleGlobalThemeChange", [] {
|
|
if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) {
|
|
obs->NotifyObservers(nullptr, "look-and-feel-changed", nullptr);
|
|
}
|
|
}));
|
|
}
|
|
|
|
#define BIT_FOR(_c) (1ull << size_t(ColorID::_c))
|
|
|
|
// We want to use a non-native color scheme for the non-native theme (except in
|
|
// high-contrast mode), so spoof some of the colors with stand-ins to prevent
|
|
// lack of contrast.
|
|
static constexpr std::bitset<size_t(ColorID::End)> sNonNativeThemeStandinColors{
|
|
// Used by default button styles.
|
|
BIT_FOR(Buttonface) | BIT_FOR(Buttontext) | BIT_FOR(MozButtonhoverface) |
|
|
BIT_FOR(MozButtonhovertext) | BIT_FOR(MozButtonactiveface) |
|
|
BIT_FOR(MozButtonactivetext) | BIT_FOR(MozButtondisabledface) |
|
|
BIT_FOR(Buttonborder) |
|
|
// Used by select elements.
|
|
BIT_FOR(MozCombobox) | BIT_FOR(MozComboboxtext) |
|
|
BIT_FOR(Threedlightshadow) |
|
|
// For symmetry with the above.
|
|
BIT_FOR(Threeddarkshadow) |
|
|
// Used by fieldset borders.
|
|
BIT_FOR(Threedface) |
|
|
// Used by input / textarea.
|
|
BIT_FOR(Field) | BIT_FOR(Fieldtext) |
|
|
// Used by disabled form controls.
|
|
BIT_FOR(MozDisabledfield) | BIT_FOR(Graytext) |
|
|
// Some pages expect these to return windows-like colors, see bug 1773795.
|
|
// Also, per spec these should match Canvas/CanvasText, see
|
|
// https://drafts.csswg.org/css-color-4/#window
|
|
BIT_FOR(Window) | BIT_FOR(Windowtext)};
|
|
#undef BIT_FOR
|
|
|
|
static bool ShouldUseStandinsForNativeColorForNonNativeTheme(
|
|
const dom::Document& aDoc, LookAndFeel::ColorID aColor,
|
|
const PreferenceSheet::Prefs& aPrefs) {
|
|
const bool shouldUseStandinsForColor = [&] {
|
|
if (sNonNativeThemeStandinColors[size_t(aColor)]) {
|
|
return true;
|
|
}
|
|
// There are platforms where we want the content-exposed accent color to be
|
|
// the windows blue rather than the system accent color, for now.
|
|
return !StaticPrefs::widget_non_native_theme_use_theme_accent() &&
|
|
(aColor == LookAndFeel::ColorID::Accentcolor ||
|
|
aColor == LookAndFeel::ColorID::Accentcolortext);
|
|
}();
|
|
|
|
return shouldUseStandinsForColor && aDoc.ShouldAvoidNativeTheme() &&
|
|
!aPrefs.NonNativeThemeShouldBeHighContrast();
|
|
}
|
|
|
|
ColorScheme LookAndFeel::sChromeColorScheme;
|
|
ColorScheme LookAndFeel::sContentColorScheme;
|
|
bool LookAndFeel::sColorSchemeInitialized;
|
|
bool LookAndFeel::sGlobalThemeChanged;
|
|
|
|
bool LookAndFeel::IsDarkColor(nscolor aColor) {
|
|
// Given https://www.w3.org/TR/WCAG20/#contrast-ratiodef, this is the
|
|
// threshold that tells us whether contrast is better against white or black.
|
|
//
|
|
// Contrast ratio against black is: (L + 0.05) / 0.05
|
|
// Contrast ratio against white is: 1.05 / (L + 0.05)
|
|
//
|
|
// So the intersection is:
|
|
//
|
|
// (L + 0.05) / 0.05 = 1.05 / (L + 0.05)
|
|
//
|
|
// And the solution to that equation is:
|
|
//
|
|
// sqrt(1.05 * 0.05) - 0.05
|
|
//
|
|
// So we consider a color dark if the contrast is below this threshold, and
|
|
// it's at least half-opaque.
|
|
constexpr float kThreshold = 0.179129;
|
|
return NS_GET_A(aColor) > 127 &&
|
|
RelativeLuminanceUtils::Compute(aColor) < kThreshold;
|
|
}
|
|
|
|
auto LookAndFeel::ColorSchemeSettingForChrome() -> ChromeColorSchemeSetting {
|
|
switch (StaticPrefs::browser_theme_toolbar_theme()) {
|
|
case 0: // Dark
|
|
return ChromeColorSchemeSetting::Dark;
|
|
case 1: // Light
|
|
return ChromeColorSchemeSetting::Light;
|
|
default:
|
|
return ChromeColorSchemeSetting::System;
|
|
}
|
|
}
|
|
|
|
ColorScheme LookAndFeel::ThemeDerivedColorSchemeForContent() {
|
|
switch (StaticPrefs::browser_theme_content_theme()) {
|
|
case 0: // Dark
|
|
return ColorScheme::Dark;
|
|
case 1: // Light
|
|
return ColorScheme::Light;
|
|
default:
|
|
return SystemColorScheme();
|
|
}
|
|
}
|
|
|
|
void LookAndFeel::RecomputeColorSchemes() {
|
|
sColorSchemeInitialized = true;
|
|
|
|
sChromeColorScheme = [] {
|
|
switch (ColorSchemeSettingForChrome()) {
|
|
case ChromeColorSchemeSetting::Light:
|
|
return ColorScheme::Light;
|
|
case ChromeColorSchemeSetting::Dark:
|
|
return ColorScheme::Dark;
|
|
case ChromeColorSchemeSetting::System:
|
|
break;
|
|
}
|
|
return SystemColorScheme();
|
|
}();
|
|
|
|
sContentColorScheme = [] {
|
|
switch (StaticPrefs::layout_css_prefers_color_scheme_content_override()) {
|
|
case 0:
|
|
return ColorScheme::Dark;
|
|
case 1:
|
|
return ColorScheme::Light;
|
|
default:
|
|
return ThemeDerivedColorSchemeForContent();
|
|
}
|
|
}();
|
|
}
|
|
|
|
ColorScheme LookAndFeel::ColorSchemeForStyle(
|
|
const dom::Document& aDoc, const StyleColorSchemeFlags& aFlags,
|
|
ColorSchemeMode aMode) {
|
|
using Choice = PreferenceSheet::Prefs::ColorSchemeChoice;
|
|
|
|
const auto& prefs = PreferenceSheet::PrefsFor(aDoc);
|
|
switch (prefs.mColorSchemeChoice) {
|
|
case Choice::Standard:
|
|
break;
|
|
case Choice::UserPreferred:
|
|
return aDoc.PreferredColorScheme();
|
|
case Choice::Light:
|
|
return ColorScheme::Light;
|
|
case Choice::Dark:
|
|
return ColorScheme::Dark;
|
|
}
|
|
|
|
StyleColorSchemeFlags style(aFlags);
|
|
if (!style) {
|
|
style.bits = aDoc.GetColorSchemeBits();
|
|
}
|
|
const bool supportsDark = bool(style & StyleColorSchemeFlags::DARK);
|
|
const bool supportsLight = bool(style & StyleColorSchemeFlags::LIGHT);
|
|
if (supportsLight && supportsDark) {
|
|
// Both color-schemes are explicitly supported, use the preferred one.
|
|
return aDoc.PreferredColorScheme();
|
|
}
|
|
if (supportsDark || supportsLight) {
|
|
// One color-scheme is explicitly supported and one isn't, so use the one
|
|
// the content supports.
|
|
return supportsDark ? ColorScheme::Dark : ColorScheme::Light;
|
|
}
|
|
// No value specified. Chrome docs always supports both, so use the preferred
|
|
// color-scheme.
|
|
if (aMode == ColorSchemeMode::Preferred ||
|
|
nsContentUtils::IsChromeDoc(&aDoc)) {
|
|
return aDoc.PreferredColorScheme();
|
|
}
|
|
// Default content to light.
|
|
return ColorScheme::Light;
|
|
}
|
|
|
|
LookAndFeel::ColorScheme LookAndFeel::ColorSchemeForFrame(
|
|
const nsIFrame* aFrame, ColorSchemeMode aMode) {
|
|
return ColorSchemeForStyle(*aFrame->PresContext()->Document(),
|
|
aFrame->StyleUI()->mColorScheme.bits, aMode);
|
|
}
|
|
|
|
// static
|
|
Maybe<nscolor> LookAndFeel::GetColor(ColorID aId, ColorScheme aScheme,
|
|
UseStandins aUseStandins) {
|
|
nscolor result;
|
|
nsresult rv = nsLookAndFeel::GetInstance()->GetColorValue(
|
|
aId, aScheme, aUseStandins, result);
|
|
if (NS_FAILED(rv)) {
|
|
return Nothing();
|
|
}
|
|
return Some(result);
|
|
}
|
|
|
|
// Returns whether there is a CSS color name for this color.
|
|
static bool ColorIsCSSAccessible(LookAndFeel::ColorID aId) {
|
|
using ColorID = LookAndFeel::ColorID;
|
|
|
|
switch (aId) {
|
|
case ColorID::TextSelectDisabledBackground:
|
|
case ColorID::TextSelectAttentionBackground:
|
|
case ColorID::TextSelectAttentionForeground:
|
|
case ColorID::TextHighlightBackground:
|
|
case ColorID::TextHighlightForeground:
|
|
case ColorID::ThemedScrollbar:
|
|
case ColorID::ThemedScrollbarInactive:
|
|
case ColorID::ThemedScrollbarThumb:
|
|
case ColorID::ThemedScrollbarThumbActive:
|
|
case ColorID::ThemedScrollbarThumbInactive:
|
|
case ColorID::ThemedScrollbarThumbHover:
|
|
case ColorID::IMERawInputBackground:
|
|
case ColorID::IMERawInputForeground:
|
|
case ColorID::IMERawInputUnderline:
|
|
case ColorID::IMESelectedRawTextBackground:
|
|
case ColorID::IMESelectedRawTextForeground:
|
|
case ColorID::IMESelectedRawTextUnderline:
|
|
case ColorID::IMEConvertedTextBackground:
|
|
case ColorID::IMEConvertedTextForeground:
|
|
case ColorID::IMEConvertedTextUnderline:
|
|
case ColorID::IMESelectedConvertedTextBackground:
|
|
case ColorID::IMESelectedConvertedTextForeground:
|
|
case ColorID::IMESelectedConvertedTextUnderline:
|
|
case ColorID::SpellCheckerUnderline:
|
|
return false;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
LookAndFeel::UseStandins LookAndFeel::ShouldUseStandins(
|
|
const dom::Document& aDoc, ColorID aId) {
|
|
const auto& prefs = PreferenceSheet::PrefsFor(aDoc);
|
|
if (ShouldUseStandinsForNativeColorForNonNativeTheme(aDoc, aId, prefs)) {
|
|
return UseStandins::Yes;
|
|
}
|
|
if (prefs.mUseStandins && ColorIsCSSAccessible(aId)) {
|
|
return UseStandins::Yes;
|
|
}
|
|
return UseStandins::No;
|
|
}
|
|
|
|
Maybe<nscolor> LookAndFeel::GetColor(ColorID aId, const nsIFrame* aFrame) {
|
|
const auto* doc = aFrame->PresContext()->Document();
|
|
return GetColor(aId, ColorSchemeForFrame(aFrame),
|
|
ShouldUseStandins(*doc, aId));
|
|
}
|
|
|
|
// static
|
|
nsresult LookAndFeel::GetInt(IntID aID, int32_t* aResult) {
|
|
return nsLookAndFeel::GetInstance()->GetIntValue(aID, *aResult);
|
|
}
|
|
|
|
// static
|
|
nsresult LookAndFeel::GetFloat(FloatID aID, float* aResult) {
|
|
return nsLookAndFeel::GetInstance()->GetFloatValue(aID, *aResult);
|
|
}
|
|
|
|
// static
|
|
bool LookAndFeel::GetFont(FontID aID, nsString& aName, gfxFontStyle& aStyle) {
|
|
return nsLookAndFeel::GetInstance()->GetFontValue(aID, aName, aStyle);
|
|
}
|
|
|
|
// static
|
|
char16_t LookAndFeel::GetPasswordCharacter() {
|
|
return nsLookAndFeel::GetInstance()->GetPasswordCharacterImpl();
|
|
}
|
|
|
|
// static
|
|
bool LookAndFeel::GetEchoPassword() {
|
|
if (StaticPrefs::editor_password_mask_delay() >= 0) {
|
|
return StaticPrefs::editor_password_mask_delay() > 0;
|
|
}
|
|
return nsLookAndFeel::GetInstance()->GetEchoPasswordImpl();
|
|
}
|
|
|
|
// static
|
|
uint32_t LookAndFeel::GetPasswordMaskDelay() {
|
|
int32_t delay = StaticPrefs::editor_password_mask_delay();
|
|
if (delay < 0) {
|
|
return nsLookAndFeel::GetInstance()->GetPasswordMaskDelayImpl();
|
|
}
|
|
return delay;
|
|
}
|
|
|
|
bool LookAndFeel::DrawInTitlebar() {
|
|
switch (StaticPrefs::browser_tabs_inTitlebar()) {
|
|
case 0:
|
|
return false;
|
|
case 1:
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
return nsLookAndFeel::GetInstance()->GetDefaultDrawInTitlebar();
|
|
}
|
|
|
|
void LookAndFeel::GetThemeInfo(nsACString& aOut) {
|
|
nsLookAndFeel::GetInstance()->GetThemeInfo(aOut);
|
|
}
|
|
|
|
// static
|
|
void LookAndFeel::Refresh() {
|
|
nsLookAndFeel::GetInstance()->RefreshImpl();
|
|
widget::Theme::LookAndFeelChanged();
|
|
}
|
|
|
|
// static
|
|
void LookAndFeel::NativeInit() { nsLookAndFeel::GetInstance()->NativeInit(); }
|
|
|
|
// static
|
|
void LookAndFeel::SetData(widget::FullLookAndFeel&& aTables) {
|
|
nsLookAndFeel::GetInstance()->SetDataImpl(std::move(aTables));
|
|
}
|
|
|
|
} // namespace mozilla
|