/* -*- 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 "nsIDocumentViewer.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 "COLRFonts.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/PerformanceMainThread.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(); if (!mPresShell) { // RebuildAllStyleData won't do anything without mPresShell. return; } 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), mDynamicToolbarMaxHeight(0), mDynamicToolbarHeight(0), mPageSize(-1, -1), mPageScale(0.0), mPPScale(1.0f), mViewportScrollOverrideElement(nullptr), mElementsRestyled(0), mFramesConstructed(0), mFramesReflowed(0), mAnimationTriggeredRestyles(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), 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), 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), mNeedsToUpdateHiddenByContentVisibilityForAnimations(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.visited_color", "dom.meta-viewport.enabled", "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; } PreferenceSheet::EnsureInitialized(); 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(); SET_BIDI_OPTION_DIRECTION(bidiOptions, StaticPrefs::bidi_direction()); 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) { if (!mPresShell) { return; } 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(); 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")) { 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; } 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(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; /* * Expected behavior in order of precedence: * 1 Chrome Rules give User Level (3) * 2 RFP gives Highest Level (1 aka Base) * 3 An RFPTarget of Base gives Base Level (1) * 4 An RFPTarget of LangPack gives LangPack Level (2) * 5 The value of the Standard Font Visibility Pref * * If the ETP toggle is disabled (aka * ContentBlockingAllowList::Check is true), it will only override 3-5, * not rules 1 or 2. */ // Rule 1: Allow all font access for privileged contexts, including // chrome and devtools contexts. if (Document()->ChromeRulesEnabled()) { mFontVisibility = FontVisibility::User; return mFontVisibility != oldValue; } // Is this a private browsing context? bool isPrivate = false; if (nsCOMPtr loadContext = mDocument->GetLoadContext()) { isPrivate = loadContext->UsePrivateBrowsing(); } int32_t level; // Rule 3 if (mDocument->ShouldResistFingerprinting( RFPTarget::FontVisibilityBaseSystem)) { // Rule 2: Check RFP pref // This is inside Rule 3 in case this document is exempted from RFP. // But if it is not exempted, and RFP is enabled, we return immediately // to prevent the override below from occurring. if (nsRFPService::IsRFPPrefEnabled(isPrivate)) { mFontVisibility = FontVisibility::Base; return mFontVisibility != oldValue; } level = int32_t(FontVisibility::Base); } // Rule 4 else if (mDocument->ShouldResistFingerprinting( RFPTarget::FontVisibilityLangPack)) { level = int32_t(FontVisibility::LangPack); } // Rule 5 else { level = StaticPrefs::layout_css_font_visibility(); } // Override Rules 3-5 Only: Determine if the user has exempted the // domain from tracking protections, if so, use the default value. if (level != StaticPrefs::layout_css_font_visibility() && ContentBlockingAllowList::Check(mDocument->CookieJarSettings())) { level = StaticPrefs::layout_css_font_visibility(); } // 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: 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; StyleContainerType mType; nscoord GetInlineSize() const { return LogicalSize(mWm, mSize).ISize(mWm); } bool Changed(const QueryContainerState& aNewState) { if (mType != aNewState.mType) { return true; } switch (mType) { 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 set"); const QueryContainerState newState{frame->GetSize(), frame->GetWritingMode(), type}; QueryContainerState* oldState = frame->GetProperty(ContainerState()); const bool changed = !oldState || oldState->Changed(newState); // 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