nsMediaFeatures.cpp (15132B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 /* the features that media queries can test */ 8 9 #include "PreferenceSheet.h" 10 #include "mozilla/GeckoBindings.h" 11 #include "mozilla/LookAndFeel.h" 12 #include "mozilla/RelativeLuminanceUtils.h" 13 #include "mozilla/StaticPrefs_browser.h" 14 #include "mozilla/StaticPrefs_gfx.h" 15 #include "mozilla/StyleSheet.h" 16 #include "mozilla/StyleSheetInlines.h" 17 #include "mozilla/dom/BrowsingContextBinding.h" 18 #include "mozilla/dom/Document.h" 19 #include "mozilla/dom/DocumentInlines.h" 20 #include "mozilla/dom/ScreenBinding.h" 21 #include "nsCSSProps.h" 22 #include "nsCSSValue.h" 23 #include "nsContentUtils.h" 24 #include "nsDeviceContext.h" 25 #include "nsGkAtoms.h" 26 #include "nsGlobalWindowOuter.h" 27 #include "nsIBaseWindow.h" 28 #include "nsIDocShell.h" 29 #include "nsIPrintSettings.h" 30 #include "nsIWidget.h" 31 #include "nsPresContext.h" 32 #include "nsStyleConsts.h" 33 34 using namespace mozilla; 35 using mozilla::dom::DisplayMode; 36 using mozilla::dom::Document; 37 38 // A helper for four features below 39 static nsSize GetSize(const Document& aDocument) { 40 nsPresContext* pc = aDocument.GetPresContext(); 41 42 // Per spec, return a 0x0 viewport if we're not being rendered. See: 43 // 44 // * https://github.com/w3c/csswg-drafts/issues/571 45 // * https://github.com/whatwg/html/issues/1813 46 // 47 if (!pc) { 48 return {}; 49 } 50 51 if (pc->IsRootPaginatedDocument()) { 52 // We want the page size, including unprintable areas and margins. 53 // 54 // FIXME(emilio, bug 1414600): Not quite! 55 return pc->GetPageSize(); 56 } 57 58 return pc->GetVisibleArea().Size(); 59 } 60 61 // A helper for three features below. 62 static nsSize GetDeviceSize(const Document& aDocument) { 63 if (aDocument.ShouldResistFingerprinting(RFPTarget::CSSDeviceSize)) { 64 return GetSize(aDocument); 65 } 66 67 // Media queries in documents in an RDM pane should use the simulated 68 // device size. 69 Maybe<CSSIntSize> deviceSize = 70 nsGlobalWindowOuter::GetRDMDeviceSize(aDocument); 71 if (deviceSize.isSome()) { 72 return CSSPixel::ToAppUnits(deviceSize.value()); 73 } 74 75 // Media queries in documents should use an override set with WebDriver BiDi 76 // if it exists. 77 if (dom::BrowsingContext* bc = aDocument.GetBrowsingContext()) { 78 Maybe<CSSIntSize> screenSize = bc->GetScreenAreaOverride(); 79 if (screenSize.isSome()) { 80 return CSSPixel::ToAppUnits(screenSize.value()); 81 } 82 } 83 84 nsPresContext* pc = aDocument.GetPresContext(); 85 // NOTE(emilio): We should probably figure out how to return an appropriate 86 // device size here, though in a multi-screen world that makes no sense 87 // really. 88 if (!pc) { 89 return {}; 90 } 91 92 if (pc->IsRootPaginatedDocument()) { 93 // We want the page size, including unprintable areas and margins. 94 // XXX The spec actually says we want the "page sheet size", but 95 // how is that different? 96 return pc->GetPageSize(); 97 } 98 99 return pc->DeviceContext()->GetDeviceSurfaceDimensions(); 100 } 101 102 bool Gecko_MediaFeatures_IsResourceDocument(const Document* aDocument) { 103 return aDocument->IsResourceDoc(); 104 } 105 106 bool Gecko_MediaFeatures_InAndroidPipMode(const Document* aDocument) { 107 return aDocument->InAndroidPipMode(); 108 } 109 110 bool Gecko_MediaFeatures_UseOverlayScrollbars(const Document* aDocument) { 111 nsPresContext* pc = aDocument->GetPresContext(); 112 return pc && pc->UseOverlayScrollbars(); 113 } 114 115 static nsDeviceContext* GetDeviceContextFor(const Document* aDocument) { 116 nsPresContext* pc = aDocument->GetPresContext(); 117 if (!pc) { 118 return nullptr; 119 } 120 121 // It would be nice to call nsLayoutUtils::GetDeviceContextForScreenInfo here, 122 // except for two things: (1) it can flush, and flushing is bad here, and (2) 123 // it doesn't really get us consistency in multi-monitor situations *anyway*. 124 return pc->DeviceContext(); 125 } 126 127 void Gecko_MediaFeatures_GetDeviceSize(const Document* aDocument, 128 nscoord* aWidth, nscoord* aHeight) { 129 nsSize size = GetDeviceSize(*aDocument); 130 *aWidth = size.width; 131 *aHeight = size.height; 132 } 133 134 int32_t Gecko_MediaFeatures_GetMonochromeBitsPerPixel( 135 const Document* aDocument) { 136 // The default bits per pixel for a monochrome device. We could propagate this 137 // further to nsIPrintSettings, but Gecko doesn't actually know this value 138 // from the hardware, so it seems silly to do so. 139 static constexpr int32_t kDefaultMonochromeBpp = 8; 140 141 nsPresContext* pc = aDocument->GetPresContext(); 142 if (!pc) { 143 return 0; 144 } 145 nsIPrintSettings* ps = pc->GetPrintSettings(); 146 if (!ps) { 147 return 0; 148 } 149 bool color = true; 150 ps->GetPrintInColor(&color); 151 return color ? 0 : kDefaultMonochromeBpp; 152 } 153 154 StyleColorGamut Gecko_MediaFeatures_ColorGamut(const Document* aDocument) { 155 auto* dx = GetDeviceContextFor(aDocument); 156 if (!dx || aDocument->ShouldResistFingerprinting(RFPTarget::CSSColorInfo)) { 157 return StyleColorGamut::Srgb; 158 } 159 switch (dx->GetColorGamut()) { 160 case dom::ScreenColorGamut::Srgb: 161 return StyleColorGamut::Srgb; 162 case dom::ScreenColorGamut::Rec2020: 163 return StyleColorGamut::Rec2020; 164 case dom::ScreenColorGamut::P3: 165 return StyleColorGamut::P3; 166 } 167 return StyleColorGamut::Srgb; 168 } 169 170 int32_t Gecko_MediaFeatures_GetColorDepth(const Document* aDocument) { 171 if (Gecko_MediaFeatures_GetMonochromeBitsPerPixel(aDocument) != 0) { 172 // If we're a monochrome device, then the color depth is zero. 173 return 0; 174 } 175 176 // Use depth of 24 when resisting fingerprinting, or when we're not being 177 // rendered. 178 int32_t depth = 24; 179 180 if (!aDocument->ShouldResistFingerprinting(RFPTarget::CSSColorInfo)) { 181 if (nsDeviceContext* dx = GetDeviceContextFor(aDocument)) { 182 depth = dx->GetDepth(); 183 } 184 } 185 186 // The spec says to use bits *per color component*, so divide by 3, 187 // and round down, since the spec says to use the smallest when the 188 // color components differ. 189 return depth / 3; 190 } 191 192 float Gecko_MediaFeatures_GetResolution(const Document* aDocument) { 193 // We're returning resolution in terms of device pixels per css pixel, since 194 // that is the preferred unit for media queries of resolution. This avoids 195 // introducing precision error from conversion to and from less-used 196 // physical units like inches. 197 nsPresContext* pc = aDocument->GetPresContext(); 198 if (!pc) { 199 return 1.; 200 } 201 202 if (pc->GetOverrideDPPX() > 0.) { 203 return pc->GetOverrideDPPX(); 204 } 205 206 if (aDocument->ShouldResistFingerprinting(RFPTarget::CSSResolution)) { 207 return float(nsRFPService::GetDevicePixelRatioAtZoom(pc->GetFullZoom())); 208 } 209 // Get the actual device pixel ratio, which also takes zoom into account. 210 return float(AppUnitsPerCSSPixel()) / 211 pc->DeviceContext()->AppUnitsPerDevPixel(); 212 } 213 214 static const Document* TopDocument(const Document* aDocument) { 215 const Document* current = aDocument; 216 while (const Document* parent = current->GetInProcessParentDocument()) { 217 current = parent; 218 } 219 return current; 220 } 221 222 StyleDisplayMode Gecko_MediaFeatures_GetDisplayMode(const Document* aDocument) { 223 const Document* rootDocument = TopDocument(aDocument); 224 225 nsCOMPtr<nsISupports> container = rootDocument->GetContainer(); 226 if (nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(container)) { 227 nsCOMPtr<nsIWidget> mainWidget = baseWindow->GetMainWidget(); 228 if (mainWidget && mainWidget->SizeMode() == nsSizeMode_Fullscreen) { 229 return StyleDisplayMode::Fullscreen; 230 } 231 } 232 233 static_assert( 234 static_cast<int32_t>(DisplayMode::Browser) == 235 static_cast<int32_t>(StyleDisplayMode::Browser) && 236 static_cast<int32_t>(DisplayMode::Minimal_ui) == 237 static_cast<int32_t>(StyleDisplayMode::MinimalUi) && 238 static_cast<int32_t>(DisplayMode::Standalone) == 239 static_cast<int32_t>(StyleDisplayMode::Standalone) && 240 static_cast<int32_t>(DisplayMode::Fullscreen) == 241 static_cast<int32_t>(StyleDisplayMode::Fullscreen) && 242 static_cast<int32_t>(DisplayMode::Picture_in_picture) == 243 static_cast<int32_t>(StyleDisplayMode::PictureInPicture), 244 "DisplayMode must mach nsStyleConsts.h"); 245 246 dom::BrowsingContext* browsingContext = aDocument->GetBrowsingContext(); 247 if (!browsingContext) { 248 return StyleDisplayMode::Browser; 249 } 250 return static_cast<StyleDisplayMode>(browsingContext->DisplayMode()); 251 } 252 253 bool Gecko_MediaFeatures_MatchesPlatform(StylePlatform aPlatform) { 254 switch (aPlatform) { 255 #if defined(XP_WIN) 256 case StylePlatform::Windows: 257 return true; 258 #elif defined(ANDROID) 259 case StylePlatform::Android: 260 return true; 261 #elif defined(MOZ_WIDGET_GTK) 262 case StylePlatform::Linux: 263 return true; 264 #elif defined(XP_MACOSX) 265 case StylePlatform::Macos: 266 return true; 267 #elif defined(XP_IOS) 268 case StylePlatform::Ios: 269 return true; 270 #else 271 # error "Unknown platform?" 272 #endif 273 default: 274 return false; 275 } 276 } 277 278 bool Gecko_MediaFeatures_PrefersReducedMotion(const Document* aDocument) { 279 if (aDocument->ShouldResistFingerprinting( 280 RFPTarget::CSSPrefersReducedMotion)) { 281 return false; 282 } 283 return LookAndFeel::GetInt(LookAndFeel::IntID::PrefersReducedMotion, 0) == 1; 284 } 285 286 bool Gecko_MediaFeatures_PrefersReducedTransparency(const Document* aDocument) { 287 if (aDocument->ShouldResistFingerprinting( 288 RFPTarget::CSSPrefersReducedTransparency)) { 289 return false; 290 } 291 return LookAndFeel::GetInt(LookAndFeel::IntID::PrefersReducedTransparency, 292 0) == 1; 293 } 294 295 StylePrefersColorScheme Gecko_MediaFeatures_PrefersColorScheme( 296 const Document* aDocument, bool aUseContent) { 297 auto scheme = aUseContent ? PreferenceSheet::ContentPrefs().mColorScheme 298 : aDocument->PreferredColorScheme(); 299 return scheme == ColorScheme::Dark ? StylePrefersColorScheme::Dark 300 : StylePrefersColorScheme::Light; 301 } 302 303 bool Gecko_MediaFeatures_MacRTL(const Document* aDocument) { 304 auto* widget = nsContentUtils::WidgetForDocument(aDocument); 305 return widget && widget->IsMacTitlebarDirectionRTL(); 306 } 307 308 // Neither Linux, Windows, nor Mac have a way to indicate that low contrast is 309 // preferred so we use the presence of an accessibility theme or forced colors 310 // as a signal. 311 StylePrefersContrast Gecko_MediaFeatures_PrefersContrast( 312 const Document* aDocument) { 313 if (aDocument->ShouldResistFingerprinting(RFPTarget::CSSPrefersContrast)) { 314 return StylePrefersContrast::NoPreference; 315 } 316 const auto& prefs = PreferenceSheet::PrefsFor(*aDocument); 317 if (!prefs.mUseAccessibilityTheme && prefs.mUseDocumentColors) { 318 return StylePrefersContrast::NoPreference; 319 } 320 const auto& colors = prefs.ColorsFor(ColorScheme::Light); 321 float ratio = RelativeLuminanceUtils::ContrastRatio(colors.mDefaultBackground, 322 colors.mDefault); 323 // https://www.w3.org/TR/WCAG21/#contrast-minimum 324 if (ratio < 4.5f) { 325 return StylePrefersContrast::Less; 326 } 327 // https://www.w3.org/TR/WCAG21/#contrast-enhanced 328 if (ratio >= 7.0f) { 329 return StylePrefersContrast::More; 330 } 331 return StylePrefersContrast::Custom; 332 } 333 334 bool Gecko_MediaFeatures_InvertedColors(const Document* aDocument) { 335 if (aDocument->ShouldResistFingerprinting(RFPTarget::CSSInvertedColors)) { 336 return false; 337 } 338 return LookAndFeel::GetInt(LookAndFeel::IntID::InvertedColors, 0) == 1; 339 } 340 341 StyleScripting Gecko_MediaFeatures_Scripting(const Document* aDocument) { 342 const auto* doc = aDocument; 343 if (aDocument->IsStaticDocument()) { 344 doc = aDocument->GetOriginalDocument(); 345 } 346 347 return doc->IsScriptEnabled() ? StyleScripting::Enabled 348 : StyleScripting::None; 349 } 350 351 StyleDynamicRange Gecko_MediaFeatures_DynamicRange(const Document* aDocument) { 352 // Bug 1759772: Once HDR color is available, update each platform 353 // LookAndFeel implementation to return StyleDynamicRange::High when 354 // appropriate. 355 return StyleDynamicRange::Standard; 356 } 357 358 StyleDynamicRange Gecko_MediaFeatures_VideoDynamicRange( 359 const Document* aDocument) { 360 if (aDocument->ShouldResistFingerprinting(RFPTarget::CSSVideoDynamicRange) || 361 !StaticPrefs::layout_css_video_dynamic_range_allows_high()) { 362 return StyleDynamicRange::Standard; 363 } 364 #ifdef MOZ_WAYLAND 365 // Wayland compositors allow to process HDR content even without HDR monitor 366 // attached. 367 if (StaticPrefs::gfx_wayland_hdr_force_enabled_AtStartup()) { 368 return StyleDynamicRange::High; 369 } 370 if (!StaticPrefs::gfx_wayland_hdr_AtStartup()) { 371 return StyleDynamicRange::Standard; 372 } 373 #endif 374 // video-dynamic-range: high has 3 requirements: 375 // 1) high peak brightness 376 // 2) high contrast ratio 377 // 3) color depth > 24 378 379 // As a proxy for those requirements, return 'High' if the screen associated 380 // with the device context claims to be HDR capable. 381 if (nsDeviceContext* dx = GetDeviceContextFor(aDocument)) { 382 if (dx->GetScreenIsHDR()) { 383 return StyleDynamicRange::High; 384 } 385 } 386 387 return StyleDynamicRange::Standard; 388 } 389 390 static PointerCapabilities GetPointerCapabilities(const Document* aDocument, 391 LookAndFeel::IntID aID) { 392 MOZ_ASSERT(aID == LookAndFeel::IntID::PrimaryPointerCapabilities || 393 aID == LookAndFeel::IntID::AllPointerCapabilities); 394 MOZ_ASSERT(aDocument); 395 396 if (dom::BrowsingContext* bc = aDocument->GetBrowsingContext()) { 397 // The touch-events-override happens only for the Responsive Design Mode so 398 // that we don't need to care about ResistFingerprinting. 399 if (bc->TouchEventsOverride() == dom::TouchEventsOverride::Enabled) { 400 return PointerCapabilities::Coarse; 401 } 402 } 403 404 // The default value for Desktop is mouse-type pointer, and for Android 405 // a coarse pointer. 406 const PointerCapabilities kDefaultCapabilities = 407 #ifdef ANDROID 408 PointerCapabilities::Coarse; 409 #else 410 PointerCapabilities::Fine | PointerCapabilities::Hover; 411 #endif 412 if (aDocument->ShouldResistFingerprinting( 413 RFPTarget::CSSPointerCapabilities)) { 414 return kDefaultCapabilities; 415 } 416 417 int32_t intValue; 418 nsresult rv = LookAndFeel::GetInt(aID, &intValue); 419 if (NS_FAILED(rv)) { 420 return kDefaultCapabilities; 421 } 422 423 return static_cast<PointerCapabilities>(intValue); 424 } 425 426 PointerCapabilities Gecko_MediaFeatures_PrimaryPointerCapabilities( 427 const Document* aDocument) { 428 return GetPointerCapabilities(aDocument, 429 LookAndFeel::IntID::PrimaryPointerCapabilities); 430 } 431 432 PointerCapabilities Gecko_MediaFeatures_AllPointerCapabilities( 433 const Document* aDocument) { 434 return GetPointerCapabilities(aDocument, 435 LookAndFeel::IntID::AllPointerCapabilities); 436 } 437 438 StyleGtkThemeFamily Gecko_MediaFeatures_GtkThemeFamily() { 439 static_assert(int32_t(StyleGtkThemeFamily::Unknown) == 0); 440 return StyleGtkThemeFamily( 441 LookAndFeel::GetInt(LookAndFeel::IntID::GTKThemeFamily)); 442 }