From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- layout/base/nsPresContext.cpp | 3129 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3129 insertions(+) create mode 100644 layout/base/nsPresContext.cpp (limited to 'layout/base/nsPresContext.cpp') diff --git a/layout/base/nsPresContext.cpp b/layout/base/nsPresContext.cpp new file mode 100644 index 0000000000..e65e6116e4 --- /dev/null +++ b/layout/base/nsPresContext.cpp @@ -0,0 +1,3129 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +/* a presentation of a document, part 1 */ + +#include "nsPresContext.h" +#include "nsPresContextInlines.h" + +#include "mozilla/ArrayUtils.h" +#if defined(MOZ_WIDGET_ANDROID) +# include "mozilla/AsyncEventDispatcher.h" +#endif +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Encoding.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/EventStateManager.h" +#include "mozilla/PresShell.h" +#include "mozilla/PresShellInlines.h" + +#include "base/basictypes.h" +#include "nsCRT.h" +#include "nsCOMPtr.h" +#include "nsCSSFrameConstructor.h" +#include "nsDocShell.h" +#include "nsIConsoleService.h" +#include "nsIContentViewer.h" +#include "nsPIDOMWindow.h" +#include "mozilla/ServoStyleSet.h" +#include "mozilla/MediaFeatureChange.h" +#include "nsIContent.h" +#include "nsIFrame.h" +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/DocumentInlines.h" +#include "nsIPrintSettings.h" +#include "nsLanguageAtomService.h" +#include "mozilla/LookAndFeel.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsHTMLDocument.h" +#include "nsIWeakReferenceUtils.h" +#include "nsThreadUtils.h" +#include "nsLayoutUtils.h" +#include "nsViewManager.h" +#include "mozilla/RestyleManager.h" +#include "gfxPlatform.h" +#include "nsFontFaceLoader.h" +#include "mozilla/AnimationEventDispatcher.h" +#include "mozilla/EffectCompositor.h" +#include "mozilla/EventListenerManager.h" +#include "prenv.h" +#include "nsTransitionManager.h" +#include "nsAnimationManager.h" +#include "CounterStyleManager.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/dom/Element.h" +#include "nsIMessageManager.h" +#include "mozilla/dom/HTMLBodyElement.h" +#include "mozilla/dom/MediaQueryList.h" +#include "mozilla/SMILAnimationController.h" +#include "mozilla/css/ImageLoader.h" +#include "mozilla/dom/PBrowserParent.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/FontFaceSet.h" +#include "mozilla/StaticPresData.h" +#include "nsRefreshDriver.h" +#include "LayerUserData.h" +#include "mozilla/dom/NotifyPaintEvent.h" +#include "nsFontCache.h" +#include "nsFrameLoader.h" +#include "nsContentUtils.h" +#include "nsPIWindowRoot.h" +#include "mozilla/Preferences.h" +#include "gfxTextRun.h" +#include "nsFontFaceUtils.h" +#include "mozilla/ContentBlockingAllowList.h" +#include "mozilla/GlobalStyleSheetCache.h" +#include "mozilla/ServoBindings.h" +#include "mozilla/StaticPrefs_bidi.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/StaticPrefs_widget.h" +#include "mozilla/StaticPrefs_zoom.h" +#include "mozilla/StyleSheet.h" +#include "mozilla/StyleSheetInlines.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TimelineManager.h" +#include "mozilla/dom/Performance.h" +#include "mozilla/dom/PerformanceTiming.h" +#include "mozilla/dom/PerformancePaintTiming.h" +#include "mozilla/layers/APZThreadUtils.h" +#include "MobileViewportManager.h" +#include "mozilla/dom/ImageTracker.h" +#ifdef ACCESSIBILITY +# include "mozilla/a11y/DocAccessible.h" +#endif + +// Needed for Start/Stop of Image Animation +#include "imgIContainer.h" +#include "nsIImageLoadingContent.h" + +#include "nsBidiUtils.h" +#include "nsServiceManagerUtils.h" + +#include "mozilla/dom/URL.h" +#include "mozilla/ServoCSSParser.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::gfx; +using namespace mozilla::layers; + +/** + * Layer UserData for ContainerLayers that want to be notified + * of local invalidations of them and their descendant layers. + * Pass a callback to ComputeDifferences to have these called. + */ +class ContainerLayerPresContext : public LayerUserData { + public: + nsPresContext* mPresContext; +}; + +bool nsPresContext::IsDOMPaintEventPending() { + if (!mTransactions.IsEmpty()) { + return true; + } + + nsRootPresContext* drpc = GetRootPresContext(); + if (drpc && drpc->mRefreshDriver->ViewManagerFlushIsPending()) { + // Since we're promising that there will be a MozAfterPaint event + // fired, we record an empty invalidation in case display list + // invalidation doesn't invalidate anything further. + NotifyInvalidation(drpc->mRefreshDriver->LastTransactionId().Next(), + nsRect(0, 0, 0, 0)); + return true; + } + return false; +} + +struct WeakRunnableMethod : Runnable { + using Method = void (nsPresContext::*)(); + + WeakRunnableMethod(const char* aName, nsPresContext* aPc, Method aMethod) + : Runnable(aName), mPresContext(aPc), mMethod(aMethod) {} + + NS_IMETHOD Run() override { + if (nsPresContext* pc = mPresContext.get()) { + (pc->*mMethod)(); + } + return NS_OK; + } + + private: + WeakPtr mPresContext; + Method mMethod; +}; + +// When forcing a font-info-update reflow from style, we don't need to reframe, +// but we'll need to restyle to pick up updated font metrics. In order to avoid +// synchronously having to deal with multiple restyles, we use an early refresh +// driver runner, which should prevent flashing for users. +// +// We might do a bit of extra work if the page flushes layout between the +// restyle and when this happens, which is a bit unfortunate, but not worse than +// what we used to do... +// +// A better solution would be to be able to synchronously initialize font +// information from style worker threads, perhaps... +void nsPresContext::ForceReflowForFontInfoUpdateFromStyle() { + if (mPendingFontInfoUpdateReflowFromStyle) { + return; + } + + mPendingFontInfoUpdateReflowFromStyle = true; + nsCOMPtr ev = new WeakRunnableMethod( + "nsPresContext::DoForceReflowForFontInfoUpdateFromStyle", this, + &nsPresContext::DoForceReflowForFontInfoUpdateFromStyle); + RefreshDriver()->AddEarlyRunner(ev); +} + +void nsPresContext::DoForceReflowForFontInfoUpdateFromStyle() { + mPendingFontInfoUpdateReflowFromStyle = false; + ForceReflowForFontInfoUpdate(false); +} + +void nsPresContext::ForceReflowForFontInfoUpdate(bool aNeedsReframe) { + // In the case of a static-clone document used for printing or print-preview, + // this is undesirable because the nsPrintJob is holding weak refs to frames + // that will get blown away unexpectedly by this reconstruction. So the + // prescontext for a print/preview doc ignores the font-list update. + // + // This means the print document may still be using cached fonts that are no + // longer present in the font list, but that should be safe given that all the + // required font instances have already been created, so it won't be depending + // on access to the font-list entries. + // + // XXX Actually, I think it's probably a bad idea to do *any* restyling of + // print documents in response to pref changes. We could be in the middle + // of printing the document, and reflowing all the frames might cause some + // kind of unwanted mid-document discontinuity. + if (IsPrintingOrPrintPreview()) { + return; + } + + // If there's a user font set, discard any src:local() faces it may have + // loaded because their font entries may no longer be valid. + if (auto* fonts = Document()->GetFonts()) { + fonts->GetImpl()->ForgetLocalFaces(); + } + + FlushFontCache(); + + nsChangeHint changeHint = + aNeedsReframe ? nsChangeHint_ReconstructFrame : NS_STYLE_HINT_REFLOW; + + // We also need to trigger restyling for ex/ch units changes to take effect, + // if needed. + auto restyleHint = StyleSet()->UsesFontMetrics() + ? RestyleHint::RecascadeSubtree() + : RestyleHint{0}; + + RebuildAllStyleData(changeHint, restyleHint); +} + +static bool IsVisualCharset(NotNull aCharset) { + return aCharset == ISO_8859_8_ENCODING; +} + +nsPresContext::nsPresContext(dom::Document* aDocument, nsPresContextType aType) + : mPresShell(nullptr), + mDocument(aDocument), + mMedium(aType == eContext_Galley ? nsGkAtoms::screen : nsGkAtoms::print), + mTextZoom(1.0), + mFullZoom(1.0), + mLastFontInflationScreenSize(gfxSize(-1.0, -1.0)), + mCurAppUnitsPerDevPixel(0), + mAutoQualityMinFontSizePixelsPref(0), + mDynamicToolbarMaxHeight(0), + mDynamicToolbarHeight(0), + mPageSize(-1, -1), + mPageScale(0.0), + mPPScale(1.0f), + mViewportScrollOverrideElement(nullptr), + mElementsRestyled(0), + mFramesConstructed(0), + mFramesReflowed(0), + mInterruptChecksToSkip(0), + mNextFrameRateMultiplier(0), + mMeasuredTicksSinceLoading(0), + mViewportScrollStyles(StyleOverflow::Auto, StyleOverflow::Auto), + // mImageAnimationMode is initialised below, in constructor body + mImageAnimationModePref(imgIContainer::kNormalAnimMode), + mType(aType), + mInflationDisabledForShrinkWrap(false), + mInteractionTimeEnabled(true), + mHasPendingInterrupt(false), + mHasEverBuiltInvisibleText(false), + mPendingInterruptFromTest(false), + mInterruptsEnabled(false), + mSendAfterPaintToContent(false), + mDrawImageBackground(true), // always draw the background + mDrawColorBackground(true), + // mNeverAnimate is initialised below, in constructor body + mPaginated(aType != eContext_Galley), + mCanPaginatedScroll(false), + mDoScaledTwips(true), + mIsRootPaginatedDocument(false), + mPrefBidiDirection(false), + mPrefScrollbarSide(0), + mPendingThemeChanged(false), + mPendingThemeChangeKind(0), + mPendingUIResolutionChanged(false), + mPendingFontInfoUpdateReflowFromStyle(false), + mIsGlyph(false), + mCounterStylesDirty(true), + mFontFeatureValuesDirty(true), + mFontPaletteValuesDirty(true), + mIsVisual(false), + mInRDMPane(false), + mHasWarnedAboutTooLargeDashedOrDottedRadius(false), + mQuirkSheetAdded(false), + mHadNonBlankPaint(false), + mHadFirstContentfulPaint(false), + mHadNonTickContentfulPaint(false), + mHadContentfulPaintComposite(false), + mUserInputEventsAllowed(false), +#ifdef DEBUG + mInitialized(false), +#endif + mOverriddenOrEmbedderColorScheme(dom::PrefersColorSchemeOverride::None) { +#ifdef DEBUG + PodZero(&mLayoutPhaseCount); +#endif + + if (!IsDynamic()) { + mImageAnimationMode = imgIContainer::kDontAnimMode; + mNeverAnimate = true; + } else { + mImageAnimationMode = imgIContainer::kNormalAnimMode; + mNeverAnimate = false; + } + NS_ASSERTION(mDocument, "Null document"); + + // if text perf logging enabled, init stats struct + if (MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_textperf), LogLevel::Warning)) { + mTextPerf = MakeUnique(); + } + + if (StaticPrefs::gfx_missing_fonts_notify()) { + mMissingFonts = MakeUnique(); + } + + if (StaticPrefs::layout_dynamic_toolbar_max_height() > 0) { + // The pref for dynamic toolbar max height is only used in reftests so it's + // fine to set here. + mDynamicToolbarMaxHeight = StaticPrefs::layout_dynamic_toolbar_max_height(); + } + + UpdateFontVisibility(); +} + +static const char* gExactCallbackPrefs[] = { + "browser.active_color", + "browser.anchor_color", + "browser.underline_anchors", + "browser.visited_color", + "dom.meta-viewport.enabled", + "dom.send_after_paint_to_content", + "image.animation_mode", + "intl.accept_languages", + "layout.css.devPixelsPerPx", + "layout.css.dpi", + "layout.css.text-transform.uppercase-eszett.enabled", + "privacy.trackingprotection.enabled", + "ui.use_standins_for_native_colors", + nullptr, +}; + +static const char* gPrefixCallbackPrefs[] = { + "bidi.", "browser.display.", "browser.viewport.", + "font.", "gfx.font_rendering.", "layout.css.font-visibility.", + nullptr, +}; + +void nsPresContext::Destroy() { + if (mEventManager) { + // unclear if these are needed, but can't hurt + mEventManager->NotifyDestroyPresContext(this); + mEventManager->SetPresContext(nullptr); + mEventManager = nullptr; + } + + if (mFontCache) { + mFontCache->Destroy(); + mFontCache = nullptr; + } + + // Unregister preference callbacks + Preferences::UnregisterPrefixCallbacks(nsPresContext::PreferenceChanged, + gPrefixCallbackPrefs, this); + Preferences::UnregisterCallbacks(nsPresContext::PreferenceChanged, + gExactCallbackPrefs, this); + + mRefreshDriver = nullptr; + MOZ_ASSERT(mManagedPostRefreshObservers.IsEmpty()); +} + +nsPresContext::~nsPresContext() { + MOZ_ASSERT(!mPresShell, "Presshell forgot to clear our mPresShell pointer"); + DetachPresShell(); + + Destroy(); +} + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsPresContext) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsPresContext) +NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(nsPresContext, LastRelease()) + +void nsPresContext::LastRelease() { + if (mMissingFonts) { + mMissingFonts->Clear(); + } +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(nsPresContext) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsPresContext) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnimationEventDispatcher); + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument); + // NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(mDeviceContext); // not xpcom + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEffectCompositor); + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEventManager); + // NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(mLanguage); // an atom + + // NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTheme); // a service + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrintSettings); +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsPresContext) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnimationEventDispatcher); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDeviceContext); // worth bothering? + NS_IMPL_CYCLE_COLLECTION_UNLINK(mEffectCompositor); + // NS_RELEASE(tmp->mLanguage); // an atom + // NS_IMPL_CYCLE_COLLECTION_UNLINK(mTheme); // a service + NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrintSettings); + NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR + + tmp->Destroy(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +bool nsPresContext::IsChrome() const { + return Document()->IsInChromeDocShell(); +} + +void nsPresContext::GetUserPreferences() { + if (!GetPresShell()) { + // No presshell means nothing to do here. We'll do this when we + // get a presshell. + return; + } + + mAutoQualityMinFontSizePixelsPref = + Preferences::GetInt("browser.display.auto_quality_min_font_size"); + + PreferenceSheet::EnsureInitialized(); + + mSendAfterPaintToContent = Preferences::GetBool( + "dom.send_after_paint_to_content", mSendAfterPaintToContent); + + mPrefScrollbarSide = Preferences::GetInt("layout.scrollbar.side"); + + Document()->SetMayNeedFontPrefsUpdate(); + + // * image animation + nsAutoCString animatePref; + Preferences::GetCString("image.animation_mode", animatePref); + if (animatePref.EqualsLiteral("normal")) + mImageAnimationModePref = imgIContainer::kNormalAnimMode; + else if (animatePref.EqualsLiteral("none")) + mImageAnimationModePref = imgIContainer::kDontAnimMode; + else if (animatePref.EqualsLiteral("once")) + mImageAnimationModePref = imgIContainer::kLoopOnceAnimMode; + else // dynamic change to invalid value should act like it does initially + mImageAnimationModePref = imgIContainer::kNormalAnimMode; + + uint32_t bidiOptions = GetBidi(); + + mPrefBidiDirection = StaticPrefs::bidi_direction(); + SET_BIDI_OPTION_DIRECTION(bidiOptions, mPrefBidiDirection); + SET_BIDI_OPTION_TEXTTYPE(bidiOptions, StaticPrefs::bidi_texttype()); + SET_BIDI_OPTION_NUMERAL(bidiOptions, StaticPrefs::bidi_numeral()); + + // We don't need to force reflow: either we are initializing a new + // prescontext or we are being called from PreferenceChanged() + // which triggers a reflow anyway. + SetBidi(bidiOptions); +} + +void nsPresContext::InvalidatePaintedLayers() { + if (!mPresShell) { + return; + } + if (nsIFrame* rootFrame = mPresShell->GetRootFrame()) { + // FrameLayerBuilder caches invalidation-related values that depend on the + // appunits-per-dev-pixel ratio, so ensure that all PaintedLayer drawing + // is completely flushed. + rootFrame->InvalidateFrameSubtree(); + } +} + +void nsPresContext::AppUnitsPerDevPixelChanged() { + int32_t oldAppUnitsPerDevPixel = mCurAppUnitsPerDevPixel; + + InvalidatePaintedLayers(); + + FlushFontCache(); + + MediaFeatureValuesChanged( + {RestyleHint::RecascadeSubtree(), NS_STYLE_HINT_REFLOW, + MediaFeatureChangeReason::ResolutionChange}, + MediaFeatureChangePropagation::JustThisDocument); + + mCurAppUnitsPerDevPixel = mDeviceContext->AppUnitsPerDevPixel(); + +#ifdef ACCESSIBILITY + if (mCurAppUnitsPerDevPixel != oldAppUnitsPerDevPixel) { + if (nsAccessibilityService* accService = GetAccService()) { + accService->NotifyOfDevPixelRatioChange(mPresShell, + mCurAppUnitsPerDevPixel); + } + } +#endif + + // Recompute the size for vh units since it's changed by the dynamic toolbar + // max height which is stored in screen coord. + if (IsRootContentDocumentCrossProcess()) { + AdjustSizeForViewportUnits(); + } + + // nsSubDocumentFrame uses a AppUnitsPerDevPixel difference between parent and + // child document to determine if it needs to build a nsDisplayZoom item. So + // if we that changes then we need to invalidate the subdoc frame so that + // item gets created/removed. + if (mPresShell) { + if (nsIFrame* frame = mPresShell->GetRootFrame()) { + frame = nsLayoutUtils::GetCrossDocParentFrameInProcess(frame); + if (frame) { + int32_t parentAPD = frame->PresContext()->AppUnitsPerDevPixel(); + if ((parentAPD == oldAppUnitsPerDevPixel) != + (parentAPD == mCurAppUnitsPerDevPixel)) { + frame->InvalidateFrame(); + } + } + } + } + + // We would also have to look at all of our child subdocuments but the + // InvalidatePaintedLayers call above calls InvalidateFrameSubtree which + // would invalidate all subdocument frames already. +} + +// static +void nsPresContext::PreferenceChanged(const char* aPrefName, void* aSelf) { + static_cast(aSelf)->PreferenceChanged(aPrefName); +} + +void nsPresContext::PreferenceChanged(const char* aPrefName) { + nsDependentCString prefName(aPrefName); + if (prefName.EqualsLiteral("layout.css.dpi") || + prefName.EqualsLiteral("layout.css.devPixelsPerPx")) { + int32_t oldAppUnitsPerDevPixel = mDeviceContext->AppUnitsPerDevPixel(); + // We need to assume the DPI changes, since `mDeviceContext` is shared with + // other documents, and we'd need to save the return value of the first call + // for all of them. + Unused << mDeviceContext->CheckDPIChange(); + if (mPresShell) { + OwningNonNull presShell(*mPresShell); + // Re-fetch the view manager's window dimensions in case there's a + // deferred resize which hasn't affected our mVisibleArea yet + nscoord oldWidthAppUnits, oldHeightAppUnits; + RefPtr vm = presShell->GetViewManager(); + if (!vm) { + return; + } + vm->GetWindowDimensions(&oldWidthAppUnits, &oldHeightAppUnits); + float oldWidthDevPixels = oldWidthAppUnits / oldAppUnitsPerDevPixel; + float oldHeightDevPixels = oldHeightAppUnits / oldAppUnitsPerDevPixel; + + UIResolutionChangedInternal(); + + nscoord width = NSToCoordRound(oldWidthDevPixels * AppUnitsPerDevPixel()); + nscoord height = + NSToCoordRound(oldHeightDevPixels * AppUnitsPerDevPixel()); + vm->SetWindowDimensions(width, height); + } + return; + } + + if (StringBeginsWith(prefName, "browser.viewport."_ns) || + StringBeginsWith(prefName, "font.size.inflation."_ns) || + prefName.EqualsLiteral("dom.meta-viewport.enabled")) { + if (mPresShell) { + mPresShell->MaybeReflowForInflationScreenSizeChange(); + } + } + + auto changeHint = nsChangeHint{0}; + auto restyleHint = RestyleHint{0}; + // Changing any of these potentially changes the value of @media + // (prefers-contrast). + // The layout.css.prefers-contrast.enabled pref itself is not handled here, + // because that pref doesn't just affect the "live" value of the media query; + // it affects whether it is parsed at all. + if (prefName.EqualsLiteral("browser.display.document_color_use") || + prefName.EqualsLiteral("browser.display.foreground_color") || + prefName.EqualsLiteral("browser.display.background_color")) { + MediaFeatureValuesChanged({MediaFeatureChangeReason::PreferenceChange}, + MediaFeatureChangePropagation::JustThisDocument); + } + if (prefName.EqualsLiteral(GFX_MISSING_FONTS_NOTIFY_PREF)) { + if (StaticPrefs::gfx_missing_fonts_notify()) { + if (!mMissingFonts) { + mMissingFonts = MakeUnique(); + // trigger reflow to detect missing fonts on the current page + changeHint |= NS_STYLE_HINT_REFLOW; + } + } else { + if (mMissingFonts) { + mMissingFonts->Clear(); + } + mMissingFonts = nullptr; + } + } + + if (StringBeginsWith(prefName, "font."_ns) || + // Changes to font family preferences don't change anything in the + // computed style data, so the style system won't generate a reflow hint + // for us. We need to do that manually. + prefName.EqualsLiteral("intl.accept_languages") || + // Changes to bidi prefs need to trigger a reflow (see bug 443629) + StringBeginsWith(prefName, "bidi."_ns) || + // Changes to font_rendering prefs need to trigger a reflow + StringBeginsWith(prefName, "gfx.font_rendering."_ns)) { + changeHint |= NS_STYLE_HINT_REFLOW; + if (StyleSet()->UsesFontMetrics()) { + restyleHint |= RestyleHint::RecascadeSubtree(); + } + } + + if (prefName.EqualsLiteral( + "layout.css.text-transform.uppercase-eszett.enabled")) { + changeHint |= NS_STYLE_HINT_REFLOW; + } + + // We will end up calling InvalidatePreferenceSheets one from each pres + // context, but all it's doing is clearing its cached sheet pointers, so it + // won't be wastefully recreating the sheet multiple times. + // + // The first pres context that flushes will be the one to cause the + // reconstruction of the pref style sheet via the UpdatePreferenceStyles call + // in FlushPendingNotifications. + if (GlobalStyleSheetCache::AffectedByPref(prefName)) { + restyleHint |= RestyleHint::RestyleSubtree(); + GlobalStyleSheetCache::InvalidatePreferenceSheets(); + } + + if (PreferenceSheet::AffectedByPref(prefName)) { + restyleHint |= RestyleHint::RestyleSubtree(); + PreferenceSheet::Refresh(); + } + + // Same, this just frees a bunch of memory. + StaticPresData::Get()->InvalidateFontPrefs(); + Document()->SetMayNeedFontPrefsUpdate(); + + // Initialize our state from the user preferences. + GetUserPreferences(); + + FlushFontCache(); + if (UpdateFontVisibility()) { + changeHint |= NS_STYLE_HINT_REFLOW; + } + + // Preferences require rerunning selector matching because we rebuild + // the pref style sheet for some preference changes. + if (changeHint || restyleHint) { + RebuildAllStyleData(changeHint, restyleHint); + } + + InvalidatePaintedLayers(); +} + +nsresult nsPresContext::Init(nsDeviceContext* aDeviceContext) { + NS_ASSERTION(!mInitialized, "attempt to reinit pres context"); + NS_ENSURE_ARG(aDeviceContext); + + mDeviceContext = aDeviceContext; + + // In certain rare cases (such as changing page mode), we tear down layout + // state and re-initialize a new prescontext for a document. Given that we + // hang style state off the DOM, we detect that re-initialization case and + // lazily drop the servo data. We don't do this eagerly during layout teardown + // because that would incur an extra whole-tree traversal that's unnecessary + // most of the time. + // + // FIXME(emilio): I'm pretty sure this doesn't happen after bug 1414999. + Element* root = mDocument->GetRootElement(); + if (root && root->HasServoData()) { + RestyleManager::ClearServoDataFromSubtree(root); + } + + if (mDeviceContext->SetFullZoom(mFullZoom)) { + FlushFontCache(); + } + mCurAppUnitsPerDevPixel = mDeviceContext->AppUnitsPerDevPixel(); + + mEventManager = new mozilla::EventStateManager(); + + mAnimationEventDispatcher = new mozilla::AnimationEventDispatcher(this); + mEffectCompositor = new mozilla::EffectCompositor(this); + mTransitionManager = MakeUnique(this); + mAnimationManager = MakeUnique(this); + mTimelineManager = MakeUnique(this); + + if (mDocument->GetDisplayDocument()) { + NS_ASSERTION(mDocument->GetDisplayDocument()->GetPresContext(), + "Why are we being initialized?"); + mRefreshDriver = + mDocument->GetDisplayDocument()->GetPresContext()->RefreshDriver(); + } else { + dom::Document* parent = mDocument->GetInProcessParentDocument(); + // Unfortunately, sometimes |parent| here has no presshell because + // printing screws up things. Assert that in other cases it does, + // but whenever the shell is null just fall back on using our own + // refresh driver. + NS_ASSERTION( + !parent || mDocument->IsStaticDocument() || parent->GetPresShell(), + "How did we end up with a presshell if our parent doesn't " + "have one?"); + if (parent && parent->GetPresContext()) { + // XXX the document can change in AttachPresShell, does this work? + dom::BrowsingContext* browsingContext = mDocument->GetBrowsingContext(); + if (browsingContext && !browsingContext->IsTop()) { + Element* containingElement = mDocument->GetEmbedderElement(); + if (!containingElement->IsXULElement() || + !containingElement->HasAttr(kNameSpaceID_None, + nsGkAtoms::forceOwnRefreshDriver)) { + mRefreshDriver = parent->GetPresContext()->RefreshDriver(); + } + } + } + + if (!mRefreshDriver) { + mRefreshDriver = new nsRefreshDriver(this); + } + } + + // Register callbacks so we're notified when the preferences change + Preferences::RegisterPrefixCallbacks(nsPresContext::PreferenceChanged, + gPrefixCallbackPrefs, this); + Preferences::RegisterCallbacks(nsPresContext::PreferenceChanged, + gExactCallbackPrefs, this); + + nsresult rv = mEventManager->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + mEventManager->SetPresContext(this); + +#if defined(MOZ_WIDGET_ANDROID) + if (IsRootContentDocumentCrossProcess() && + MOZ_LIKELY( + !Preferences::HasUserValue("layout.dynamic-toolbar-max-height"))) { + if (BrowserChild* browserChild = + BrowserChild::GetFrom(mDocument->GetDocShell())) { + mDynamicToolbarMaxHeight = browserChild->GetDynamicToolbarMaxHeight(); + mDynamicToolbarHeight = mDynamicToolbarMaxHeight; + } + } +#endif + +#ifdef DEBUG + mInitialized = true; +#endif + + return NS_OK; +} + +bool nsPresContext::UpdateFontVisibility() { + FontVisibility oldValue = mFontVisibility; + + // Chrome presContext is allowed access to all fonts. + if (IsChrome()) { + mFontVisibility = FontVisibility::User; + return mFontVisibility != oldValue; + } + + // Is this a private browsing context? + bool isPrivate = false; + if (nsCOMPtr loadContext = mDocument->GetLoadContext()) { + isPrivate = loadContext->UsePrivateBrowsing(); + } + + // Read the relevant pref depending on RFP/trackingProtection state + // to determine the visibility level to use. + int32_t level; + if (mDocument->ShouldResistFingerprinting(RFPTarget::Unknown)) { + level = StaticPrefs::layout_css_font_visibility_resistFingerprinting(); + } else if (StaticPrefs::privacy_trackingprotection_enabled() || + (isPrivate && + StaticPrefs::privacy_trackingprotection_pbmode_enabled())) { + level = StaticPrefs::layout_css_font_visibility_trackingprotection(); + } else { + level = StaticPrefs::layout_css_font_visibility_standard(); + } + + // For private browsing contexts, apply the private-mode limit. + if (isPrivate) { + int32_t priv = StaticPrefs::layout_css_font_visibility_private(); + level = std::max(std::min(level, priv), int32_t(FontVisibility::Base)); + } + + // Determine if the user has exempted the domain from tracking protections, + // if so, use the standard value. + if (ContentBlockingAllowList::Check(mDocument->CookieJarSettings())) { + level = StaticPrefs::layout_css_font_visibility_standard(); + } + + // Clamp result to the valid range of levels. + level = std::max(std::min(level, int32_t(FontVisibility::User)), + int32_t(FontVisibility::Base)); + + mFontVisibility = FontVisibility(level); + return mFontVisibility != oldValue; +} + +void nsPresContext::ReportBlockedFontFamilyName(const nsCString& aFamily, + FontVisibility aVisibility) { + if (!mBlockedFonts.EnsureInserted(aFamily)) { + return; + } + nsAutoString msg; + msg.AppendPrintf( + "Request for font \"%s\" blocked at visibility level %d (requires %d)\n", + aFamily.get(), int(GetFontVisibility()), int(aVisibility)); + nsContentUtils::ReportToConsoleNonLocalized(msg, nsIScriptError::warningFlag, + "Security"_ns, mDocument); +} + +void nsPresContext::ReportBlockedFontFamily(const fontlist::Family& aFamily) { + auto* fontList = gfxPlatformFontList::PlatformFontList()->SharedFontList(); + const nsCString& name = aFamily.DisplayName().AsString(fontList); + ReportBlockedFontFamilyName(name, aFamily.Visibility()); +} + +void nsPresContext::ReportBlockedFontFamily(const gfxFontFamily& aFamily) { + ReportBlockedFontFamilyName(aFamily.Name(), aFamily.Visibility()); +} + +void nsPresContext::InitFontCache() { + if (!mFontCache) { + mFontCache = new nsFontCache(); + mFontCache->Init(this); + } +} + +void nsPresContext::UpdateFontCacheUserFonts(gfxUserFontSet* aUserFontSet) { + if (mFontCache) { + mFontCache->UpdateUserFonts(aUserFontSet); + } +} + +already_AddRefed nsPresContext::GetMetricsFor( + const nsFont& aFont, const nsFontMetrics::Params& aParams) { + InitFontCache(); + return mFontCache->GetMetricsFor(aFont, aParams); +} + +nsresult nsPresContext::FlushFontCache() { + if (mFontCache) { + mFontCache->Flush(); + } + return NS_OK; +} + +nsresult nsPresContext::FontMetricsDeleted(const nsFontMetrics* aFontMetrics) { + if (mFontCache) { + mFontCache->FontMetricsDeleted(aFontMetrics); + } + return NS_OK; +} + +// Note: We don't hold a reference on the shell; it has a reference to +// us +void nsPresContext::AttachPresShell(mozilla::PresShell* aPresShell) { + MOZ_ASSERT(!mPresShell); + mPresShell = aPresShell; + + mRestyleManager = MakeUnique(this); + + // Since CounterStyleManager is also the name of a method of + // nsPresContext, it is necessary to prefix the class with the mozilla + // namespace here. + mCounterStyleManager = new mozilla::CounterStyleManager(this); + + dom::Document* doc = mPresShell->GetDocument(); + MOZ_ASSERT(doc); + // Have to update PresContext's mDocument before calling any other methods. + mDocument = doc; + + LookAndFeel::HandleGlobalThemeChange(); + + // Initialize our state from the user preferences, now that we + // have a presshell, and hence a document. + GetUserPreferences(); + + EnsureTheme(); + + nsIURI* docURI = doc->GetDocumentURI(); + + if (IsDynamic() && docURI) { + if (!docURI->SchemeIs("chrome") && !docURI->SchemeIs("resource")) + mImageAnimationMode = mImageAnimationModePref; + else + mImageAnimationMode = imgIContainer::kNormalAnimMode; + } + + UpdateCharSet(doc->GetDocumentCharacterSet()); +} + +Maybe nsPresContext::GetOverriddenOrEmbedderColorScheme() const { + if (Medium() == nsGkAtoms::print) { + return Some(ColorScheme::Light); + } + + switch (mOverriddenOrEmbedderColorScheme) { + case dom::PrefersColorSchemeOverride::Dark: + return Some(ColorScheme::Dark); + case dom::PrefersColorSchemeOverride::Light: + return Some(ColorScheme::Light); + case dom::PrefersColorSchemeOverride::None: + case dom::PrefersColorSchemeOverride::EndGuard_: + break; + } + + return Nothing(); +} + +void nsPresContext::SetColorSchemeOverride( + PrefersColorSchemeOverride aOverride) { + auto oldScheme = mDocument->PreferredColorScheme(); + + mOverriddenOrEmbedderColorScheme = aOverride; + + if (mDocument->PreferredColorScheme() != oldScheme) { + MediaFeatureValuesChanged( + MediaFeatureChange::ForPreferredColorSchemeChange(), + MediaFeatureChangePropagation::JustThisDocument); + } +} + +void nsPresContext::RecomputeBrowsingContextDependentData() { + MOZ_ASSERT(mDocument); + dom::Document* doc = mDocument; + // Resource documents inherit all this state from their display document. + while (dom::Document* outer = doc->GetDisplayDocument()) { + doc = outer; + } + auto* browsingContext = doc->GetBrowsingContext(); + if (!browsingContext) { + // This can legitimately happen for e.g. SVG images. Those just get scaled + // as a result of the zoom on the embedder document so it doesn't really + // matter... Medium also doesn't affect those. + return; + } + if (!IsPrintingOrPrintPreview()) { + auto systemZoom = LookAndFeel::SystemZoomSettings(); + SetFullZoom(browsingContext->FullZoom() * systemZoom.mFullZoom); + SetTextZoom(browsingContext->TextZoom() * systemZoom.mTextZoom); + SetOverrideDPPX(browsingContext->OverrideDPPX()); + } + + auto* top = browsingContext->Top(); + SetColorSchemeOverride([&] { + auto overriden = top->PrefersColorSchemeOverride(); + if (overriden != PrefersColorSchemeOverride::None) { + return overriden; + } + if (!StaticPrefs:: + layout_css_iframe_embedder_prefers_color_scheme_content_enabled()) { + return top->GetEmbedderColorSchemes().mPreferred; + } + return browsingContext->GetEmbedderColorSchemes().mPreferred; + }()); + + SetInRDMPane(top->GetInRDMPane()); + + if (doc == mDocument) { + // Medium doesn't apply to resource documents, etc. + RefPtr mediumToEmulate; + if (MOZ_UNLIKELY(!top->GetMediumOverride().IsEmpty())) { + nsAutoString lower; + nsContentUtils::ASCIIToLower(top->GetMediumOverride(), lower); + mediumToEmulate = NS_Atomize(lower); + } + EmulateMedium(mediumToEmulate); + } + + mDocument->EnumerateExternalResources([](dom::Document& aSubResource) { + if (nsPresContext* subResourcePc = aSubResource.GetPresContext()) { + subResourcePc->RecomputeBrowsingContextDependentData(); + } + return CallState::Continue; + }); +} + +void nsPresContext::DetachPresShell() { + // The counter style manager's destructor needs to deallocate with the + // presshell arena. Disconnect it before nulling out the shell. + // + // XXXbholley: Given recent refactorings, it probably makes more sense to + // just null our mPresShell at the bottom of this function. I'm leaving it + // this way to preserve the old ordering, but I doubt anything would break. + if (mCounterStyleManager) { + mCounterStyleManager->Disconnect(); + mCounterStyleManager = nullptr; + } + + mPresShell = nullptr; + + CancelManagedPostRefreshObservers(); + + if (mAnimationEventDispatcher) { + mAnimationEventDispatcher->Disconnect(); + mAnimationEventDispatcher = nullptr; + } + if (mEffectCompositor) { + mEffectCompositor->Disconnect(); + mEffectCompositor = nullptr; + } + if (mTransitionManager) { + mTransitionManager->Disconnect(); + mTransitionManager = nullptr; + } + if (mAnimationManager) { + mAnimationManager->Disconnect(); + mAnimationManager = nullptr; + } + if (mTimelineManager) { + mTimelineManager->Disconnect(); + mTimelineManager = nullptr; + } + if (mRestyleManager) { + mRestyleManager->Disconnect(); + mRestyleManager = nullptr; + } + if (mRefreshDriver && mRefreshDriver->GetPresContext() == this) { + mRefreshDriver->Disconnect(); + // Can't null out the refresh driver here. + } +} + +struct QueryContainerState { + nsSize mSize; + WritingMode mWm; + + nscoord GetInlineSize() const { return LogicalSize(mWm, mSize).ISize(mWm); } + + bool Changed(const QueryContainerState& aNewState, StyleContainerType aType) { + switch (aType) { + case StyleContainerType::Normal: + break; + case StyleContainerType::Size: + return mSize != aNewState.mSize; + case StyleContainerType::InlineSize: + return GetInlineSize() != aNewState.GetInlineSize(); + } + return false; + } +}; +NS_DECLARE_FRAME_PROPERTY_DELETABLE(ContainerState, QueryContainerState); + +void nsPresContext::RegisterContainerQueryFrame(nsIFrame* aFrame) { + mContainerQueryFrames.Add(aFrame); +} + +void nsPresContext::UnregisterContainerQueryFrame(nsIFrame* aFrame) { + mContainerQueryFrames.Remove(aFrame); +} + +void nsPresContext::FinishedContainerQueryUpdate() { + mUpdatedContainerQueryContents.Clear(); +} + +bool nsPresContext::UpdateContainerQueryStyles() { + if (mContainerQueryFrames.IsEmpty()) { + return false; + } + + AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Container Query Styles Update", LAYOUT); + AUTO_PROFILER_MARKER_TEXT("UpdateContainerQueryStyles", LAYOUT, {}, ""_ns); + + PresShell()->DoFlushLayout(/* aInterruptible = */ false); + + AutoTArray framesToUpdate; + + bool anyChanged = false; + for (nsIFrame* frame : mContainerQueryFrames.IterFromShallowest()) { + MOZ_ASSERT(frame->IsPrimaryFrame()); + + auto type = frame->StyleDisplay()->mContainerType; + MOZ_ASSERT(type != StyleContainerType::Normal, + "Non-container frames shouldn't be in this type"); + + const QueryContainerState newState{frame->GetSize(), + frame->GetWritingMode()}; + QueryContainerState* oldState = frame->GetProperty(ContainerState()); + + const bool changed = !oldState || oldState->Changed(newState, type); + + // Make sure to update the state regardless. It's cheap and it keeps tracks + // of both axes correctly even if only one axis is contained. + if (oldState) { + *oldState = newState; + } else { + frame->SetProperty(ContainerState(), new QueryContainerState(newState)); + } + + if (!changed) { + continue; + } + + const bool updatingAncestor = [&] { + for (nsIFrame* f : framesToUpdate) { + if (nsLayoutUtils::IsProperAncestorFrame(f, frame)) { + return true; + } + } + return false; + }(); + + if (updatingAncestor) { + // We're going to update an ancestor container of this frame already, + // avoid updating this one too until all our ancestor containers are + // updated. + continue; + } + + // To prevent unstable layout, only update once per-element per-flush. + if (NS_WARN_IF(!mUpdatedContainerQueryContents.EnsureInserted( + frame->GetContent()))) { + continue; + } + + framesToUpdate.AppendElement(frame); + + // TODO(emilio): More fine-grained invalidation rather than invalidating the + // whole subtree, probably! + RestyleManager()->PostRestyleEvent(frame->GetContent()->AsElement(), + RestyleHint::RestyleSubtree(), + nsChangeHint(0)); + anyChanged = true; + } + return anyChanged; +} + +void nsPresContext::DocumentCharSetChanged(NotNull aCharSet) { + UpdateCharSet(aCharSet); + FlushFontCache(); + + // If a document contains one or more