Bug 1762298 - Inherit used color-scheme from embedder <browser> elements. r=nika,dao,Gijs

This allows popups and sidebars to use the chrome preferred
color-scheme.

This moves the responsibility of setting the content-preferred color
scheme to the appropriate browsers to the front-end (via tabs.css).

We still return the PreferredColorSchemeForContent() when there's no
pres context (e.g., for display:none in-process iframes). We could
potentially move a bunch of the pres-context data to the document
instead, but that should be acceptable IMO as for general web content
there's no behavior change in any case.

Differential Revision: https://phabricator.services.mozilla.com/D142578
This commit is contained in:
Emilio Cobos Álvarez 2022-04-04 18:22:04 +00:00
parent c3844882b4
commit 6553f27967
18 changed files with 250 additions and 40 deletions

View File

@ -17,6 +17,10 @@
--tabpanel-background-color: #F9F9FB;
}
#tabbrowser-tabpanels browser {
color-scheme: env(-moz-content-preferred-color-scheme);
}
@media (-moz-content-prefers-color-scheme: dark) {
:root {
/* --tabpanel-background-color matches $in-content-page-background in newtab

View File

@ -703,8 +703,8 @@ void BrowsingContext::SetEmbedderElement(Element* aEmbedder) {
}
txn.SetMessageManagerGroup(messageManagerGroup);
bool useGlobalHistory = !aEmbedder->HasAttr(
kNameSpaceID_None, nsGkAtoms::disableglobalhistory);
bool useGlobalHistory =
!aEmbedder->HasAttr(nsGkAtoms::disableglobalhistory);
txn.SetUseGlobalHistory(useGlobalHistory);
}
@ -2781,19 +2781,21 @@ bool BrowsingContext::CanSet(FieldIndex<IDX_TouchEventsOverrideInternal>,
return XRE_IsParentProcess() && !aSource;
}
void BrowsingContext::DidSet(FieldIndex<IDX_EmbedderColorScheme>,
dom::PrefersColorSchemeOverride aOldValue) {
if (GetEmbedderColorScheme() == aOldValue) {
return;
}
PresContextAffectingFieldChanged();
}
void BrowsingContext::DidSet(FieldIndex<IDX_PrefersColorSchemeOverride>,
dom::PrefersColorSchemeOverride aOldValue) {
MOZ_ASSERT(IsTop());
if (PrefersColorSchemeOverride() == aOldValue) {
return;
}
PreOrderWalk([&](BrowsingContext* aContext) {
if (nsIDocShell* shell = aContext->GetDocShell()) {
if (nsPresContext* pc = shell->GetPresContext()) {
pc->RecomputeBrowsingContextDependentData();
}
}
});
PresContextAffectingFieldChanged();
}
void BrowsingContext::DidSet(FieldIndex<IDX_MediumOverride>,
@ -2802,13 +2804,7 @@ void BrowsingContext::DidSet(FieldIndex<IDX_MediumOverride>,
if (GetMediumOverride() == aOldValue) {
return;
}
PreOrderWalk([&](BrowsingContext* aContext) {
if (nsIDocShell* shell = aContext->GetDocShell()) {
if (nsPresContext* pc = shell->GetPresContext()) {
pc->RecomputeBrowsingContextDependentData();
}
}
});
PresContextAffectingFieldChanged();
}
void BrowsingContext::DidSet(FieldIndex<IDX_DisplayMode>,
@ -2878,7 +2874,10 @@ void BrowsingContext::DidSet(FieldIndex<IDX_OverrideDPPX>, float aOldValue) {
if (GetOverrideDPPX() == aOldValue) {
return;
}
PresContextAffectingFieldChanged();
}
void BrowsingContext::PresContextAffectingFieldChanged() {
PreOrderWalk([&](BrowsingContext* aContext) {
if (nsIDocShell* shell = aContext->GetDocShell()) {
if (nsPresContext* pc = shell->GetPresContext()) {

View File

@ -210,8 +210,12 @@ enum class ExplicitActiveStatus : uint8_t {
FIELD(AuthorStyleDisabledDefault, bool) \
FIELD(ServiceWorkersTestingEnabled, bool) \
FIELD(MediumOverride, nsString) \
FIELD(PrefersColorSchemeOverride, mozilla::dom::PrefersColorSchemeOverride) \
FIELD(DisplayMode, mozilla::dom::DisplayMode) \
/* DevTools override for prefers-color-scheme */ \
FIELD(PrefersColorSchemeOverride, dom::PrefersColorSchemeOverride) \
/* prefers-color-scheme override based on the color-scheme style of our \
* <browser> embedder element. */ \
FIELD(EmbedderColorScheme, dom::PrefersColorSchemeOverride) \
FIELD(DisplayMode, dom::DisplayMode) \
/* The number of entries added to the session history because of this \
* browsing context. */ \
FIELD(HistoryEntryCount, uint32_t) \
@ -1017,6 +1021,11 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
return IsTop();
}
bool CanSet(FieldIndex<IDX_EmbedderColorScheme>,
dom::PrefersColorSchemeOverride, ContentParent* aSource) {
return CheckOnlyEmbedderCanSet(aSource);
}
bool CanSet(FieldIndex<IDX_PrefersColorSchemeOverride>,
dom::PrefersColorSchemeOverride, ContentParent*) {
return IsTop();
@ -1024,9 +1033,14 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
void DidSet(FieldIndex<IDX_InRDMPane>, bool aOldValue);
void DidSet(FieldIndex<IDX_EmbedderColorScheme>,
dom::PrefersColorSchemeOverride aOldValue);
void DidSet(FieldIndex<IDX_PrefersColorSchemeOverride>,
dom::PrefersColorSchemeOverride aOldValue);
void PresContextAffectingFieldChanged();
void DidSet(FieldIndex<IDX_MediumOverride>, nsString&& aOldValue);
bool CanSet(FieldIndex<IDX_SuspendMediaWhenInactive>, bool, ContentParent*) {

View File

@ -306,6 +306,7 @@ void CanonicalBrowsingContext::ReplacedBy(
txn.SetBrowserId(GetBrowserId());
txn.SetHistoryID(GetHistoryID());
txn.SetExplicitActive(GetExplicitActive());
txn.SetEmbedderColorScheme(GetEmbedderColorScheme());
txn.SetHasRestoreData(GetHasRestoreData());
txn.SetShouldDelayMediaFromStart(GetShouldDelayMediaFromStart());
// As this is a different BrowsingContext, set InitialSandboxFlags to the

View File

@ -17741,7 +17741,7 @@ ColorScheme Document::PreferredColorScheme(IgnoreRFP aIgnoreRFP) const {
}
if (nsPresContext* pc = GetPresContext()) {
if (auto scheme = pc->GetOverriddenColorScheme()) {
if (auto scheme = pc->GetOverriddenOrEmbedderColorScheme()) {
return *scheme;
}
}

View File

@ -2657,6 +2657,7 @@ bool nsFrameLoader::TryRemoteBrowserInternal() {
// - about:preferences (in Thunderbird only) so it can load remote
// extension options pages for FileLink providers
// - DevTools webext panels if DevTools is loaded in a content frame
// - Chrome mochitests can also do this.
//
// Note that the new frame's message manager will not be a child of the
// chrome window message manager, and, the values of window.top and
@ -2672,16 +2673,37 @@ bool nsFrameLoader::TryRemoteBrowserInternal() {
return false;
}
if (!(specIgnoringRef.EqualsLiteral("about:addons") ||
specIgnoringRef.EqualsLiteral(
"chrome://mozapps/content/extensions/aboutaddons.html") ||
const bool allowed = [&] {
const nsLiteralCString kAllowedURIs[] = {
"about:addons"_ns,
"chrome://mozapps/content/extensions/aboutaddons.html"_ns,
#ifdef MOZ_THUNDERBIRD
specIgnoringRef.EqualsLiteral("about:3pane") ||
specIgnoringRef.EqualsLiteral("about:message") ||
specIgnoringRef.EqualsLiteral("about:preferences") ||
"about:3pane"_ns,
"about:message"_ns,
"about:preferences"_ns,
#endif
specIgnoringRef.EqualsLiteral(
"chrome://browser/content/webext-panels.xhtml"))) {
"chrome://browser/content/webext-panels.xhtml"_ns,
};
for (const auto& allowedURI : kAllowedURIs) {
if (specIgnoringRef.Equals(allowedURI)) {
return true;
}
}
if (xpc::IsInAutomation() &&
StringBeginsWith(specIgnoringRef,
"chrome://mochitests/content/chrome/"_ns)) {
return true;
}
return false;
}();
if (!allowed) {
NS_WARNING(
nsPrintfCString("Forbidden remote frame from content docshell %s",
specIgnoringRef.get())
.get());
return false;
}
}

View File

@ -20,16 +20,29 @@ function snapshotsEqual() {
return compareSnapshots(s1, s2, true)[0];
}
function waitForColorSchemeToBe(scheme) {
return new Promise(resolve => {
let mq = matchMedia(`(prefers-color-scheme: ${scheme})`);
if (mq.matches) {
resolve();
} else {
mq.addEventListener("change", resolve, { once: true });
}
});
}
async function run() {
let loadedFrame1 = new Promise(resolve => f1.onload = resolve);
let loadedFrame2 = new Promise(resolve => f2.onload = resolve);
await SpecialPowers.pushPrefEnv({ set: [["layout.css.prefers-color-scheme.content-override", 1]] });
await waitForColorSchemeToBe("light");
f1.src = "mq_dynamic_svg_test.html";
f2.src = "mq_dynamic_svg_ref.html";
await loadedFrame1;
await loadedFrame2;
ok(!snapshotsEqual(), "In light mode snapshot comparison should be false");
await SpecialPowers.pushPrefEnv({ set: [["layout.css.prefers-color-scheme.content-override", 0]] });
await waitForColorSchemeToBe("dark");
ok(snapshotsEqual(), "In dark mode snapshot comparison should be true");
SimpleTest.finish();
}

View File

@ -285,7 +285,7 @@ nsPresContext::nsPresContext(dom::Document* aDocument, nsPresContextType aType)
#ifdef DEBUG
mInitialized(false),
#endif
mColorSchemeOverride(dom::PrefersColorSchemeOverride::None) {
mOverriddenOrEmbedderColorScheme(dom::PrefersColorSchemeOverride::None) {
#ifdef DEBUG
PodZero(&mLayoutPhaseCount);
#endif
@ -869,12 +869,12 @@ void nsPresContext::AttachPresShell(mozilla::PresShell* aPresShell) {
UpdateCharSet(doc->GetDocumentCharacterSet());
}
Maybe<ColorScheme> nsPresContext::GetOverriddenColorScheme() const {
Maybe<ColorScheme> nsPresContext::GetOverriddenOrEmbedderColorScheme() const {
if (IsPrintingOrPrintPreview()) {
return Some(ColorScheme::Light);
}
switch (mColorSchemeOverride) {
switch (mOverriddenOrEmbedderColorScheme) {
case dom::PrefersColorSchemeOverride::Dark:
return Some(ColorScheme::Dark);
case dom::PrefersColorSchemeOverride::Light:
@ -905,9 +905,23 @@ void nsPresContext::RecomputeBrowsingContextDependentData() {
SetTextZoom(browsingContext->TextZoom());
SetOverrideDPPX(browsingContext->OverrideDPPX());
auto oldOverride = mColorSchemeOverride;
mColorSchemeOverride = browsingContext->Top()->PrefersColorSchemeOverride();
if (oldOverride != mColorSchemeOverride) {
auto oldScheme = mDocument->PreferredColorScheme();
auto* top = browsingContext->Top();
mOverriddenOrEmbedderColorScheme = [&] {
auto overriden = top->PrefersColorSchemeOverride();
if (overriden != PrefersColorSchemeOverride::None) {
return overriden;
}
for (auto* cur = browsingContext; cur; cur = cur->GetParent()) {
auto embedder = cur->GetEmbedderColorScheme();
if (embedder != PrefersColorSchemeOverride::None) {
return embedder;
}
}
return PrefersColorSchemeOverride::None;
}();
if (mDocument->PreferredColorScheme() != oldScheme) {
// We need to restyle because not only media queries have changed, system
// colors may as well via the prefers-color-scheme meta tag / effective
// color-scheme property value.
@ -918,7 +932,6 @@ void nsPresContext::RecomputeBrowsingContextDependentData() {
if (doc == mDocument) {
// Medium doesn't apply to resource documents, etc.
auto* top = browsingContext->Top();
RefPtr<nsAtom> mediumToEmulate;
if (MOZ_UNLIKELY(!top->GetMediumOverride().IsEmpty())) {
nsAutoString lower;

View File

@ -580,11 +580,11 @@ class nsPresContext : public nsISupports, public mozilla::SupportsWeakPtr {
float GetOverrideDPPX() const { return mMediaEmulationData.mDPPX; }
// Gets the forced color-scheme if any via either DevTools emulation or
// printing.
// Gets the forced color-scheme if any via either our embedder, or DevTools
// emulation, or printing.
//
// NOTE(emilio): This might be called from an stylo thread.
Maybe<mozilla::ColorScheme> GetOverriddenColorScheme() const;
Maybe<mozilla::ColorScheme> GetOverriddenOrEmbedderColorScheme() const;
/**
* Recomputes the data dependent on the browsing context, like zoom and text
@ -1377,7 +1377,7 @@ class nsPresContext : public nsISupports, public mozilla::SupportsWeakPtr {
// FIXME(emilio): These would be better packed on top of the bitfields, but
// that breaks bindgen in win32.
FontVisibility mFontVisibility = FontVisibility::Unknown;
mozilla::dom::PrefersColorSchemeOverride mColorSchemeOverride;
mozilla::dom::PrefersColorSchemeOverride mOverriddenOrEmbedderColorScheme;
protected:
virtual ~nsPresContext();

View File

@ -94,6 +94,7 @@ support-files =
bug1041200_frame.html
bug1041200_window.html
[test_chrome_content_integration.xhtml]
[test_color_scheme_browser.xhtml]
[test_default_background.xhtml]
[test_dialog_with_positioning.html]
tags = openwindow

View File

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
windowtype="Toolkit:PictureInPicture"
chromemargin="0,0,0,0">
<head>
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script src="chrome://mochikit/content/chrome-harness.js"></script>
<style>
#light { color-scheme: light }
#dark { color-scheme: dark }
</style>
</head>
<body>
<div id="dynamic-test">
<xul:browser type="content" remote="true" src="about:blank" class="remote" />
<xul:browser type="content" src="about:blank" class="nonremote" />
</div>
<div id="light">
<xul:browser type="content" remote="true" src="about:blank" class="remote" />
<xul:browser type="content" src="about:blank" class="nonremote" />
</div>
<div id="dark">
<xul:browser type="content" remote="true" src="about:blank" class="remote" />
<xul:browser type="content" src="about:blank" class="nonremote" />
</div>
<script><![CDATA[
async function getBrowserColorScheme(browser) {
return SpecialPowers.spawn(browser, [], () => {
return content.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
});
}
async function tick() {
return new Promise(resolve => {
requestAnimationFrame(() => requestAnimationFrame(resolve));
});
}
async function testElement(id, expected) {
let element = document.getElementById(id);
for (let browser of element.querySelectorAll("browser")) {
let scheme = await getBrowserColorScheme(browser);
is(scheme, expected, `${id}: ${browser.className} should be ${expected}`);
}
}
add_task(async function test_browser_color_scheme() {
for (let id of ["dynamic-test", "light", "dark"]) {
let expected = id == "dynamic-test"
? (matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light")
: id;
await testElement(id, expected);
}
});
add_task(async function test_browser_color_scheme_dynamic_style() {
let dynamicTest = document.getElementById("dynamic-test");
for (let value of ["light", "dark"]) {
await tick();
dynamicTest.style.colorScheme = value;
await testElement("dynamic-test", value);
}
dynamicTest.style.colorScheme = "";
await tick();
});
add_task(async function test_browser_color_scheme_dynamic_system() {
for (let dark of [true, false]) {
await SpecialPowers.pushPrefEnv({ set: [["ui.systemUsesDarkTheme", dark ? 1 : 0]] });
await tick();
await testElement("dynamic-test", dark ? "dark" : "light");
await SpecialPowers.popPrefEnv();
}
});
]]></script>
</body>
</html>

View File

@ -779,7 +779,7 @@ void nsContainerFrame::SyncWindowProperties(nsPresContext* aPresContext,
windowWidget->SetWindowShadowStyle(shadow);
// For macOS, apply color scheme overrides to the top level window widget.
if (auto scheme = aPresContext->GetOverriddenColorScheme()) {
if (auto scheme = aPresContext->GetOverriddenOrEmbedderColorScheme()) {
windowWidget->SetColorScheme(scheme);
}
}

View File

@ -203,6 +203,8 @@ void nsSubDocumentFrame::ShowViewer() {
frameloader->UpdatePositionAndSize(this);
}
MaybeUpdateEmbedderColorScheme();
if (!weakThis.IsAlive()) {
return;
}
@ -807,9 +809,49 @@ nsresult nsSubDocumentFrame::AttributeChanged(int32_t aNameSpaceID,
return NS_OK;
}
void nsSubDocumentFrame::MaybeUpdateEmbedderColorScheme() {
if (!mContent->IsXULElement()) {
// We only do this for XUL <browser>s.
return;
}
nsFrameLoader* fl = mFrameLoader.get();
if (!fl) {
return;
}
BrowsingContext* bc = fl->GetExtantBrowsingContext();
if (!bc) {
return;
}
auto usedColorScheme = LookAndFeel::ColorSchemeForFrame(this);
bool needUpdate = [&] {
switch (bc->GetEmbedderColorScheme()) {
case PrefersColorSchemeOverride::Light:
return usedColorScheme != ColorScheme::Light;
case PrefersColorSchemeOverride::Dark:
return usedColorScheme != ColorScheme::Dark;
case PrefersColorSchemeOverride::None:
case PrefersColorSchemeOverride::EndGuard_:
return true;
}
}();
if (!needUpdate) {
return;
}
auto value = usedColorScheme == ColorScheme::Dark
? PrefersColorSchemeOverride::Dark
: PrefersColorSchemeOverride::Light;
Unused << bc->SetEmbedderColorScheme(value);
}
void nsSubDocumentFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) {
nsAtomicContainerFrame::DidSetComputedStyle(aOldComputedStyle);
MaybeUpdateEmbedderColorScheme();
// If this presshell has invisible ancestors, we don't need to propagate the
// visibility style change to the subdocument since the subdocument should
// have already set the IsUnderHiddenEmbedderElement flag in

View File

@ -133,6 +133,7 @@ class nsSubDocumentFrame final : public nsAtomicContainerFrame,
void ResetFrameLoader(RetainPaintData);
void ClearRetainedPaintData();
void MaybeUpdateEmbedderColorScheme();
void PropagateIsUnderHiddenEmbedderElementToSubView(
bool aIsUnderHiddenEmbedderElement);

View File

@ -9,6 +9,7 @@ const NON_CONTENT_ACCESSIBLE_ENV_VARS = [
"-moz-gtk-csd-minimize-button-position",
"-moz-gtk-csd-maximize-button-position",
"-moz-gtk-csd-close-button-position",
"-moz-content-preferred-color-scheme",
];
const div = document.querySelector("div");

View File

@ -64,6 +64,20 @@ fn get_safearea_inset_right(device: &Device) -> VariableValue {
VariableValue::pixels(device.safe_area_insets().right)
}
fn get_content_preferred_color_scheme(device: &Device) -> VariableValue {
use crate::gecko::media_features::PrefersColorScheme;
let prefers_color_scheme = unsafe {
crate::gecko_bindings::bindings::Gecko_MediaFeatures_PrefersColorScheme(
device.document(),
/* use_content = */ true,
)
};
VariableValue::ident(match prefers_color_scheme {
PrefersColorScheme::Light => "light",
PrefersColorScheme::Dark => "dark",
})
}
static ENVIRONMENT_VARIABLES: [EnvironmentVariable; 4] = [
make_variable!(atom!("safe-area-inset-top"), get_safearea_inset_top),
make_variable!(atom!("safe-area-inset-bottom"), get_safearea_inset_bottom),
@ -90,7 +104,7 @@ macro_rules! lnf_int_variable {
}};
}
static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 5] = [
static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 6] = [
lnf_int_variable!(
atom!("-moz-gtk-csd-titlebar-radius"),
TitlebarRadius,
@ -112,6 +126,7 @@ static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 5] = [
GTKCSDMaximizeButtonPosition,
integer
),
make_variable!(atom!("-moz-content-preferred-color-scheme"), get_content_preferred_color_scheme),
];
impl CssEnvironment {
@ -319,6 +334,11 @@ impl VariableValue {
})
}
/// Create VariableValue from an int.
fn ident(ident: &'static str) -> Self {
Self::from_token(Token::Ident(ident.into()))
}
/// Create VariableValue from a float amount of CSS pixels.
fn pixels(number: f32) -> Self {
// FIXME (https://github.com/servo/rust-cssparser/issues/266):

View File

@ -380,6 +380,7 @@ const BackgroundPageThumbs = {
// thumbnails are blank and transparent -- but setting the style does.
browser.style.width = bwidth + "px";
browser.style.height = (bwidth * sheight.value) / swidth.value + "px";
browser.style.colorScheme = "env(-moz-content-preferred-color-scheme)";
this._parentWin.document.documentElement.appendChild(browser);

View File

@ -2246,6 +2246,7 @@ STATIC_ATOMS = [
Atom("_moz_gtk_csd_reversed_placement", "-moz-gtk-csd-reversed-placement"),
Atom("_moz_gtk_csd_menu_radius", "-moz-gtk-csd-menu-radius"),
Atom("_moz_content_prefers_color_scheme", "-moz-content-prefers-color-scheme"),
Atom("_moz_content_preferred_color_scheme", "-moz-content-preferred-color-scheme"),
Atom("_moz_proton_places_tooltip", "-moz-proton-places-tooltip"),
Atom("_moz_system_dark_theme", "-moz-system-dark-theme"),
# application commands