/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// RangePaintInfo is used to paint ranges to offscreen buffers struct RangePaintInfo {
nsDisplayListBuilder mBuilder;
nsDisplayList mList;
// offset of builder's reference frame to the root frame
nsPoint mRootOffset;
// Resolution at which the items are normally painted. So if we're painting // these items in a range separately from the "full display list", we may want // to paint them at this resolution. float mResolution = 1.0;
// The upper bound on the amount of time to spend reflowing, in // microseconds. When this bound is exceeded and reflow commands are // still queued up, a reflow event is posted. The idea is for reflow // to not hog the processor beyond the time specifed in // gMaxRCProcessingTime. This data member is initialized from the // layout.reflow.timeslice pref. #define NS_MAX_REFLOW_TIME 1000000 static int32_t gMaxRCProcessingTime = -1;
class nsAutoCauseReflowNotifier { public:
MOZ_CAN_RUN_SCRIPT explicit nsAutoCauseReflowNotifier(PresShell* aPresShell)
: mPresShell(aPresShell) {
mPresShell->WillCauseReflow();
}
MOZ_CAN_RUN_SCRIPT ~nsAutoCauseReflowNotifier() { // This check should not be needed. Currently the only place that seem // to need it is the code that deals with bug 337586. if (!mPresShell->mHaveShutDown) {
RefPtr<PresShell> presShell(mPresShell);
presShell->DidCauseReflow();
} else {
nsContentUtils::RemoveScriptBlocker();
}
}
PresShell* mPresShell;
};
class MOZ_STACK_CLASS nsPresShellEventCB : public EventDispatchingCallback { public: explicit nsPresShellEventCB(PresShell* aPresShell) : mPresShell(aPresShell) {}
MOZ_CAN_RUN_SCRIPT virtualvoid HandleEvent(EventChainPostVisitor& aVisitor) override { if (aVisitor.mPresContext && aVisitor.mEvent->mClass != eBasicEventClass) { if (aVisitor.mEvent->mMessage == eMouseDown ||
aVisitor.mEvent->mMessage == eMouseUp) { // Mouse-up and mouse-down events call nsIFrame::HandlePress/Release // which call GetContentOffsetsFromPoint which requires up-to-date // layout. Bring layout up-to-date now so that GetCurrentEventFrame() // below will return a real frame and we don't have to worry about // destroying it by flushing later.
MOZ_KnownLive(mPresShell)->FlushPendingNotifications(FlushType::Layout);
} elseif (aVisitor.mEvent->mMessage == eWheel &&
aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault) {
nsIFrame* frame = mPresShell->GetCurrentEventFrame(); if (frame) { // chrome (including addons) should be able to know if content // handles both D3E "wheel" event and legacy mouse scroll events. // We should dispatch legacy mouse events before dispatching the // "wheel" event into system group.
RefPtr<EventStateManager> esm =
aVisitor.mPresContext->EventStateManager();
esm->DispatchLegacyMouseScrollEvents(
frame, aVisitor.mEvent->AsWheelEvent(), &aVisitor.mEventStatus);
}
}
nsIFrame* frame = mPresShell->GetCurrentEventFrame(); if (!frame && (aVisitor.mEvent->mMessage == eMouseUp ||
aVisitor.mEvent->mMessage == eTouchEnd)) { // Redirect BUTTON_UP and TOUCH_END events to the root frame to ensure // that capturing is released.
frame = mPresShell->GetRootFrame();
} if (frame) {
frame->HandleEvent(aVisitor.mPresContext, aVisitor.mEvent->AsGUIEvent(),
&aVisitor.mEventStatus);
}
}
}
RefPtr<PresShell> mPresShell;
};
class nsBeforeFirstPaintDispatcher : public Runnable { public: explicit nsBeforeFirstPaintDispatcher(Document* aDocument)
: mozilla::Runnable("nsBeforeFirstPaintDispatcher"),
mDocument(aDocument) {}
// Fires the "before-first-paint" event so that interested parties (right now, // the mobile browser) are aware of it.
NS_IMETHOD Run() override {
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService(); if (observerService) {
observerService->NotifyObservers(ToSupports(mDocument), "before-first-paint", nullptr);
} return NS_OK;
}
private:
RefPtr<Document> mDocument;
};
// This is a helper class to track whether the targeted frame is destroyed after // dispatching pointer events. In that case, we need the original targeted // content so that we can dispatch the mouse events to it. class MOZ_RAII AutoPointerEventTargetUpdater final { public:
AutoPointerEventTargetUpdater(PresShell* aShell, WidgetEvent* aEvent,
nsIFrame* aFrame, nsIContent* aTargetContent,
nsIContent** aOutTargetContent) {
MOZ_ASSERT(aEvent); if (!aOutTargetContent || aEvent->mClass != ePointerEventClass) { // Make the destructor happy.
mOutTargetContent = nullptr; return;
}
MOZ_ASSERT(aShell);
MOZ_ASSERT_IF(aFrame && aFrame->GetContent(),
aShell->GetDocument() == aFrame->GetContent()->OwnerDoc());
mShell = aShell;
mWeakFrame = aFrame;
mOutTargetContent = aOutTargetContent;
mFromTouch = aEvent->AsPointerEvent()->mFromTouchEvent; // Touch event target may have no frame, e.g., removed from the DOM
MOZ_ASSERT_IF(!mFromTouch, aFrame); // The frame may be a text frame, but the event target should be an element // node. Therefore, refer aTargetContent first, then, if we have only a // frame, we should use inclusive ancestor of the content.
mOriginalPointerEventTarget = [&]() -> nsIContent* {
nsIContent* const target =
aTargetContent ? aTargetContent
: (aFrame ? aFrame->GetContent() : nullptr); if (MOZ_UNLIKELY(!target)) { return nullptr;
} if (target->IsElement() ||
!IsForbiddenDispatchingToNonElementContent(aEvent->mMessage)) { return target;
} return target->GetInclusiveFlattenedTreeAncestorElement();
}(); if (mOriginalPointerEventTarget &&
mOriginalPointerEventTarget->IsInComposedDoc()) {
mPointerEventTargetTracker.emplace(*mOriginalPointerEventTarget);
}
}
~AutoPointerEventTargetUpdater() { if (!mOutTargetContent || !mShell || mWeakFrame.IsAlive()) { return;
} if (mFromTouch) { // If the source event is a touch event, the touch event target should // always be same target as preceding ePointerDown. Therefore, we should // always set it back to the original event target.
mOriginalPointerEventTarget.swap(*mOutTargetContent);
} else { // If the source event is not a touch event (must be a mouse event in // this case), the event should be fired on the closest inclusive ancestor // of the pointer event target which is still connected. The mutations // are tracked by PresShell::ContentRemoved. Therefore, we should set it. if (!mPointerEventTargetTracker ||
!mPointerEventTargetTracker->ContentWasRemoved()) {
mOriginalPointerEventTarget.swap(*mOutTargetContent);
} else {
nsCOMPtr<nsIContent> connectedAncestor =
mPointerEventTargetTracker->GetConnectedContent();
connectedAncestor.swap(*mOutTargetContent);
}
}
}
/* static */ bool PresShell::AccessibleCaretEnabled(nsIDocShell* aDocShell) { // If the pref forces it on, then enable it. if (StaticPrefs::layout_accessiblecaret_enabled()) { returntrue;
} // If the touch pref is on, and touch events are enabled (this depends // on the specific device running), then enable it. if (StaticPrefs::layout_accessiblecaret_enabled_on_touch() &&
dom::TouchEvent::PrefEnabled(aDocShell)) { returntrue;
} // Otherwise, disabled. returnfalse;
}
NS_INTERFACE_TABLE_HEAD(PresShell)
NS_INTERFACE_TABLE_BEGIN // In most cases, PresShell should be treated as concrete class, but need to // QI for weak reference. Therefore, the case needed by do_QueryReferent() // should be tested first.
NS_INTERFACE_TABLE_ENTRY(PresShell, PresShell)
NS_INTERFACE_TABLE_ENTRY(PresShell, nsIDocumentObserver)
NS_INTERFACE_TABLE_ENTRY(PresShell, nsISelectionController)
NS_INTERFACE_TABLE_ENTRY(PresShell, nsISelectionDisplay)
NS_INTERFACE_TABLE_ENTRY(PresShell, nsIObserver)
NS_INTERFACE_TABLE_ENTRY(PresShell, nsISupportsWeakReference)
NS_INTERFACE_TABLE_ENTRY(PresShell, nsIMutationObserver)
NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(PresShell, nsISupports, nsIObserver)
NS_INTERFACE_TABLE_END
NS_INTERFACE_TABLE_TO_MAP_SEGUE
NS_INTERFACE_MAP_END
PresShell::~PresShell() {
MOZ_RELEASE_ASSERT(!mForbiddenToFlush, "Flag should only be set temporarily, while doing things " "that shouldn't cause destruction");
MOZ_LOG(gLog, LogLevel::Debug, ("PresShell::~PresShell this=%p", this));
if (!mHaveShutDown) {
MOZ_ASSERT_UNREACHABLE("Someone did not call PresShell::Destroy()");
Destroy();
}
NS_ASSERTION(mCurrentEventTargetStack.IsEmpty(), "Huh, event content left on the stack in pres shell dtor!");
NS_ASSERTION(mFirstCallbackEventRequest == nullptr &&
mLastCallbackEventRequest == nullptr, "post-reflow queues not empty. This means we're leaking");
MOZ_ASSERT(!mAllocatedPointers || mAllocatedPointers->IsEmpty(), "Some pres arena objects were not freed");
// mDocument is now set. It might have a display document whose "need layout/ // style" flush flags are not set, but ours will be set. To keep these // consistent, call the flag setting functions to propagate those flags up // to the display document.
SetNeedLayoutFlush();
SetNeedStyleFlush();
// Bind the context to the presentation shell. // FYI: We cannot initialize mPresContext in the constructor because we // cannot call AttachPresShell() in it and once we initialize // mPresContext, other objects may refer refresh driver or restyle // manager via mPresContext and that causes hitting MOZ_ASSERT in some // places. Therefore, we should initialize mPresContext here with // const_cast hack since we want to guarantee that mPresContext lives // as long as the PresShell. const_cast<RefPtr<nsPresContext>&>(mPresContext) = aPresContext;
mPresContext->AttachPresShell(this);
mPresContext->InitFontCache();
// FIXME(emilio, bug 1544185): Some Android code somehow depends on the shell // being eagerly registered as a style flush observer. This shouldn't be // needed otherwise.
EnsureStyleFlush();
constbool accessibleCaretEnabled =
AccessibleCaretEnabled(mDocument->GetDocShell()); if (accessibleCaretEnabled) { // Need to happen before nsFrameSelection has been set up.
mAccessibleCaretEventHub = MakeRefPtr<AccessibleCaretEventHub>(this);
mAccessibleCaretEventHub->Init();
}
// Important: this has to happen after the selection has been set up #ifdef SHOW_CARET // make the caret
mCaret = MakeRefPtr<nsCaret>();
mCaret->Init(this);
mOriginalCaret = mCaret;
// SetCaretEnabled(true); // make it show in browser windows #endif // set up selection to be displayed in document // Don't enable selection for print media
nsPresContext::nsPresContextType type = mPresContext->Type(); if (type != nsPresContext::eContext_PrintPreview &&
type != nsPresContext::eContext_Print) {
SetDisplaySelection(nsISelectionController::SELECTION_DISABLED);
}
if (gMaxRCProcessingTime == -1) {
gMaxRCProcessingTime =
Preferences::GetInt("layout.reflow.timeslice", NS_MAX_REFLOW_TIME);
}
if (nsStyleSheetService* ss = nsStyleSheetService::GetInstance()) {
ss->RegisterPresShell(this);
}
// Get our activeness from the docShell.
ActivenessMaybeChanged();
// Setup our font inflation preferences.
mFontSizeInflationEmPerLine = StaticPrefs::font_size_inflation_emPerLine();
mFontSizeInflationMinTwips = StaticPrefs::font_size_inflation_minTwips();
mFontSizeInflationLineThreshold =
StaticPrefs::font_size_inflation_lineThreshold();
mFontSizeInflationForceEnabled =
StaticPrefs::font_size_inflation_forceEnabled(); // We'll compute the font size inflation state in Initialize(), when we know // the document type.
mTouchManager.Init(this, mDocument);
if (mPresContext->IsRootContentDocumentCrossProcess()) {
mZoomConstraintsClient = MakeRefPtr<ZoomConstraintsClient>();
mZoomConstraintsClient->Init(this, mDocument);
// We call this to create mMobileViewportManager, if it is needed.
MaybeRecreateMobileViewportManager(false);
}
if (nsCOMPtr<nsIDocShell> docShell = mPresContext->GetDocShell()) { if (BrowsingContext* bc = docShell->GetBrowsingContext()) {
mUnderHiddenEmbedderElement = bc->IsUnderHiddenEmbedderElement();
}
}
}
void PresShell::Destroy() { // Do not add code before this line please! if (mHaveShutDown) { return;
}
NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(), "destroy called on presshell while scripts not blocked");
nsIURI* uri = mDocument->GetDocumentURI();
AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_RELEVANT_FOR_JS( "Layout tree destruction", LAYOUT_Destroy,
uri ? uri->GetSpecOrDefault() : "N/A"_ns);
// Try to determine if the page is the user had a meaningful opportunity to // zoom this page. This is not 100% accurate but should be "good enough" for // telemetry purposes. auto isUserZoomablePage = [&]() -> bool { if (mIsFirstPaint) { // Page was never painted, so it wasn't zoomable by the user. We get a // handful of these "transient" presShells. returnfalse;
} if (!mPresContext->IsRootContentDocumentCrossProcess()) { // Not a root content document, so APZ doesn't support zooming it. returnfalse;
} if (InRDMPane()) { // Responsive design mode is a special case that we want to ignore here. returnfalse;
} if (mDocument && mDocument->IsInitialDocument()) { // Ignore initial about:blank page loads returnfalse;
} if (XRE_IsContentProcess() &&
IsExtensionRemoteType(ContentChild::GetSingleton()->GetRemoteType())) { // Also omit presShells from the extension process because they sometimes // can't be zoomed by the user. returnfalse;
} // Otherwise assume the page is user-zoomable. returntrue;
}; if (isUserZoomablePage()) {
glean::apz_zoom::activity
.EnumGet(static_cast<glean::apz_zoom::ActivityLabel>(
IsResolutionUpdatedByApz()))
.Add();
}
// dump out cumulative text perf metrics
gfxTextPerfMetrics* tp; if (mPresContext && (tp = mPresContext->GetTextPerfMetrics())) {
tp->Accumulate(); if (tp->cumulative.numChars > 0) {
LogTextPerfStats(tp, this, tp->cumulative, 0.0, eLog_totals, nullptr);
}
} if (mPresContext) { if (gfxUserFontSet* fs = mPresContext->GetUserFontSet()) {
uint32_t fontCount;
uint64_t fontSize;
fs->GetLoadStatistics(fontCount, fontSize);
glean::webfont::per_page.Add(fontCount);
glean::webfont::size_per_page.Accumulate(uint32_t(fontSize / 1024));
} else {
glean::webfont::per_page.Add(0);
glean::webfont::size_per_page.Accumulate(0);
}
}
if (mZoomConstraintsClient) {
mZoomConstraintsClient->Destroy();
mZoomConstraintsClient = nullptr;
} if (mMobileViewportManager) {
mMobileViewportManager->Destroy();
mMobileViewportManager = nullptr;
mMVMContext = nullptr;
}
MaybeReleaseCapturingContent();
EventHandler::OnPresShellDestroy(mDocument);
if (mContentToScrollTo) {
mContentToScrollTo->RemoveProperty(nsGkAtoms::scrolling);
mContentToScrollTo = nullptr;
}
if (mPresContext) { // We need to notify the destroying the nsPresContext to ESM for // suppressing to use from ESM.
mPresContext->EventStateManager()->NotifyDestroyPresContext(mPresContext);
}
if (nsStyleSheetService* ss = nsStyleSheetService::GetInstance()) {
ss->UnregisterPresShell(this);
}
{
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); if (os) {
os->RemoveObserver(this, "memory-pressure");
os->RemoveObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC); if (XRE_IsParentProcess()) {
os->RemoveObserver(this, "sessionstore-one-or-no-tab-restored");
}
os->RemoveObserver(this, "font-info-updated");
os->RemoveObserver(this, "internal-look-and-feel-changed");
}
}
// If our paint suppression timer is still active, kill it.
CancelPaintSuppressionTimer();
// We can't release all the event content in // mCurrentEventContentStack here since there might be code on the // stack that will release the event content too. Double release // bad!
// The frames will be torn down, so remove them from the current // event frame stack (since they'd be dangling references if we'd // leave them in) and null out the mCurrentEventFrame pointer as // well.
mCurrentEventTarget.ClearFrame();
for (EventTargetInfo& eventTargetInfo : mCurrentEventTargetStack) {
eventTargetInfo.ClearFrame();
}
// This shell must be removed from the document before the frame // hierarchy is torn down to avoid finding deleted frames through // this presshell while the frames are being torn down if (mDocument) {
NS_ASSERTION(mDocument->GetPresShell() == this, "Wrong shell?");
mDocument->ClearServoRestyleRoot();
mDocument->DeletePresShell();
}
if (mPresContext) {
mPresContext->AnimationEventDispatcher()->ClearEventQueue();
}
// Revoke any pending events. We need to do this and cancel pending reflows // before we destroy the frame constructor, since apparently frame destruction // sometimes spins the event queue when plug-ins are involved(!). // XXXmats is this still needed now that plugins are gone?
CancelAllPendingReflows();
CancelPostedReflowCallbacks();
// Destroy the frame constructor. This will destroy the frame hierarchy
mFrameConstructor->WillDestroyFrameTree();
NS_WARNING_ASSERTION(!mAutoWeakFrames && mWeakFrames.IsEmpty(), "Weak frames alive after destroying FrameManager"); while (mAutoWeakFrames) {
mAutoWeakFrames->Clear(this);
} const nsTArray<WeakFrame*> weakFrames = ToArray(mWeakFrames); for (WeakFrame* weakFrame : weakFrames) {
weakFrame->Clear(this);
}
// Clear the embedding frame only after tearing down the frame tree, since we // rely on reaching the display root frame from frame destruction. if (nsSubDocumentFrame* f = GetInProcessEmbedderFrame()) {
f->RemoveEmbeddingPresShell(this);
}
mEmbedderFrame = nullptr;
// Terminate AccessibleCaretEventHub after tearing down the frame tree so that // we don't need to remove caret element's frame in // AccessibleCaret::RemoveCaretElement(). if (mAccessibleCaretEventHub) {
mAccessibleCaretEventHub->Terminate();
mAccessibleCaretEventHub = nullptr;
}
mWidgetListener = nullptr;
if (mPresContext) { // We hold a reference to the pres context, and it holds a weak link back // to us. To avoid the pres context having a dangling reference, set its // pres shell to nullptr
mPresContext->DetachPresShell();
}
// NOTE(emilio): It'd be ideal if instead of this explicit tracking we could // rely on mDocument->GetEmbedderElement()->GetPrimaryFrame() + relevant // null-checks. However, given how things are set up now, the embedder element // in BrowsingContext / Window get cleared before tearing down the pres shell, // and RDL relies on getting ahold of it to get the display root. void PresShell::SetInProcessEmbedderFrame(nsSubDocumentFrame* aFrame) {
mEmbedderFrame = aFrame;
}
void PresShell::AddUserSheet(StyleSheet* aSheet) { // Make sure this does what nsDocumentViewer::CreateStyleSet does wrt // ordering. We want this new sheet to come after all the existing stylesheet // service sheets (which are at the start), but before other user sheets; see // nsIStyleSheetService.idl for the ordering.
// Search for the place to insert the new user sheet. Since all of the // stylesheet service provided user sheets should be at the start of the style // set's list, and aSheet should be at the end of userSheets. Given that, we // can find the right place to insert the new sheet based on the length of // userSheets.
MOZ_ASSERT(aSheet);
MOZ_ASSERT(userSheets.LastElement() == aSheet);
size_t index = userSheets.Length() - 1;
// Assert that all of userSheets (except for the last, new element) matches up // with what's in the style set. for (size_t i = 0; i < index; ++i) {
MOZ_ASSERT(StyleSet()->SheetAt(StyleOrigin::User, i) == userSheets[i]);
}
void PresShell::AddAgentSheet(StyleSheet* aSheet) { // Make sure this does what nsDocumentViewer::CreateStyleSet does // wrt ordering.
StyleSet()->AppendStyleSheet(*aSheet);
mDocument->ApplicableStylesChanged();
}
void PresShell::AddAuthorSheet(StyleSheet* aSheet) { // Document specific "additional" Author sheets should be stronger than the // ones added with the StyleSheetService.
StyleSheet* firstAuthorSheet = mDocument->GetFirstAdditionalAuthorSheet(); if (firstAuthorSheet) {
StyleSet()->InsertStyleSheetBefore(*aSheet, *firstAuthorSheet);
} else {
StyleSet()->AppendStyleSheet(*aSheet);
}
mDocument->ApplicableStylesChanged();
}
bool PresShell::NeedsFocusFixUp() const { if (NS_WARN_IF(!mDocument)) { returnfalse;
}
nsIContent* currentFocus = mDocument->GetUnretargetedFocusedContent(
Document::IncludeChromeOnly::Yes); if (!currentFocus) { returnfalse;
}
// If focus target is an area element with one or more shapes that are // focusable areas. if (auto* area = HTMLAreaElement::FromNode(currentFocus)) { if (nsFocusManager::IsAreaElementFocusable(*area)) { returnfalse;
}
}
nsIFrame* f = currentFocus->GetPrimaryFrame(); if (f && f->IsFocusable()) { returnfalse;
}
bool PresShell::FixUpFocus() { if (!NeedsFocusFixUp()) { returnfalse;
}
RefPtr fm = nsFocusManager::GetFocusManager();
nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow(); if (NS_WARN_IF(!window)) { returnfalse;
} if (auto* element = fm->GetFocusedElement()) { // Set focus navigation starting point, so that focus navigation still // starts from this element.
element->OwnerDoc()->SetFocusNavigationStartingPoint(element);
}
fm->ClearFocus(window); returntrue;
}
void PresShell::SelectionWillTakeFocus() { if (mSelection) {
FrameSelectionWillTakeFocus(*mSelection,
CanMoveLastSelectionForToString::No);
}
}
void PresShell::SelectionWillLoseFocus() { // Do nothing, the main selection is the default focused selection.
}
// Selection repainting code relies on selection offsets being properly // adjusted (see bug 1626291), so we need to wait until the DOM is finished // notifying. staticvoid RepaintNormalSelectionWhenSafe(nsFrameSelection& aFrameSelection) { if (nsContentUtils::IsSafeToRunScript()) {
aFrameSelection.RepaintSelection(SelectionType::eNormal); return;
}
// Note that importantly we don't defer changing the DisplaySelection. That'd // be potentially racy with other code that may change it.
nsContentUtils::AddScriptRunner(NS_NewRunnableFunction( "RepaintNormalSelectionWhenSafe",
[sel = RefPtr<nsFrameSelection>(&aFrameSelection)] {
sel->RepaintSelection(SelectionType::eNormal);
}));
}
// Do nothing, the main selection is the default focused selection. if (&aFrameSelection == mSelection) { return;
}
RefPtr<nsFrameSelection> old = std::move(mFocusedFrameSelection);
MOZ_ASSERT(!mFocusedFrameSelection);
if (old->GetDisplaySelection() != nsISelectionController::SELECTION_HIDDEN) {
old->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN);
RepaintNormalSelectionWhenSafe(*old);
}
if (mSelection) {
FrameSelectionWillTakeFocus(*mSelection,
CanMoveLastSelectionForToString::No);
}
}
void PresShell::FrameSelectionWillTakeFocus(
nsFrameSelection& aFrameSelection,
CanMoveLastSelectionForToString aCanMoveLastSelectionForToString) { if (StaticPrefs::dom_selection_mimic_chrome_tostring_enabled()) { if (aCanMoveLastSelectionForToString ==
CanMoveLastSelectionForToString::Yes) {
UpdateLastSelectionForToString(&aFrameSelection);
}
} if (mFocusedFrameSelection == &aFrameSelection) { #ifdef XP_MACOSX // FIXME: Mac needs to update the global selection cache, even if the // document's focused selection doesn't change, and this is currently done // from RepaintSelection. Maybe we should move part of the global selection // handling here, or something of that sort, unclear.
RepaintNormalSelectionWhenSafe(aFrameSelection); #endif return;
}
RefPtr<nsFrameSelection> old = std::move(mFocusedFrameSelection);
mFocusedFrameSelection = &aFrameSelection;
if (old &&
old->GetDisplaySelection() != nsISelectionController::SELECTION_HIDDEN) {
old->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN);
RepaintNormalSelectionWhenSafe(*old);
}
if (aFrameSelection.GetDisplaySelection() !=
nsISelectionController::SELECTION_ON) {
aFrameSelection.SetDisplaySelection(nsISelectionController::SELECTION_ON);
RepaintNormalSelectionWhenSafe(aFrameSelection);
}
}
// Make shell be a document observer void PresShell::BeginObservingDocument() { if (mDocument && !mIsDestroying) {
mIsObservingDocument = true; if (mIsDocumentGone) {
NS_WARNING( "Adding a presshell that was disconnected from the document " "as a document observer? Sounds wrong...");
mIsDocumentGone = false;
}
}
}
// Make shell stop being a document observer void PresShell::EndObservingDocument() { // XXXbz do we need to tell the frame constructor that the document // is gone, perhaps? Except for printing it's NOT gone, sometimes.
mIsDocumentGone = true;
mIsObservingDocument = false;
}
// Ensure the pres context doesn't think it has changed, since we haven't even // started layout. This avoids spurious restyles / reflows afterwards. // // Note that this is very intentionally before setting mDidInitialize so it // doesn't notify the document, or run media query change events.
mPresContext->FlushPendingMediaFeatureValuesChanged();
MOZ_DIAGNOSTIC_ASSERT(!mIsDestroying);
mDidInitialize = true;
// Get the root frame from the frame constructor. // XXXbz it would be nice to move this somewhere else... like frame manager // Init(), say. But we need to make sure our views are all set up by the // time we do this!
MOZ_ASSERT(!mFrameConstructor->GetRootFrame(), "How did that happen, exactly?");
ViewportFrame* rootFrame;
{
nsAutoScriptBlocker scriptBlocker;
rootFrame = mFrameConstructor->ConstructRootFrame();
mFrameConstructor->SetRootFrame(rootFrame);
}
NS_ENSURE_STATE(!mHaveShutDown);
if (!rootFrame) { return NS_ERROR_OUT_OF_MEMORY;
}
if (Element* root = mDocument->GetRootElement()) {
{
nsAutoCauseReflowNotifier reflowNotifier(this); // Have the style sheet processor construct frame for the root // content object down
mFrameConstructor->ContentInserted(
root, nsCSSFrameConstructor::InsertionKind::Sync);
} // Something in mFrameConstructor->ContentInserted may have caused // Destroy() to get called, bug 337586. Or, nsAutoCauseReflowNotifier // (which sets up a script blocker) going out of scope may have killed us // too
NS_ENSURE_STATE(!mHaveShutDown);
}
mDocument->MaybeScheduleRendering();
NS_ASSERTION(rootFrame, "How did that happen?");
// Note: when the frame was created above it had the NS_FRAME_IS_DIRTY bit // set, but XBL processing could have caused a reflow which clears it. if (MOZ_LIKELY(rootFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY))) { // Unset the DIRTY bits so that FrameNeedsReflow() will work right.
rootFrame->RemoveStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN);
NS_ASSERTION(!mDirtyRoots.Contains(rootFrame), "Why is the root in mDirtyRoots already?");
FrameNeedsReflow(rootFrame, IntrinsicDirty::None, NS_FRAME_IS_DIRTY);
NS_ASSERTION(mDirtyRoots.Contains(rootFrame), "Should be in mDirtyRoots now");
NS_ASSERTION(mNeedStyleFlush || mNeedLayoutFlush, "Why no reflow scheduled?");
}
// Restore our root scroll position now if we're getting here after EndLoad // got called, since this is our one chance to do it. Note that we need not // have reflowed for this to work; when the scrollframe is finally reflowed // it'll pick up the position we store in it here. if (!mDocumentLoading) {
RestoreRootScrollPosition();
}
// For printing, we just immediately unsuppress. if (!mPresContext->IsPaginated()) { // Kick off a one-shot timer based off our pref value. When this timer // fires, if painting is still locked down, then we will go ahead and // trigger a full invalidate and allow painting to proceed normally.
mPaintingSuppressed = true; // Don't suppress painting if the document isn't loading. However, // the initial about:blank appears not to be loading, but we still // want to suppress painting.
nsIDocShell* docShell = mDocument->GetDocShell(); if ((docShell &&
!nsDocShell::Cast(docShell)
->HasStartedLoadingOtherThanInitialBlankURI() &&
mDocument->IsInitialDocument()) ||
mDocument->GetReadyStateEnum() != Document::READYSTATE_COMPLETE) {
mPaintSuppressionTimer = NS_NewTimer();
} if (!mPaintSuppressionTimer) {
mPaintingSuppressed = false;
} else { // Initialize the timer.
mPaintSuppressionTimer->SetTarget(GetMainThreadSerialEventTarget());
InitPaintSuppressionTimer(); if (mHasTriedFastUnsuppress) { // Someone tried to unsuppress painting before Initialize was called so // unsuppress painting rather soon.
mHasTriedFastUnsuppress = false;
TryUnsuppressPaintingSoon();
MOZ_ASSERT(mHasTriedFastUnsuppress);
}
}
}
// If we get here and painting is not suppressed, we still want to run the // unsuppression logic, so set mShouldUnsuppressPainting to true. if (!mPaintingSuppressed) {
mShouldUnsuppressPainting = true;
}
if (!mDidInitialize || !IsPaintingSuppressed() || !XRE_IsContentProcess()) { return;
}
if (!mDocument->IsInitialDocument() &&
mDocument->DidHitCompleteSheetCache() &&
mPresContext->IsRootContentDocumentCrossProcess()) { // Try to unsuppress faster on a top level page if it uses stylesheet // cache, since that hints that many resources can be painted sooner than // in a cold page load case.
NS_DispatchToCurrentThreadQueue(
NS_NewRunnableFunction("PresShell::TryUnsuppressPaintingSoon",
[self = RefPtr{this}]() -> void { if (self->IsPaintingSuppressed()) {
PROFILER_MARKER_UNTYPED( "Fast paint unsuppression", GRAPHICS);
self->UnsuppressPainting();
}
}),
EventQueuePriority::Control);
}
}
void PresShell::RefreshZoomConstraintsForScreenSizeChange() { if (mZoomConstraintsClient) {
mZoomConstraintsClient->ScreenSizeChanged();
}
}
void PresShell::ResizeReflow(const nsSize& aSize,
ResizeReflowOptions aOptions) { if (mZoomConstraintsClient) { // If we have a ZoomConstraintsClient and the available screen area // changed, then we might need to disable double-tap-to-zoom, so notify // the ZCC to update itself.
mZoomConstraintsClient->ScreenSizeChanged();
} if (UsesMobileViewportSizing()) { // If we are using mobile viewport sizing, request a reflow from the MVM. // It can recompute the final CSS viewport and trigger a call to // ResizeReflowIgnoreOverride if it changed. We don't force adjusting // of resolution, because that is only necessary when we are destroying // the MVM.
MOZ_ASSERT(mMobileViewportManager);
mMobileViewportManager->RequestReflow(false); return;
}
ResizeReflowIgnoreOverride(aSize, aOptions);
}
// If we (or any descendant docs) have any content visibility: auto elements, // we also need to run its proximity to the viewport on scroll. Same for // intersection observers. // // We don't need to mark ourselves as needing a layout flush. We don't need to // get flushed, we just need the viewport relevancy / content-visibility: auto // viewport proximity phases to run.
mPresContext->RefreshDriver()->ScheduleRenderingPhases(
{RenderingPhase::ScrollSteps, RenderingPhase::Layout,
RenderingPhase::UpdateIntersectionObservations});
}
bool PresShell::ResizeReflowIgnoreOverride(const nsSize& aSize,
ResizeReflowOptions aOptions) {
MOZ_ASSERT(!mIsReflowing, "Shouldn't be in reflow here!");
// Historically we never fired resize events if there was no root frame by the // time this function got called. constbool initialized = mDidInitialize;
RefPtr<PresShell> kungFuDeathGrip(this);
auto postResizeEventIfNeeded = [this, initialized]() { if (initialized) {
ScheduleResizeEventIfNeeded(ResizeEventKind::Regular);
}
};
// If there are orthogonal flows that were dependent on the ICB size, mark // them as dirty to ensure they will be reflowed. for (auto* frame : mOrthogonalFlows) {
FrameNeedsReflow(frame, IntrinsicDirty::None, NS_FRAME_HAS_DIRTY_CHILDREN);
}
mOrthogonalFlows.Clear();
if (!(aOptions & ResizeReflowOptions::BSizeLimit)) {
nsSize oldSize = mPresContext->GetVisibleArea().Size(); if (oldSize == aSize) { returnfalse;
}
// Make sure that style is flushed before setting the pres context // VisibleArea. // // Otherwise we may end up with bogus viewport units resolved against the // unconstrained bsize, or restyling the whole document resolving viewport // units against targetWidth, which may end up doing wasteful work.
mDocument->FlushPendingNotifications(FlushType::Frames);
nsIFrame* rootFrame = GetRootFrame(); if (mIsDestroying || !rootFrame) { // If we don't have a root frame yet, that means we haven't had our initial // reflow... If that's the case, and aWidth or aHeight is unconstrained, // ignore them altogether. if (aSize.height == NS_UNCONSTRAINEDSIZE ||
aSize.width == NS_UNCONSTRAINEDSIZE) { // We can't do the work needed for SizeToContent without a root // frame, and we want to return before setting the visible area. returnfalse;
}
mPresContext->SetVisibleArea(nsRect(nsPoint(), aSize)); // There isn't anything useful we can do if the initial reflow hasn't // happened. returntrue;
}
mPresContext->SetVisibleArea(nsRect(nsPoint(), targetSize)); // XXX Do a full invalidate at the beginning so that invalidates along // the way don't have region accumulation issues?
// For height:auto BSizes (i.e. layout-controlled), descendant // intrinsic sizes can't depend on them. So the only other case is // viewport-controlled BSizes which we handle here.
nsLayoutUtils::MarkIntrinsicISizesDirtyIfDependentOnBSize(rootFrame);
rootFrame->SetHasBSizeChange(true);
FrameNeedsReflow(rootFrame, IntrinsicDirty::None,
NS_FRAME_HAS_DIRTY_CHILDREN);
// Now, we may have been destroyed by the destructor of // `nsAutoCauseReflowNotifier`.
mPendingDidDoReflow = true;
DidDoReflow(true);
// the reflow above should've set our bsize if it was NS_UNCONSTRAINEDSIZE, // and the isize shouldn't be NS_UNCONSTRAINEDSIZE anyway.
MOZ_DIAGNOSTIC_ASSERT(
mPresContext->GetVisibleArea().width != NS_UNCONSTRAINEDSIZE, "width should not be NS_UNCONSTRAINEDSIZE after reflow");
MOZ_DIAGNOSTIC_ASSERT(
mPresContext->GetVisibleArea().height != NS_UNCONSTRAINEDSIZE, "height should not be NS_UNCONSTRAINEDSIZE after reflow");
RefPtr window = nsGlobalWindowInner::Cast(mDocument->GetInnerWindow()); if (!window) { return;
}
if (mResizeEventPending) { // Clear it before firing, just in case the event triggers another resize // event. Such event will fire next tick.
mResizeEventPending = false;
WidgetEvent event(true, mozilla::eResize);
nsEventStatus status = nsEventStatus_eIgnore;
if (RefPtr<nsPIDOMWindowOuter> outer = window->GetOuterWindow()) { // MOZ_KnownLive due to bug 1506441
EventDispatcher::Dispatch(MOZ_KnownLive(nsGlobalWindowOuter::Cast(outer)),
mPresContext, &event, nullptr, &status);
}
}
// https://drafts.csswg.org/cssom-view/#document-run-the-scroll-steps // But note: https://github.com/w3c/csswg-drafts/issues/11164 void PresShell::RunScrollSteps() { // Scroll events are one-shot, so after running them we can drop them. // However, dispatching a scroll event can potentially cause more scroll // events to be posted, so we move the initial set into a temporary array // first. (Newly posted scroll events will be dispatched on the next tick.) auto events = std::move(mPendingScrollEvents); for (auto& event : events) {
event->Run();
}
}
void PresShell::SetIgnoreFrameDestruction(bool aIgnore) { if (mDocument) { // We need to tell the ImageLoader to drop all its references to frames // because they're about to go away and it won't get notifications of that.
mDocument->EnsureStyleImageLoader().ClearFrames(mPresContext);
}
mIgnoreFrameDestruction = aIgnore;
}
void PresShell::NotifyDestroyingFrame(nsIFrame* aFrame) { // We must remove these from FrameLayerBuilder::DisplayItemData::mFrameList // here, otherwise the DisplayItemData destructor will use the destroyed frame // when it tries to remove it from the (array) value of this property.
aFrame->RemoveDisplayItemDataForDeletion();
if (!mIgnoreFrameDestruction) { if (aFrame->HasImageRequest()) {
mDocument->EnsureStyleImageLoader().DropRequestsForFrame(aFrame);
}
if (aFrame == mCurrentEventTarget.mFrame) {
mCurrentEventTarget.UpdateFrameAndContent(
nullptr, ComputeTargetContent(mCurrentEventTarget));
}
for (EventTargetInfo& eventTargetInfo : mCurrentEventTargetStack) { if (aFrame == eventTargetInfo.mFrame) { // One of our stack frames was deleted. Get its content so that when we // pop it we can still get its new frame from its content
eventTargetInfo.UpdateFrameAndContent(
nullptr, ComputeTargetContent(eventTargetInfo));
}
}
NS_IMETHODIMP
PresShell::WordMove(bool aForward, bool aExtend) {
RefPtr<nsFrameSelection> frameSelection = mSelection;
nsresult result = frameSelection->WordMove(aForward, aExtend); // if we can't go down/up any more we must then move caret completely to // end/beginning respectively. if (NS_FAILED(result)) {
result = CompleteMove(aForward, aExtend);
} return result;
}
NS_IMETHODIMP
PresShell::LineMove(bool aForward, bool aExtend) {
RefPtr<nsFrameSelection> frameSelection = mSelection;
nsresult result = frameSelection->LineMove(aForward, aExtend); // if we can't go down/up any more we must then move caret completely to // end/beginning respectively. if (NS_FAILED(result)) {
result = CompleteMove(aForward, aExtend);
} return result;
}
NS_IMETHODIMP
PresShell::PageMove(bool aForward, bool aExtend) {
nsIFrame* frame = nullptr; if (!aExtend) {
frame = GetScrollContainerFrameToScroll(VerticalScrollDirection); // If there is no scrollable frame, get the frame to move caret instead.
} if (!frame || frame->PresContext() != mPresContext) {
frame = mSelection->GetFrameToPageSelect(); if (!frame) { return NS_OK;
}
} // We may scroll parent scrollable element of current selection limiter. // In such case, we don't want to scroll selection into view unless // selection is changed.
RefPtr<nsFrameSelection> frameSelection = mSelection; return frameSelection->PageMove(
aForward, aExtend, frame, nsFrameSelection::SelectionIntoView::IfChanged);
}
const nsFrameSelection::FocusMode focusMode =
aExtend ? nsFrameSelection::FocusMode::kExtendSelection
: nsFrameSelection::FocusMode::kCollapseToNewPoint;
frameSelection->HandleClick(
MOZ_KnownLive(pos->mResultContent) /* bug 1636889 */, pos->mContentOffset,
pos->mContentOffset, focusMode,
aForward ? CaretAssociationHint::After : CaretAssociationHint::Before); if (IsDestroying()) [[unlikely]] { return NS_OK;
} if (limiter && GetDocument() == limiter->GetComposedDoc()) { // HandleClick resets ancestorLimiter, so set it again.
frameSelection->SetAncestorLimiter(limiter);
}
// After ScrollSelectionIntoView(), the pending notifications might be // flushed and PresShell/PresContext/Frames may be dead. See bug 418470. return ScrollSelectionIntoView(SelectionType::eNormal,
nsISelectionController::SELECTION_FOCUS_REGION,
SelectionScrollMode::SyncFlush);
}
#ifdef DEBUG void PresShell::VerifyHasDirtyRootAncestor(nsIFrame* aFrame) { // XXXbz due to bug 372769, can't actually assert anything here... // XXX Since bug 372769 is now fixed, the assertion is being enabled in bug // 1758104. # if0 // XXXbz shouldn't need this part; remove it once FrameNeedsReflow // handles the root frame correctly. if (!aFrame->GetParent()) { return;
}
// Make sure that there is a reflow root ancestor of |aFrame| that's // in mDirtyRoots already. while (aFrame && aFrame->HasAnyStateBits(NS_FRAME_HAS_DIRTY_CHILDREN)) { if ((aFrame->HasAnyStateBits(NS_FRAME_REFLOW_ROOT |
NS_FRAME_DYNAMIC_REFLOW_ROOT) ||
!aFrame->GetParent()) &&
mDirtyRoots.Contains(aFrame)) { return;
}
aFrame = aFrame->GetParent();
}
MOZ_ASSERT_UNREACHABLE( "Frame has dirty bits set but isn't scheduled to be " "reflowed?"); # endif
} #endif
do {
nsIFrame* subtreeRoot = subtrees.PopLastElement();
// Grab |wasDirty| now so we can go ahead and update the bits on // subtreeRoot. bool wasDirty = subtreeRoot->IsSubtreeDirty();
subtreeRoot->AddStateBits(aBitToAdd);
// Determine whether we need to keep looking for the next ancestor // reflow root if subtreeRoot itself is a reflow root. bool targetNeedsReflowFromParent; switch (aRootHandling) { case ReflowRootHandling::PositionOrSizeChange:
targetNeedsReflowFromParent = true; break; case ReflowRootHandling::NoPositionOrSizeChange:
targetNeedsReflowFromParent = false; break; case ReflowRootHandling::InferFromBitToAdd:
targetNeedsReflowFromParent = (aBitToAdd == NS_FRAME_IS_DIRTY); break;
}
// Mark the intrinsic widths as dirty on the frame, all of its ancestors, // and all of its descendants, if needed:
if (aIntrinsicDirty != IntrinsicDirty::None) { // Mark argument and all ancestors dirty. (Unless we hit a reflow root // that should contain the reflow. for (nsIFrame* a = subtreeRoot;
a && !CanStopClearingAncestorIntrinsics(a); a = a->GetParent()) {
a->MarkIntrinsicISizesDirty(); if (a->IsAbsolutelyPositioned()) { // If we get here, 'a' is abspos, so its subtree's intrinsic sizing // has no effect on its ancestors' intrinsic sizing. So, don't loop // upwards any further. break;
}
}
}
constbool frameAncestorAndDescendantISizesDirty =
(aIntrinsicDirty == IntrinsicDirty::FrameAncestorsAndDescendants); constbool dirty = (aBitToAdd == NS_FRAME_IS_DIRTY); if (frameAncestorAndDescendantISizesDirty || dirty) { // Mark all descendants dirty (using an nsTArray stack rather than // recursion). // Note that ReflowInput::InitResizeFlags has some similar // code; see comments there for how and why it differs.
AutoTArray<nsIFrame*, 32> stack;
stack.AppendElement(subtreeRoot);
do {
nsIFrame* f = stack.PopLastElement();
if (frameAncestorAndDescendantISizesDirty && f->IsPlaceholderFrame()) { // Call `GetOutOfFlowFrame` directly because we can get here from // frame destruction and the placeholder might be already torn down. if (nsIFrame* oof = static_cast<nsPlaceholderFrame*>(f)->GetOutOfFlowFrame()) { if (!nsLayoutUtils::IsProperAncestorFrame(subtreeRoot, oof)) { // We have another distinct subtree we need to mark.
subtrees.AppendElement(oof);
}
}
}
for (constauto& childList : f->ChildLists()) { for (nsIFrame* kid : childList.mList) { if (frameAncestorAndDescendantISizesDirty) {
kid->MarkIntrinsicISizesDirty();
} if (dirty) {
kid->AddStateBits(NS_FRAME_IS_DIRTY);
}
stack.AppendElement(kid);
}
}
} while (stack.Length() != 0);
}
// Skip setting dirty bits up the tree if we weren't given a bit to add. if (!aBitToAdd) { continue;
}
// Set NS_FRAME_HAS_DIRTY_CHILDREN bits (via nsIFrame::ChildIsDirty) // up the tree until we reach either a frame that's already dirty or // a reflow root.
nsIFrame* f = subtreeRoot; for (;;) { if (IsReflowBoundary(f) || !f->GetParent()) { // we've hit a reflow root or the root frame if (!wasDirty) {
mDirtyRoots.Add(f);
SetNeedLayoutFlush();
} #ifdef DEBUG else {
VerifyHasDirtyRootAncestor(f);
} #endif
break;
}
nsIFrame* child = f;
f = f->GetParent();
wasDirty = f->IsSubtreeDirty();
f->ChildIsDirty(child);
NS_ASSERTION(f->HasAnyStateBits(NS_FRAME_HAS_DIRTY_CHILDREN), "ChildIsDirty didn't do its job"); if (wasDirty) { // This frame was already marked dirty. #ifdef DEBUG
VerifyHasDirtyRootAncestor(f); #endif break;
}
}
} while (subtrees.Length() != 0);
EnsureLayoutFlush();
}
void PresShell::FrameNeedsToContinueReflow(nsIFrame* aFrame) {
NS_ASSERTION(mIsReflowing, "Must be in reflow when marking path dirty.");
MOZ_ASSERT(mCurrentReflowRoot, "Must have a current reflow root here");
NS_ASSERTION(
aFrame == mCurrentReflowRoot ||
nsLayoutUtils::IsProperAncestorFrame(mCurrentReflowRoot, aFrame), "Frame passed in is not the descendant of mCurrentReflowRoot");
NS_ASSERTION(aFrame->HasAnyStateBits(NS_FRAME_IN_REFLOW), "Frame passed in not in reflow?");
// If the old slot is about to become empty and show fallback, let layout know // that it needs to do work. if (aOldSlot && aOldSlot->AssignedNodes().Length() == 1 &&
aOldSlot->HasChildren()) {
DestroyFramesForAndRestyle(aOldSlot);
}
// Ensure the new element starts off clean.
DestroyFramesAndStyleDataFor(&aElement, *mPresContext,
RestyleManager::IncludeRoot::Yes);
if (aNewSlot) { // If the new slot will stop showing fallback content, we need to reframe it // altogether. if (aNewSlot->AssignedNodes().IsEmpty() && aNewSlot->HasChildren()) {
DestroyFramesForAndRestyle(aNewSlot); // Otherwise we just care about the element, but we need to ensure that // something takes care of traversing to the relevant slot, if needed.
} elseif (aNewSlot->HasServoData() &&
!Servo_Element_IsDisplayNone(aNewSlot)) { // Set the reframe bits...
aNewSlot->NoteDescendantsNeedFramesForServo();
aElement.SetFlags(NODE_NEEDS_FRAME); // Now the style dirty bits. Note that we can't just do // aElement.NoteDirtyForServo(), because the new slot is not setup yet.
aNewSlot->SetHasDirtyDescendantsForServo();
aNewSlot->NoteDirtySubtreeForServo();
}
}
}
#ifdef DEBUG staticvoid AssertNoFramesOrStyleDataInDescendants(Element& aElement) { for (nsINode* node : ShadowIncludingTreeIterator(aElement)) {
nsIContent* c = nsIContent::FromNode(node); if (c == &aElement) { continue;
} // FIXME(emilio): The <area> check is needed because of bug 135040.
MOZ_ASSERT(!c->GetPrimaryFrame() || c->IsHTMLElement(nsGkAtoms::area));
MOZ_ASSERT(!c->IsElement() || !c->AsElement()->HasServoData());
}
} #endif
MOZ_ASSERT(aElement); if (!aElement->HasServoData()) { // Nothing to do here, the element already is out of the flat tree or is not // styled. return;
}
// Mark ourselves as not safe to flush while we're doing frame destruction.
nsAutoScriptBlocker scriptBlocker;
++mChangeNestCount;
constbool didReconstruct = FrameConstructor()->DestroyFramesFor(aElement); // Clear the style data from all the flattened tree descendants, but _not_ // from us, since otherwise we wouldn't see the reframe.
RestyleManager::ClearServoDataFromSubtree(aElement,
RestyleManager::IncludeRoot::No); auto changeHint =
didReconstruct ? nsChangeHint(0) : nsChangeHint_ReconstructFrame;
mPresContext->RestyleManager()->PostRestyleEvent(
aElement, RestyleHint::RestyleSubtree(), changeHint);
if (!aElement.HasServoData()) { // Nothing to do here, the element already is out of the flat tree or is not // styled. return;
}
if (!aElement.HasChildren()) { // The element has no children, just avoid the work. return;
}
// Mark ourselves as not safe to flush while we're doing frame destruction.
nsAutoScriptBlocker scriptBlocker;
++mChangeNestCount;
// NOTE(emilio): We use FlattenedChildIterator intentionally here (rather than // StyleChildrenIterator), since we don't want to remove ::before / ::after // content.
FlattenedChildIterator iter(&aElement);
nsCSSFrameConstructor* fc = FrameConstructor(); for (nsIContent* c = iter.GetNextChild(); c; c = iter.GetNextChild()) {
fc->DestroyFramesFor(c); if (c->IsElement()) {
RestyleManager::ClearServoDataFromSubtree(c->AsElement());
}
}
void PresShell::PostRecreateFramesFor(Element* aElement) { if (MOZ_UNLIKELY(!mDidInitialize)) { // Nothing to do here. In fact, if we proceed and aElement is the root, we // will crash. return;
}
void PresShell::RestyleForAnimation(Element* aElement, RestyleHint aHint) { // Now that we no longer have separate non-animation and animation // restyles, this method having a distinct identity is less important, // but it still seems useful to offer as a "more public" API and as a // checkpoint for these restyles to go through.
mPresContext->RestyleManager()->PostRestyleEvent(aElement, aHint,
nsChangeHint(0));
}
AutoWeakFrame* weakFrame = mAutoWeakFrames; while (weakFrame) {
AutoWeakFrame* prev = weakFrame->GetPreviousWeakFrame(); if (weakFrame->GetFrame() == aFrame) { // This removes weakFrame from mAutoWeakFrames.
weakFrame->Clear(this);
}
weakFrame = prev;
}
AutoTArray<WeakFrame*, 4> toRemove; for (WeakFrame* weakFrame : mWeakFrames) { if (weakFrame->GetFrame() == aFrame) {
toRemove.AppendElement(weakFrame);
}
} for (WeakFrame* weakFrame : toRemove) {
weakFrame->Clear(this);
}
}
UniquePtr<gfxContext> PresShell::CreateReferenceRenderingContext() { if (mPresContext->IsScreen()) { return gfxContext::CreateOrNull(
gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget().get());
}
// We assume the devCtx has positive width and height for this call. // However, width and height, may be outside of the reasonable range // so rc may still be null.
nsDeviceContext* devCtx = mPresContext->DeviceContext(); return devCtx->CreateReferenceRenderingContext();
}
if (mDocument->GetSVGRootElement()) { // We need to execute this even if there is an empty anchor name // so that any existing SVG fragment identifier effect is removed if (SVGFragmentIdentifier::ProcessFragmentIdentifier(mDocument,
aAnchorName)) { return NS_OK;
}
}
// Hold a reference to the ESM in case event dispatch tears us down.
RefPtr<EventStateManager> esm = mPresContext->EventStateManager();
// https://wicg.github.io/scroll-to-text-fragment/#invoking-text-directives // From "Monkeypatching HTML § 7.4.6.3 Scrolling to a fragment:" // 3.4. If target is a range, then: // 3.4.1 Set target to be the first common ancestor of target's start node and // end node. // 3.4.2 While target is non-null and is not an element, set target to // target's parent. // ------ // Common closest ancestor is not suitable here, as it can scroll to positions // where no text directive is visible. Instead, scroll to the start container // of the text directive. // see https://bugzil.la/1906895 and // https://github.com/WICG/scroll-to-text-fragment/issues/259
Element* textFragmentTargetElement = [&aFirstTextDirective]() -> Element* {
nsINode* node = aFirstTextDirective
? aFirstTextDirective->GetStartContainer()
: nullptr; while (node && !node->IsElement()) {
node = node->GetParent();
} return Element::FromNodeOrNull(node);
}(); constbool thereIsATextFragment = !!textFragmentTargetElement;
// 1. If there is no indicated part of the document, set the Document's target // element to null. // // FIXME(emilio): Per spec empty fragment string should take the same // code-path as "top"! if (aAnchorName.IsEmpty() && !thereIsATextFragment) {
NS_ASSERTION(!aScroll, "can't scroll to empty anchor name");
esm->SetContentState(nullptr, ElementState::URLTARGET); return NS_OK;
}
// 2. If the indicated part of the document is the top of the document, // then: // (handled below when `target` is null, and anchor is `top`)
// 1. If there is no indicated part of the document, set the Document's // target element to null. // 2.1. Set the Document's target element to null. // 3.2. Set the Document's target element to target.
esm->SetContentState(target, ElementState::URLTARGET);
if (target) { // 3.4 Run the ancestor revealing algorithm on target.
ErrorResult rv;
target->AncestorRevealingAlgorithm(rv); if (MOZ_UNLIKELY(rv.Failed())) { return rv.StealNSResult();
}
if (aScroll) { // https://wicg.github.io/scroll-to-text-fragment/#invoking-text-directives // From "Monkeypatching HTML § 7.4.6.3 Scrolling to a fragment:" // 3.9 Let blockPosition be "center" if scrollTarget is a range, "start" // otherwise. // Implementation note: Use `ScrollSelectionIntoView` for text fragment, // since the text fragment is stored as a `eTargetText` selection. // // 3.4. Scroll target into view, with behavior set to "auto", block set to // "auto", and inline set to "auto". // https://github.com/w3c/csswg-drafts/issues/9576 // FIXME(emilio): Not all callers pass ScrollSmoothAuto (but we use auto // smooth scroll for `top` regardless below, so maybe they should!).
ScrollingInteractionContext scrollToAnchorContext(true); if (thereIsATextFragment) {
MOZ_TRY(ScrollSelectionIntoView(
SelectionType::eTargetText,
nsISelectionController::SELECTION_ANCHOR_REGION,
AxisScrollParams(WhereToScroll::Center, WhenToScroll::Always),
AxisScrollParams(WhereToScroll::Center, WhenToScroll::Always),
ScrollFlags::AnchorScrollFlags | aAdditionalScrollFlags,
SelectionScrollMode::SyncFlush));
} else {
MOZ_TRY(ScrollContentIntoView(
target, AxisScrollParams(WhereToScroll::Auto, WhenToScroll::Always),
AxisScrollParams(WhereToScroll::Auto),
ScrollFlags::AnchorScrollFlags | aAdditionalScrollFlags));
} if (ScrollContainerFrame* rootScroll = GetRootScrollContainerFrame()) {
mLastAnchorScrolledTo = target;
mLastAnchorScrollPositionY = rootScroll->GetScrollPosition().y;
mLastAnchorScrollType = thereIsATextFragment
? AnchorScrollType::TextDirective
: AnchorScrollType::Anchor;
}
}
{ // 3.6. Move the sequential focus navigation starting point to target. // // Move the caret to the anchor. That way tabbing will start from the new // location. // // TODO(emilio): Do we want to do this even if aScroll is false? // // NOTE: Intentionally out of order for now with the focus steps, see // https://github.com/whatwg/html/issues/7759
RefPtr<nsRange> jumpToRange = nsRange::Create(mDocument);
nsCOMPtr<nsIContent> nodeToSelect = target.get(); while (nodeToSelect->GetFirstChild()) {
nodeToSelect = nodeToSelect->GetFirstChild();
}
jumpToRange->SelectNodeContents(*nodeToSelect, IgnoreErrors());
RefPtr sel = &mSelection->NormalSelection();
MOZ_ASSERT(sel);
sel->RemoveAllRanges(IgnoreErrors());
sel->AddRangeAndSelectFramesAndNotifyListeners(*jumpToRange,
IgnoreErrors()); if (!StaticPrefs::layout_selectanchor()) { // Use a caret (collapsed selection) at the start of the anchor.
sel->CollapseToStart(IgnoreErrors());
}
}
// 3.5. Run the focusing steps for target, with the Document's viewport as // the fallback target. // // Note that ScrollContentIntoView flushes, so we don't need to do that // again here. We also don't need to scroll again either. // // We intentionally focus the target only when aScroll is true, we need to // sort out if the spec needs to differentiate these cases. When aScroll is // false we still clear the focus unconditionally, that's legacy behavior, // maybe we shouldn't do it. // // TODO(emilio): Do we really want to clear the focus even if aScroll is // false? constbool shouldFocusTarget = [&] { if (!aScroll || thereIsATextFragment) { returnfalse;
}
nsIFrame* targetFrame = target->GetPrimaryFrame(); return targetFrame && targetFrame->IsFocusable();
}();
if (shouldFocusTarget) {
FocusOptions options;
options.mPreventScroll = true;
target->Focus(options, CallerType::NonSystem, IgnoreErrors());
} elseif (RefPtr<nsIFocusManager> fm = nsFocusManager::GetFocusManager()) { if (nsPIDOMWindowOuter* win = mDocument->GetWindow()) { // Now focus the document itself if focus is on an element within it.
nsCOMPtr<mozIDOMWindowProxy> focusedWindow;
fm->GetFocusedWindow(getter_AddRefs(focusedWindow)); if (SameCOMIdentity(win, focusedWindow)) {
fm->ClearFocus(focusedWindow);
}
}
} // The focusing stuff above could have set the dedicated sequential // focus navigation starting point due to blurring the focused element. // However, we want focus navigation to start from the the selection // (which is now target), so we clear that here.
mDocument->SetFocusNavigationStartingPoint(nullptr);
// If the target is an animation element, activate the animation if (auto* animationElement = SVGAnimationElement::FromNode(target.get())) {
animationElement->ActivateByHyperlink();
}
#ifdef ACCESSIBILITY if (nsAccessibilityService* accService = GetAccService()) {
nsIContent* a11yTarget = target; if (thereIsATextFragment) { // A text fragment starts in a text leaf node. `target` is the element // parent, but there may be many other children of that element before // the start of the text fragment. Explicitly use the start leaf node // here to get a11y clients as close as possible to the fragment (on // platforms which support this).
a11yTarget = nsIContent::FromNodeOrNull(
aFirstTextDirective->GetStartContainer()); if (!a11yTarget) {
a11yTarget = target;
}
}
accService->NotifyOfAnchorJumpTo(a11yTarget);
} #endif
} elseif (nsContentUtils::EqualsIgnoreASCIICase(aAnchorName, u"top"_ns)) { // 2.2. Scroll to the beginning of the document for the Document.
ScrollContainerFrame* sf = GetRootScrollContainerFrame(); // Check |aScroll| after setting |rv| so we set |rv| to the same // thing whether or not |aScroll| is true. if (aScroll && sf) {
ScrollMode scrollMode = sf->ScrollModeForScrollBehavior(); // Scroll to the top of the page
sf->ScrollTo(nsPoint(0, 0), scrollMode);
}
} else { return NS_ERROR_FAILURE;
}
return NS_OK;
}
nsresult PresShell::ScrollToAnchor() {
NS_ASSERTION(mDidInitialize, "should have done initial reflow by now"); if (mLastAnchorScrollType == AnchorScrollType::Anchor) {
nsCOMPtr<nsIContent> lastAnchor = std::move(mLastAnchorScrolledTo); if (!lastAnchor) { return NS_OK;
}
// If this is an inline frame and either the bounds height is 0 (quirks // layout model) or aUseWholeLineHeightForInlines is set, we need to // change the top of the bounds to include the whole line. if (frameBounds.height == 0 || aUseWholeLineHeightForInlines) {
nsIFrame* prevFrame = aFrame;
nsIFrame* f = aFrame;
while (f && f->IsLineParticipant() && !f->IsTransformed() &&
!f->IsAbsPosContainingBlock()) {
prevFrame = f;
f = prevFrame->GetParent();
}
if (f != aFrame && f && f->IsBlockFrame()) { // find the line containing aFrame and increase the top of |offset|. if (f != aPrevBlock) {
aLines = f->GetLineIterator();
aPrevBlock = f;
aCurLine = 0;
} if (aLines) {
int32_t index = aLines->FindLineContaining(prevFrame, aCurLine); if (index >= 0) { auto line = aLines->GetLine(index).unwrap();
frameBounds += frame->GetOffsetTo(f);
frame = f; if (line.mLineBounds.y < frameBounds.y) {
frameBounds.height = frameBounds.YMost() - line.mLineBounds.y;
frameBounds.y = line.mLineBounds.y;
}
}
}
}
}
if (aHaveRect) { // We can't use nsRect::UnionRect since it drops empty rects on // the floor, and we need to include them. (Thus we need // aHaveRect to know when to drop the initial value on the floor.)
aRect = aRect.UnionEdges(transformedBounds);
} else {
aHaveRect = true;
aRect = transformedBounds;
}
}
staticbool ComputeNeedToScroll(WhenToScroll aWhenToScroll, nscoord aLineSize,
nscoord aRectMin, nscoord aRectMax,
nscoord aViewMin, nscoord aViewMax) { // See how the rect should be positioned in a given axis. switch (aWhenToScroll) { case WhenToScroll::Always: // The caller wants the frame as visible as possible returntrue; case WhenToScroll::IfNotVisible: if (aLineSize > (aRectMax - aRectMin)) { // If the line size is greater than the size of the rect // to scroll into view, do not use the line size to determine // if we need to scroll.
aLineSize = 0;
}
// Scroll only if no part of the frame is visible in this view. return aRectMax - aLineSize <= aViewMin ||
aRectMin + aLineSize >= aViewMax; case WhenToScroll::IfNotFullyVisible: // Scroll only if part of the frame is hidden and more can fit in view return !(aRectMin >= aViewMin && aRectMax <= aViewMax) &&
std::min(aViewMax, aRectMax) - std::max(aRectMin, aViewMin) <
aViewMax - aViewMin;
} returnfalse;
}
static nscoord ComputeWhereToScroll(WhereToScroll aWhereToScroll,
nscoord aOriginalCoord, nscoord aRectMin,
nscoord aRectMax, nscoord aViewSize,
nscoord aScrollMin, nscoord aScrollMax) {
nscoord resultCoord = aOriginalCoord; if (!aWhereToScroll.mPercentage) { // Scroll the minimum amount necessary to show as much as possible of the // frame. If the frame is too large, don't hide any initially visible part // of it.
nscoord min = std::min(aRectMin, aRectMax - aViewSize);
nscoord max = std::max(aRectMin, aRectMax - aViewSize);
resultCoord = std::clamp(aOriginalCoord, min, max);
} else { float percent = aWhereToScroll.mPercentage.value() / 100.0f;
nscoord frameAlignCoord =
NSToCoordRound(aRectMin + (aRectMax - aRectMin) * percent);
resultCoord = NSToCoordRound(frameAlignCoord - aViewSize * percent);
} // Clamp scroll offset to the viable range. return std::clamp(resultCoord, aScrollMin, aScrollMax);
}
switch (align) { case StyleScrollSnapAlignKeyword::None: return aAutoDefault; case StyleScrollSnapAlignKeyword::Start: return WhereToScroll::Start; case StyleScrollSnapAlignKeyword::Center: return WhereToScroll::Center; case StyleScrollSnapAlignKeyword::End: return WhereToScroll::End;
} return aAutoDefault;
}
static ScrollMode GetScrollModeForScrollIntoView( const ScrollContainerFrame* aScrollContainerFrame,
ScrollFlags aScrollFlags) { // Default to an instant scroll, but if the scroll behavior given is "auto" // or "smooth", use that as the specified behavior. If the user has disabled // smooth scrolls, a given mode of "auto" or "smooth" should not result in // a smooth scroll.
ScrollBehavior behavior = ScrollBehavior::Instant; if (aScrollFlags & ScrollFlags::ScrollSmooth) {
behavior = ScrollBehavior::Smooth;
} elseif (aScrollFlags & ScrollFlags::ScrollSmoothAuto) {
behavior = ScrollBehavior::Auto;
} return aScrollContainerFrame->ScrollModeForScrollBehavior(behavior);
}
nsSize lineSize; // Don't call GetLineScrollAmount unless we actually need it. Not only // does this save time, but it's not safe to call GetLineScrollAmount // during reflow (because it depends on font size inflation and doesn't // use the in-reflow-safe font-size inflation path). If we did call it, // it would assert and possible give the wrong result. if (aVertical.mWhenToScroll == WhenToScroll::IfNotVisible ||
aHorizontal.mWhenToScroll == WhenToScroll::IfNotVisible) {
lineSize = aScrollContainerFrame->GetLineScrollAmount();
}
ScrollStyles ss = aScrollContainerFrame->GetScrollStyles();
nsRect scrollRange(scrollPt, nsSize(0, 0)); constauto scrollConstraint = aScrollContainerFrame->GetVisualScrollRange(); if ((aScrollFlags & ScrollFlags::ScrollOverflowHidden) ||
ss.mVertical != StyleOverflow::Hidden) { if (ComputeNeedToScroll(aVertical.mWhenToScroll, lineSize.height, aRect.y,
aRect.YMost(), visibleRect.y + padding.top,
visibleRect.YMost() - padding.bottom)) {
WhereToScroll whereToScroll = GetApplicableWhereToScroll(
aScrollContainerFrame, aScrollableFrame, aTarget,
ScrollDirection::eVertical, aVertical.mWhereToScroll,
aVerticalAutoDefault);
// If we don't need to scroll, then don't try since it might cancel // a current smooth scroll operation. if (scrollPt == originalScrollPt) { return Nothing();
}
NS_ASSERTION(mDidInitialize, "should have done initial reflow by now");
if (mContentToScrollTo) {
mContentToScrollTo->RemoveProperty(nsGkAtoms::scrolling);
}
mContentToScrollTo = aContent;
ScrollIntoViewData* data = new ScrollIntoViewData();
data->mContentScrollVAxis = aVertical;
data->mContentScrollHAxis = aHorizontal;
data->mContentToScrollToFlags = aScrollFlags; if (NS_FAILED(mContentToScrollTo->SetProperty(
nsGkAtoms::scrolling, data,
nsINode::DeleteProperty<PresShell::ScrollIntoViewData>))) {
mContentToScrollTo = nullptr;
}
// If the target frame has an ancestor of a `content-visibility: auto` // element ensure that it is laid out, so that the boundary rectangle is // correct. // Additionally, ensure that all ancestor elements with 'content-visibility: // auto' are set to 'visible'. so that they are laid out as visible before // scrolling, improving the accuracy of the scroll position, especially when // the scroll target is within the overflow area. And here invoking // 'SetTemporarilyVisibleForScrolledIntoViewDescendant' would make the // intersection observer knows that it should generate entries for these // c-v:auto ancestors, so that the content relevancy could be checked again // after scrolling. https://drafts.csswg.org/css-contain-2/#cv-notes bool reflowedForHiddenContent = false; if (mContentToScrollTo) { if (nsIFrame* frame = mContentToScrollTo->GetPrimaryFrame()) { bool hasContentVisibilityAutoAncestor = false; auto* ancestor = frame->GetClosestContentVisibilityAncestor(
nsIFrame::IncludeContentVisibility::Auto); while (ancestor) { if (auto* element = Element::FromNodeOrNull(ancestor->GetContent())) {
hasContentVisibilityAutoAncestor = true;
element->SetTemporarilyVisibleForScrolledIntoViewDescendant(true);
element->SetVisibleForContentVisibility(true);
}
ancestor = ancestor->GetClosestContentVisibilityAncestor(
nsIFrame::IncludeContentVisibility::Auto);
} if (hasContentVisibilityAutoAncestor) {
UpdateHiddenContentInForcedLayout(frame); // TODO: There might be the other already scheduled relevancy updates, // other than caused be scrollIntoView.
UpdateContentRelevancyImmediately(ContentRelevancyReason::Visible);
reflowedForHiddenContent = ReflowForHiddenContentIfNeeded();
}
}
}
if (!reflowedForHiddenContent) { // Flush layout and attempt to scroll in the process. if (PresShell* presShell = composedDoc->GetPresShell()) {
presShell->SetNeedLayoutFlush();
}
composedDoc->FlushPendingNotifications(FlushType::InterruptibleLayout);
}
// If mContentToScrollTo is non-null, that means we interrupted the reflow // (or suppressed it altogether because we're suppressing interruptible // flushes right now) and won't necessarily get the position correct, but do // a best-effort scroll here. The other option would be to do this inside // FlushPendingNotifications, but I'm not sure the repeated scrolling that // could trigger if reflows keep getting interrupted would be more desirable // than a single best-effort scroll followed by one final scroll on the first // completed reflow. if (mContentToScrollTo) {
DoScrollContentIntoView();
} return NS_OK;
}
static nsMargin GetScrollMargin(const nsIFrame* aFrame) {
MOZ_ASSERT(aFrame); // If we're focusing something that can't be targeted by content, allow // content to customize the margin. // // TODO: This is also a bit of an issue for delegated focus, see // https://github.com/whatwg/html/issues/7033. if (aFrame->GetContent() && aFrame->GetContent()->ChromeOnlyAccess()) { // XXX Should we use nsIContent::FindFirstNonChromeOnlyAccessContent() // instead of nsINode::GetClosestNativeAnonymousSubtreeRootParentOrHost()? if (const nsIContent* userContent =
aFrame->GetContent()
->GetClosestNativeAnonymousSubtreeRootParentOrHost()) { if (const nsIFrame* frame = userContent->GetPrimaryFrame()) { return frame->StyleMargin()->GetScrollMargin();
}
}
} return aFrame->StyleMargin()->GetScrollMargin();
}
void PresShell::DoScrollContentIntoView() {
NS_ASSERTION(mDidInitialize, "should have done initial reflow by now");
if (frame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { // The reflow flush before this scroll got interrupted, and this frame's // coords and size are all zero, and it has no content showing anyway. // Don't bother scrolling to it. We'll try again when we finish up layout. return;
}
auto* data = static_cast<ScrollIntoViewData*>(
mContentToScrollTo->GetProperty(nsGkAtoms::scrolling)); if (MOZ_UNLIKELY(!data)) {
mContentToScrollTo = nullptr; return;
}
staticbool NeedToVisuallyScroll(const nsSize& aLayoutViewportSize, const nsRect& aPositionFixedRect) { // position:fixed elements are fixed to the layout viewport, thus the // coordinate system is (0, 0) origin. // (and the maximum visible position is the layout viewport size, elements // outside of the size will never be laid out) const nsRect layoutViewport = nsRect(nsPoint(), aLayoutViewportSize);
// `BaseRect::Intersects(const Sub& aRect)` does return false if `aRect` is // empty, but we do want to visually scroll to empty position:fixed elements // if the elements are inside the layout viewport. if (aPositionFixedRect.IsEmpty()) { if (aPositionFixedRect.x > layoutViewport.XMost() ||
aPositionFixedRect.XMost() < layoutViewport.x ||
aPositionFixedRect.y > layoutViewport.YMost() ||
aPositionFixedRect.YMost() < layoutViewport.y) { returnfalse;
} returntrue;
}
if (!layoutViewport.Intersects(aPositionFixedRect)) { returnfalse;
} returntrue;
}
if (!root->GetPresContext()->IsRootContentDocumentCrossProcess()) { return;
}
ScrollContainerFrame* rootScrollContainer =
root->GetRootScrollContainerFrame(); if (!rootScrollContainer) { return;
}
if (!aDestination) {
MOZ_ASSERT(aPositionFixedFrame); // If we have in the top level content document but we didn't reach to // the root scroll container in the frame tree walking up loop in // ScrollFrameIntoView, it means the target element is inside a // position:fixed subtree. if (!StaticPrefs::layout_scroll_fixed_content_into_view_visually()) { return;
}
const nsSize layoutViewportSize = root->GetLayoutViewportSize(); const nsRect layoutViewport = nsRect(nsPoint(), layoutViewportSize); // `positon:fixed` element are attached/fixed to the ViewportFrame, which is // the parent of the root scroll container frame, thus what we need here is // the visible area of the position:fixed element inside the root scroll // container frame. // For example, if the top left position of the fixed element is (-100, // -100), it's outside of the scrollable range either in the layout viewport // or the visual viewport. Likewise, if the right bottom position of the // fixed element is (110vw, 110vh), it's also outside of the scrollable // range. // If the position:fixed element is totally outside of the layout viewport, // it will never be in the viewport. if (!NeedToVisuallyScroll(layoutViewportSize, aPositionFixedRect)) { return;
}
// If the position:fixed element is already in the visual viewport, no // visual scroll is needed. Convert to layout coordinates first since the // visual viewport may have been panned from the origin.
nsRect clampedPositionFixedRect =
aPositionFixedRect.MoveInsideAndClamp(layoutViewport);
nsPoint layoutOffset = rootScrollContainer->GetScrollPosition();
clampedPositionFixedRect.MoveBy(layoutOffset); const nsRect visualViewport(GetVisualViewportOffset(), visualViewportSize); // Use Contains(point) twice rather than Contains(rect) because the latter // returns true for empty rects regardless of position. if (visualViewport.Contains(clampedPositionFixedRect.TopLeft()) &&
visualViewport.Contains(clampedPositionFixedRect.BottomRight())) { return;
} // Offset the position:fixed element position by the layout scroll // position because the position:fixed origin (0, 0) is the layout scroll // position. Otherwise if we've already scrolled, this scrollIntoView // operation will jump back to near (0, 0) position. constauto scrollRange = rootScrollContainer->GetVisualScrollRange();
// NOTE: It seems chrome doesn't respect the root element's // scroll-behavior for visual scrolling.
ScrollMode scrollMode =
GetScrollModeForScrollIntoView(rootScrollContainer, aScrollFlags);
root->ScrollToVisual(*aDestination, ScrollOffsetUpdateType::MainThread,
scrollMode);
}
bool PresShell::ScrollFrameIntoView(
nsIFrame* aTargetFrame, const Maybe<nsRect>& aKnownRectRelativeToTarget,
AxisScrollParams aVertical, AxisScrollParams aHorizontal,
ScrollFlags aScrollFlags) { // If the AxesAreLogical flag is set, the aVertical and aHorizontal params // actually refer to block and inline axes respectively, so we resolve them // to physical axes/directions here. // XXX Maybe we should convert more of the following code to logical axes, // if it's convenient for more callers to work that way?
// The default of WhereToScroll for physical vertical/horizontal axes: // block-start (top) and inline-nearest respectively, matching the spec for // horizontal writing modes. These are overridden below when AxesAreLogical is // set.
WhereToScroll verticalAutoDefault = WhereToScroll::Start;
WhereToScroll horizontalAutoDefault = WhereToScroll::Nearest;
if (aScrollFlags & ScrollFlags::AxesAreLogical) { // The aVertical parameter actually refers to the element's block axis, // and aHorizontal to its inline axis. Potentially reverse/swap them, // according to its writing mode and directionality.
WritingMode wm = aTargetFrame->GetWritingMode(); if (wm.IsVerticalRL()) { // Reverse the block-axis percentage. if (aVertical.mWhereToScroll.mPercentage) {
aVertical.mWhereToScroll.mPercentage =
Some(100 - aVertical.mWhereToScroll.mPercentage.value());
}
} if (wm.IsInlineReversed()) { // Reverse the inline-axis percentage. if (aHorizontal.mWhereToScroll.mPercentage) {
aHorizontal.mWhereToScroll.mPercentage =
Some(100 - aHorizontal.mWhereToScroll.mPercentage.value());
}
} if (wm.IsVertical()) {
std::swap(aVertical, aHorizontal);
// After the swap, physical vertical = inline and physical horizontal = // block for vertical writing modes. Compute auto defaults accordingly.
verticalAutoDefault = WhereToScroll::Nearest;
horizontalAutoDefault = wm.IsVerticalRL()
? WhereToScroll{WhereToScroll::End}
: WhereToScroll{WhereToScroll::Start};
} // Remove the AxesAreLogical flag, to make it clear that methods we call // always get physical axes from here on.
aScrollFlags &= ~ScrollFlags::AxesAreLogical;
}
// The scroll margin only applies to the whole bounds of the element, so don't // apply it if we get an arbitrary rect / point to scroll to. const nsMargin scrollMargin =
aKnownRectRelativeToTarget ? nsMargin() : GetScrollMargin(aTargetFrame);
Sides skipPaddingSides; constauto MaybeSkipPaddingSides = [&](nsIFrame* aFrame) { if (!aFrame->IsStickyPositioned()) { return;
} const nsPoint pos = aFrame->GetPosition(); const nsPoint normalPos = aFrame->GetNormalPosition(); if (pos == normalPos) { return; // Frame is not stuck.
} // If we're targetting a sticky element, make sure not to apply // scroll-padding on the direction we're stuck. constauto* stylePosition = aFrame->StylePosition(); constauto anchorResolutionParams =
AnchorPosOffsetResolutionParams::UseCBFrameSize(
AnchorPosResolutionParams::From(aFrame)); for (auto side : AllPhysicalSides()) { if (stylePosition->GetAnchorResolvedInset(side, anchorResolutionParams)
->IsAuto()) { continue;
} // See if this axis is stuck. constbool yAxis = side == eSideTop || side == eSideBottom; constbool stuck = yAxis ? pos.y != normalPos.y : pos.x != normalPos.x; if (!stuck) { continue;
}
skipPaddingSides |= SideToSideBit(side);
}
};
nsIFrame* container = aTargetFrame;
const nsIFrame* positionFixedFrame = nullptr; auto isPositionFixed = [&](const nsIFrame* aFrame) -> bool { return aFrame->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
nsLayoutUtils::IsReallyFixedPos(aFrame);
}; // This function needs to work even if rect has a width or height of 0.
nsRect rect = [&] { if (aKnownRectRelativeToTarget) { return *aKnownRectRelativeToTarget;
}
MaybeSkipPaddingSides(aTargetFrame); while (nsIFrame* parent = container->GetParent()) { if (isPositionFixed(container)) {
positionFixedFrame = container;
}
container = parent; if (container->IsScrollContainerOrSubclass()) { // We really just need a non-fragmented frame so that we can accumulate // the bounds of all our continuations relative to it. We shouldn't jump // out of our nearest scrollable frame, and that's an ok reference // frame, so try to use that, or the root frame if there's nothing to // scroll in this document. break;
}
MaybeSkipPaddingSides(container);
}
MOZ_DIAGNOSTIC_ASSERT(container);
nsRect targetFrameBounds;
{ bool haveRect = false; constbool useWholeLineHeightForInlines =
aVertical.mWhenToScroll != WhenToScroll::IfNotFullyVisible;
AutoAssertNoDomMutations
guard; // Ensure use of nsILineIterators is safe.
nsIFrame* prevBlock = nullptr; // Reuse the same line iterator across calls to AccumulateFrameBounds. // We set it every time we detect a new block (stored in prevBlock).
nsILineIterator* lines = nullptr; // The last line we found a continuation on in |lines|. We assume that // later continuations cannot come on earlier lines.
int32_t curLine = 0;
nsIFrame* frame = aTargetFrame; do {
AccumulateFrameBounds(container, frame, useWholeLineHeightForInlines,
targetFrameBounds, haveRect, prevBlock, lines,
curLine);
} while ((frame = frame->GetNextContinuation()));
}
return targetFrameBounds;
}(); bool didScroll = false; const nsIFrame* target = aTargetFrame;
Maybe<nsPoint> rootScrollDestination; // Walk up the frame hierarchy scrolling the rect into view and // keeping rect relative to container do { if (isPositionFixed(container)) {
positionFixedFrame = container;
}
if (ScrollContainerFrame* sf = do_QueryFrame(container)) {
nsPoint oldPosition = sf->GetScrollPosition();
nsRect targetRect = rect - sf->GetScrolledFrame()->GetPosition();
if (sf->IsRootScrollFrameOfDocument() &&
sf->PresContext()->IsRootContentDocumentCrossProcess()) {
rootScrollDestination = destination;
}
}
nsPoint newPosition = sf->LastScrollDestination(); // If the scroll position increased, that means our content moved up, // so our rect's offset should decrease
rect += oldPosition - newPosition;
if (oldPosition != newPosition) {
didScroll = true;
}
// only scroll one container when this flag is set if (aScrollFlags & ScrollFlags::ScrollFirstAncestorOnly) { break;
}
// This scroll container will be the next target element in the nearest // ancestor scroll container.
target = container; // We found a sticky scroll container, we shouldn't skip that side // anymore.
skipPaddingSides = {};
}
MaybeSkipPaddingSides(container);
nsIFrame* parent = container->GetParent();
NS_ASSERTION(parent || !container->IsTransformed(), "viewport shouldnt be transformed"); if (parent && container->IsTransformed()) {
rect =
nsLayoutUtils::TransformFrameRectToAncestor(container, rect, parent);
} else {
rect += container->GetPosition();
} if (!parent && !(aScrollFlags & ScrollFlags::ScrollNoParentFrames)) {
nsPoint extraOffset(0, 0);
int32_t APD = container->PresContext()->AppUnitsPerDevPixel();
parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(container,
&extraOffset); if (parent) {
int32_t parentAPD = parent->PresContext()->AppUnitsPerDevPixel();
rect = rect.ScaleToOtherAppUnitsRoundOut(APD, parentAPD);
rect += extraOffset;
} else {
nsCOMPtr<nsIDocShell> docShell =
container->PresContext()->GetDocShell(); if (BrowserChild* browserChild = BrowserChild::GetFrom(docShell)) { // Defer to the parent document if this is an out-of-process iframe.
(void)browserChild->SendScrollRectIntoView(
rect, aVertical, aHorizontal, aScrollFlags, APD);
}
}
}
container = parent;
} while (container);
// If this is inside the top level content document process (and a direct // descendant of it), also call ScrollToVisual() since we want to // scroll the rect into view visually, and that may require scrolling // the visual viewport in scenarios where there is not enough layout // scroll range. if (!rootScrollDestination && !positionFixedFrame) { return didScroll;
}
nsresult PresShell::CaptureHistoryState(nsILayoutHistoryState** aState) {
MOZ_ASSERT(nullptr != aState, "null state pointer");
// We actually have to mess with the docshell here, since we want to // store the state back in it. // XXXbz this isn't really right, since this is being called in the // content viewer's Hide() method... by that point the docshell's // state could be wrong. We should sort out a better ownership // model for the layout history state.
nsCOMPtr<nsIDocShell> docShell(mPresContext->GetDocShell()); if (!docShell) { return NS_ERROR_FAILURE;
}
nsCOMPtr<nsILayoutHistoryState> historyState;
docShell->GetLayoutHistoryState(getter_AddRefs(historyState)); if (!historyState) { // Create the document state object
historyState = NS_NewLayoutHistoryState();
docShell->SetLayoutHistoryState(historyState);
}
*aState = historyState;
NS_IF_ADDREF(*aState);
// Capture frame state for the entire frame hierarchy
nsIFrame* rootFrame = mFrameConstructor->GetRootFrame(); if (!rootFrame) { return NS_OK;
}
void PresShell::ScheduleBeforeFirstPaint() { if (!mDocument->IsResourceDoc()) { // Notify observers that a new page is about to be drawn. Execute this // as soon as it is safe to run JS, which is guaranteed to be before we // go back to the event loop and actually draw the page.
MOZ_LOG(gLog, LogLevel::Debug,
("PresShell::ScheduleBeforeFirstPaint this=%p", this));
void PresShell::UnsuppressAndInvalidate() { // Note: We ignore the EnsureVisible check for resource documents, because // they won't have a docshell, so they'll always fail EnsureVisible. if ((!mDocument->IsResourceDoc() && !mPresContext->EnsureVisible()) ||
mHaveShutDown) { // No point; we're about to be torn down anyway. return;
}
mPaintingSuppressed = false; if (nsIFrame* rootFrame = mFrameConstructor->GetRootFrame()) { // let's assume that outline on a root frame is not supported
rootFrame->InvalidateFrame();
}
if (mPresContext->IsRootContentDocumentCrossProcess()) { if (auto* bc = BrowserChild::GetFrom(mDocument->GetDocShell())) { if (mDocument->IsInitialDocument()) {
bc->SendDidUnsuppressPaintingNormalPriority();
} else {
bc->SendDidUnsuppressPainting();
}
}
}
// now that painting is unsuppressed, focus may be set on the document if (nsPIDOMWindowOuter* win = mDocument->GetWindow()) {
win->SetReadyForFocus();
}
if (!mHaveShutDown) {
SynthesizeMouseMove(false);
ScheduleApproximateFrameVisibilityUpdateNow();
}
}
if (mIsDocumentGone || !mPaintingSuppressed) { return;
}
// If we have reflows pending, just wait until we process // the reflows and get all the frames where we want them // before actually unlocking the painting. Otherwise // go ahead and unlock now. if (!mDirtyRoots.IsEmpty()) {
mShouldUnsuppressPainting = true;
} else {
UnsuppressAndInvalidate();
}
}
// Post a request to handle an arbitrary callback after reflow has finished.
nsresult PresShell::PostReflowCallback(nsIReflowCallback* aCallback) { void* result = AllocateByObjectID(eArenaObjectID_nsCallbackEventRequest, sizeof(nsCallbackEventRequest));
nsCallbackEventRequest* request = (nsCallbackEventRequest*)result;
void PresShell::HandlePostedReflowCallbacks(bool aInterruptible) { while (true) { // Call all our callbacks, tell us if we need to flush again. bool shouldFlush = false; while (mFirstCallbackEventRequest) {
nsCallbackEventRequest* node = mFirstCallbackEventRequest;
mFirstCallbackEventRequest = node->next; if (!mFirstCallbackEventRequest) {
mLastCallbackEventRequest = nullptr;
}
nsIReflowCallback* callback = node->callback;
FreeByObjectID(eArenaObjectID_nsCallbackEventRequest, node); if (callback && callback->ReflowFinished()) {
shouldFlush = true;
}
}
if (!shouldFlush || mIsDestroying) { return;
}
// The flush might cause us to have more callbacks. constauto flushType =
aInterruptible ? FlushType::InterruptibleLayout : FlushType::Layout;
FlushPendingNotifications(flushType);
}
}
bool PresShell::IsSafeToFlush() const { // Not safe if we are getting torn down, reflowing, or in the middle of frame // construction. if (mIsReflowing || mChangeNestCount || mIsDestroying) { returnfalse;
} // Not safe either if we're painting. // TODO(emilio): What could call us while painting now? return !mIsPainting;
}
void PresShell::NotifyFontFaceSetOnRefresh() { if (FontFaceSet* set = mDocument->GetFonts()) {
set->DidRefresh();
}
}
#ifdef DEBUG staticvoid AssertFrameSubtreeIsSane(const nsIFrame& aRoot) { if (const nsIContent* content = aRoot.GetContent()) {
MOZ_ASSERT(content->GetFlattenedTreeParentNodeForStyle(), "Node not in the flattened tree still has a frame?");
}
for (constauto& childList : aRoot.ChildLists()) { for (const nsIFrame* child : childList.mList) {
AssertFrameSubtreeIsSane(*child);
}
}
} #endif
void PresShell::DoFlushPendingNotifications(mozilla::ChangesToFlush aFlush) { // FIXME(emilio, bug 1530177): Turn into a release assert when bug 1530188 and // bug 1530190 are fixed.
MOZ_DIAGNOSTIC_ASSERT(!mForbiddenToFlush, "This is bad!");
// Per our API contract, hold a strong ref to ourselves until we return.
RefPtr<PresShell> kungFuDeathGrip = this;
if (aFlush.mUpdateRelevancy) { // If needed, first update the relevancy of any content of elements with // `content-visibility: auto` so that the values returned from e.g. script // queries are up-to-date.
UpdateRelevancyOfContentVisibilityAutoFrames();
}
MOZ_ASSERT(NeedFlush(flushType), "Why did we get called?");
#ifdef ACCESSIBILITY # ifdef DEBUG if (nsAccessibilityService* accService = GetAccService()) {
NS_ASSERTION(!accService->IsProcessingRefreshDriverNotification(), "Flush during accessible tree update!");
} # endif #endif
NS_ASSERTION(flushType >= FlushType::Style, "Why did we get called?");
bool isSafeToFlush = IsSafeToFlush();
// If layout could possibly trigger scripts, then it's only safe to flush if // it's safe to run script. bool hasHadScriptObject; if (mDocument->GetScriptHandlingObject(hasHadScriptObject) ||
hasHadScriptObject) {
isSafeToFlush = isSafeToFlush && nsContentUtils::IsSafeToRunScript();
}
// Don't flush if the doc is already in the bfcache. if (MOZ_UNLIKELY(mDocument->GetPresShell() != this)) {
MOZ_DIAGNOSTIC_ASSERT(!mDocument->GetPresShell(), "Where did this shell come from?");
isSafeToFlush = false;
}
// We need to make sure external resource documents are flushed too (for // example, svg filters that reference a filter in an external document // need the frames in the external document to be constructed for the // filter to work). We only need external resources to be flushed when the // main document is flushing >= FlushType::Frames, so we flush external // resources here instead of Document::FlushPendingNotifications.
mDocument->FlushExternalResources(flushType);
// Force flushing of any pending content notifications that might have // queued up while our event was pending. That will ensure that we don't // construct frames for content right now that's still waiting to be // notified on,
mDocument->FlushPendingNotifications(FlushType::ContentAndNotify);
mDocument->UpdateSVGUseElementShadowTrees();
// Process pending restyles, since any flush of the presshell wants // up-to-date style data. if (MOZ_LIKELY(!mIsDestroying)) {
FlushDelayedResize();
mPresContext->FlushPendingMediaFeatureValuesChanged();
}
if (MOZ_LIKELY(!mIsDestroying)) { // Now that we have flushed media queries, update the rules before looking // up @font-face / @counter-style / @font-feature-values rules.
StyleSet()->UpdateStylistIfNeeded();
// Flush any pending update of the user font set, since that could // cause style changes (for updating ex/ch units, and to cause a // reflow).
mDocument->FlushUserFontSet();
mPresContext->FlushCounterStyles();
mPresContext->FlushFontFeatureValues();
mPresContext->FlushFontPaletteValues();
// Flush any requested SMIL samples. if (mDocument->HasAnimationController()) {
mDocument->GetAnimationController()->FlushResampleRequests();
}
}
// The FlushResampleRequests() above might have flushed style changes. if (MOZ_LIKELY(!mIsDestroying)) { if (aFlush.mFlushAnimations) {
mPresContext->EffectCompositor()->PostRestyleForThrottledAnimations();
mNeedThrottledAnimationFlush = false;
}
if (flushType >= (SuppressInterruptibleReflows()
? FlushType::Layout
: FlushType::InterruptibleLayout) &&
!mIsDestroying) { if (DoFlushLayout(/* aInterruptible = */ flushType < FlushType::Layout)) { if (mContentToScrollTo) {
DoScrollContentIntoView(); if (mContentToScrollTo) {
mContentToScrollTo->RemoveProperty(nsGkAtoms::scrolling);
mContentToScrollTo = nullptr;
}
}
} // FIXME(emilio): Maybe we should assert here but it's not 100% sure it'd // hold right now, UnsuppressAndInvalidate and so on can run script... if (MOZ_LIKELY(mDirtyRoots.IsEmpty())) {
mNeedLayoutFlush = false;
}
}
// XXXwaterson it might be more elegant to wait until after the // initial reflow to begin observing the document. That would // squelch any other inappropriate notifications as well. if (mDidInitialize) {
nsAutoCauseReflowNotifier crNotifier(this);
mPresContext->RestyleManager()->AttributeWillChange(aElement, aNameSpaceID,
aAttribute, aModType);
}
}
// XXXwaterson it might be more elegant to wait until after the // initial reflow to begin observing the document. That would // squelch any other inappropriate notifications as well. if (mDidInitialize) {
nsAutoCauseReflowNotifier crNotifier(this);
mPresContext->RestyleManager()->AttributeChanged(
aElement, aNameSpaceID, aAttribute, aModType, aOldValue);
}
}
// We never call ContentAppended with a document as the container, so we can // assert that we have an nsIContent parent.
MOZ_ASSERT(aFirstNewContent->GetParent());
MOZ_ASSERT(aFirstNewContent->GetParent()->IsElement() ||
aFirstNewContent->GetParent()->IsShadowRoot());
if (!mDidInitialize) { return;
}
#ifdef ACCESSIBILITY if (mDocAccessible) {
mDocAccessible->MaybeHandleChangeToHiddenNameOrDescription(
aFirstNewContent);
} #endif
if (aInfo.mOldParent) {
MaybeDestroyFramesAndStyles(aFirstNewContent, *mPresContext);
}
nsAutoCauseReflowNotifier crNotifier(this);
// Call this here so it only happens for real content mutations and // not cases when the frame constructor calls its own methods to force // frame reconstruction.
mPresContext->RestyleManager()->ContentAppended(aFirstNewContent);
#ifdef ACCESSIBILITY if (mDocAccessible) {
mDocAccessible->MaybeHandleChangeToHiddenNameOrDescription(aChild);
} #endif
if (aInfo.mOldParent) {
MaybeDestroyFramesAndStyles(aChild, *mPresContext);
}
nsAutoCauseReflowNotifier crNotifier(this);
// Call this here so it only happens for real content mutations and // not cases when the frame constructor calls its own methods to force // frame reconstruction.
mPresContext->RestyleManager()->ContentInserted(aChild);
MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::ContentWillBeRemoved(
nsIContent* aChild, const ContentRemoveInfo& aInfo) {
MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
MOZ_ASSERT(!mIsDocumentGone, "Unexpected ContentRemoved");
MOZ_ASSERT(aChild->OwnerDoc() == mDocument, "Unexpected document"); // Notify the ESM that the content has been removed, so that // it can clean up any state related to the content.
// NOTE(emilio): It's important that this goes after the frame constructor // stuff, otherwise the frame constructor can't see elements which are // display: contents / display: none, because we'd have cleared all the style // data from there.
mPresContext->RestyleManager()->ContentWillBeRemoved(aChild);
}
void PresShell::NotifyCounterStylesAreDirty() { // TODO: Looks like that nsFrameConstructor::NotifyCounterStylesAreDirty() // does not run script. If so, we don't need to block script with // nsAutoCauseReflowNotifier here. Instead, there should be methods // and stack only class which manages only mChangeNestCount for // avoiding unnecessary `MOZ_CAN_RUN_SCRIPT` marking.
nsAutoCauseReflowNotifier reflowNotifier(this);
mFrameConstructor->NotifyCounterStylesAreDirty();
}
void PresShell::ReconstructFrames() {
MOZ_ASSERT(!mFrameConstructor->GetRootFrame() || mDidInitialize, "Must not have root frame before initial reflow"); if (!mDidInitialize || mIsDestroying) { // Nothing to do here return;
}
if (Element* root = mDocument->GetRootElement()) {
PostRecreateFramesFor(root);
}
nsRootPresContext* rootPresContext = mPresContext->GetRootPresContext(); if (rootPresContext) {
rootPresContext->FlushWillPaintObservers(); if (mIsDestroying) { return NS_OK;
}
}
nsAutoScriptBlocker blockScripts;
// Set up the rectangle as the path in aThebesContext
gfxRect r(0, 0, nsPresContext::AppUnitsToFloatCSSPixels(aRect.width),
nsPresContext::AppUnitsToFloatCSSPixels(aRect.height));
aThebesContext->NewPath(); #ifdef MOZ_GFX_OPTIMIZE_MOBILE
aThebesContext->SnappedRectangle(r); #else
aThebesContext->Rectangle(r); #endif
nsIFrame* rootFrame = mFrameConstructor->GetRootFrame(); if (!rootFrame) { // Nothing to paint, just fill the rect
aThebesContext->SetColor(sRGBColor::FromABGR(aBackgroundColor));
aThebesContext->Fill(); return NS_OK;
}
// Since canvas APIs use floats to set up their matrices, we may have some // slight rounding errors here. We use NudgeToIntegers() here to adjust // matrix components that are integers up to the accuracy of floats to be // those integers.
gfxMatrix newTM = aThebesContext->CurrentMatrixDouble()
.PreTranslate(offset)
.PreScale(scale, scale)
.NudgeToIntegers();
aThebesContext->SetMatrixDouble(newTM);
AutoSaveRestoreRenderingState _(this);
bool wouldFlushRetainedLayers = false;
PaintFrameFlags flags = PaintFrameFlags::IgnoreSuppression; if (aThebesContext->CurrentMatrix().HasNonIntegerTranslation()) {
flags |= PaintFrameFlags::InTransform;
} if (!(aFlags & RenderDocumentFlags::AsyncDecodeImages)) {
flags |= PaintFrameFlags::SyncDecodeImages;
} if (aFlags & RenderDocumentFlags::UseHighQualityScaling) {
flags |= PaintFrameFlags::UseHighQualityScaling;
} if (aFlags & RenderDocumentFlags::UseWidgetLayers) { // We only support using widget layers on display root's with widgets.
nsIWidget* widget = rootFrame->GetOwnWidget(); if (widget && nsLayoutUtils::GetDisplayRootFrame(rootFrame) == rootFrame) {
WindowRenderer* renderer = widget->GetWindowRenderer(); // WebRenderLayerManagers in content processes // don't support taking snapshots. if (renderer &&
(!renderer->AsKnowsCompositor() || XRE_IsParentProcess())) {
flags |= PaintFrameFlags::WidgetLayers;
}
}
} if (!(aFlags & RenderDocumentFlags::DrawCaret)) {
wouldFlushRetainedLayers = true;
flags |= PaintFrameFlags::HideCaret;
} if (aFlags & RenderDocumentFlags::IgnoreViewportScrolling) {
wouldFlushRetainedLayers = !IgnoringViewportScrolling();
mRenderingStateFlags |= RenderingStateFlags::IgnoringViewportScrolling;
} if (aFlags & RenderDocumentFlags::ResetViewportScrolling) {
wouldFlushRetainedLayers = true;
flags |= PaintFrameFlags::ResetViewportScrolling;
} if (aFlags & RenderDocumentFlags::DrawWindowNotFlushing) {
mRenderingStateFlags |= RenderingStateFlags::DrawWindowNotFlushing;
} if (aFlags & RenderDocumentFlags::DocumentRelative) { // XXX be smarter about this ... drawWindow might want a rect // that's "pretty close" to what our retained layer tree covers. // In that case, it wouldn't disturb normal rendering too much, // and we should allow it.
wouldFlushRetainedLayers = true;
flags |= PaintFrameFlags::DocumentRelative;
}
// Don't let drawWindow blow away our retained layer tree if ((flags & PaintFrameFlags::WidgetLayers) && wouldFlushRetainedLayers) {
flags &= ~PaintFrameFlags::WidgetLayers;
}
/* *ClipthedisplaylistaListtoarange.Returnstheclipped *rectanglesurroundingtherange.
*/
nsRect PresShell::ClipListToRange(nsDisplayListBuilder* aBuilder,
nsDisplayList* aList, nsRange* aRange) { // iterate though the display items and add up the bounding boxes of each. // This will allow the total area of the frames within the range to be // determined. To do this, remove an item from the bottom of the list, check // whether it should be part of the range, and if so, append it to the top // of the temporary list tmpList. If the item is a text frame at the end of // the selection range, clip it to the portion of the text frame that is // part of the selection. Then, append the wrapper to the top of the list. // Otherwise, just delete the item and don't append it.
nsRect surfaceRect;
for (nsDisplayItem* i : aList->TakeItems()) { if (i->GetType() == DisplayItemType::TYPE_CONTAINER) {
aList->AppendToTop(i);
surfaceRect.UnionRect(
surfaceRect, ClipListToRange(aBuilder, i->GetChildren(), aRange)); continue;
}
// itemToInsert indiciates the item that should be inserted into the // temporary list. If null, no item should be inserted.
nsDisplayItem* itemToInsert = nullptr;
nsIFrame* frame = i->Frame();
nsIContent* content = frame->GetContent(); if (content) { bool atStart =
content == aRange->GetMayCrossShadowBoundaryStartContainer(); bool atEnd = content == aRange->GetMayCrossShadowBoundaryEndContainer(); if ((atStart || atEnd) && frame->IsTextFrame()) { auto [frameStartOffset, frameEndOffset] = frame->GetOffsets();
int32_t highlightStart =
atStart ? std::max(static_cast<int32_t>(
aRange->MayCrossShadowBoundaryStartOffset()),
frameStartOffset)
: frameStartOffset;
int32_t highlightEnd =
atEnd ? std::min(static_cast<int32_t>(
aRange->MayCrossShadowBoundaryEndOffset()),
frameEndOffset)
: frameEndOffset; if (highlightStart < highlightEnd) { // determine the location of the start and end edges of the range.
nsPoint startPoint, endPoint;
frame->GetPointFromOffset(highlightStart, &startPoint);
frame->GetPointFromOffset(highlightEnd, &endPoint);
// The clip rectangle is determined by taking the the start and // end points of the range, offset from the reference frame. // Because of rtl, the end point may be to the left of (or above, // in vertical mode) the start point, so x (or y) is set to the // lower of the values.
nsRect textRect(aBuilder->ToReferenceFrame(frame), frame->GetSize()); if (frame->GetWritingMode().IsVertical()) {
nscoord y = std::min(startPoint.y, endPoint.y);
textRect.y += y;
textRect.height = std::max(startPoint.y, endPoint.y) - y;
} else {
nscoord x = std::min(startPoint.x, endPoint.x);
textRect.x += x;
textRect.width = std::max(startPoint.x, endPoint.x) - x;
}
surfaceRect.UnionRect(surfaceRect, textRect);
const ActiveScrolledRoot* asr = i->GetActiveScrolledRoot();
i->IntersectClip(aBuilder, newClipChain, true);
itemToInsert = i;
}
} // Don't try to descend into subdocuments. // If this ever changes we'd need to add handling for subdocuments with // different zoom levels. elseif (content->GetComposedDoc() ==
aRange->GetMayCrossShadowBoundaryStartContainer()
->GetComposedDoc()) { // if the node is within the range, append it to the temporary list bool before, after;
nsresult rv =
RangeUtils::CompareNodeToRange<TreeKind::ShadowIncludingDOM>(
content, aRange, &before, &after); if (NS_SUCCEEDED(rv) && !before && !after) {
itemToInsert = i; bool snap;
surfaceRect.UnionRect(surfaceRect, i->GetBounds(aBuilder, &snap));
}
}
}
// insert the item into the list if necessary. If the item has a child // list, insert that as well
nsDisplayList* sublist = i->GetSameCoordinateSystemChildren(); if (itemToInsert || sublist) {
aList->AppendToTop(itemToInsert ? itemToInsert : i); // if the item is a list, iterate over it as well if (sublist) {
surfaceRect.UnionRect(surfaceRect,
ClipListToRange(aBuilder, sublist, aRange));
}
} else { // otherwise, just delete the item and don't readd it to the list
i->Destroy(aBuilder);
}
}
// If the start or end of the range is the document, just use the root // frame, otherwise get the common ancestor of the two endpoints of the // range.
nsINode* startContainer = aRange->GetMayCrossShadowBoundaryStartContainer();
nsINode* endContainer = aRange->GetMayCrossShadowBoundaryEndContainer();
Document* doc = startContainer->GetComposedDoc(); if (startContainer == doc || endContainer == doc) {
ancestorFrame = rootFrame;
} else {
nsINode* ancestor =
nsContentUtils::GetClosestCommonShadowIncludingInclusiveAncestor(
startContainer, endContainer);
NS_ASSERTION(!ancestor || ancestor->IsContent(), "common ancestor is not content");
while (ancestor && ancestor->IsContent()) {
ancestorFrame = ancestor->AsContent()->GetPrimaryFrame(); if (ancestorFrame) { break;
}
// use the nearest ancestor frame that includes all continuations as the // root for building the display list while (ancestorFrame &&
nsLayoutUtils::GetNextContinuationOrIBSplitSibling(ancestorFrame)) {
ancestorFrame = ancestorFrame->GetParent();
}
}
if (!ancestorFrame) { return nullptr;
}
// get a display list containing the range auto info = MakeUnique<RangePaintInfo>(ancestorFrame);
info->mBuilder.SetIncludeAllOutOfFlows(); if (aForPrimarySelection) {
info->mBuilder.SetSelectedFramesOnly();
}
info->mBuilder.EnterPresShell(ancestorFrame);
auto BuildDisplayListForNode = [&](nsINode* aNode) { if (MOZ_UNLIKELY(!aNode->IsContent())) { return;
}
nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame(); // XXX deal with frame being null due to display:contents for (; frame;
frame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(frame)) { if (frame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { continue;
}
info->mBuilder.SetVisibleRect(frame->InkOverflowRect());
info->mBuilder.SetDirtyRect(frame->InkOverflowRect());
frame->BuildDisplayListForStackingContext(&info->mBuilder, &info->mList);
}
}; if (startContainer->NodeType() == nsINode::TEXT_NODE) {
BuildDisplayListForNode(startContainer);
} for (; !subtreeIter.IsDone(); subtreeIter.Next()) {
nsCOMPtr<nsINode> node = subtreeIter.GetCurrentNode();
BuildDisplayListForNode(node);
} if (endContainer != startContainer &&
endContainer->NodeType() == nsINode::TEXT_NODE) {
BuildDisplayListForNode(endContainer);
}
// If one of the ancestor presShells (including this one) has a resolution // set, we may have some APZ zoom applied. That means we may want to rasterize // the nodes at that zoom level. Populate `info` with the relevant information // so that the caller can decide what to do. Also wrap the display list in // appropriate nsDisplayAsyncZoom display items. This code handles the general // case with nested async zooms (even though that never actually happens), // because it fell out of the implementation for free. // // TODO: Do we need to do the same for ancestor transforms? for (nsPresContext* ctx = GetPresContext(); ctx;
ctx = ctx->GetParentPresContext()) {
PresShell* shell = ctx->PresShell(); float resolution = shell->GetResolution();
// If we are at the root document in the process, try to see if documents // in enclosing processes have a resolution and include that as well. if (!ctx->GetParentPresContext()) { // xScale is an arbitrary choice. Outside of edge cases involving CSS // transforms, xScale == yScale so it doesn't matter.
resolution *= ViewportUtils::TryInferEnclosingResolution(shell).xScale;
}
#ifdef DEBUG if (gDumpRangePaintList) {
fprintf(stderr, "CreateRangePaintInfo --- after ClipListToRange:\n");
nsIFrame::PrintDisplayList(&(info->mBuilder), info->mList);
} #endif
// determine the offset of the reference frame for the display list // to the root frame. This will allow the coordinates used when painting // to all be offset from the same point
info->mRootOffset = ancestorFrame->GetBoundingClientRect().TopLeft();
rangeRect.MoveBy(info->mRootOffset);
aSurfaceRect.UnionRect(aSurfaceRect, rangeRect);
// use the rectangle to create the surface
LayoutDeviceIntRect pixelArea = LayoutDeviceIntRect::FromAppUnitsToOutside(
aArea, pc->AppUnitsPerDevPixel());
// if the image should not be resized, scale must be 1 float scale = 1.0;
// check if the image should be resized bool resize = !!(aFlags & RenderImageFlags::AutoScale);
if (resize) { // check if image-resizing-algorithm should be used if (aFlags & RenderImageFlags::IsImage) { // get max screensize
int32_t maxWidth = pc->AppUnitsToDevPixels(maxSize.width);
int32_t maxHeight = pc->AppUnitsToDevPixels(maxSize.height); // resize image relative to the screensize // get best height/width relative to screensize float bestHeight = float(maxHeight) * RELATIVE_SCALEFACTOR; float bestWidth = float(maxWidth) * RELATIVE_SCALEFACTOR; // calculate scale for bestWidth float adjustedScale = bestWidth / float(pixelArea.width); // get the worst height (height when width is perfect) float worstHeight = float(pixelArea.height) * adjustedScale; // get the difference of best and worst height float difference = bestHeight - worstHeight; // halve the difference and add it to worstHeight to get // the best compromise between bestHeight and bestWidth, // then calculate the corresponding scale factor
adjustedScale = (worstHeight + difference / 2) / float(pixelArea.height); // prevent upscaling
scale = std::min(scale, adjustedScale);
} else { // get half of max screensize
int32_t maxWidth = pc->AppUnitsToDevPixels(maxSize.width >> 1);
int32_t maxHeight = pc->AppUnitsToDevPixels(maxSize.height >> 1); if (pixelArea.width > maxWidth || pixelArea.height > maxHeight) { // divide the maximum size by the image size in both directions. // Whichever direction produces the smallest result determines how much // should be scaled. if (pixelArea.width > maxWidth) {
scale = std::min(scale, float(maxWidth) / pixelArea.width);
} if (pixelArea.height > maxHeight) {
scale = std::min(scale, float(maxHeight) / pixelArea.height);
}
}
}
// Pick a resolution scale factor that is the highest we need for any of // the items. This means some items may get rendered at a higher-than-needed // resolution but at least nothing will be avoidably blurry. float resolutionScale = 1.0; for (const UniquePtr<RangePaintInfo>& rangeInfo : aItems) {
resolutionScale = std::max(resolutionScale, rangeInfo->mResolution);
} float unclampedResolution = resolutionScale; // Clamp the resolution scale so that `pixelArea` when scaled by `scale` and // `resolutionScale` isn't bigger than `maxSize`. This prevents creating // giant/unbounded images.
resolutionScale =
std::min(resolutionScale, maxSize.width / (scale * pixelArea.width));
resolutionScale =
std::min(resolutionScale, maxSize.height / (scale * pixelArea.height)); // The following assert should only get hit if pixelArea scaled by `scale` // alone would already have been bigger than `maxSize`, which should never // be the case. For release builds we handle gracefully by reverting // resolutionScale to 1.0 to avoid unexpected consequences.
MOZ_ASSERT(resolutionScale >= 1.0);
resolutionScale = std::max(1.0f, resolutionScale);
scale *= resolutionScale;
// Now we need adjust the output screen position of the surface based on the // scaling factor and any APZ zoom that may be in effect. The goal is here // to set `aScreenRect`'s top-left corner (in screen-relative LD pixels) // such that the scaling effect on the surface appears anchored at `aPoint` // ("anchor" here is like "transform-origin"). When this code is e.g. used // to generate a drag image for dragging operations, `aPoint` refers to the // position of the mouse cursor (also in screen-relative LD pixels), and the // user-visible effect of doing this is that the point at which the user // clicked to start the drag remains under the mouse during the drag.
// In order to do this we first compute the top-left corner of the // pixelArea is screen-relative LD pixels.
LayoutDevicePoint visualPoint = ViewportUtils::ToScreenRelativeVisual(
LayoutDevicePoint(pixelArea.TopLeft()), pc); // And then adjust the output screen position based on that, which we can do // since everything here is screen-relative LD pixels. Note that the scale // factor we use here is the effective "transform" scale applied to the // content we're painting, relative to the scale at which it would normally // get painted at as part of page rendering (`unclampedResolution`). float scaleRelativeToNormalContent = scale / unclampedResolution;
aScreenRect->x =
NSToIntFloor(aPoint.x - float(aPoint.x.value - visualPoint.x.value) *
scaleRelativeToNormalContent);
aScreenRect->y =
NSToIntFloor(aPoint.y - float(aPoint.y.value - visualPoint.y.value) *
scaleRelativeToNormalContent);
pixelArea.width = NSToIntFloor(float(pixelArea.width) * scale);
pixelArea.height = NSToIntFloor(float(pixelArea.height) * scale); if (!pixelArea.width || !pixelArea.height) { return nullptr;
}
} else { // move aScreenRect to the position of the surface in screen coordinates
LayoutDevicePoint visualPoint = ViewportUtils::ToScreenRelativeVisual(
LayoutDevicePoint(pixelArea.TopLeft()), pc);
aScreenRect->MoveTo(RoundedToInt(visualPoint));
}
aScreenRect->width = pixelArea.width;
aScreenRect->height = pixelArea.height;
if (aRegion) {
RefPtr<PathBuilder> builder = dt->CreatePathBuilder(FillRule::FILL_WINDING);
// Convert aRegion from CSS pixels to dev pixels
nsIntRegion region = aRegion->ToAppUnits(AppUnitsPerCSSPixel())
.ToOutsidePixels(pc->AppUnitsPerDevPixel()); for (auto iter = region.RectIter(); !iter.Done(); iter.Next()) { const IntRect& rect = iter.Get();
// translate so that points are relative to the surface area
gfxPoint surfaceOffset = nsLayoutUtils::PointToGfxPoint(
-aArea.TopLeft(), pc->AppUnitsPerDevPixel());
initialTM.PreTranslate(surfaceOffset);
// temporarily hide the selection so that text is drawn normally. If a // selection is being rendered, use that, otherwise use the presshell's // selection.
RefPtr<nsFrameSelection> frameSelection; if (aSelection) {
frameSelection = aSelection->GetFrameSelection();
} else {
frameSelection = FrameSelection();
}
int16_t oldDisplaySelection = frameSelection->GetDisplaySelection();
frameSelection->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN);
// next, paint each range in the selection for (const UniquePtr<RangePaintInfo>& rangeInfo : aItems) { // the display lists paint relative to the offset from the reference // frame, so account for that translation too:
gfxPoint rootOffset = nsLayoutUtils::PointToGfxPoint(
rangeInfo->mRootOffset, pc->AppUnitsPerDevPixel());
ctx.SetMatrixDouble(initialTM.PreTranslate(rootOffset));
aArea.MoveBy(-rangeInfo->mRootOffset.x, -rangeInfo->mRootOffset.y);
nsRegion visible(aArea);
rangeInfo->mList.PaintRoot(&rangeInfo->mBuilder, &ctx,
nsDisplayList::PAINT_DEFAULT, Nothing());
aArea.MoveBy(rangeInfo->mRootOffset.x, rangeInfo->mRootOffset.y);
}
// restore the old selection display state
frameSelection->SetDisplaySelection(oldDisplaySelection);
return dt->Snapshot();
}
already_AddRefed<SourceSurface> PresShell::RenderNode(
nsINode* aNode, const Maybe<CSSIntRegion>& aRegion, const LayoutDeviceIntPoint aPoint, LayoutDeviceIntRect* aScreenRect,
RenderImageFlags aFlags) { // area will hold the size of the surface needed to draw the node, measured // from the root frame.
nsRect area;
nsTArray<UniquePtr<RangePaintInfo>> rangeItems;
// nothing to draw if the node isn't in a document if (!aNode->IsInComposedDoc()) { return nullptr;
}
RefPtr<nsRange> range = nsRange::Create(aNode);
IgnoredErrorResult rv;
range->SelectNode(*aNode, rv); if (rv.Failed()) { return nullptr;
}
UniquePtr<RangePaintInfo> info = CreateRangePaintInfo(range, area, false); if (info) { // XXX(Bug 1631371) Check if this should use a fallible operation as it // pretended earlier, or change the return type to void.
rangeItems.AppendElement(std::move(info));
}
Maybe<CSSIntRegion> region = aRegion; if (region) { // combine the area with the supplied region
CSSIntRect rrectPixels = region->GetBounds();
nsPresContext* pc = GetPresContext(); if (!pc) { return nullptr;
}
// move the region so that it is offset from the topleft corner of the // surface
region->MoveBy(-nsPresContext::AppUnitsToIntCSSPixels(area.x),
-nsPresContext::AppUnitsToIntCSSPixels(area.y));
}
already_AddRefed<SourceSurface> PresShell::RenderSelection(
Selection* aSelection, const LayoutDeviceIntPoint aPoint,
LayoutDeviceIntRect* aScreenRect, RenderImageFlags aFlags) { // area will hold the size of the surface needed to draw the selection, // measured from the root frame.
nsRect area;
nsTArray<UniquePtr<RangePaintInfo>> rangeItems;
// iterate over each range and collect them into the rangeItems array. // This is done so that the size of selection can be determined so as // to allocate a surface area const uint32_t rangeCount = aSelection->RangeCount();
NS_ASSERTION(rangeCount > 0, "RenderSelection called with no selection"); for (const uint32_t r : IntegerRange(rangeCount)) {
MOZ_ASSERT(aSelection->RangeCount() == rangeCount);
RefPtr<nsRange> range = aSelection->GetRangeAt(r);
UniquePtr<RangePaintInfo> info = CreateRangePaintInfo(range, area, true); if (info) { // XXX(Bug 1631371) Check if this should use a fallible operation as it // pretended earlier.
rangeItems.AppendElement(std::move(info));
}
}
void PresShell::AddCanvasBackgroundColorItem(nsDisplayListBuilder* aBuilder,
nsDisplayList* aList,
nsIFrame* aFrame, const nsRect& aBounds,
nscolor aBackstopColor) { if (aBounds.IsEmpty() || !aFrame->IsViewportFrame()) { // We don't want to add an item for the canvas background color if the frame // (sub)tree we are painting doesn't include any canvas frames. return;
}
// With async scrolling, we'd like to have two instances of the background // color: one that scrolls with the content and one underneath which does not // scroll with the content, but which can be shown during checkerboarding and // overscroll and the dynamic toolbar movement. We can only do that if the // color is opaque. // // We also need to paint the background if CSS hasn't specified it (since // otherwise nsCanvasFrame might not paint it). Note that non-CSS-specified // backgrounds shouldn't ever be semi-transparent. constbool forceUnscrolledItem =
nsLayoutUtils::UsesAsyncScrolling(aFrame) && NS_GET_A(bgcolor) == 255; if (canvasBg.mCSSSpecified && !forceUnscrolledItem) { return;
}
bool PresShell::IsTransparentContainerElement() const { if (mDocument->IsInitialDocument()) { switch (StaticPrefs::layout_css_initial_document_transparency()) { case3: returntrue; case2: if (!mDocument->IsTopLevelContentDocument()) { returntrue;
}
[[fallthrough]]; case1: if (mDocument->IsLikelyContentInaccessibleTopLevelAboutBlank()) { returntrue;
}
[[fallthrough]]; default: break;
}
}
nsPresContext* pc = GetPresContext(); if (!pc->IsRootContentDocumentCrossProcess()) { if (mDocument->IsInChromeDocShell()) { returntrue;
} // Frames are transparent except if their used embedder color-scheme is // mismatched, in which case we use an opaque background to avoid // black-on-black or white-on-white text, see // https://github.com/w3c/csswg-drafts/issues/4772 if (BrowsingContext* bc = mDocument->GetBrowsingContext()) { switch (bc->GetEmbedderColorSchemes().mUsed) { case dom::PrefersColorSchemeOverride::Light: return pc->DefaultBackgroundColorScheme() == ColorScheme::Light; case dom::PrefersColorSchemeOverride::Dark: return pc->DefaultBackgroundColorScheme() == ColorScheme::Dark; case dom::PrefersColorSchemeOverride::None: break;
}
} returntrue;
}
nsIDocShell* docShell = pc->GetDocShell(); if (!docShell) { returnfalse;
}
nsPIDOMWindowOuter* pwin = docShell->GetWindow(); if (!pwin) { returnfalse;
} if (Element* containerElement = pwin->GetFrameElementInternal()) { return containerElement->HasAttr(nsGkAtoms::transparent);
} if (BrowserChild* tab = BrowserChild::GetFrom(docShell)) { // Check if presShell is the top PresShell. Only the top can influence the // canvas background color. returnthis == tab->GetTopLevelPresShell() && tab->IsTransparent();
} returnfalse;
}
static SingleCanvasBackground ComputeSingleCanvasBackground(nsIFrame* aCanvas) {
MOZ_ASSERT(aCanvas->IsCanvasFrame()); const nsIFrame* bgFrame = nsCSSRendering::FindBackgroundFrame(aCanvas); static constexpr nscolor kTransparent = NS_RGBA(0, 0, 0, 0); if (bgFrame->IsThemed()) { // Ignore the CSS background-color if `appearance` is used on the root. return {kTransparent, false};
} bool drawBackgroundImage = false; bool drawBackgroundColor = false;
nscolor color = nsCSSRendering::DetermineBackgroundColor(
aCanvas->PresContext(), bgFrame->Style(), aCanvas, drawBackgroundImage,
drawBackgroundColor); if (!drawBackgroundColor) { // No need to draw the CSS-specified background (or no CSS-specified // background at all). return {kTransparent, false};
} return {color, true};
}
PresShell::CanvasBackground PresShell::ComputeCanvasBackground() const { // If we have a frame tree and it has style information that // specifies the background color of the canvas, update our local // cache of that color.
nsIFrame* canvas = GetCanvasFrame(); if (!canvas) {
nscolor color = GetDefaultBackgroundColorToDraw(); // If the root element of the document (ie html) has style 'display: none' // then the document's background color does not get drawn; return the color // we actually draw. return {{color, false}, {color, false}};
}
auto viewportBg = ComputeSingleCanvasBackground(canvas); if (!IsTransparentContainerElement()) {
viewportBg.mColor =
NS_ComposeColors(GetDefaultBackgroundColorToDraw(), viewportBg.mColor);
} auto pageBg = viewportBg;
nsCanvasFrame* docElementCb =
mFrameConstructor->GetDocElementContainingBlock(); if (canvas != docElementCb) { // We're in paged mode / print / print-preview, and just computed the "root" // canvas background. Compute the doc element containing block background // too.
MOZ_ASSERT(mPresContext->IsRootPaginatedDocument());
pageBg = ComputeSingleCanvasBackground(docElementCb);
} return {viewportBg, pageBg};
}
nscolor PresShell::ComputeBackstopColor(nsIFrame* aDisplayRoot) {
nsIWidget* widget =
aDisplayRoot ? aDisplayRoot->GetNearestWidget() : GetNearestWidget(); if (widget &&
(widget->GetTransparencyMode() != widget::TransparencyMode::Opaque ||
widget->WidgetPaintsBackground())) { // Within a transparent widget, so the backstop color must be // totally transparent. return NS_RGBA(0, 0, 0, 0);
} // Within an opaque widget (or no widget at all), so the backstop // color must be totally opaque. The user's default background // as reported by the prescontext is guaranteed to be opaque. return GetDefaultBackgroundColorToDraw();
}
// GetResolution handles mResolution being nothing by returning 1 so this // is checking that the resolution is actually changing. bool resolutionUpdated = aResolution != GetResolution();
mLastResolutionChangeOrigin = aOrigin;
RenderingState state(this);
state.mResolution = Some(aResolution);
SetRenderingState(state); if (mMobileViewportManager) {
mMobileViewportManager->ResolutionUpdated(aOrigin);
} // Changing the resolution changes the visual viewport size which may // make the current visual viewport offset out-of-bounds (if the size // increased). APZ will reconcile this by sending a clamped visual // viewport offset on the next repaint, but to avoid main-thread code // observing an out-of-bounds offset until then, reclamp it here. if (IsVisualViewportOffsetSet()) {
SetVisualViewportOffset(GetVisualViewportOffset(),
GetLayoutViewportOffset());
} if (aOrigin == ResolutionChangeOrigin::Apz) {
mResolutionUpdatedByApz = true;
} elseif (resolutionUpdated) {
mResolutionUpdated = true;
}
if (auto* window = nsGlobalWindowInner::Cast(mDocument->GetInnerWindow())) {
window->VisualViewport()->PostResizeEvent();
}
void PresShell::SynthesizeMouseMove(bool aFromScroll) { if (!StaticPrefs::layout_reflow_synthMouseMove()) { return;
}
if (mPaintingSuppressed || !mIsActive || !mPresContext) { return;
}
if (!IsRoot()) { if (PresShell* rootPresShell = GetRootPresShell()) {
rootPresShell->SynthesizeMouseMove(aFromScroll);
} return;
}
if (mLastMousePointerId.isNothing() && mPointerIds.IsEmpty()) { // After a same-tab navigation, the new PresShell hasn't received // any mouse events yet so its per-PresShell pointer state is // empty. The static last-mouse state in PointerEventHandler may // still hold a valid position from before the navigation; if so, // claim it for this PresShell so cursor / :hover restyles can // take effect without requiring the user to move the mouse first // (bug 2038491). if (Maybe<uint32_t> claimedPointerId =
PointerEventHandler::TryClaimOrphanedLastMouseInfo(*this)) {
mLastMousePointerId = claimedPointerId;
} else { return;
}
}
if (!mSynthMouseMoveEvent.IsPending()) { auto ev = MakeRefPtr<nsSynthMouseMoveEvent>(this, aFromScroll);
void PresShell::ProcessSynthMouseMoveEvent(bool aFromScroll) { auto forgetMouseMove = MakeScopeExit([&]() { // Must be safe to refer `this` without grabbing it with a RefPtr since this // method is marked as MOZ_CAN_RUN_SCRIPT, and we don't want to use RefPtr // here because of a hot path
mSynthMouseMoveEvent.Forget();
}); // If drag session has started, we shouldn't synthesize mousemove event.
nsIWidget* widget = GetOwnWidget(); if (!widget) { return;
}
nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession(widget); if (dragSession) { // Don't forget it. We need to synthesize a mouse move when the drag // session ends.
forgetMouseMove.release(); return;
}
if (!mPresContext) { return;
}
// allow new event to be posted while handling this one only if the // source of the event is a scroll (to prevent infinite reflow loops) if (aFromScroll) {
mSynthMouseMoveEvent.Forget();
forgetMouseMove.release();
}
NS_ASSERTION(IsRoot(), "Only a root pres shell should be here");
if (StaticPrefs::dom_event_pointer_boundary_dispatch_when_layout_change()) { const AutoTArray<uint32_t, 16> pointerIds(mPointerIds.Clone()); for (const uint32_t pointerId : pointerIds) { const PointerInfo* const pointerInfo =
PointerEventHandler::GetPointerInfo(pointerId); if (MOZ_UNLIKELY(!pointerInfo) || !pointerInfo->HasLastState() || // We shouldn't dispatch pointer boundary events when a layout change // if the pointer is not a stationary device.
!pointerInfo->InputSourceSupportsHover()) { continue;
} // If the pointer is captured, we don't need to dispatch pointer boundary // events since pointer boundary events should be fired before // gotpointercapture.
PointerCaptureInfo* const captureInfo =
PointerEventHandler::GetPointerCaptureInfo(pointerId); if (captureInfo && captureInfo->mOverrideElement) { continue;
}
ProcessSynthMouseOrPointerMoveEvent(ePointerMove, pointerId,
*pointerInfo);
}
}
if (mLastMousePointerId.isSome()) { if (const PointerInfo* const lastMouseInfo =
PointerEventHandler::GetLastMouseInfo(this)) { if (lastMouseInfo->HasLastState()) {
ProcessSynthMouseOrPointerMoveEvent(eMouseMove, *mLastMousePointerId,
*lastMouseInfo);
}
}
}
}
void PresShell::ProcessSynthMouseOrPointerMoveEvent(
EventMessage aMoveMessage, uint32_t aPointerId, const PointerInfo& aPointerInfo) {
MOZ_ASSERT(aMoveMessage == eMouseMove || aMoveMessage == ePointerMove);
NS_ASSERTION(IsRoot(), "Only a root pres shell should be here");
// mRefPoint will be mMouseLocation relative to the widget of |view|, the // widget we will put in the event we dispatch, in widgetAPD appunits
nsPoint refpoint(0, 0);
nsIWidget* ownWidget = GetOwnWidget(); if (!ownWidget) { return;
}
MOZ_ASSERT(!nsCOMPtr{nsContentUtils::GetDragSession(ownWidget)});
// We need a widget to put in the event we are going to dispatch so we look // for a view that has a widget and the mouse location is over. We first look // for floating views, if there isn't one we use the root view. |view| holds // that view.
nsCOMPtr<nsIWidget> widget; // We always dispatch the event to the pres shell that contains the view that // the mouse is over. pointShell is that.
RefPtr<PresShell> pointShell; // The appunits per devpixel ratio of |widget|.
int32_t widgetAPD; // If we're in a child process and the view points to an OOP iframe, this is // its BrowserBridgeChild.
RefPtr<BrowserBridgeChild> bbc;
// We either dispatch the event to a popup, or a view.
nsMenuPopupFrame* popupFrame =
FindPopupFrame(mPresContext, ownWidget,
LayoutDeviceIntPoint::FromAppUnitsToNearest(
aPointerInfo.mLastRefPointInRootDoc, APD)); if (popupFrame) {
pointShell = popupFrame->PresShell();
widget = popupFrame->GetWidget();
widgetAPD = popupFrame->PresContext()->AppUnitsPerDevPixel();
refpoint = aPointerInfo.mLastRefPointInRootDoc;
DebugOnly<nsLayoutUtils::TransformResult> result =
nsLayoutUtils::TransformPoint(
RelativeTo{GetRootFrame(), ViewportType::Visual},
RelativeTo{popupFrame, ViewportType::Layout}, refpoint);
MOZ_ASSERT(result == nsLayoutUtils::TRANSFORM_SUCCEEDED);
} if (!widget) {
widget = ownWidget;
widgetAPD = APD;
pointShell = this;
refpoint = aPointerInfo.mLastRefPointInRootDoc;
}
NS_ASSERTION(widget, "view should have a widget here");
Maybe<WidgetMouseEvent> mouseMoveEvent;
Maybe<WidgetPointerEvent> pointerMoveEvent; if (aMoveMessage == eMouseMove) {
mouseMoveEvent.emplace(true, eMouseMove, widget,
WidgetMouseEvent::eSynthesized);
mouseMoveEvent->mButton = MouseButton::ePrimary; // We don't want to dispatch preceding pointer event since the caller // should've already been dispatched it. However, if the target is an OOP // iframe, we'll set this to true again below.
mouseMoveEvent->convertToPointer = false;
} else {
pointerMoveEvent.emplace(true, ePointerMove, widget);
pointerMoveEvent->mButton = MouseButton::eNotPressed;
pointerMoveEvent->mReason = WidgetMouseEvent::eSynthesized;
}
WidgetMouseEvent& event =
mouseMoveEvent ? mouseMoveEvent.ref() : pointerMoveEvent.ref();
// If the last cursor location was set by a synthesized mouse event for tests, // running test should expect a restyle or a DOM mutation under the cursor may // cause mouse boundary events in a remote process if the cursor is over a // remote content. Therefore, the events should not be ignored by // PresShell::HandleEvent in the remote process. So we need to mark the // synthesized eMouseMove as "synthesized for tests".
event.mFlags.mIsSynthesizedForTests = aPointerInfo.mIsSynthesizedForTests;
MOZ_ASSERT(pointShell); // Since this gets run in a refresh tick there isn't an InputAPZContext on // the stack from the nsIWidget. We need to simulate one with at least // the correct target guid, so that the correct callback transform gets // applied if this event goes to a child process. The input block id is set // to 0 because this is a synthetic event which doesn't really belong to any // input block. Same for the APZ response field.
InputAPZContext apzContext(aPointerInfo.mLastTargetGuid, 0,
nsEventStatus_eIgnore);
AUTO_PROFILER_MARKER_DOCSHELL("DispatchSynthMouseOrPointerMove", GRAPHICS,
pointShell->GetPresContext()->GetDocShell());
nsEventStatus status = nsEventStatus_eIgnore; if (auto* eventFrame = popupFrame ? popupFrame : GetRootFrame()) {
pointShell->HandleEvent(eventFrame, &event, false, &status);
}
}
// Use the presshell containing the frame.
PresShell* presShell = frame->PresShell();
MOZ_ASSERT(!presShell->AssumeAllFramesVisible()); if (presShell->mApproximatelyVisibleFrames.EnsureInserted(frame)) { // The frame was added to mApproximatelyVisibleFrames, so increment its // visible count.
frame->IncApproximateVisibleCount();
}
}
}
/* static */ void PresShell::DecApproximateVisibleCount(
VisibleFrames& aFrames, const Maybe<OnNonvisible>& aNonvisibleAction /* = Nothing() */) { for (nsIFrame* frame : aFrames) { // Decrement the frame's visible count if we're still tracking its // visibility. (We may not be, if the frame disabled visibility tracking // after we added it to the visible frames list.) if (frame->TrackingVisibility()) {
frame->DecApproximateVisibleCount(aNonvisibleAction);
}
}
}
// Remove the entries of the mApproximatelyVisibleFrames hashtable and put // them in oldApproxVisibleFrames.
VisibleFrames oldApproximatelyVisibleFrames =
std::move(mApproximatelyVisibleFrames);
// aRect is relative to aFrame // aPreserve3DRect is set upon entering a preserve3d context and it doesn't // change, it stays relative to the root frame in the preserve3d context. Any // frame that is in a preserve3d context ignores aRect but takes aPreserve3DRect // and transforms it from the root of the preserve3d context to itself // (nsDisplayTransform::UntransformRect does this by default), and passes the // result down as aRect (leaving aPreserve3DRect untouched). Additionally, we // descend into every frame inside the preserve3d context (we skip the rect // intersection test). Any frame that is not in a preserve3d context just uses // aRect and doesn't need to know about any of this, even if it's parent frame // is in the preserve3d context. Any frame that is extend3d (ie has preserve3d // transform style) but not combines3d (ie its either transformed or backface // visibility hidden and its parent has preserve3d style) forms the root of a // preserve3d context. And any frame that is combines3d is in a preserve3d // context. void PresShell::MarkFramesInSubtreeApproximatelyVisible(
nsIFrame* aFrame, const nsRect& aRect, const nsRect& aPreserve3DRect, bool aRemoveOnly /* = false */) {
MOZ_DIAGNOSTIC_ASSERT(aFrame, "aFrame arg should be a valid frame pointer");
MOZ_ASSERT(aFrame->PresShell() == this, "wrong presshell");
if (aFrame->TrackingVisibility() && aFrame->StyleVisibility()->IsVisible() &&
(!aRemoveOnly ||
aFrame->GetVisibility() == Visibility::ApproximatelyVisible)) {
MOZ_ASSERT(!AssumeAllFramesVisible()); if (mApproximatelyVisibleFrames.EnsureInserted(aFrame)) { // The frame was added to mApproximatelyVisibleFrames, so increment its // visible count.
aFrame->IncApproximateVisibleCount();
}
}
nsSubDocumentFrame* subdocFrame = do_QueryFrame(aFrame); if (subdocFrame) {
PresShell* presShell = subdocFrame->GetSubdocumentPresShellForPainting(
nsSubDocumentFrame::IGNORE_PAINT_SUPPRESSION); if (presShell && !presShell->AssumeAllFramesVisible()) {
nsRect rect = aRect;
nsIFrame* root = presShell->GetRootFrame(); if (root) {
rect.MoveBy(aFrame->GetOffsetToCrossDoc(root));
} else {
rect.MoveBy(-aFrame->GetContentRectRelativeToSelf().TopLeft());
}
rect = rect.ScaleToOtherAppUnitsRoundOut(
aFrame->PresContext()->AppUnitsPerDevPixel(),
presShell->GetPresContext()->AppUnitsPerDevPixel());
if (ScrollContainerFrame* scrollFrame = do_QueryFrame(aFrame)) { bool ignoreDisplayPort = false; if (DisplayPortUtils::IsMissingDisplayPortBaseRect(aFrame->GetContent())) { // We can properly set the base rect for root scroll frames on top level // and root content documents. Otherwise the base rect we compute might // be way too big without the limiting that // ScrollContainerFrame::DecideScrollableLayer does, so we just ignore the // displayport in that case.
nsPresContext* pc = aFrame->PresContext(); if (scrollFrame->IsRootScrollFrameOfDocument() &&
(pc->IsRootContentDocumentCrossProcess() ||
(pc->IsChrome() && !pc->GetParentPresContext()))) {
nsRect baseRect(
nsPoint(), nsLayoutUtils::CalculateCompositionSizeForFrame(aFrame));
DisplayPortUtils::SetDisplayPortBase(aFrame->GetContent(), baseRect);
} else {
ignoreDisplayPort = true;
}
}
if (usingDisplayport) {
rect = displayPort;
} else {
rect = rect.Intersect(scrollFrame->GetScrollPortRect());
}
rect = scrollFrame->ExpandRectToNearlyVisible(rect);
}
for (constauto& [list, listID] : aFrame->ChildLists()) { for (nsIFrame* child : list) { // Note: This assert should be trivially satisfied, just by virtue of how // nsFrameList and its iterator works (with nullptr being an end-of-list // sentinel which should terminate the loop). But we do somehow get // crash reports inside this loop that suggest `child` is null...
MOZ_DIAGNOSTIC_ASSERT(child, "shouldn't have null values in child lists");
nsIFrame* rootFrame = GetRootFrame(); if (!rootFrame) { return;
}
// Remove the entries of the mApproximatelyVisibleFrames hashtable and put // them in oldApproximatelyVisibleFrames.
VisibleFrames oldApproximatelyVisibleFrames =
std::move(mApproximatelyVisibleFrames);
nsRect vis(nsPoint(0, 0), rootFrame->GetSize()); if (aRect) {
vis = *aRect;
}
// If we are in-process root but not the top level content, we need to take // the intersection with the iframe visible rect. if (mPresContext->IsRootContentDocumentInProcess() &&
!mPresContext->IsRootContentDocumentCrossProcess()) { // There are two possibilities that we can't get the iframe's visible // rect other than the iframe is out side of ancestors' display ports. // a) the BrowserChild is being torn down // b) the visible rect hasn't been delivered the BrowserChild // In both cases we consider the visible rect is empty.
Maybe<nsRect> visibleRect; if (BrowserChild* browserChild = BrowserChild::GetFrom(this)) {
visibleRect = browserChild->GetVisibleRect();
}
vis = vis.Intersect(visibleRect.valueOr(nsRect()));
}
#ifdef DEBUG_FRAME_VISIBILITY_DISPLAY_LIST // This can be used to debug the frame walker by comparing beforeFrameList // and mApproximatelyVisibleFrames in RebuildFrameVisibilityDisplayList to see // if they produce the same results (mApproximatelyVisibleFrames holds the // frames the display list thinks are visible, beforeFrameList holds the // frames the frame walker thinks are visible).
nsDisplayListBuilder builder(
rootFrame, nsDisplayListBuilderMode::FRAME_VISIBILITY, false);
nsRect updateRect(nsPoint(0, 0), rootFrame->GetSize());
nsIFrame* rootScroll = GetRootScrollFrame(); if (rootScroll) {
nsIContent* content = rootScroll->GetContent(); if (content) {
(void)nsLayoutUtils::GetDisplayPortForVisibilityTesting(
content, &updateRect, RelativeTo::ScrollFrame);
}
// We assume all frames are visible in print, print preview, chrome, and // resource docs and don't keep track of them. if (mPresContext->Type() == nsPresContext::eContext_PrintPreview ||
mPresContext->Type() == nsPresContext::eContext_Print ||
mPresContext->IsChrome() || mDocument->IsResourceDoc()) { returntrue;
}
// If we're assuming all frames are visible in the top level content // document, we need to in subdocuments as well. Otherwise we can get in a // situation where things like animations won't work in subdocuments because // their frames appear not to be visible, since we won't schedule an image // visibility update if the top level content document is assuming all // frames are visible. // // Note that it's not safe to call IsRootContentDocumentInProcess() if we're // currently being destroyed, so we have to check that first. if (!mHaveShutDown && !mIsDestroying &&
!mPresContext->IsRootContentDocumentInProcess()) {
nsPresContext* presContext =
mPresContext->GetInProcessRootContentDocumentPresContext(); if (presContext && presContext->PresShell()->AssumeAllFramesVisible()) { returntrue;
}
}
returnfalse;
}
void PresShell::ScheduleApproximateFrameVisibilityUpdateSoon() { if (AssumeAllFramesVisible()) { return;
}
if (!mPresContext) { return;
}
nsRefreshDriver* refreshDriver = mPresContext->RefreshDriver(); if (!refreshDriver) { return;
}
// Ask the refresh driver to update frame visibility soon.
refreshDriver->ScheduleFrameVisibilityUpdate();
}
void PresShell::ScheduleApproximateFrameVisibilityUpdateNow() { if (AssumeAllFramesVisible()) { return;
}
if (!mPresContext->IsRootContentDocumentInProcess()) {
nsPresContext* presContext =
mPresContext->GetInProcessRootContentDocumentPresContext(); if (!presContext) { return;
}
MOZ_ASSERT(presContext->IsRootContentDocumentInProcess(), "Didn't get a root prescontext from " "GetInProcessRootContentDocumentPresContext?");
presContext->PresShell()->ScheduleApproximateFrameVisibilityUpdateNow(); return;
}
if (mHaveShutDown || mIsDestroying) { return;
}
if (mUpdateApproximateFrameVisibilityEvent.IsPending()) { return;
}
if (NS_SUCCEEDED(rv)) {
mUpdateApproximateFrameVisibilityEvent = std::move(event);
}
}
void PresShell::EnsureFrameInApproximatelyVisibleList(nsIFrame* aFrame) { if (!aFrame->TrackingVisibility()) { return;
}
if (AssumeAllFramesVisible()) {
aFrame->IncApproximateVisibleCount(); return;
}
#ifdef DEBUG // Make sure it's in this pres shell.
nsCOMPtr<nsIContent> content = aFrame->GetContent(); if (content) {
PresShell* presShell = content->OwnerDoc()->GetPresShell();
MOZ_ASSERT(!presShell || presShell == this, "wrong shell");
} #endif
if (mApproximatelyVisibleFrames.EnsureInserted(aFrame)) { // We inserted a new entry.
aFrame->IncApproximateVisibleCount();
}
}
void PresShell::RemoveFrameFromApproximatelyVisibleList(nsIFrame* aFrame) { #ifdef DEBUG // Make sure it's in this pres shell.
nsCOMPtr<nsIContent> content = aFrame->GetContent(); if (content) {
PresShell* presShell = content->OwnerDoc()->GetPresShell();
MOZ_ASSERT(!presShell || presShell == this, "wrong shell");
} #endif
if (AssumeAllFramesVisible()) {
MOZ_ASSERT(mApproximatelyVisibleFrames.Count() == 0, "Shouldn't have any frames in the table"); return;
}
if (mApproximatelyVisibleFrames.EnsureRemoved(aFrame) &&
aFrame->TrackingVisibility()) { // aFrame was in the hashtable, and we're still tracking its visibility, // so we need to decrement its visible count.
aFrame->DecApproximateVisibleCount();
}
}
NS_ASSERTION(aRenderer, "Must be in paint event"); if (aRenderer->AsFallback()) { // The fallback renderer doesn't do any retaining, so we just need to // notify the view and widget that we're invalid, and we'll do a // paint+composite from the PaintWindow callback. if (nsIWidget* widget = aFrame ? aFrame->GetOwnWidget() : nullptr) { auto bounds = widget->GetBounds();
widget->Invalidate(LayoutDeviceIntRect({}, bounds.Size()));
} return;
}
// Otherwise we're a retained WebRenderLayerManager, so we want to call // Paint to update with any changes and push those to WR.
PaintInternalFlags flags = PaintInternalFlags::None; if (aFlags & PaintFlags::PaintSyncDecodeImages) {
flags |= PaintInternalFlags::PaintSyncDecodeImages;
} if (aFlags & PaintFlags::PaintCompositeOffscreen) {
flags |= PaintInternalFlags::PaintCompositeOffscreen;
}
PaintInternal(aFrame, aRenderer, flags);
}
// On Android, Flash can call into content JS during painting, so we can't // assert there. However, we don't rely on this assertion on Android because // we don't paint while JS is running. #if !defined(MOZ_WIDGET_ANDROID) if (!(aFlags & PaintInternalFlags::PaintComposite)) { // We need to allow content JS when the flag is set since we may trigger // MozAfterPaint events in content in those cases.
nojs.emplace(dom::danger::GetJSContext());
} #endif
NS_ASSERTION(!mIsDestroying, "painting a destroyed PresShell");
NS_ASSERTION(aRenderer, "null renderer");
MOZ_ASSERT(!mApproximateFrameVisibilityVisited, "Should have been cleared");
if (!mIsActive) { return;
}
FocusTarget focusTarget; if (StaticPrefs::apz_keyboard_enabled_AtStartup()) { // Update the focus target for async keyboard scrolling. This will be // forwarded to APZ by nsDisplayList::PaintRoot. We need to to do this // before we enter the paint phase because dispatching eVoid events can // cause layout to happen.
uint64_t focusSequenceNumber = mAPZFocusSequenceNumber; if (nsMenuPopupFrame* popup = do_QueryFrame(aFrame)) {
focusSequenceNumber = popup->GetAPZFocusSequenceNumber();
}
focusTarget = FocusTarget(this, focusSequenceNumber);
}
// Whether or not we should set first paint when painting is suppressed // is debatable. For now we'll do it because B2G relied on first paint // to configure the viewport and we only want to do that when we have // real content to paint. See Bug 798245 if (mIsFirstPaint && !mPaintingSuppressed) {
MOZ_LOG(gLog, LogLevel::Debug,
("PresShell::Paint, first paint, this=%p", this));
if (layerManager) {
layerManager->SetIsFirstPaint();
}
mIsFirstPaint = false;
}
if (!aRenderer->BeginTransaction(url)) { return;
}
// Send an updated focus target with this transaction. Be sure to do this // before we paint in the case this is an empty transaction. if (layerManager) {
layerManager->SetFocusTarget(focusTarget);
}
if (aFrame) { if (!(aFlags & PaintInternalFlags::PaintSyncDecodeImages) &&
!aFrame->HasAnyStateBits(NS_FRAME_UPDATE_LAYER_TREE)) { if (layerManager) {
layerManager->SetTransactionIdAllocator(presContext->RefreshDriver());
}
// We force sync-decode for printing / print-preview (printing already does // this from nsPageSequenceFrame::PrintNextSheet). // We also force sync-decoding via pref for reftests. if (aFlags & PaintInternalFlags::PaintSyncDecodeImages ||
mDocument->IsStaticDocument() ||
StaticPrefs::image_testing_decode_sync_enabled()) {
flags |= PaintFrameFlags::SyncDecodeImages;
} if (aFlags & PaintInternalFlags::PaintCompositeOffscreen) {
flags |= PaintFrameFlags::CompositeOffscreen;
} if (aRenderer->GetBackendType() == layers::LayersBackend::LAYERS_WR) {
flags |= PaintFrameFlags::ForWebRender;
}
if (aFrame) { // We can paint directly into the widget using its layer manager.
SelectionNodeCache cache(*this);
nsLayoutUtils::PaintFrame(nullptr, aFrame, nsRegion(), bgcolor,
nsDisplayListBuilderMode::Painting, flags); return;
}
// static void PresShell::SetCapturingContent(nsIContent* aContent, CaptureFlags aFlags,
WidgetEvent* aEvent) { // If capture was set for pointer lock, don't unlock unless we are coming // out of pointer lock explicitly. if (!aContent && sCapturingContentInfo.mPointerLock &&
!(aFlags & CaptureFlags::PointerLock)) { return;
}
nsIFrame* PresShell::GetCurrentEventFrame() { if (MOZ_UNLIKELY(mIsDestroying)) { return nullptr;
}
// GetCurrentEventContent() makes sure the content is still in the // same document that this pres shell belongs to. If not, then the // frame shouldn't get an event, nor should we even assume its safe // to try and find the frame.
nsIContent* content = GetCurrentEventContent(); if (!mCurrentEventTarget.mFrame && content) {
mCurrentEventTarget.mFrame = content->GetPrimaryFrame();
MOZ_ASSERT_IF(
mCurrentEventTarget.mFrame,
mCurrentEventTarget.mFrame->PresContext()->GetPresShell() == this);
} return mCurrentEventTarget.mFrame;
}
nsIContent* PresShell::GetExplicitEventTargetContent( const WidgetEvent* aEvent /* = nullptr */) {
nsIContent* content = GetCurrentEventContent(); if (!content) { if (nsIFrame* currentEventFrame = GetCurrentEventFrame()) {
content = currentEventFrame->GetExplicitEventTargetContent(aEvent);
NS_ASSERTION(!content || content->GetComposedDoc() == mDocument, "handing out content from a different doc");
}
} return content;
}
void PresShell::PushCurrentEventInfo(const EventTargetInfo& aInfo) { if (mCurrentEventTarget.IsSet()) { // XXX Why do we insert first item instead of append it? This requires to // move the previous items...
mCurrentEventTargetStack.InsertElementAt(0, std::move(mCurrentEventTarget));
}
mCurrentEventTarget = aInfo;
}
if (!mCurrentEventTargetStack.IsEmpty()) {
mCurrentEventTarget = std::move(mCurrentEventTargetStack[0]);
mCurrentEventTargetStack.RemoveElementAt(0);
// Don't use it if it has moved to a different document. if (mCurrentEventTarget.mContent &&
mCurrentEventTarget.mContent->GetComposedDoc() != mDocument) {
mCurrentEventTarget.Clear();
}
}
}
// static bool PresShell::EventHandler::InZombieDocument(nsIContent* aContent) { // If a content node points to a null document, or the document is not // attached to a window, then it is possibly in a zombie document, // about to be replaced by a newly loading document. // Such documents cannot handle DOM events. // It might actually be in a node not attached to any document, // in which case there is not parent presshell to retarget it to.
Document* doc = aContent->GetComposedDoc(); return !doc || !doc->GetWindow();
}
// If we don't have DOM window, we're zombie, we should find the root window // with our parent shell.
RefPtr<PresShell> parentPresShell = GetParentPresShellForEventHandling();
NS_ENSURE_TRUE(parentPresShell, nullptr); return parentPresShell->GetRootWindow();
}
already_AddRefed<PresShell> PresShell::GetParentPresShellForEventHandling() { if (!mPresContext) { return nullptr;
}
// Now, find the parent pres shell and send the event there
RefPtr<nsDocShell> docShell = mPresContext->GetDocShell(); if (!docShell) {
docShell = mForwardingContainer.get();
}
// Might have gone away, or never been around to start with if (!docShell) { return nullptr;
}
BrowsingContext* bc = docShell->GetBrowsingContext(); if (!bc) { return nullptr;
}
nsresult PresShell::EventHandler::RetargetEventToParent(
WidgetGUIEvent* aGUIEvent, nsEventStatus* aEventStatus) { // Send this events straight up to the parent pres shell. // We do this for keystroke events in zombie documents or if either a frame // or a root content is not present. // That way at least the UI key bindings can work.
// Fake the event as though it's from the parent pres shell's root frame. return parentPresShell->HandleEvent(parentPresShell->GetRootFrame(),
aGUIEvent, true, aEventStatus);
}
constauto StoreLastPointerEventLocation =
[&](const WidgetMouseEvent& aMouseOrPointerEvent) { // TODO: instead, encapsulate mMouseLocation and // `mLastOverWindowPointerLocation` in a struct.
mLastOverWindowPointerLocation = GetEventLocation(aMouseOrPointerEvent);
};
switch (aEvent->mMessage) { case eMouseMove: case eMouseEnterIntoWidget: case eMouseDown: case eMouseUp: case eDragEnter: case eDragStart: case eDragOver: case eDrop: { const WidgetMouseEvent& mouseEvent = *aEvent->AsMouseEvent(); if (StoreMouseLocation(mouseEvent) &&
(aEvent->mMessage == eMouseEnterIntoWidget ||
aEvent->mClass == eDragEventClass)) {
SynthesizeMouseMove(false);
} // In a drag session, we won't dispatch pointer events except // ePointerCancel immediately after eDragStart. However, once the drag // session ends, we want to synthesize ePointerMove at the dropped point. // Therefore, we should update the last state of the pointer when we start // handling a drag event. // We also need to store the pointer location for eMouseEnterIntoWidget, // so that the pointer boundary event can be generated earlier. if (aEvent->mMessage == eMouseEnterIntoWidget ||
aEvent->mClass == eDragEventClass) {
StorePointerLocation(mouseEvent);
} break;
} case eDragExit: { const WidgetMouseEvent& mouseEvent = *aEvent->AsMouseEvent(); if (aEvent->mRelatedTarget) { // not exit from the widget break;
}
ClearMouseLocation(mouseEvent);
ClearPointerLocation(mouseEvent); break;
} case eMouseExitFromWidget: { const WidgetMouseEvent& mouseEvent = *aEvent->AsMouseEvent(); // Although we only care about the mouse moving into an area for which // this pres shell doesn't receive mouse move events, we don't check which // widget the mouse exit was for since this seems to vary by platform. // Hopefully this won't matter at all since we'll get the mouse move or // enter after the mouse exit when the mouse moves from one of our widgets // into another.
ClearMouseLocation(mouseEvent);
ClearPointerLocation(mouseEvent); break;
} case ePointerMove: case ePointerRawUpdate: case eMouseRawUpdate: { const WidgetMouseEvent& mouseEvent = *aEvent->AsMouseEvent(); if (!mouseEvent.IsReal()) { break;
}
StoreLastPointerEventLocation(mouseEvent); if (const WidgetPointerEvent* const pointerEvent =
mouseEvent.AsPointerEvent()) {
StorePointerLocation(*pointerEvent);
} break;
} case ePointerDown: { const WidgetPointerEvent& pointerEvent = *aEvent->AsPointerEvent();
StoreLastPointerEventLocation(pointerEvent);
StorePointerLocation(pointerEvent); break;
} case ePointerUp: { const WidgetPointerEvent& pointerEvent = *aEvent->AsPointerEvent();
StoreLastPointerEventLocation(pointerEvent); // If the pointer supports hover, we need to keep storing the last // position to synthesize ePointerMove after layout changes. if (pointerEvent.InputSourceSupportsHover()) {
StorePointerLocation(pointerEvent);
} // If the pointer does not support hover, we won't synthesize ePointerMove // for that. So, we can clear the pointer location. else {
ClearPointerLocation(pointerEvent);
} // If the pointer is for a touch, we need to forget the last state of // mMouseLocation if it was set by a touch because the touch is being // removed from the active pointers.
ClearMouseLocationIfSetByTouch(pointerEvent); break;
} case ePointerCancel: { // If a touch is canceled, it means that the touch input is tracked by a // gesture like swipe to scroll, pinch to zoom or DnD. So, it means that // a normal touch sequence finished. Then, we shouldn't give `:hover` // state to the element underneath the last touch point anymore. For // example, it's odd that new element which comes underneath the first // touch position gets `:hover` style even though the scroll is caused // by swipe (i.e., has moved the touch position).
ClearMouseLocationIfSetByTouch(*aEvent->AsPointerEvent()); break;
} default: break;
}
}
// static void PresShell::RecordModifiers(WidgetGUIEvent* aEvent) { switch (aEvent->mMessage) { case eKeyPress: case eKeyUp: case eKeyDown: case eMouseMove: case eMouseUp: case eMouseDown: case eMouseEnterIntoWidget: case eMouseExitFromWidget: case eMouseActivate: case eMouseTouchDrag: case eMouseLongTap: case eMouseRawUpdate: case eMouseExploreByTouch: case ePointerCancel: case eContextMenu: case eTouchStart: case eTouchMove: case eTouchEnd: case eTouchCancel: case eTouchPointerCancel: case eTouchRawUpdate: case eWheel:
sCurrentModifiers = aEvent->AsInputEvent()->mModifiers; break; default: break;
}
}
/* static */
PresShell* PresShell::GetShellForTouchEvent(WidgetGUIEvent* aEvent) { switch (aEvent->mMessage) { case eTouchMove: case eTouchRawUpdate: case eTouchCancel: case eTouchEnd: { // get the correct shell to dispatch to
WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent(); for (dom::Touch* touch : touchEvent->mTouches) { if (!touch) { return nullptr;
}
RefPtr<dom::Touch> oldTouch =
TouchManager::GetCapturedTouch(touch->Identifier()); if (!oldTouch) { return nullptr;
}
// Running tests must not expect that some mouse boundary events are fired // when something occurs in the parent process, e.g., when a popup is // opened/closed at the last mouse cursor position in the parent process (the // position may be different from the position which stored in this process). // Therefore, let's ignore synthesized mouse events coming form another // process if and only if they are not caused by the API. if (aGUIEvent->CameFromAnotherProcess() && XRE_IsContentProcess() &&
!aGUIEvent->mFlags.mIsSynthesizedForTests) { const PointerInfo* const lastMouseInfo =
PointerEventHandler::GetLastMouseInfo(); if (lastMouseInfo && lastMouseInfo->mIsSynthesizedForTests) { switch (aGUIEvent->mMessage) { // Synthesized eMouseMove will case mouse boundary events like // mouseover, mouseout, and :hover state is changed at dispatching the // events. case eMouseMove: // eMouseExitFromWidget comes from the parent process if the cursor // crosses a puppet widget boundary. Then, the event will be handled as // a synthesized eMouseMove in this process and may cause unexpected // `mouseout` and `mouseleave`. case eMouseExitFromWidget: // eMouseEnterIntoWidget causes updating the hover state under the event // position which may be different from the last cursor position // synthesized in this process. case eMouseEnterIntoWidget: if (!aGUIEvent->AsMouseEvent()->IsReal()) { return NS_OK;
} break; default: break;
}
}
}
// Here we are granting some delays to ensure that user input events are // created while the page content may not be visible to the user are not // processed. // The main purpose of this is to avoid user inputs are handled in the // new document where as the user inputs were originally targeting some // content in the old document. if (!CanHandleUserInputEvents(aGUIEvent)) { return NS_OK;
}
// If there is a composition and we got a pointing device events which may not // impossible to continue the composition, we should notify the editor of the // event before dispatching it. Then, composition will be commited before // the editor loses focus. This behavior is compatible with Chrome. // FIXME: Perhaps, we should do same thing before dispatching touch events. switch (aGUIEvent->mMessage) { case eMouseDown: case eMouseUp: {
nsPIDOMWindowOuter* const focusedWindow =
nsFocusManager::GetFocusedWindowStatic(); if (!focusedWindow) { break;
}
Document* const focusedDocument = focusedWindow->GetExtantDoc(); if (!focusedDocument) { break;
}
nsPresContext* const focusedPresContext =
focusedDocument->GetPresContext(); if (!focusedPresContext) { break;
} const RefPtr<TextComposition> textComposition =
IMEStateManager::GetTextCompositionFor(focusedPresContext); if (!textComposition) { break;
} // If there is a composition and it's managed by an editor, let's notify // the editor of mouse button event. The editor commits the composition // unless IME consumes the event. if (RefPtr<EditorBase> editorBase = textComposition->GetEditorBase()) {
MOZ_ASSERT(aGUIEvent->AsMouseEvent());
editorBase->WillHandleMouseButtonEvent(*aGUIEvent->AsMouseEvent());
} // Otherwise, we should commit the orphan composition instead. elseif (nsCOMPtr<nsIWidget> widget = textComposition->GetWidget()) {
textComposition->RequestToCommit(widget, false);
} if (!CanHandleUserInputEvents(aGUIEvent)) { return NS_OK;
} // Before bug 1945988, we dispatched the mouse button event without // committing composition and the editor will commit composition. // However, for compatibility with the other browsers, we started // committing composition before dispatching the mouse event. To keep // the traditional behavior, we should compute aFrameForPresShell if it's // reframed by dispatching the composition events (and input events) // above. Otherwise, we stop dispatching the mouse button events newly in // such case. if (MOZ_UNLIKELY(!weakFrameForPresShell.IsAlive())) {
FlushPendingNotifications(FlushType::Layout); if (MOZ_UNLIKELY(IsDestroying())) { return NS_OK;
}
nsIFrame* const newFrameForPresShell = GetRootFrame(); if (MOZ_UNLIKELY(!newFrameForPresShell)) { return NS_OK;
}
weakFrameForPresShell = newFrameForPresShell;
} break;
} default: break;
}
if (mPresContext) { switch (aGUIEvent->mMessage) { case eMouseMove: case eMouseRawUpdate: if (!aGUIEvent->AsMouseEvent()->IsReal()) { break;
}
[[fallthrough]]; case eMouseDown: case eMouseUp: { // We should flush pending mousemove event now because some mouse // boundary events which should've already been dispatched before a user // input may have not been dispatched. E.g., if a mousedown event // listener removed or appended an element under the cursor and mouseup // event comes immediately after that, mouseover or mouseout may have // not been dispatched on the new element yet. // XXX If eMouseMove is not propery dispatched before eMouseDown and // a `mousedown` event listener removes the event target or its // ancestor, eMouseOver will be dispatched between eMouseDown and // eMouseUp. That could cause unexpected behavior if a `mouseover` // event listener assumes it's always disptached before `mousedown`. // However, we're not sure whether it could happen with users' input. // FIXME: Perhaps, we need to do this for all events which are directly // caused by user input, e.g., eKeyDown, etc. const RefPtr<PresShell> rootPresShell =
IsRoot() ? this : GetRootPresShell(); if (rootPresShell && rootPresShell->mSynthMouseMoveEvent.IsPending()) {
RefPtr<nsSynthMouseMoveEvent> synthMouseMoveEvent =
rootPresShell->mSynthMouseMoveEvent.get();
synthMouseMoveEvent->Run(); if (IsDestroying()) { return NS_OK;
} // XXX If the frame or "this" is reframed, it might be better to // recompute the frame. However, it could treat the user input on // unexpected element. Therefore, we should not do that until we'd // get a bug report caused by that. if (MOZ_UNLIKELY(!weakFrameForPresShell.IsAlive())) { return NS_OK;
}
} break;
} default: break;
}
}
// If the event may cause ePointerMove, we need to dispatch ePointerRawUpdate // before that if and only if there are some `pointerrawupdate` event // listeners. Note that if a `pointerrawupdate` event listener destroys its // document/window, we need to dispatch the following pointer event (e.g., // ePointerMove) in the parent document/window with the parent PresShell. // Therefore, we need to consider the target PresShell for each event // (ePointerRawUpdate and the following pointer event) in // EventHandler::HandleEvent(). Thus, we need to dispatch the internal event // for ePointerRawUpdate before calling EventHandler::HandleEvent() below. if (!aDontRetargetEvents &&
StaticPrefs::dom_event_pointer_rawupdate_enabled()) {
nsresult rv = EnsurePrecedingPointerRawUpdate(
weakFrameForPresShell, *aGUIEvent, aDontRetargetEvents); if (NS_FAILED(rv)) { return rv;
} if (!CanHandleUserInputEvents(aGUIEvent)) { return NS_OK;
}
}
// We should not dispatch ePointerRawUpdate directly because dispatching // it requires some steps which are defined by "fire a pointer event" section // in the spec. https://w3c.github.io/pointerevents/#dfn-fire-a-pointer-event // We handle the steps when we call DispatchPrecedingPointerEvent(). // Therefore, this method dispatches eMouseRawUpdate or eTouchRawUpdate event // if the event should follow a ePointerRawUpdate. Then, // HandleEventUsingCoordinates() will stop handling the internal events after // calling DispatchPrecedingPointerEvent().
// If no window in the browser child has `pointerrawupdate` event listener, // we should do nothing. if (auto* const browserChild = BrowserChild::GetFrom(this)) { if (!browserChild->HasPointerRawUpdateEventListeners()) { return NS_OK;
}
}
if (const WidgetMouseEvent* const mouseEvent = aSourceEvent.AsMouseEvent()) { // If `convertToPointer` is `false`, it means that we've already handled the // event to dispatch a preceding pointer event. Therefore, its preceding // event should've already been handled. // If `convertToPointerRawUpdate` is `false`, it means that the event was in // the queue in BrowserChild and BrowserChild has already dispatched // `eMouseRawUpdate`. Therefore, we don't need to dispatch it again here. if (mouseEvent->IsSynthesized() || !mouseEvent->convertToPointer ||
!mouseEvent->convertToPointerRawUpdate) { return NS_OK;
}
WidgetMouseEvent mouseRawUpdateEvent(*mouseEvent);
mouseRawUpdateEvent.mMessage = eMouseRawUpdate;
mouseRawUpdateEvent.mCoalescedWidgetEvents = nullptr; // PointerEvent.button of `pointerrawupdate` should always be -1 if the // source event is not eMouseDown nor eMouseUp. PointerEventHandler cannot // distinguish whether eMouseRawUpdate is caused by eMouseDown/eMouseUp or // not. Therefore, we need to set the proper value in the latter case here // (In the former case, the copy constructor did it already). if (mouseEvent->mMessage != eMouseDown &&
mouseEvent->mMessage != eMouseUp) {
mouseRawUpdateEvent.mButton = MouseButton::eNotPressed;
}
nsEventStatus rawUpdateStatus = nsEventStatus_eIgnore;
EventHandler eventHandler(*this); return eventHandler.HandleEvent(aWeakFrameForPresShell,
&mouseRawUpdateEvent, aDontRetargetEvents,
&rawUpdateStatus);
} if (const WidgetTouchEvent* const touchEvent = aSourceEvent.AsTouchEvent()) {
WidgetTouchEvent touchRawUpdate(*touchEvent,
WidgetTouchEvent::CloneTouches::No);
touchRawUpdate.mMessage = eTouchRawUpdate;
touchRawUpdate.mTouches.Clear(); for (const RefPtr<Touch>& touch : touchEvent->mTouches) { // If `convertToPointer` is `false`, it means that we've already handled // the event to dispatch a preceding pointer event. Therefore, its // preceding event should've already been handled. // If ShouldConvertTouchToPointer() returns `false`, the touch is not an // active pointer or the touch hasn't been changed from the previous // state. Therefore, we don't need to dispatch ePointerRawUpdate for the // touch. if (!touch->convertToPointerRawUpdate ||
!TouchManager::ShouldConvertTouchToPointer(touch, &touchRawUpdate)) { continue;
} auto newTouch = MakeRefPtr<Touch>(*touch);
newTouch->mMessage = eTouchRawUpdate;
newTouch->mCoalescedWidgetEvents = nullptr;
touchRawUpdate.mTouches.AppendElement(std::move(newTouch));
}
nsEventStatus rawUpdateStatus = nsEventStatus_eIgnore; if (touchRawUpdate.mTouches.IsEmpty()) { return NS_OK;
}
EventHandler eventHandler(*this); return eventHandler.HandleEvent(aWeakFrameForPresShell, &touchRawUpdate,
aDontRetargetEvents, &rawUpdateStatus);
}
MOZ_ASSERT_UNREACHABLE("Handle the event to dispatch ePointerRawUpdate"); return NS_OK;
}
NS_ASSERTION(aWeakFrameForPresShell.IsAlive(), "aWeakFrameForPresShell should refer a frame");
// Update the latest focus sequence number with this new sequence number; // the next transasction that gets sent to the compositor will carry this over if (UpdateFocusSequenceNumber(aWeakFrameForPresShell.GetFrame(),
aGUIEvent->mFocusSequenceNumber)) { if (aWeakFrameForPresShell.IsAlive() &&
StaticPrefs::apz_keyboard_focus_optimization()) {
aWeakFrameForPresShell->SchedulePaint(nsIFrame::PAINT_COMPOSITE_ONLY);
}
}
constbool wasFrameForPresShellNull = !aWeakFrameForPresShell.GetFrame(); if (MaybeHandleEventWithAccessibleCaret(aWeakFrameForPresShell, aGUIEvent,
aEventStatus)) { // Handled by AccessibleCaretEventHub. return NS_OK;
}
if (MaybeDiscardEvent(aGUIEvent)) { // Cannot handle the event for now. return NS_OK;
}
if (!aDontRetargetEvents) { const DebugOnly<bool> wasFrameForPresShellAlive =
aWeakFrameForPresShell.IsAlive(); // If aGUIEvent should be handled in another PresShell, we should call its // HandleEvent() and do nothing here.
nsresult rv = NS_OK; if (MaybeHandleEventWithAnotherPresShell(aWeakFrameForPresShell, aGUIEvent,
aEventStatus, &rv)) { // Handled by another PresShell or nobody can handle the event. return rv;
} // If MaybeHandleEventWithAnotherPresShell() returns false, it shouldn't // have run any script. So, aWeakFrameForPresShell must be alive.
MOZ_ASSERT_IF(wasFrameForPresShellAlive, aWeakFrameForPresShell.IsAlive());
}
if (MaybeDiscardOrDelayKeyboardEvent(aGUIEvent)) { // The event is discarded or put into the delayed event queue. return NS_OK;
}
if (aGUIEvent->IsUsingCoordinates()) { return HandleEventUsingCoordinates(aWeakFrameForPresShell, aGUIEvent,
aEventStatus, aDontRetargetEvents);
}
// Activation events need to be dispatched even if no frame was found, since // we don't want the focus to be out of sync. if (MOZ_UNLIKELY(wasFrameForPresShellNull)) { if (!NS_EVENT_NEEDS_FRAME(aGUIEvent)) { // Push nullptr for both current event target content and frame since // there is no frame but the event does not require a frame.
AutoCurrentEventInfoSetter eventInfoSetter(*this); return HandleEventWithCurrentEventInfo(aGUIEvent, aEventStatus, true,
nullptr);
}
if (aGUIEvent->HasKeyEventMessage()) { // Keypress events in new blank tabs should not be completely thrown away. // Retarget them -- the parent chrome shell might make use of them. return RetargetEventToParent(aGUIEvent, aEventStatus);
}
return NS_OK;
}
if (aGUIEvent->IsTargetedAtFocusedContent()) { return HandleEventAtFocusedContent(aGUIEvent, aEventStatus);
}
// Flush pending notifications to handle the event with the latest layout. // But if it causes destroying the frame for mPresShell, stop handling the // event. (why?)
MaybeFlushPendingNotifications(aGUIEvent); if (MOZ_UNLIKELY(!aWeakFrameForPresShell.IsAlive())) {
*aEventStatus = nsEventStatus_eIgnore; return NS_OK;
}
// If we are trying to dispatch an ePointerRawUpdate but it's not allowed in // the (maybe retargetted) document, we should not flush the capture state // below. if (aGUIEvent->mMessage == eMouseRawUpdate ||
aGUIEvent->mMessage == eTouchRawUpdate) {
EventTargetDataWithCapture eventTargetData =
EventTargetDataWithCapture::QueryEventTargetUsingCoordinates(
*this, aWeakFrameForPresShell,
EventTargetDataWithCapture::Query::PendingState, aGUIEvent); if (!PointerEventHandler::NeedToDispatchPointerRawUpdate(
eventTargetData.GetDocument())) { return NS_OK;
} // Then, we need to recompute the target with processing the pending pointer // capture. Note that the result may be differnet since `gotpointercapture` // event listener does something tricky things.
}
EventTargetDataWithCapture eventTargetData =
EventTargetDataWithCapture::QueryEventTargetUsingCoordinates(
*this, aWeakFrameForPresShell,
EventTargetDataWithCapture::Query::LatestState, aGUIEvent,
aEventStatus); if (MOZ_UNLIKELY(!eventTargetData.CanHandleEvent())) { // We cannot handle the event within the PresShell anymore. Let's stop // handling the event without returning error since it's not illegal // case. return NS_OK;
} if (MOZ_UNLIKELY(!eventTargetData.GetFrame())) { if (eventTargetData.mPointerCapturingElement &&
aWeakFrameForPresShell.IsAlive()) { return HandleEventWithPointerCapturingContentWithoutItsFrame(
aWeakFrameForPresShell, aGUIEvent,
MOZ_KnownLive(eventTargetData.mPointerCapturingElement),
aEventStatus);
} return NS_OK;
}
// Suppress mouse event if it's being targeted at an element inside // a document which needs events suppressed if (MaybeDiscardOrDelayMouseEvent(eventTargetData.GetFrame(), aGUIEvent)) { return NS_OK;
}
// Check if we have an active EventStateManager which isn't the // EventStateManager of the current PresContext. If that is the case, and // mouse is over some ancestor document, forward event handling to the // active document. This way content can get mouse events even when mouse // is over the chrome or outside the window. if (eventTargetData.MaybeRetargetToActiveDocument(aGUIEvent) &&
NS_WARN_IF(!eventTargetData.GetFrame())) { return NS_OK;
}
// Wheel events only apply to elements. If this is a wheel event, attempt to // update the event target from the current wheel transaction before we // compute the element from the target frame.
eventTargetData.UpdateWheelEventTarget(aGUIEvent);
if (!eventTargetData.ComputeElementFromFrame(aGUIEvent)) { return NS_OK;
} // Note that even if ComputeElementFromFrame() returns true, // eventTargetData.mContent can be nullptr here.
// Dispatch a pointer event if Pointer Events is enabled. Note that if // pointer event listeners change the layout, eventTargetData is // automatically updated. if (!DispatchPrecedingPointerEvent(
aWeakFrameForPresShell, aGUIEvent,
MOZ_KnownLive(eventTargetData.mPointerCapturingElement),
aDontRetargetEvents, &eventTargetData, aEventStatus)) { return NS_OK;
}
// Handle the event in the correct shell. // We pass the subshell's root frame as the frame to start from. This is // the only correct alternative; if the event was captured then it // must have been captured by us or some ancestor shell and we // now ask the subshell to dispatch it normally.
EventHandler eventHandler(*eventTargetData.mPresShell);
AutoCurrentEventInfoSetter eventInfoSetter(eventHandler, aGUIEvent->mMessage,
eventTargetData); // eventTargetData is on the stack and is guaranteed to keep its // mOverrideClickTarget alive, so we can just use MOZ_KnownLive here.
nsresult rv = eventHandler.HandleEventWithCurrentEventInfo(
aGUIEvent, aEventStatus, true,
MOZ_KnownLive(eventTargetData.mOverrideClickTarget)); if (NS_FAILED(rv) ||
MOZ_UNLIKELY(eventTargetData.mPresShell->IsDestroying())) { return rv;
}
if (aGUIEvent->mMessage == eTouchEnd) {
MaybeSynthesizeCompatMouseEventsForTouchEnd(aGUIEvent->AsTouchEvent(),
aEventStatus);
}
return NS_OK;
}
// static
PresShell::EventHandler::EventTargetDataWithCapture::EventTargetDataWithCapture(
EventHandler& aEventHandler, AutoWeakFrame& aWeakFrameForPresShell,
Query aQueryState, WidgetGUIEvent* aGUIEvent,
nsEventStatus* aEventStatus /* = nullptr*/)
: EventTargetData(aWeakFrameForPresShell.GetFrame()) {
MOZ_ASSERT(aGUIEvent);
MOZ_ASSERT(aGUIEvent->IsUsingCoordinates()); // EventHandler::GetFrameToHandleNonTouchEvent() may need to flush pending // notifications of the target child document if eMouseDown or eMouseUp. // Currently, this class does not support the case with Query::PendingState.
MOZ_ASSERT_IF(aQueryState == Query::PendingState,
aGUIEvent->mMessage != eMouseDown);
MOZ_ASSERT_IF(aQueryState == Query::PendingState,
aGUIEvent->mMessage != eMouseUp);
// XXX Retrieving capturing content here. However, some of the following // methods allow to run script. So, isn't it possible the capturing // content outdated?
mCapturingContent = EventHandler::GetCapturingContentFor(aGUIEvent); if (queryLatestState) { if (GetDocument() && aGUIEvent->mClass == eTouchEventClass) {
PointerLockManager::Unlock("TouchEvent");
} // XXX If aGUIEvent is eMouseRawUpdate or eTouchRawUpdate and it's // dispatched by BrowserChild, i.e., the event won't cause ePointerMove // immediately after ePointerRawUpdate, should we skip fluhsing pending // animations here? Doing this could cause different animation result while // the user moves mouse cursor during a long animation whether there is a // `pointerrawupdate` event listener or not.
aEventHandler.MaybeFlushThrottledStyles(aWeakFrameForPresShell); // Previously, MaybeFlushThrottledStyles() recomputed the closest ancestor // frame for view of mPresShell if it's reframed. Therefore, we should keep // computing it here. // FIXME: GetFrame() may be target content's frame if aGUIEvent is a touch // event. So, we need to use different computation for such cases. if (MOZ_UNLIKELY(!aWeakFrameForPresShell.IsAlive())) {
Clear();
MOZ_ASSERT(!CanHandleEvent()); return;
}
}
// The order to generate pointer event is // 1. check pending pointer capture. // 2. check if there is a capturing content. // 3. hit test // 4. dispatch pointer events // 5. check whether the targets of all Touch instances are in the same // document and suppress invalid instances. // 6. dispatch mouse or touch events.
// Try to keep frame for following check, because frame can be damaged // during MaybeProcessPointerCapture. if (queryLatestState) {
PointerEventHandler::MaybeProcessPointerCapture(aGUIEvent); // Prevent application crashes, in case damaged frame. if (NS_WARN_IF(!weakRootFrameToHandleEvent.IsAlive())) {
Clear();
MOZ_ASSERT(!CanHandleEvent()); return;
}
}
// We want to query the pointer capture element which **will** capture the // following pointer event. If we've already processed the pointer capture // above, current override element is it. Otherwise, we will process the // pending pointer capture before dispatching a pointer event. Therefore, // the pending pointer capture element will be the next override element // if and only if they are different. (If they are the same element, the // element will keep capturing the pointer. So, referring to the pending // element is also fine in the case.)
mPointerCapturingElement =
queryLatestState
? PointerEventHandler::GetPointerCapturingElement(aGUIEvent)
: PointerEventHandler::GetPendingPointerCapturingElement(aGUIEvent);
if (mPointerCapturingElement) {
weakRootFrameToHandleEvent = mPointerCapturingElement->GetPrimaryFrame(); if (!weakRootFrameToHandleEvent.IsAlive()) { // The caller should not keep handling the event with the frame stored by // the super class. Therefore, we need to clear the frame.
ClearFrameToHandleEvent(); // Although the pointer capturing element does not have a frame, the event // should be handled on the element.
MOZ_ASSERT(CanHandleEvent()); return;
}
}
// Get the frame at the event point. However, don't do this if we're // capturing and retargeting the event because the captured frame will // be used instead below. Also keep using the root frame if we're dealing // with a window-level mouse exit event since we want to start sending // mouse out events at the root EventStateManager.
SetFrameAndComputePresShell(weakRootFrameToHandleEvent.GetFrame()); if (!mCaptureRetargeted && !isWindowLevelMouseExit &&
!mPointerCapturingElement) { if (!aEventHandler.ComputeEventTargetFrameAndPresShellAtEventPoint(
weakRootFrameToHandleEvent, aGUIEvent, this)) {
Clear();
MOZ_ASSERT(!CanHandleEvent()); if (aEventStatus) {
*aEventStatus = nsEventStatus_eIgnore;
} return;
}
}
// if a node is capturing the mouse, check if the event needs to be // retargeted at the capturing content instead. This will be the case when // capture retargeting is being used, no frame was found or the frame's // content is not a descendant of the capturing content. if (mCapturingContent && !mPointerCapturingElement &&
(PresShell::sCapturingContentInfo.mRetargetToElement ||
!GetFrameContent() ||
!nsContentUtils::ContentIsCrossDocDescendantOf(GetFrameContent(),
mCapturingContent))) { if (nsIFrame* const capturingFrame = mCapturingContent->GetPrimaryFrame()) {
SetFrameAndComputePresShell(capturingFrame);
}
}
switch (aGUIEvent->mMessage) { case eMouseDown: case eMouseUp: // XXX How about eContextMenu?
{ if (NS_WARN_IF(!mPresShell->GetPresContext())) { returnfalse;
}
MOZ_KnownLive(mPresShell)->FlushPendingNotifications(FlushType::Layout); returntrue;
} default: returnfalse;
}
}
// The type of coordinates to use for hit-testing input events // that are relative to the RCD's viewport frame. // On most platforms, use visual coordinates so that scrollbars // can be targeted. // On mobile, use layout coordinates because hit-testing in // visual coordinates clashes with mobile viewport sizing, where // the ViewportFrame is sized to the initial containing block // (ICB) size, which is in layout coordinates. This is fine // because we don't need to be able to target scrollbars on mobile // (scrollbar dragging isn't supported). static ViewportType ViewportTypeForInputEventsRelativeToRoot() { #ifdef MOZ_WIDGET_ANDROID return ViewportType::Layout; #else return ViewportType::Visual; #endif
}
if (targetFrame->PresShell() == mPresShell) { // If found target is in mPresShell, we've already found it in the latest // layout so that we can use it. return targetFrame;
}
// If target is in a child document, we've not flushed its layout yet.
PresShell* childPresShell = targetFrame->PresShell();
EventHandler childEventHandler(*childPresShell); const AutoWeakFrame targetFrameWeak(targetFrame); const DebugOnly<bool> flushedPendingNotifications =
childEventHandler.MaybeFlushPendingNotifications(aGUIEvent); if (!aWeakRootFrameToHandleEvent.IsAlive()) { // Stop handling the event if the root frame to handle event is destroyed // by the reflow. (but why?) return nullptr;
} if (targetFrameWeak.IsAlive()) { // If the target frame is alive, we don't need to recompute the target. return targetFrame;
}
MOZ_ASSERT(flushedPendingNotifications);
// Finally, we need to recompute the target with the latest layout.
targetFrame =
FindFrameTargetedByInputEvent(aGUIEvent, relativeTo, eventPoint, flags);
// Dispatch pointer events from the mouse or touch events. Regarding // pointer events from mouse, we should dispatch those pointer events to // the same target as the source mouse events. We pass the frame found // in hit test to PointerEventHandler and dispatch pointer events to it. // // Regarding pointer events from touch, the behavior is different. Touch // events are dispatched to the same target as the target of touchstart. // Multiple touch points must be dispatched to the same document. Pointer // events from touch can be dispatched to different documents. We Pass the // original frame to PointerEventHandler, reentry PresShell::HandleEvent, // and do hit test for each point. auto targetFrameOrError = [&]() -> Result<nsIFrame*, nsresult> { if (aGUIEvent->mClass == eTouchEventClass) { // If aWeakFrameForPresShell has already been reframed before this is // called, we don't need to handle the event. if (MOZ_UNLIKELY(!aWeakFrameForPresShell.IsAlive())) { return Err(NS_ERROR_FAILURE);
} return aWeakFrameForPresShell.GetFrame();
} return aEventTargetData->GetFrame();
}(); if (MOZ_UNLIKELY(targetFrameOrError.isErr())) { returnfalse;
}
nsIFrame* targetFrame = targetFrameOrError.unwrap();
if (aPointerCapturingElement) {
Result<nsIContent*, nsresult> overrideClickTargetOrError =
GetOverrideClickTarget(aGUIEvent, aWeakFrameForPresShell.GetFrame(),
aPointerCapturingElement); if (MOZ_UNLIKELY(overrideClickTargetOrError.isErr())) { returnfalse;
}
aEventTargetData->mOverrideClickTarget =
overrideClickTargetOrError.unwrap();
aEventTargetData->mPresShell =
PresShell::GetShellForEventTarget(nullptr, aPointerCapturingElement); if (!aEventTargetData->mPresShell) { // If we can't process event for the capturing content, release // the capture.
PointerEventHandler::ReleaseIfCaptureByDescendant(
aPointerCapturingElement); returnfalse;
}
// If the target frame is alive, the caller should keep handling the event // unless event target frame is destroyed. if (weakTargetFrame.IsAlive() && weakFrame.IsAlive()) {
aEventTargetData->UpdateTouchEventTarget(aGUIEvent); return maybeCallerCanHandleEvent;
}
presShell->FlushPendingNotifications(FlushType::Layout); if (MOZ_UNLIKELY(mPresShell->IsDestroying())) { returnfalse;
}
// The spec defines that mouse events must be dispatched to the same target as // the pointer event. // The Touch Events spec defines that touch events must be dispatched to the // same target as touch start and the other browsers dispatch touch events // even if the touch event target is not connected to the document. // Retargetting the event is handled by AutoPointerEventTargetUpdater and // mouseOrTouchEventTargetContent stores the result.
// If the target is no longer participating in its ownerDocument's tree, // fire the event at the original target's nearest ancestor node. if (!mouseOrTouchEventTargetContent) {
MOZ_ASSERT(aGUIEvent->IsMouseEventClassOrHasClickRelatedPointerEvent()); returnfalse;
}
// Don't dispatch event to AccessibleCaretEventHub when the event status // is nsEventStatus_eConsumeNoDefault. This might be happened when content // preventDefault on the pointer events. In such case, we also call // preventDefault on mouse events to stop default behaviors. if (*aEventStatus == nsEventStatus_eConsumeNoDefault) { returnfalse;
}
if (!AccessibleCaretEnabled(GetDocument()->GetDocShell())) { returnfalse;
}
// AccessibleCaretEventHub handles only mouse, touch, and keyboard events. if (!aGUIEvent->IsMouseEventClassOrHasClickRelatedPointerEvent() &&
aGUIEvent->mClass != eTouchEventClass &&
aGUIEvent->mClass != eKeyboardEventClass) { returnfalse;
}
// AccessibleCaretEventHub::HandleEvent may return nsEventStatus_eIgnore to // allow the context menu to appear correctly on desktop Firefox (see // AccessibleCaretEventHub::LongTapState::OnLongTap). When this happens, and // the event hub at the event location is the same as the hub attached to // the currently‑focused window, HandleEvent would be invoked a second time. // Avoid the duplicate call by explicitly checking for this condition.
AccessibleCaretEventHub* alreadyHandledEventHub = nullptr;
AutoEventTargetPointResetter autoEventTargetPointResetter(aGUIEvent); // First, try the event hub at the event point to handle a long press to // select a word in an unfocused window. do {
EventTargetData eventTargetData(nullptr); if (!ComputeEventTargetFrameAndPresShellAtEventPoint(
aWeakFrameForPresShell, aGUIEvent, &eventTargetData)) { break;
}
*aEventStatus = eventHub->HandleEvent(aGUIEvent); if (*aEventStatus != nsEventStatus_eConsumeNoDefault) { break;
}
// If the event is consumed, cancel APZC panning by setting // mMultipleActionsPrevented.
aGUIEvent->mFlags.mMultipleActionsPrevented = true;
autoEventTargetPointResetter.SetHandledByAccessibleCaret(); returntrue;
} while (false);
// Then, we target the event to the event hub at the focused window.
nsCOMPtr<nsPIDOMWindowOuter> window = GetFocusedDOMWindowInOurWindow(); if (!window) { returnfalse;
}
RefPtr<Document> retargetEventDoc = window->GetExtantDoc(); if (!retargetEventDoc) { returnfalse;
}
RefPtr<PresShell> presShell = retargetEventDoc->GetPresShell(); if (!presShell) { returnfalse;
}
RefPtr<AccessibleCaretEventHub> eventHub =
presShell->GetAccessibleCaretEventHub(); if (!eventHub || eventHub.get() == alreadyHandledEventHub) { returnfalse;
}
*aEventStatus = eventHub->HandleEvent(aGUIEvent); if (*aEventStatus != nsEventStatus_eConsumeNoDefault) { returnfalse;
} // If the event is consumed, cancel APZC panning by setting // mMultipleActionsPrevented.
aGUIEvent->mFlags.mMultipleActionsPrevented = true;
autoEventTargetPointResetter.SetHandledByAccessibleCaret(); returntrue;
}
// If the eTouchEnd event is dispatched via APZ, APZCCallbackHelper dispatches // a set of mouse events with better handling. Therefore, we don't need to // handle that here. if (!aTouchEndEvent->mFlags.mIsSynthesizedForTests ||
StaticPrefs::test_events_async_enabled()) { return;
}
auto cleanUpPointerCapturingElementAtLastPointerUp = MakeScopeExit([]() {
PointerEventHandler::ReleasePointerCapturingElementAtLastPointerUp();
});
// If the tap was consumed or 2 or more touches occurred, we don't need the // compatibility mouse events. if (*aStatus == nsEventStatus_eConsumeNoDefault ||
!TouchManager::IsSingleTapEndToDoDefault(aTouchEndEvent)) { return;
}
if (NS_WARN_IF(!aTouchEndEvent->mWidget)) { return;
}
// NOTE: I think that we don't need to implement a double click here becase // WebDriver does not support a way to synthesize a double click and Chrome // does not fire "dblclick" even if doing `pointerDown().pointerUp()` twice. // FIXME: Currently we don't support long tap.
RefPtr<PresShell> presShell = mPresShell; for (const EventMessage message : {eMouseMove, eMouseDown, eMouseUp}) { if (MOZ_UNLIKELY(presShell->IsDestroying())) { break;
}
nsIFrame* const frameForPresShell = presShell->GetRootFrame(); if (!frameForPresShell) { break;
}
WidgetMouseEvent event(true, message, widget, WidgetMouseEvent::eReal);
event.mFlags.mIsSynthesizedForTests =
aTouchEndEvent->mFlags.mIsSynthesizedForTests;
event.mRefPoint = aTouchEndEvent->mTouches[0]->mRefPoint;
event.mButton = MouseButton::ePrimary;
event.mButtons = message == eMouseDown ? MouseButtonsFlag::ePrimaryFlag
: MouseButtonsFlag::eNoButtons;
event.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH;
event.mClickCount = message == eMouseMove ? 0 : 1;
event.mModifiers = aTouchEndEvent->mModifiers;
event.pointerId = aTouchEndEvent->mTouches[0]->mIdentifier;
event.convertToPointer = false; if (TouchManager::IsPrecedingTouchPointerDownConsumedByContent()) {
event.PreventDefault(false);
event.mFlags.mOnlyChromeDispatch = true;
}
nsEventStatus mouseEventStatus = nsEventStatus_eIgnore;
presShell->HandleEvent(frameForPresShell, &event, false, &mouseEventStatus);
}
}
// If it is safe to dispatch events now, don't discard the event. if (nsContentUtils::IsSafeToRunScript()) { returnfalse;
}
// If the event does not cause dispatching DOM event (i.e., internal event), // we can keep handling it even when it's not safe to run script. if (!aGUIEvent->IsAllowedToDispatchDOMEvent()) { returnfalse;
}
// If the event is a composition event, we need to let IMEStateManager know // it's discarded because it needs to listen all composition events to manage // TextComposition instance. if (aGUIEvent->mClass == eCompositionEventClass) {
IMEStateManager::OnCompositionEventDiscarded(
aGUIEvent->AsCompositionEvent());
}
#ifdef DEBUG if (aGUIEvent->IsIMERelatedEvent()) {
nsPrintfCString warning("%s event is discarded",
ToChar(aGUIEvent->mMessage));
NS_WARNING(warning.get());
} #endif// #ifdef DEBUG
// PointerEventHandler may synthesize ePointerMove event before releasing the // mouse capture (it's done by a default handler of eMouseUp) after handling // ePointerUp. Then, we need to dispatch pointer boundary events for the // element under the pointer to emulate a pointer move after a pointer // capture. Therefore, we need to ignore the capturing element if the event // dispatcher requests it. if (aGUIEvent->ShouldIgnoreCapturingContent()) { return nullptr;
}
// key and IME related events should not cross top level window boundary. // Basically, such input events should be fired only on focused widget. // However, some IMEs might need to clean up composition after focused // window is deactivated. And also some tests on MozMill want to test key // handling on deactivated window because MozMill window can be activated // during tests. So, there is no merit the events should be redirected to // active window. So, the events should be handled on the last focused // content in the last focused DOM window in same top level window. // Note, if no DOM window has been focused yet, we can discard the events. if (aGUIEvent->IsTargetedAtFocusedWindow()) {
nsCOMPtr<nsPIDOMWindowOuter> window = GetFocusedDOMWindowInOurWindow(); // No DOM window in same top level window has not been focused yet, // discard the events. if (!window) { returnfalse;
}
const nsIContent* const capturingContent =
aGUIEvent->ShouldIgnoreCapturingContent()
? nullptr
: EventHandler::GetCapturingContentFor(aGUIEvent); if (capturingContent) { // if the mouse is being captured then retarget the mouse event at the // document that is being captured.
RefPtr<Document> retargetEventDoc = capturingContent->GetComposedDoc();
retargetEventDoc.forget(aRetargetEventDocument); returntrue;
}
RefPtr<PresShell> retargetPresShell = aRetargetDocument->GetPresShell(); // Even if the document doesn't have PresShell, i.e., it's invisible, we // need to dispatch only KeyboardEvent in its nearest visible document // because key focus shouldn't be caught by invisible document. if (!retargetPresShell) { if (!aGUIEvent->HasKeyEventMessage()) { return nullptr;
}
Document* retargetEventDoc = aRetargetDocument; while (!retargetPresShell) {
retargetEventDoc = retargetEventDoc->GetInProcessParentDocument(); if (!retargetEventDoc) { return nullptr;
}
retargetPresShell = retargetEventDoc->GetPresShell();
}
}
// If the found PresShell is this instance, caller needs to keep handling // aGUIEvent by itself. Therefore, return the given frame which was set // to aFrame of HandleEvent(). if (retargetPresShell == mPresShell) { return aFrameForPresShell;
}
// Use root frame of the new PresShell if there is.
nsIFrame* rootFrame = retargetPresShell->GetRootFrame(); if (rootFrame) { return rootFrame;
}
// Otherwise, and if aGUIEvent requires content of PresShell, caller should // stop handling the event. if (aGUIEvent->mMessage == eQueryTextContent ||
aGUIEvent->IsContentCommandEvent()) { return nullptr;
}
// Otherwise, use nearest ancestor frame which includes the PresShell. return retargetPresShell->GetRootFrame();
}
RefPtr<Document> retargetEventDoc; if (!GetRetargetEventDocument(aGUIEvent, getter_AddRefs(retargetEventDoc))) { // Nobody can handle this event. So, treat as handled by somebody to make // caller do nothing anymore. returntrue;
}
// If there is no proper retarget document, the caller should handle the // event by itself. if (!retargetEventDoc) { returnfalse;
}
nsIFrame* frame = GetFrameForHandlingEventWith(
aGUIEvent, retargetEventDoc, aWeakFrameForPresShell.GetFrame()); if (!frame) { // Nobody can handle this event. So, treat as handled by somebody to make // caller do nothing anymore. // NOTE: If aWeakFrameForPresShell does not refer to a frame (i.e., it's // already been reframed) and aGUIEvent needs to be handled in mPresShell, // we are here because GetFrameForHandlingEventWith() returns // aWeakFrameForPresShell.GetFrame() as-is. In the case, we don't need to // handle aGUIEvent, so, it's fine to return true from this method. returntrue;
}
// If we reached same frame as set to HandleEvent(), the caller should handle // the event by itself. if (frame == aWeakFrameForPresShell.GetFrame()) { returnfalse;
}
// We need to handle aGUIEvent with another PresShell.
RefPtr<PresShell> presShell = frame->PresContext()->PresShell();
*aRv = presShell->HandleEvent(frame, aGUIEvent, true, aEventStatus); returntrue;
}
// We must not need to let suspend listeners know ePointerRawUpdate events. // And also the delayed events will be dispatched via widget. Therefore, // ePointerRawUpdate event will be dispatched by PresShell::HandleEvent() // again. if (aGUIEvent->mMessage == eMouseRawUpdate ||
aGUIEvent->mMessage == eTouchRawUpdate ||
aGUIEvent->mMessage == ePointerRawUpdate) { returnfalse;
}
if (!aGUIEvent->IsMouseEventClassOrHasClickRelatedPointerEvent() &&
aGUIEvent->mMessage != eTouchStart) { returnfalse;
}
if (!aFrameToHandleEvent->PresContext()
->Document()
->EventHandlingSuppressed()) { returnfalse;
}
switch (aGUIEvent->mMessage) { case eTouchStart: { // If we receive a single touch start during the suppression, its // compatibility mouse events should not be fired later because the single // tap sequence has not been sent to the web app. const WidgetTouchEvent* const touchEvent = aGUIEvent->AsTouchEvent(); if (touchEvent->mTouches.Length() == 1) {
ps->mNoDelayedSingleTap = true;
} // We won't dispatch eTouchStart as a delayed event later so that return // false. returnfalse;
} case eMouseDown: { // If we receive a click sequence start during the suppression, we should // not fire `click` event later because its sequence has not been send to // the web app. Note that if the eMouseDown is caused by a touch, we may // have already sent the touch sequence to the web app. In such case, // the eMouseDown is NOT start of the click sequence. const WidgetMouseEvent* const mouseEvent = aGUIEvent->AsMouseEvent(); if (ps->mNoDelayedSingleTap ||
mouseEvent->mInputSource != MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
ps->mNoDelayedMouseEvents = true; break;
} // Otherwise, put the event into the queue.
[[fallthrough]];
} case eMouseUp: case eMouseExitFromWidget: { if (ps->mNoDelayedMouseEvents) { break;
}
UniquePtr<DelayedMouseEvent> delayedMouseEvent =
MakeUnique<DelayedMouseEvent>(aGUIEvent->AsMouseEvent());
ps->mDelayedEvents.AppendElement(std::move(delayedMouseEvent)); break;
} case eContextMenu: { if (ps->mNoDelayedMouseEvents) { break;
} // contextmenu is triggered after right mouseup on Windows and right // mousedown on other platforms.
UniquePtr<DelayedPointerEvent> delayedPointerEvent =
MakeUnique<DelayedPointerEvent>(aGUIEvent->AsPointerEvent());
ps->mDelayedEvents.AppendElement(std::move(delayedPointerEvent)); break;
} default: break;
}
// If there is a suppressed event listener associated with the document, // notify it about the suppressed mouse event. This allows devtools // features to continue receiving mouse events even when the devtools // debugger has paused execution in a page.
RefPtr<EventListener> suppressedListener = aFrameToHandleEvent->PresContext()
->Document()
->GetSuppressedEventListener(); if (!suppressedListener ||
aGUIEvent->AsMouseEvent()->mReason == WidgetMouseEvent::eSynthesized) { returntrue;
}
if (auto* target = aFrameToHandleEvent->GetEventTargetContent(aGUIEvent)) {
aGUIEvent->mTarget = target;
}
void PresShell::EventHandler::MaybeFlushThrottledStyles(
AutoWeakFrame& aWeakFrameForPresShell) { if (!GetDocument()) { return;
}
PresShell* rootPresShell = mPresShell->GetRootPresShell(); if (NS_WARN_IF(!rootPresShell)) { return;
}
Document* rootDocument = rootPresShell->GetDocument(); if (NS_WARN_IF(!rootDocument)) { return;
}
{ // scope for scriptBlocker.
nsAutoScriptBlocker scriptBlocker;
FlushThrottledStyles(*rootDocument);
}
if (MOZ_UNLIKELY(!aWeakFrameForPresShell.IsAlive()) &&
MOZ_LIKELY(!mPresShell->IsDestroying())) { // FIXME: aWeakFrameForPresShell may be target content's frame if aGUIEvent // of the caller is a touch event. So, we need to use different computation // for such cases.
aWeakFrameForPresShell = mPresShell->GetRootFrame();
}
}
nsIFrame* rootFrameToHandleEvent = ComputeRootFrameToHandleEventWithPopup(
aFrameForPresShell, aGUIEvent, aCapturingContent,
aIsCapturingContentIgnored); if (*aIsCapturingContentIgnored) { // If the capturing content is ignored, we don't need to respect it. return rootFrameToHandleEvent;
}
if (!aCapturingContent) { return rootFrameToHandleEvent;
}
// If a capture is active, determine if the BrowsingContext is active. If // not, clear the capture and target the mouse event normally instead. This // would occur if the mouse button is held down while a tab change occurs. // If the BrowsingContext is active, look for a scrolling container.
BrowsingContext* bc = GetPresContext()->Document()->GetBrowsingContext(); if (!bc || !bc->IsActive()) {
ClearMouseCapture();
*aIsCapturingContentIgnored = true; return rootFrameToHandleEvent;
}
// As a special case, if a listbox is capturing, return its scrolled frame, // see bug 519693. // FIXME(emilio): This is super inconsistent, there should be a better way of // making that work. if (nsListControlFrame* lcf =
do_QueryFrame(aCapturingContent->GetPrimaryFrame())) { return lcf->GetScrolledFrame();
} return rootFrameToHandleEvent;
}
nsPresContext* framePresContext = aRootFrameToHandleEvent->PresContext();
nsPresContext* rootPresContext = framePresContext->GetRootPresContext();
NS_ASSERTION(rootPresContext == GetPresContext()->GetRootPresContext(), "How did we end up outside the connected " "prescontext hierarchy?");
nsIFrame* popupFrame = nsLayoutUtils::GetPopupFrameForEventCoordinates(
rootPresContext, aGUIEvent); if (!popupFrame) { return aRootFrameToHandleEvent;
}
// If a remote browser is currently capturing input break out if we // detect a chrome generated popup. // XXXedgar, do we need to check fission OOP iframe? if (aCapturingContent &&
EventStateManager::IsTopLevelRemoteTarget(aCapturingContent)) {
*aIsCapturingContentIgnored = true;
}
// If the popupFrame is an ancestor of the 'frame', the frame should // handle the event, otherwise, the popup should handle it. if (nsContentUtils::ContentIsCrossDocDescendantOf(
framePresContext->GetPresShell()->GetDocument(),
popupFrame->GetContent())) { return aRootFrameToHandleEvent;
}
// If we aren't starting our event dispatch from the root frame of the // root prescontext, then someone must be capturing the mouse. In that // case we only want to use the popup list if the capture is // inside the popup. if (framePresContext == rootPresContext &&
aRootFrameToHandleEvent == FrameConstructor()->GetRootFrame()) { return popupFrame;
}
if (aCapturingContent && !*aIsCapturingContentIgnored &&
aCapturingContent->IsInclusiveDescendantOf(popupFrame->GetContent())) { return popupFrame;
}
return aRootFrameToHandleEvent;
}
nsresult
PresShell::EventHandler::HandleEventWithPointerCapturingContentWithoutItsFrame(
AutoWeakFrame& aWeakFrameForPresShell, WidgetGUIEvent* aGUIEvent,
Element* aPointerCapturingElement, nsEventStatus* aEventStatus) {
MOZ_ASSERT(aGUIEvent);
MOZ_ASSERT(aPointerCapturingElement);
MOZ_ASSERT(!aPointerCapturingElement->GetPrimaryFrame(), "Handle the event with frame rather than only with the content");
MOZ_ASSERT(aEventStatus);
RefPtr<PresShell> presShellForCapturingContent =
PresShell::GetShellForEventTarget(nullptr, aPointerCapturingElement); if (!presShellForCapturingContent) { // If we can't process event for the capturing content, release // the capture.
PointerEventHandler::ReleaseIfCaptureByDescendant(aPointerCapturingElement); // Since we don't dispatch ePointeUp nor ePointerCancel in this case, // EventStateManager::PostHandleEvent does not have a chance to dispatch // ePointerLostCapture event. Therefore, we need to dispatch it here.
PointerEventHandler::MaybeImplicitlyReleasePointerCapture(aGUIEvent); return NS_OK;
}
// mCurrentEventTarget is cleared by eventInfoSetter and // ComputeFocusedEventTargetElement shouldn't set it again.
MOZ_ASSERT(!mPresShell->mCurrentEventTarget.IsSet());
if (eventTargetElement) {
nsresult rv = NS_OK; if (MaybeHandleEventWithAnotherPresShell(eventTargetElement, aGUIEvent,
aEventStatus, &rv)) { return rv;
}
}
// If we cannot handle the event with mPresShell, let's try to handle it // with parent PresShell. // However, we don't want to handle IME related events with parent document // because it may leak the content of parent document and the IME state was // set for the empty document. So, dispatching on the parent document may be // handled by nobody. Additionally, IMEContentObserver may send notifications // to PuppetWidget in a content process while document which is in the design // mode but does not have content nodes has focus. At that time, PuppetWidget // makes ContentCacheInChild collect the latest content data with dispatching // query content events. Therefore, we want they handle in the empty document // rather than the parent document. So, we must not retarget in this case // anyway.
mPresShell->mCurrentEventTarget.SetFrameAndContent(
aGUIEvent->mMessage, nullptr, eventTargetElement); if (aGUIEvent->mClass != eCompositionEventClass &&
aGUIEvent->mClass != eQueryContentEventClass &&
aGUIEvent->mClass != eSelectionEventClass &&
(!mPresShell->GetCurrentEventContent() ||
!mPresShell->GetCurrentEventFrame() ||
InZombieDocument(mPresShell->mCurrentEventTarget.mContent))) { return RetargetEventToParent(aGUIEvent, aEventStatus);
}
// key and IME related events go to the focused frame in this DOM window.
nsPIDOMWindowOuter* window = GetDocument()->GetWindow();
nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
Element* eventTargetElement = nsFocusManager::GetFocusedDescendant(
window, nsFocusManager::eOnlyCurrentWindow,
getter_AddRefs(focusedWindow));
// otherwise, if there is no focused content or the focused content has // no frame, just use the root content. This ensures that key events // still get sent to the window properly if nothing is focused or if a // frame goes away while it is focused. if (!eventTargetElement || !eventTargetElement->GetPrimaryFrame()) {
eventTargetElement = GetDocument()->GetUnfocusedKeyEventTarget();
}
switch (aGUIEvent->mMessage) { case eKeyDown:
sLastKeyDownEventTargetElement = eventTargetElement; return eventTargetElement; case eKeyPress: case eKeyUp: if (!sLastKeyDownEventTargetElement) { return eventTargetElement;
} // If a different element is now focused for the keypress/keyup event // than what was focused during the keydown event, check if the new // focused element is not in a chrome document any more, and if so, // retarget the event back at the keydown target. This prevents a // content area from grabbing the focus from chrome in-between key // events. if (eventTargetElement) { bool keyDownIsChrome = nsContentUtils::IsChromeDoc(
sLastKeyDownEventTargetElement->GetComposedDoc()); if (keyDownIsChrome != nsContentUtils::IsChromeDoc(
eventTargetElement->GetComposedDoc()) ||
(keyDownIsChrome && BrowserParent::GetFrom(eventTargetElement))) {
eventTargetElement = sLastKeyDownEventTargetElement;
}
}
RefPtr<PresShell> eventTargetPresShell = eventTargetDocument->GetPresShell(); if (!eventTargetPresShell) {
*aRv = NS_OK; returntrue; // No PresShell can handle the event.
}
// If we cannot handle the event with mPresShell because of no target, // just record the response time. // XXX Is this intentional? In such case, the score is really good because // of nothing to do. So, it may make average and median better. if (NS_EVENT_NEEDS_FRAME(aEvent) && !mPresShell->GetCurrentEventFrame() &&
!mPresShell->GetCurrentEventContent()) {
RecordEventHandlingResponsePerformance(aEvent); return NS_OK;
}
if (mPresShell->mCurrentEventTarget.mContent &&
aEvent->IsTargetedAtFocusedWindow() &&
aEvent->AllowFlushingPendingNotifications()) { if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) { // This may run script now. So, mPresShell might be destroyed after here.
nsCOMPtr<nsIContent> currentEventContent =
mPresShell->mCurrentEventTarget.mContent;
fm->FlushBeforeEventHandlingIfNeeded(currentEventContent);
}
}
if (!mPresShell->IsDestroying() && aIsHandlingNativeEvent &&
aEvent->mClass != eQueryContentEventClass) { // Ensure that notifications to IME should be sent before getting next // native event from the event queue. // XXX Should we check the event message or event class instead of // using aIsHandlingNativeEvent?
manager->TryToFlushPendingNotificationsToIME();
}
// 1. Give event to event manager for pre event state changes and // generation of synthetic events.
{ // Scope for presContext
RefPtr<nsPresContext> presContext = GetPresContext();
nsCOMPtr<nsIContent> eventContent =
mPresShell->mCurrentEventTarget.mContent;
nsresult rv = aEventStateManager->PreHandleEvent(
presContext, aEvent, mPresShell->mCurrentEventTarget.mFrame,
eventContent, aEventStatus, aOverrideClickTarget); if (NS_FAILED(rv)) { return rv;
} // Let's retarget eMouseMove target if the preceding mouse boundary events // caused removing the target from the tree and EventStateManager knows that // the deepest connected mouseenter target which was an ancestor of the // removed target. This matches with Chrome Canary with enabling the // new mouse/pointer boundary event feature. However, they stop dispatching // "pointermove" in the same case. Therefore, for now, we should do this // only for eMouseMove. if (eventContent && aEvent->mMessage == eMouseMove &&
(!eventContent->IsInComposedDoc() ||
eventContent->OwnerDoc() != mPresShell->GetDocument())) { const OverOutElementsWrapper* const boundaryEventTargets =
aEventStateManager->GetExtantMouseBoundaryEventTarget(); const nsIContent* outEventTarget =
boundaryEventTargets ? boundaryEventTargets->GetOutEventTarget()
: nullptr;
nsIContent* const deepestLeaveEventTarget =
boundaryEventTargets
? boundaryEventTargets->GetDeepestLeaveEventTarget()
: nullptr; // If the last "over" target (next "out" target) is there, it means that // it was temporarily removed. In such case, EventStateManager treats // it as never disconnected. Therefore, we need to do nothing here. // Additionally, if there is no last deepest "enter" event target, we // lost the target. Therefore, we should keep the traditional behavior, // to dispatch it on the Document node. if (!outEventTarget && deepestLeaveEventTarget) {
nsIFrame* const frame =
deepestLeaveEventTarget->GetPrimaryFrame(FlushType::Layout); if (MOZ_UNLIKELY(mPresShell->IsDestroying())) { return NS_OK;
} if (frame) {
mPresShell->mCurrentEventTarget.mFrame = frame;
mPresShell->mCurrentEventTarget.mContent = deepestLeaveEventTarget;
}
}
}
}
// 2. Give event to the DOM for third party and JS use. bool wasHandlingKeyBoardEvent = nsContentUtils::IsHandlingKeyBoardEvent(); if (aEvent->mClass == eKeyboardEventClass) {
nsContentUtils::SetIsHandlingKeyBoardEvent(true);
} // If EventStateManager or something wants reply from remote process and // needs to win any other event listeners in chrome, the event is both // stopped its propagation and marked as "waiting reply from remote // process". In this case, PresShell shouldn't dispatch the event into // the DOM tree because they don't have a chance to stop propagation in // the system event group. On the other hand, if its propagation is not // stopped, that means that the event may be reserved by chrome. If it's // reserved by chrome, the event shouldn't be sent to any remote // processes. In this case, PresShell needs to dispatch the event to // the DOM tree for checking if it's reserved. if (aEvent->IsAllowedToDispatchDOMEvent() &&
!(aEvent->PropagationStopped() &&
aEvent->IsWaitingReplyFromRemoteProcess())) {
MOZ_ASSERT(nsContentUtils::IsSafeToRunScript(), "Somebody changed aEvent to cause a DOM event!");
nsPresShellEventCB eventCB(mPresShell); if (nsIFrame* target = mPresShell->GetCurrentEventFrame()) { if (target->OnlySystemGroupDispatch(aEvent->mMessage)) {
aEvent->StopPropagation();
}
} if (aEvent->mClass == eTouchEventClass) {
DispatchTouchEventToDOM(aEvent, aEventStatus, &eventCB, aTouchIsNew);
} else {
DispatchEventToDOM(aEvent, aEventStatus, &eventCB);
}
}
// 3. Give event to event manager for post event state changes and // generation of synthetic events. // Refetch the prescontext, in case it changed.
RefPtr<nsPresContext> presContext = GetPresContext(); return aEventStateManager->PostHandleEvent(
presContext, aEvent, mPresShell->GetCurrentEventFrame(), aEventStatus,
aOverrideClickTarget);
}
*aTouchIsNew = false; if (aEvent->IsUserAction()) {
mPresShell->mHasHandledUserInput = true;
}
switch (aEvent->mMessage) { case eKeyPress: case eKeyDown: case eKeyUp: {
WidgetKeyboardEvent* keyboardEvent = aEvent->AsKeyboardEvent();
MaybeHandleKeyboardEventBeforeDispatch(keyboardEvent); returntrue;
} case eMouseRawUpdate:
MOZ_ASSERT_UNREACHABLE( "eMouseRawUpdate shouldn't be handled as a DOM event"); returnfalse;
case eMouseMove: { bool allowCapture = EventStateManager::GetActiveEventStateManager() &&
GetPresContext() &&
GetPresContext()->EventStateManager() ==
EventStateManager::GetActiveEventStateManager();
PresShell::AllowMouseCapture(allowCapture); returntrue;
} case eDrop: {
nsCOMPtr<nsIDragSession> session =
nsContentUtils::GetDragSession(GetPresContext()); if (session) { bool onlyChromeDrop = false;
session->GetOnlyChromeDrop(&onlyChromeDrop); if (onlyChromeDrop) {
aEvent->mFlags.mOnlyChromeDispatch = true;
}
} returntrue;
} case eDragExit: { if (!StaticPrefs::dom_event_dragexit_enabled()) {
aEvent->mFlags.mOnlyChromeDispatch = true;
} returntrue;
} case eContextMenu: { // If we cannot open context menu even though eContextMenu is fired, we // should stop dispatching it into the DOM.
WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent(); if (mouseEvent->IsContextMenuKeyEvent() &&
!AdjustContextMenuKeyEvent(mouseEvent)) { returnfalse;
}
// If "Shift" state is active, context menu should be forcibly opened even // if web apps want to prevent it since we respect our users' intention. // In this case, we don't fire "contextmenu" event on web content because // of not cancelable. if (mouseEvent->IsShift() &&
StaticPrefs::dom_event_contextmenu_shift_suppresses_event()) {
aEvent->mFlags.mOnlyChromeDispatch = true;
aEvent->mFlags.mRetargetToNonNativeAnonymous = true;
} returntrue;
} case eTouchStart: case eTouchMove: case eTouchEnd: case eTouchCancel: case eTouchPointerCancel: return mPresShell->mTouchManager.PreHandleEvent(
aEvent, aEventStatus, *aTouchIsNew,
mPresShell->mCurrentEventTarget.mContent); case eTouchRawUpdate:
MOZ_ASSERT_UNREACHABLE( "eTouchRawUpdate shouldn't be handled as a DOM event"); returnfalse; default: returntrue;
}
}
void PresShell::EventHandler::FinalizeHandlingEvent(
WidgetEvent* aEvent, const nsEventStatus* aStatus) { switch (aEvent->mMessage) { case eKeyPress: case eKeyDown: case eKeyUp: { if (aEvent->AsKeyboardEvent()->mKeyCode == NS_VK_ESCAPE) { if (aEvent->mMessage == eKeyUp) { // Reset this flag after key up is handled.
mPresShell->mIsLastChromeOnlyEscapeKeyConsumed = false;
} else { if (aEvent->mFlags.mOnlyChromeDispatch &&
aEvent->mFlags.mDefaultPreventedByChrome) {
mPresShell->mIsLastChromeOnlyEscapeKeyConsumed = true;
} if (aEvent->mMessage == eKeyDown &&
!aEvent->mFlags.mDefaultPrevented) { if (RefPtr<Document> doc = GetDocument()) { if (StaticPrefs::dom_closewatcher_enabled()) {
doc->ProcessCloseRequest();
} else {
doc->HandleEscKey();
}
}
}
}
} if (aEvent->mMessage == eKeyDown) {
mPresShell->mIsLastKeyDownCanceled = aEvent->mFlags.mDefaultPrevented;
} break;
} case eMouseUp: // reset the capturing content now that the mouse button is up
PresShell::ReleaseCapturingContent(); break; case eMouseRawUpdate:
MOZ_ASSERT_UNREACHABLE( "eMouseRawUpdate shouldn't be handled as a DOM event"); break; case eMouseMove:
PresShell::AllowMouseCapture(false); break; case eDrag: case eDragEnd: case eDragEnter: case eDragExit: case eDragLeave: case eDragOver: case eDrop: { // After any drag event other than dragstart (which is handled // separately, as we need to collect the data first), the DataTransfer // needs to be made protected, and then disconnected.
DataTransfer* dataTransfer = aEvent->AsDragEvent()->mDataTransfer; if (dataTransfer) {
dataTransfer->Disconnect();
} break;
} case eTouchStart: case eTouchMove: case eTouchEnd: case eTouchCancel: case eTouchPointerCancel: case eMouseLongTap: case eContextMenu: {
mPresShell->mTouchManager.PostHandleEvent(aEvent, aStatus); break;
} case eTouchRawUpdate:
MOZ_ASSERT_UNREACHABLE( "eTouchRawUpdate shouldn't be handled as a DOM event"); break; default: break;
}
if (WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent()) { if (mouseEvent->mSynthesizeMoveAfterDispatch) {
PointerEventHandler::SynthesizeMoveToDispatchBoundaryEvents(mouseEvent);
}
}
}
void PresShell::MaybeExitKeyboardLockedFullscreen(
WidgetKeyboardEvent* aKeyboardEvent, Document* aFullscreenRoot) { // No recorded keydown? if (mFirstUnmatchedEscapeKeyDownForFullscreen.IsNull()) { return;
} constbool escapeKeyDeltaLargeEnough =
(aKeyboardEvent->mTimeStamp -
mFirstUnmatchedEscapeKeyDownForFullscreen) >=
TimeDuration::FromMilliseconds(
StaticPrefs::dom_fullscreen_keyboard_lock_long_press_interval());
if (escapeKeyDeltaLargeEnough) {
Document::AsyncExitFullscreen(aFullscreenRoot); if (XRE_IsParentProcess() && (PointerLockManager::GetLockedRemoteTarget() ||
PointerLockManager::IsLocked())) {
PointerLockManager::Unlock("EscapeKey");
}
}
}
if (aKeyboardEvent->mKeyCode != NS_VK_ESCAPE) {
mPresShell->CleanupFullscreenState(); return;
}
// If we're in fullscreen mode, exit from it forcibly when Escape key is // pressed.
Document* doc = mPresShell->GetCurrentEventContent()
? mPresShell->mCurrentEventTarget.mContent->OwnerDoc()
: nullptr;
Document* root = nsContentUtils::GetInProcessSubtreeRootDocument(doc); if (root && root->GetFullscreenElement()) {
Document* fullscreenLeaf = Document::GetFullscreenLeaf(root); if (fullscreenLeaf->HasFullscreenKeyboardLockEnabled()) { if (aKeyboardEvent->mMessage == eKeyDown) { // Instead of exiting fullscreen on a single press of the escape key, we // now wait for the escape key to be held for longer than a given // interval before exiting. if (!aKeyboardEvent->mIsRepeat) {
mPresShell->mHasShownFullscreenWarningForCurrentEscapeKeyLongPress = false;
mPresShell->mFirstUnmatchedEscapeKeyDownForFullscreen =
aKeyboardEvent->mTimeStamp;
mPresShell->MaybeExitKeyboardLockedFullscreen(aKeyboardEvent, root);
} elseif (aKeyboardEvent->mMessage == eKeyUp) { // When keyboard key repeat is disabled by the OS, no repeat keydown // events are generated while holding Escape. Check on keyup whether the // key was held long enough to exit fullscreen.
mPresShell->MaybeExitKeyboardLockedFullscreen(aKeyboardEvent, root);
}
// If fullscreen keyboard-lock is enabled, return here, as we don't allow // escaping from pointerlock when escape is pressed. return;
}
// Prevent default action on ESC key press when exiting // DOM fullscreen mode. This prevents the browser ESC key // handler from stopping all loads in the document, which // would cause <video> loads to stop. // XXX We need to claim the Escape key event which will be // dispatched only into chrome is already consumed by // content because we need to prevent its default here // for some reasons (not sure) but we need to detect // if a chrome event handler will call PreventDefault() // again and check it later.
aKeyboardEvent->PreventDefaultBeforeDispatch(CrossProcessForwarding::eStop);
aKeyboardEvent->mFlags.mOnlyChromeDispatch = true;
// The event listeners in chrome can prevent this ESC behavior by // calling prevent default on the preceding keydown/press events. if (aKeyboardEvent->mMessage == eKeyUp) { bool shouldExitFullscreen =
!mPresShell->mIsLastChromeOnlyEscapeKeyConsumed; if (!shouldExitFullscreen) { if (mPresShell->mLastConsumedEscapeKeyUpForFullscreen &&
(aKeyboardEvent->mTimeStamp -
mPresShell->mLastConsumedEscapeKeyUpForFullscreen) <=
TimeDuration::FromMilliseconds(
StaticPrefs::
dom_fullscreen_force_exit_on_multiple_escape_interval())) {
shouldExitFullscreen = true;
mPresShell->mLastConsumedEscapeKeyUpForFullscreen = TimeStamp();
} else {
mPresShell->mLastConsumedEscapeKeyUpForFullscreen =
aKeyboardEvent->mTimeStamp;
}
}
if (shouldExitFullscreen) { // ESC key released while in DOM fullscreen mode. // Fully exit fullscreen mode for the browser window and documents // that received the event.
Document::AsyncExitFullscreen(root);
}
}
}
if (XRE_IsParentProcess() &&
!mPresShell->mIsLastChromeOnlyEscapeKeyConsumed) { if (PointerLockManager::GetLockedRemoteTarget() ||
PointerLockManager::IsLocked()) { // XXX See above comment to understand the reason why this needs // to claim that the Escape key event is consumed by content // even though it will be dispatched only into chrome.
aKeyboardEvent->PreventDefaultBeforeDispatch(
CrossProcessForwarding::eStop);
aKeyboardEvent->mFlags.mOnlyChromeDispatch = true; if (aKeyboardEvent->mMessage == eKeyUp) {
PointerLockManager::Unlock("EscapeKey");
}
}
}
}
switch (aEvent->mMessage) { case eKeyPress: case eKeyDown: case eKeyUp: if (aEvent->AsKeyboardEvent()->ShouldInteractionTimeRecorded()) {
GetPresContext()->RecordInteractionTime(
nsPresContext::InteractionType::KeyInteraction, aEvent->mTimeStamp);
}
glean::layout::input_event_queued_keyboard.AccumulateRawDuration(
TimeStamp::Now() - aEvent->mTimeStamp); return;
case eMouseDown: case eMouseUp:
glean::layout::input_event_queued_click.AccumulateRawDuration(
TimeStamp::Now() - aEvent->mTimeStamp);
[[fallthrough]]; case ePointerDown: case ePointerUp:
GetPresContext()->RecordInteractionTime(
nsPresContext::InteractionType::ClickInteraction, aEvent->mTimeStamp); return;
case eMouseRawUpdate:
MOZ_ASSERT_UNREACHABLE( "eMouseRawUpdate shouldn't be handled as a DOM event"); break; case eMouseMove:
GetPresContext()->RecordInteractionTime(
nsPresContext::InteractionType::MouseMoveInteraction,
aEvent->mTimeStamp); return;
TimeStamp now = TimeStamp::Now();
TimeDuration duration = now - aEvent->mTimeStamp;
glean::layout::input_event_response.AccumulateRawDuration(duration); if (GetDocument() &&
GetDocument()->GetReadyStateEnum() != Document::READYSTATE_COMPLETE) {
glean::layout::load_input_event_response.AccumulateRawDuration(duration);
}
if (!sLastInputProcessed || sLastInputProcessed < aEvent->mTimeStamp) { if (sLastInputProcessed) { // This input event was created after we handled the last one. // Accumulate the previous events' coalesced duration.
glean::layout::input_event_response_coalesced.AccumulateRawDuration(
sLastInputProcessed - sLastInputCreated);
if (MOZ_UNLIKELY(!PresShell::sProcessInteractable)) { // For content process, we use the ready state of // top-level-content-document to know if the process has finished the // start-up. // For parent process, see the topic // 'sessionstore-one-or-no-tab-restored' in PresShell::Observe. if (XRE_IsContentProcess() && GetDocument() &&
GetDocument()->IsTopLevelContentDocument()) { switch (GetDocument()->GetReadyStateEnum()) { case Document::READYSTATE_INTERACTIVE: case Document::READYSTATE_COMPLETE:
PresShell::sProcessInteractable = true; break; default: break;
}
}
}
}
sLastInputCreated = aEvent->mTimeStamp;
} elseif (aEvent->mTimeStamp < sLastInputCreated) { // This event was created before the last input. May be processing out // of order, so coalesce backwards, too.
sLastInputCreated = aEvent->mTimeStamp;
}
sLastInputProcessed = now;
}
nsresult PresShell::EventHandler::DispatchEventToDOM(
WidgetEvent* aEvent, nsEventStatus* aEventStatus,
nsPresShellEventCB* aEventCB) {
nsresult rv = NS_OK;
nsCOMPtr<nsINode> eventTarget = mPresShell->mCurrentEventTarget.mContent;
nsPresShellEventCB* eventCBPtr = aEventCB; if (!eventTarget) {
nsCOMPtr<nsIContent> targetContent; if (mPresShell->mCurrentEventTarget.mFrame) {
targetContent =
mPresShell->mCurrentEventTarget.mFrame->GetEventTargetContent(aEvent);
} if (targetContent) {
eventTarget = targetContent;
} elseif (GetDocument()) {
eventTarget = GetDocument(); // If we don't have any content, the callback wouldn't probably // do nothing.
eventCBPtr = nullptr;
}
} if (eventTarget) { if (eventTarget->OwnerDoc()->ShouldResistFingerprinting(
RFPTarget::WidgetEvents) &&
aEvent->IsBlockedForFingerprintingResistance()) {
aEvent->mFlags.mOnlySystemGroupDispatchInContent = true;
} elseif (aEvent->mMessage == eKeyPress) { // If eKeyPress event is marked as not dispatched in the default event // group in web content, it's caused by non-printable key or key // combination. In this case, UI Events declares that browsers // shouldn't dispatch keypress event. However, some web apps may be // broken with this strict behavior due to historical issue. // Therefore, we need to keep dispatching keypress event for such keys // even with breaking the standard. // Similarly, the other browsers sets non-zero value of keyCode or // charCode of keypress event to the other. Therefore, we should // behave so, however, some web apps may be broken. On such web apps, // we should keep using legacy our behavior. if (!mPresShell->mInitializedWithKeyPressEventDispatchingBlacklist) {
mPresShell->mInitializedWithKeyPressEventDispatchingBlacklist = true;
nsCOMPtr<nsIPrincipal> principal =
GetDocumentPrincipalToCompareWithBlacklist(*mPresShell); if (principal) {
mPresShell->mForceDispatchKeyPressEventsForNonPrintableKeys =
principal->IsURIInPrefList( "dom.keyboardevent.keypress.hack.dispatch_non_printable_" "keys") ||
principal->IsURIInPrefList( "dom.keyboardevent.keypress.hack." "dispatch_non_printable_keys.addl");
if (aEvent->mClass == eCompositionEventClass) {
RefPtr<nsPresContext> presContext = GetPresContext();
RefPtr<BrowserParent> browserParent =
IMEStateManager::GetActiveBrowserParent();
IMEStateManager::DispatchCompositionEvent(
eventTarget, presContext, browserParent, aEvent->AsCompositionEvent(),
aEventStatus, eventCBPtr);
} else { if (aEvent->IsMouseEventClassOrHasClickRelatedPointerEvent()) {
PointerEventHandler::RecordMouseButtons(*aEvent->AsMouseEvent()); #ifdef DEBUG if (eventTarget->IsContent() && !eventTarget->IsElement()) {
NS_WARNING(nsPrintfCString( "%s (IsReal()=%s) target is not an elemnet content " "node, %s\n",
ToChar(aEvent->mMessage),
aEvent->AsMouseEvent()->IsReal() ? "true" : "false",
ToString(*eventTarget).c_str())
.get());
MOZ_CRASH("MouseEvent target must be an element");
} #endif// #ifdef DEBUG
}
RefPtr<nsPresContext> presContext = GetPresContext();
EventDispatcher::Dispatch(eventTarget, presContext, aEvent, nullptr,
aEventStatus, eventCBPtr);
}
} return rv;
}
void PresShell::EventHandler::DispatchTouchEventToDOM(
WidgetEvent* aEvent, nsEventStatus* aEventStatus,
nsPresShellEventCB* aEventCB, bool aTouchIsNew) {
MOZ_ASSERT(aEvent->mMessage != eTouchRawUpdate); // calling preventDefault on touchstart or the first touchmove for a // point prevents mouse events. calling it on the touchend should // prevent click dispatching. bool canPrevent = (aEvent->mMessage == eTouchStart) ||
(aEvent->mMessage == eTouchMove && aTouchIsNew) ||
(aEvent->mMessage == eTouchEnd); bool preventDefault = false;
nsEventStatus tmpStatus = nsEventStatus_eIgnore;
WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
// loop over all touches and dispatch events on any that have changed for (dom::Touch* touch : touchEvent->mTouches) { // We should remove all suppressed touch instances in // TouchManager::PreHandleEvent.
MOZ_ASSERT(!touch->mIsTouchEventSuppressed);
// Dispatch event to content only (NOT full processing) // See also HandleEventWithTarget which does full event processing.
nsresult PresShell::HandleDOMEventWithTarget(nsIContent* aTargetContent,
WidgetEvent* aEvent,
nsEventStatus* aStatus) {
nsresult rv = NS_OK;
// Bug 41013: Check if the event should be dispatched to content. // It's possible that we are in the middle of destroying the window // and the js context is out of date. This check detects the case // that caused a crash in bug 41013, but there may be a better way // to handle this situation!
nsCOMPtr<nsISupports> container = mPresContext->GetContainerWeak(); if (container) { // Dispatch event to content
rv = EventDispatcher::Dispatch(aTargetContent, mPresContext, aEvent,
nullptr, aStatus);
}
PopCurrentEventInfo(); return rv;
}
// See the method above.
nsresult PresShell::HandleDOMEventWithTarget(nsIContent* aTargetContent,
Event* aEvent,
nsEventStatus* aStatus) {
nsresult rv = NS_OK;
bool PresShell::EventHandler::AdjustContextMenuKeyEvent(
WidgetMouseEvent* aMouseEvent) { // if a menu is open, open the context menu relative to the active item on the // menu. if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
nsIFrame* popupFrame = pm->GetTopPopup(widget::PopupType::Menu); if (popupFrame) {
nsIFrame* itemFrame = (static_cast<nsMenuPopupFrame*>(popupFrame))
->GetCurrentMenuItemFrame(); if (!itemFrame) {
itemFrame = popupFrame;
}
// If we're here because of the key-equiv for showing context menus, we // have to twiddle with the NS event to make sure the context menu comes // up in the upper left of the relevant content area before we create // the DOM event. Since we never call InitMouseEvent() on the event, // the client X/Y will be 0,0. We can make use of that if the widget is null. // Use the root widget since it's most likely to exist, and the coordinates // returned by GetCurrentItemAndPositionForElement are relative to it.
nsRootPresContext* rootPC = GetPresContext()->GetRootPresContext();
aMouseEvent->mRefPoint = LayoutDeviceIntPoint(); if (rootPC) {
aMouseEvent->mWidget = rootPC->PresShell()->GetRootWidget(); if (aMouseEvent->mWidget) { // default the refpoint to the topleft of our document if (nsIFrame* rootFrame = FrameConstructor()->GetRootFrame()) { auto frameToWidgetOffset =
nsLayoutUtils::FrameToWidgetOffset(rootFrame, aMouseEvent->mWidget);
MOZ_ASSERT(frameToWidgetOffset, "If rootPC has a widget, so should we"); if (frameToWidgetOffset) {
aMouseEvent->mRefPoint = LayoutDeviceIntPoint::FromAppUnitsToNearest(
*frameToWidgetOffset, GetPresContext()->AppUnitsPerDevPixel());
}
}
}
} else {
aMouseEvent->mWidget = nullptr;
}
// see if we should use the caret position for the popup
LayoutDeviceIntPoint caretPoint; // Beware! This may flush notifications via synchronous // ScrollSelectionIntoView. if (PrepareToUseCaretPosition(MOZ_KnownLive(aMouseEvent->mWidget),
caretPoint)) { // caret position is good
int32_t devPixelRatio = GetPresContext()->AppUnitsPerDevPixel();
caretPoint = LayoutDeviceIntPoint::FromAppUnitsToNearest(
ViewportUtils::LayoutToVisual(
LayoutDeviceIntPoint::ToAppUnits(caretPoint, devPixelRatio),
GetPresContext()->PresShell()),
devPixelRatio);
aMouseEvent->mRefPoint = caretPoint; returntrue;
}
// If we're here because of the key-equiv for showing context menus, we // have to reset the event target to the currently focused element. Get it // from the focus controller.
RefPtr<Element> currentFocus = nsFocusManager::GetFocusedElementStatic();
// Reset event coordinates relative to focused frame in view if (currentFocus) {
nsCOMPtr<nsIContent> currentPointElement;
GetCurrentItemAndPositionForElement(
currentFocus, getter_AddRefs(currentPointElement),
aMouseEvent->mRefPoint, MOZ_KnownLive(aMouseEvent->mWidget)); if (currentPointElement) {
mPresShell->mCurrentEventTarget.SetFrameAndContent(
aMouseEvent->mMessage, nullptr, currentPointElement);
mPresShell->GetCurrentEventFrame();
}
}
returntrue;
}
// PresShell::EventHandler::PrepareToUseCaretPosition // // This checks to see if we should use the caret position for popup context // menus. Returns true if the caret position should be used, and the // coordinates of that position is returned in aTargetPt. This function // will also scroll the window as needed to make the caret visible. // // The event widget should be the widget that generated the event, and // whose coordinate system the resulting event's mRefPoint should be // relative to. The returned point is in device pixels realtive to the // widget passed in. bool PresShell::EventHandler::PrepareToUseCaretPosition(
nsIWidget* aEventWidget, LayoutDeviceIntPoint& aTargetPt) {
nsresult rv;
bool caretVisible = caret->IsVisible(); if (!caretVisible) { returnfalse;
}
// caret selection, this is a temporary weak reference, so no refcounting is // needed
Selection* domSelection = caret->GetSelection();
NS_ENSURE_TRUE(domSelection, false);
// since the match could be an anonymous textnode inside a // <textarea> or text <input>, we need to get the outer frame // note: frames are not refcounted
nsIFrame* frame = nullptr; // may be nullptr
nsINode* node = domSelection->GetFocusNode();
NS_ENSURE_TRUE(node, false);
nsCOMPtr<nsIContent> content = nsIContent::FromNode(node); if (content) {
nsIContent* nonNative = content->FindFirstNonChromeOnlyAccessContent();
content = nonNative;
}
if (content) { // It seems like ScrollSelectionIntoView should be enough, but it's // not. The problem is that scrolling the selection into view when it is // below the current viewport will align the top line of the frame exactly // with the bottom of the window. This is fine, BUT, the popup event causes // the control to be re-focused which does this exact call to // ScrollContentIntoView, which has a one-pixel disagreement of whether the // frame is actually in view. The result is that the frame is aligned with // the top of the window, but the menu is still at the bottom. // // Doing this call first forces the frame to be in view, eliminating the // problem. The only difference in the result is that if your cursor is in // an edit box below the current view, you'll get the edit box aligned with // the top of the window. This is arguably better behavior anyway.
rv = MOZ_KnownLive(mPresShell)
->ScrollContentIntoView(
content,
AxisScrollParams(WhereToScroll::Nearest,
WhenToScroll::IfNotVisible),
AxisScrollParams(WhereToScroll::Nearest,
WhenToScroll::IfNotVisible),
ScrollFlags::ScrollOverflowHidden);
NS_ENSURE_SUCCESS(rv, false);
frame = content->GetPrimaryFrame();
NS_WARNING_ASSERTION(frame, "No frame for focused content?");
}
// Actually scroll the selection (ie caret) into view. Note that this must // be synchronous since we will be checking the caret position on the screen. // // Be easy about errors, and just don't scroll in those cases. Better to have // the correct menu at a weird place than the wrong menu. // After ScrollSelectionIntoView(), the pending notifications might be // flushed and PresShell/PresContext/Frames may be dead. See bug 418470. const nsCOMPtr<nsISelectionController> selCon =
frame ? frame->GetSelectionController()
: static_cast<nsISelectionController*>(mPresShell); if (selCon) {
rv = selCon->ScrollSelectionIntoView(
SelectionType::eNormal, nsISelectionController::SELECTION_FOCUS_REGION,
SelectionScrollMode::SyncFlush);
NS_ENSURE_SUCCESS(rv, false);
}
nsPresContext* presContext = GetPresContext();
if (!aEventWidget) { returnfalse;
} // get caret position relative to the closest widget
nsRect caretCoords;
nsIFrame* caretFrame = caret->GetGeometry(&caretCoords); if (!caretFrame) { returnfalse;
}
if (aEventWidget) { if (auto offset =
nsLayoutUtils::FrameToWidgetOffset(caretFrame, aEventWidget)) {
caretCoords.MoveBy(*offset);
}
}
// caret coordinates are in app units, convert to pixels
aTargetPt.x =
presContext->AppUnitsToDevPixels(caretCoords.x + caretCoords.width);
aTargetPt.y =
presContext->AppUnitsToDevPixels(caretCoords.y + caretCoords.height);
// make sure rounding doesn't return a pixel which is outside the caret // (e.g. one line lower)
aTargetPt.y -= 1;
// Set the position to just underneath the current item for multi-select // lists or just underneath the selected item for single-select lists. If // the element is not a list, or there is no selection, leave the position // as is.
nsCOMPtr<Element> item;
nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSelect =
aFocusedElement->AsXULMultiSelectControl(); if (multiSelect) {
checkLineHeight = false;
int32_t currentIndex;
multiSelect->GetCurrentIndex(¤tIndex); if (currentIndex >= 0) {
RefPtr<XULTreeElement> tree = XULTreeElement::FromNode(focusedContent); // Tree view special case (tree items have no frames) // Get the focused row and add its coordinates, which are already in // pixels // XXX Boris, should we create a new interface so that this doesn't // need to know about trees? Something like nsINodelessChildCreator // which could provide the current focus coordinates? if (tree) {
tree->EnsureRowIsVisible(currentIndex);
int32_t firstVisibleRow = tree->GetFirstVisibleRow();
int32_t rowHeight = tree->RowHeight();
RefPtr<nsTreeColumns> cols = tree->GetColumns(); if (cols) {
nsTreeColumn* col = cols->GetFirstColumn(); if (col) {
RefPtr<Element> colElement = col->Element();
nsIFrame* frame = colElement->GetPrimaryFrame(); if (frame) {
extraTreeY += frame->GetSize().height;
}
}
}
} else {
multiSelect->GetCurrentItem(getter_AddRefs(item));
}
}
} else { // don't check menulists as the selected item will be inside a popup.
nsCOMPtr<nsIDOMXULMenuListElement> menulist =
aFocusedElement->AsXULMenuList(); if (!menulist) {
nsCOMPtr<nsIDOMXULSelectControlElement> select =
aFocusedElement->AsXULSelectControl(); if (select) {
checkLineHeight = false;
select->GetSelectedItem(getter_AddRefs(item));
}
}
}
if (item) {
focusedContent = item;
}
if (nsIFrame* frame = focusedContent->GetPrimaryFrame()) {
NS_ASSERTION(
frame->PresContext() == GetPresContext(), "handling event for focused content that is not in our document?");
nsPoint widgetOffset; if (aRootWidget) { if (auto offset =
nsLayoutUtils::FrameToWidgetOffset(frame, aRootWidget)) {
widgetOffset = *offset;
}
}
// Start context menu down and to the right from top left of frame // use the lineheight. This is a good distance to move the context // menu away from the top left corner of the frame. If we always // used the frame height, the context menu could end up far away, // for example when we're focused on linked images. // On the other hand, we want to use the frame height if it's less // than the current line height, so that the context menu appears // associated with the correct frame.
nscoord extra = 0; if (!istree) {
extra = frame->GetSize().height; if (checkLineHeight) {
ScrollContainerFrame* scrollContainerFrame =
nsLayoutUtils::GetNearestScrollContainerFrame(
frame, nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN |
nsLayoutUtils::SCROLLABLE_FIXEDPOS_FINDS_ROOT); if (scrollContainerFrame) {
nsSize scrollAmount = scrollContainerFrame->GetLineScrollAmount();
int32_t APD = presContext->AppUnitsPerDevPixel();
int32_t scrollAPD =
scrollContainerFrame->PresContext()->AppUnitsPerDevPixel();
scrollAmount = scrollAmount.ScaleToOtherAppUnits(scrollAPD, APD); if (extra > scrollAmount.height) {
extra = scrollAmount.height;
}
}
}
}
void PresShell::WillPaint() { // Check the simplest things first. In particular, it's important to // check mIsActive before making any of the more expensive calls such // as GetRootPresContext, for the case of a browser with a large // number of tabs. // Don't bother doing anything if we still have painting suppressed or we are // not active. if (!mIsActive || mPaintingSuppressed || !IsVisible()) { return;
}
nsRootPresContext* rootPresContext = mPresContext->GetRootPresContext(); if (!rootPresContext) { // In some edge cases, such as when we don't have a root frame yet, // we can't find the root prescontext. There's nothing to do in that // case. return;
}
rootPresContext->FlushWillPaintObservers(); if (mIsDestroying) { return;
}
// Process reflows, if we have them, to reduce flicker due to invalidates and // reflow being interspersed. Note that we _do_ allow this to be // interruptible; if we can't do all the reflows it's better to flicker a bit // than to freeze up.
FlushPendingNotifications(ChangesToFlush(FlushType::InterruptibleLayout, /* aFlushAnimations = */ false, /* aUpdateRelevancy = */ false)); if (mIsDestroying) { return;
}
mDocument->EnumerateSubDocuments(
[](Document& aSubdoc) MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA { if (RefPtr ps = aSubdoc.GetPresShell()) { if (!ps->IsUnderHiddenEmbedderElement()) {
ps->WillPaint();
}
} return CallState::Continue;
});
}
if (aIncludeSubDocuments && mDocument) {
mDocument->EnumerateSubDocuments([](Document& aSubDoc) { if (PresShell* presShell = aSubDoc.GetPresShell()) {
presShell->Thaw();
} return CallState::Continue;
});
}
// Get the activeness of our presshell, as this might have changed // while we were in the bfcache
ActivenessMaybeChanged();
// We're now unfrozen
mFrozen = false;
UpdateImageLockingState();
UnsuppressPainting();
// In case the above UnsuppressPainting call didn't start the // refresh driver, we manually start the refresh driver to // ensure nsPresContext::MaybeIncreaseMeasuredTicksSinceLoading // can be called for user input events handling. if (presContext && presContext->IsRoot()) { if (!presContext->RefreshDriver()->HasPendingTick()) {
presContext->RefreshDriver()->InitializeTimer();
}
}
}
//-------------------------------------------------------- // Start of protected and private methods on the PresShell //--------------------------------------------------------
void PresShell::DidDoReflow(bool aInterruptible) {
MOZ_ASSERT(mPendingDidDoReflow); if (!nsContentUtils::IsSafeToRunScript()) { // If we're reflowing while script-blocked (e.g. from container query // updates), defer our reflow callbacks until the end of our next layout // flush.
SetNeedLayoutFlush(); return;
}
auto clearPendingDidDoReflow =
MakeScopeExit([&] { mPendingDidDoReflow = false; });
mHiddenContentInForcedLayout.Clear();
HandlePostedReflowCallbacks(aInterruptible);
if (mIsDestroying) { return;
}
{
nsAutoScriptBlocker scriptBlocker;
AutoAssertNoFlush noReentrantFlush(*this); if (nsCOMPtr<nsIDocShell> docShell = mPresContext->GetDocShell()) {
DOMHighResTimeStamp now = GetPerformanceNowUnclamped();
docShell->NotifyReflowObservers(aInterruptible, mLastReflowStart, now);
}
SynthesizeMouseMove(false);
mPresContext->NotifyMissingFonts();
}
if (mIsDestroying) { return;
}
if (mDirtyRoots.IsEmpty()) { // We only unsuppress painting if we're out of reflows. It's pointless to // do so if reflows are still pending, since reflows are just going to // thrash the frames around some more. By waiting we avoid an overeager // "jitter" effect. if (mShouldUnsuppressPainting) {
mShouldUnsuppressPainting = false;
UnsuppressAndInvalidate();
}
} else { // If any new reflow commands were enqueued during the reflow (or we didn't // reflow everything because we were interrupted), schedule another reflow // event to process them. // // Note that we want to do this after DidDoReflow(), since that method can // change whether there are dirty roots around by flushing, and there's no // point in posting a reflow event just to have the flush revoke it.
EnsureLayoutFlush();
}
}
DOMHighResTimeStamp PresShell::GetPerformanceNowUnclamped() {
DOMHighResTimeStamp now = 0;
if (nsPIDOMWindowInner* window = mDocument->GetInnerWindow()) {
Performance* perf = window->GetPerformance();
if (perf) {
now = perf->NowUnclamped();
}
}
return now;
}
bool PresShell::DoReflow(nsIFrame* target, bool aInterruptible,
OverflowChangedTracker* aOverflowTracker) {
nsIURI* uri = mDocument->GetDocumentURI();
AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_RELEVANT_FOR_JS( "Reflow", LAYOUT_Reflow, uri ? uri->GetSpecOrDefault() : "N/A"_ns);
// set up a cache that saves all nodes contained in each selection, // allowing a fast lookup in `nsTextFrame::IsFrameSelected()`. // This cache only lives throughout this reflow call.
SelectionNodeCache cache(*this);
// Schedule a paint, but don't actually mark this frame as changed for // retained DL building purposes. If any child frames get moved, then // they will schedule paint again. We could probaby skip this, and just // schedule a similar paint when a frame is deleted.
target->SchedulePaint(nsIFrame::PAINT_DEFAULT, false);
MOZ_ASSERT(isRoot || aOverflowTracker, "caller must provide overflow tracker when reflowing " "non-root frames");
// CreateReferenceRenderingContext can return nullptr
UniquePtr<gfxContext> rcx(CreateReferenceRenderingContext());
#ifdef DEBUG
mCurrentReflowRoot = target; #endif
// If the target frame is the root of the frame hierarchy, then // use all the available space. If it's simply a `reflow root', // then use the target frame's size as the available space.
WritingMode wm = target->GetWritingMode();
LogicalSize size(wm); if (isRoot) {
size = LogicalSize(wm, mPresContext->GetVisibleArea().Size());
} else {
size = target->GetLogicalSize();
}
OverflowAreas oldOverflow; // initialized and used only when !isRoot if (!isRoot) {
oldOverflow = target->GetOverflowAreas();
}
NS_ASSERTION(!target->GetNextInFlow() && !target->GetPrevInFlow(), "reflow roots should never split");
// Don't pass size directly to the reflow input, since a // constrained height implies page/column breaking.
LogicalSize reflowSize(wm, size.ISize(wm), NS_UNCONSTRAINEDSIZE);
ReflowInput reflowInput(mPresContext, target, rcx.get(), reflowSize,
ReflowInput::InitFlag::CallerWillInit);
if (isRoot) {
reflowInput.Init(mPresContext);
} else { // Initialize reflow input with current used border and padding, // in case this was set specially by the parent frame when the reflow root // was reflowed by its parent.
reflowInput.Init(mPresContext, Nothing(),
Some(target->GetLogicalUsedBorder(wm)),
Some(target->GetLogicalUsedPadding(wm)));
}
// fix the computed height
NS_ASSERTION(reflowInput.ComputedPhysicalMargin() == nsMargin(0, 0, 0, 0), "reflow input should not set margin for reflow roots"); if (size.BSize(wm) != NS_UNCONSTRAINEDSIZE) {
nscoord computedBSize =
size.BSize(wm) -
reflowInput.ComputedLogicalBorderPadding(wm).BStartEnd(wm);
computedBSize = std::max(computedBSize, 0);
reflowInput.SetComputedBSize(computedBSize);
}
NS_ASSERTION(
reflowInput.ComputedISize() ==
size.ISize(wm) -
reflowInput.ComputedLogicalBorderPadding(wm).IStartEnd(wm), "reflow input computed incorrect inline size");
// If an incremental reflow is initiated at a frame other than the // root frame, then its desired size had better not change! If it's // initiated at the root, then the size better not change unless its // height was unconstrained to start with.
nsRect boundsRelativeToTarget =
nsRect(0, 0, desiredSize.Width(), desiredSize.Height()); constbool isBSizeLimitReflow =
isRoot && size.BSize(wm) == NS_UNCONSTRAINEDSIZE;
NS_ASSERTION(isBSizeLimitReflow || desiredSize.Size(wm) == size, "non-root frame's desired size changed during an " "incremental reflow");
NS_ASSERTION(status.IsEmpty(), "reflow roots should never split");
target->SetSize(boundsRelativeToTarget.Size());
target->DidReflow(mPresContext, nullptr); if (target->IsInScrollAnchorChain()) {
ScrollAnchorContainer* container = ScrollAnchorContainer::FindFor(target);
PostPendingScrollAnchorAdjustment(container);
} if (MOZ_UNLIKELY(isBSizeLimitReflow)) {
mPresContext->SetVisibleArea(boundsRelativeToTarget);
}
#ifdef DEBUG
mCurrentReflowRoot = nullptr; #endif
if (!isRoot && oldOverflow != target->GetOverflowAreas()) { // The overflow area changed. Propagate this change to ancestors.
aOverflowTracker->AddFrame(target->GetParent(),
OverflowChangedTracker::CHILDREN_CHANGED);
}
NS_ASSERTION(
mPresContext->HasPendingInterrupt() || mFramesToDirty.Count() == 0, "Why do we need to dirty anything if not interrupted?");
mIsReflowing = false; bool interrupted = mPresContext->HasPendingInterrupt(); if (interrupted) { // Make sure target gets reflowed again. for (constauto& key : mFramesToDirty) { // Mark frames dirty until target frame. for (nsIFrame* f = key; f && !f->IsSubtreeDirty(); f = f->GetParent()) {
f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); if (f->IsFlexItem()) {
nsFlexContainerFrame::MarkCachedFlexMeasurementsDirty(f);
}
if (f == target) { break;
}
}
}
NS_ASSERTION(target->IsSubtreeDirty(), "Why is the target not dirty?");
mDirtyRoots.Add(target);
SetNeedLayoutFlush();
// Clear mFramesToDirty after we've done the target->IsSubtreeDirty() // assertion so that if it fails it's easier to see what's going on. #ifdef NOISY_INTERRUPTIBLE_REFLOW
printf("mFramesToDirty.Count() == %u\n", mFramesToDirty.Count()); #endif/* NOISY_INTERRUPTIBLE_REFLOW */
mFramesToDirty.Clear();
// Any FlushPendingNotifications with interruptible reflows // should be suppressed now. We don't want to do extra reflow work // before our reflow event happens.
mWasLastReflowInterrupted = true;
EnsureLayoutFlush();
}
// dump text perf metrics for reflows with significant text processing if (tp) { if (tp->current.numChars > 100) {
TimeDuration reflowTime = TimeStamp::Now() - timeStart;
LogTextPerfStats(tp, this, tp->current, reflowTime.ToMilliseconds(),
eLog_reflow, nullptr);
}
tp->Accumulate();
}
return !interrupted;
}
// used with Telemetry metrics #define NS_LONG_REFLOW_TIME_MS 5000
bool PresShell::ProcessReflowCommands(bool aInterruptible) { if (mDirtyRoots.IsEmpty() && !mShouldUnsuppressPainting &&
!mPendingDidDoReflow) { // Nothing to do; bail out returntrue;
}
auto timerStart = mozilla::TimeStamp::Now(); bool interrupted = false; if (!mDirtyRoots.IsEmpty()) { // If reflow is interruptible, then make a note of our deadline. const PRIntervalTime deadline =
aInterruptible
? PR_IntervalNow() + PR_MicrosecondsToInterval(gMaxRCProcessingTime)
: (PRIntervalTime)0;
// Scope for the reflow entry point
nsAutoScriptBlocker scriptBlocker;
WillDoReflow();
AUTO_LAYOUT_PHASE_ENTRY_POINT(GetPresContext(), Reflow);
OverflowChangedTracker overflowTracker;
do { // Send an incremental reflow notification to the target frame.
nsIFrame* target = mDirtyRoots.PopShallowestRoot();
if (!target->IsSubtreeDirty()) { // It's not dirty anymore, which probably means the notification // was posted in the middle of a reflow (perhaps with a reflow // root in the middle). Don't do anything. continue;
}
// Keep going until we're out of reflow commands, or we've run // past our deadline, or we're interrupted.
} while (!interrupted && !mDirtyRoots.IsEmpty() &&
(!aInterruptible || PR_IntervalNow() < deadline));
interrupted = !mDirtyRoots.IsEmpty();
overflowTracker.Flush();
if (!interrupted) { // We didn't get interrupted. Go ahead and perform scroll anchor // adjustments.
FlushPendingScrollAnchorAdjustments();
}
mPendingDidDoReflow = true;
}
// Exiting the scriptblocker might have killed us. If we were processing // scroll commands, let the outermost call deal with it. if (!mIsDestroying && mPendingDidDoReflow && !wasProcessingReflowCommands) {
DidDoReflow(aInterruptible);
}
void PresShell::WindowSizeMoveDone() { if (mPresContext) {
EventStateManager::ClearGlobalActiveContent(nullptr);
ClearMouseCapture();
}
}
NS_IMETHODIMP
PresShell::Observe(nsISupports* aSubject, constchar* aTopic, const char16_t* aData) { if (mIsDestroying) {
NS_WARNING("our observers should have been unregistered by now"); return NS_OK;
}
if (!nsCRT::strcmp(aTopic, "memory-pressure")) { if (!AssumeAllFramesVisible() &&
mPresContext->IsRootContentDocumentInProcess()) {
DoUpdateApproximateFrameVisibility(/* aRemoveOnly = */ true);
} return NS_OK;
}
if (!nsCRT::strcmp(aTopic, NS_WIDGET_WAKE_OBSERVER_TOPIC)) {
mLastOSWake = TimeStamp::Now(); return NS_OK;
}
// For parent process, user may expect the UI is interactable after a // tab (previously opened page or home page) has restored. if (!nsCRT::strcmp(aTopic, "sessionstore-one-or-no-tab-restored")) {
MOZ_ASSERT(XRE_IsParentProcess());
sProcessInteractable = true;
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); if (os) {
os->RemoveObserver(this, "sessionstore-one-or-no-tab-restored");
} return NS_OK;
}
if (!nsCRT::strcmp(aTopic, "font-info-updated")) { // See how gfxPlatform::ForceGlobalReflow encodes this. bool needsReframe = aData && !!aData[0];
mPresContext->ForceReflowForFontInfoUpdate(needsReframe); return NS_OK;
}
// The "look-and-feel-changed" notification for JS observers will be // dispatched HandleGlobalThemeChange once LookAndFeel caches are cleared. if (!nsCRT::strcmp(aTopic, "internal-look-and-feel-changed")) { // See how LookAndFeel::NotifyChangedAllWindows encodes this. auto kind = widget::ThemeChangeKind(aData[0]);
mPresContext->ThemeChanged(kind); return NS_OK;
}
//------------------------------------------------------ // End of protected and private methods on the PresShell //------------------------------------------------------
// The root element's frame's ComputedStyle is the root of a separate tree.
Element* rootElement = mDocument->GetRootElement(); if (rootElement) {
nsIFrame* rootElementFrame = rootElement->GetPrimaryFrame(); if (rootElementFrame) {
rootElementFrame->Style()->List(out, aIndent);
}
}
} #endif
#ifdefined(DEBUG) || defined(MOZ_LAYOUT_DEBUGGER) void PresShell::ListStyleSheets(FILE* out, int32_t aIndent) { auto ListStyleSheetsAtOrigin = [this, out, aIndent](StyleOrigin origin) {
int32_t sheetCount = StyleSet()->SheetCount(origin); for (int32_t i = 0; i < sheetCount; ++i) {
StyleSet()->SheetAt(origin, i)->List(out, aIndent);
}
};
// We don't care about the document language or user fonts here; // just get a default Latin font.
nsFont font(StyleGenericFontFamily::Serif, Length::FromPixels(11));
nsFontMetrics::Params params;
params.language = nsGkAtoms::x_western;
params.textPerf = aPresContext->GetTextPerfMetrics();
params.featureValueLookup = aPresContext->GetFontFeatureValuesLookup();
RefPtr<nsFontMetrics> fm = aPresContext->GetMetricsFor(font, params);
char buf[16]; int len = SprintfLiteral(buf, "%d", counter->mCount);
nscoord x = 0, y = fm->MaxAscent();
nscoord width, height = fm->MaxHeight();
fm->SetTextRunRTL(false);
width = fm->GetWidth(buf, len, drawTarget);
size_t matchOrInsertionIdx = entry.Length(); // If the same element is already in the array, // someone forgot to call RemoveAnchorPosAnchor. if (BinarySearchIf(entry, 0, entry.Length(), cmp, &matchOrInsertionIdx)) { if (entry.ElementAt(matchOrInsertionIdx) == aFrame) { // nsLayoutUtils::CompareTreePosition() returns 0 when the frames are // in different documents or child lists. This could indicate that // the tree is being restructured and we can defer anchor insertion // to a MergeAnchorPosAnchors call after the restructuring is complete.
MOZ_ASSERT_UNREACHABLE("Attempt to insert a frame twice was made"); return;
}
MOZ_ASSERT(!entry.Contains(aFrame));
if (!aForMerge) { // nsLayoutUtils::CompareTreePosition() returns 0 when the frames are // in different documents or child lists. This could indicate that // the tree is being restructured and we can defer anchor insertion // to a MergeAnchorPosAnchors call after the restructuring is complete.
mLazyAnchorPosAnchorChanges.AppendElement(
AnchorPosAnchorChange{RefPtr<const nsAtom>(aName), aFrame}); return;
}
}
staticbool NeedReflowForAnchorPos( const nsIFrame* aAnchor, const nsIFrame* aPositioned, const Maybe<AnchorPosResolutionData>& aData) { constbool validityChanged = (aAnchor && !aData) || (!aAnchor && aData); if (validityChanged) { returntrue;
} if (!aData) { // Was invalid, still invalid. No more consideration needed. returnfalse;
} // Was valid, still valid Did the referenced value change? if (!aAnchor) {
MOZ_ASSERT_UNREACHABLE("Anchor is supposed to be valid"); returnfalse;
} constauto& anchorReference = aData.ref(); constauto border = aPositioned->GetParent()->GetUsedBorder(); const nsPoint borderTopLeft{border.left, border.top}; constauto anchorRect = AnchorPositioningUtils::ReassembleAnchorRect(
aAnchor, aPositioned->GetParent()) -
borderTopLeft; if (anchorReference.mSize != anchorRect.Size()) { // Size changed, needs reflow. returntrue;
} if (!anchorReference.mOffsetData) { // Didn't resolve offsets, no need to reflow based on it. returnfalse;
}
constauto nearestScrollFrameInfo =
AnchorPositioningUtils::GetNearestScrollFrame(aAnchor); if (anchorReference.mOffsetData->mDistanceToNearestScrollContainer !=
nearestScrollFrameInfo.mDistance) { // Scroll container relationship changed, need to reflow. returntrue;
}
constauto newOrigin = anchorRect.TopLeft(); constauto& prevOrigin = anchorReference.mOffsetData.ref().mOrigin; // Did the offset change? return newOrigin != prevOrigin;
}
PresShell::AnchorPosUpdateResult PresShell::UpdateAnchorPosLayout() { if (mAnchorPosPositioned.IsEmpty()) { return AnchorPosUpdateResult::NotApplicable;
}
// Flush the layout, so that positioned frames' anchor references are valid.
DoFlushLayout(/* aInterruptible = */ false);
auto result = AnchorPosUpdateResult::Flushed;
AUTO_PROFILER_MARKER_UNTYPED("UpdateAnchorPosLayout", LAYOUT, {}); for (auto* positioned : mAnchorPosPositioned) {
MOZ_ASSERT(positioned->IsAbsolutelyPositioned(), "Anchor positioned frame is not absolutely positioned?"); constauto* anchorPosReferenceData =
positioned->GetProperty(nsIFrame::AnchorPosReferences()); // Note that it's possible (Though unlikely) to register as anchor // positioned but not actually make any anchor resolution - e.g. // `position-anchor` is set, but no other anchor positioning property is // used. if (!anchorPosReferenceData || anchorPosReferenceData->IsEmpty()) { continue;
} if (positioned->HasAnyStateBits(NS_FRAME_IS_DIRTY)) { // Already marked for reflow. continue;
} constauto defaultAnchorInfo = [&]() -> Maybe<DefaultAnchorInfo> { auto usedAnchorName = AnchorPositioningUtils::GetUsedAnchorName(
positioned, ScopedNameRef{nullptr, StyleCascadeLevel::Default()}); if (!usedAnchorName) { return Nothing{};
} const ScopedNameRef& usedName = *usedAnchorName; constauto* anchor = GetAnchorPosAnchor(usedName, positioned); if (!anchor) { return Nothing{};
} constauto nearestScrollFrame =
AnchorPositioningUtils::GetNearestScrollFrame(anchor); return Some(DefaultAnchorInfo{usedName.mName, usedName.mTreeScope, anchor,
nearestScrollFrame.mDistance});
}(); bool shouldReflow = false; if (defaultAnchorInfo &&
defaultAnchorInfo->mDistanceToNearestScrollContainer !=
anchorPosReferenceData->mDistanceToDefaultScrollContainer) { // Default anchor's nearest scroller changed, reflow.
shouldReflow = true;
} else { constauto GetAnchor =
[&](const ScopedNameRef& aNameRef, const nsIFrame* aPositioned) -> const nsIFrame* { if (!defaultAnchorInfo) { return GetAnchorPosAnchor(aNameRef, aPositioned);
} constauto* defaultAnchorName = defaultAnchorInfo->mName; if (aNameRef.mName != defaultAnchorName) { return GetAnchorPosAnchor(aNameRef, aPositioned);
} return defaultAnchorInfo->mAnchor;
}; for (constauto& kv : *anchorPosReferenceData) { constauto& data = kv.GetData(); constauto& anchorKey = kv.GetKey(); constauto* anchor = GetAnchor(anchorKey, positioned); if (NeedReflowForAnchorPos(anchor, positioned, data)) {
shouldReflow = true; break;
}
}
} if (shouldReflow) {
result = AnchorPosUpdateResult::NeedReflow;
MarkPositionedFrameForReflow(positioned);
}
} return result;
}
void PresShell::ActivenessMaybeChanged() { if (!mDocument) { return;
}
SetIsActive(ComputeActiveness());
}
// A PresShell being active means that it is visible (or close to be visible, if // the front-end is warming it). That means that when it is active we always // tick its refresh driver at full speed if needed. // // Image documents behave specially in the sense that they are always "active" // and never "in the active tab". However these documents tick manually so // there's not much to worry about there. bool PresShell::ComputeActiveness() const {
MOZ_LOG(gLog, LogLevel::Debug,
("PresShell::ComputeActiveness(%s, %d)\n",
mDocument->GetDocumentURI()
? mDocument->GetDocumentURI()->GetSpecOrDefault().get()
: "(no uri)",
mIsActive));
Document* doc = mDocument;
if (doc->IsBeingUsedAsImage()) { // Documents used as an image can remain active. They do not tick their // refresh driver if not painted, and they can't run script or such so they // can't really observe much else. // // Image docs can be displayed in multiple docs at the same time so the "in // active tab" bool doesn't make much sense for them. returntrue;
}
if (Document* displayDoc = doc->GetDisplayDocument()) { // Ok, we're an external resource document -- we need to use our display // document's docshell to determine "IsActive" status, since we lack // a browsing context of our own.
MOZ_ASSERT(!doc->GetBrowsingContext(), "external resource doc shouldn't have its own BC");
doc = displayDoc;
}
BrowsingContext* bc = doc->GetBrowsingContext(); constbool inActiveTab = bc && bc->IsActive();
if (StaticPrefs::layout_testing_top_level_always_active() && bc &&
bc->IsTop()) {
MOZ_LOG(gLog, LogLevel::Debug, (" > Activeness overridden by pref")); returntrue;
}
if (bc && bc->GetControlsDocumentPiP()) {
MOZ_ASSERT(inActiveTab, "BrowsingContext should be active due to PiP window");
MOZ_LOG(gLog, LogLevel::Debug,
(" > BrowsingContext controls a PiP window")); returntrue;
}
Document* root = nsContentUtils::GetInProcessSubtreeRootDocument(doc); if (auto* browserChild = BrowserChild::GetFrom(root->GetDocShell())) { // We might want to activate a tab even though the browsing-context is not // active if the BrowserChild is considered visible. This serves two // purposes: // // * For top-level tabs, we use this for tab warming. The browsing-context // might still be inactive, but we want to activate the pres shell and // the refresh driver. // // * For oop iframes, we do want to throttle them if they're not visible. // // TODO(emilio): Consider unifying the in-process vs. fission iframe // throttling code (in-process throttling for non-visible iframes lives // right now in Document::ShouldThrottleFrameRequests(), but that only // throttles rAF). if (!browserChild->IsVisible()) {
MOZ_LOG(gLog, LogLevel::Debug,
(" > BrowserChild %p is not visible", browserChild)); returnfalse;
}
// If the browser is visible but just due to be preserving layers // artificially, we do want to fall back to the browsing context activeness // instead. Otherwise we do want to be active for the use cases above. if (!browserChild->IsPreservingLayers()) {
MOZ_LOG(gLog, LogLevel::Debug,
(" > BrowserChild %p is visible and not preserving layers",
browserChild)); returntrue;
}
MOZ_LOG(
gLog, LogLevel::Debug,
(" > BrowserChild %p is visible and preserving layers", browserChild));
} return inActiveTab;
}
void PresShell::SetIsActive(bool aIsActive) {
MOZ_ASSERT(mDocument, "should only be called with a document");
if (activityChanged) { // Propagate state-change to my resource documents' PresShells and other // subdocuments. // // Note that it is fine to not propagate to fission iframes. Those will // become active / inactive as needed as a result of they getting painted / // not painted eventually. auto recurse = [aIsActive](Document& aSubDoc) { if (PresShell* presShell = aSubDoc.GetPresShell()) {
presShell->SetIsActive(aIsActive);
} return CallState::Continue;
};
mDocument->EnumerateExternalResources(recurse);
mDocument->EnumerateSubDocuments(recurse);
}
UpdateImageLockingState();
if (activityChanged) { #ifdefined(MOZ_WIDGET_ANDROID) if (!aIsActive && presContext &&
presContext->IsRootContentDocumentCrossProcess()) { // Reset the dynamic toolbar offset state.
presContext->UpdateDynamicToolbarOffset(0);
} #endif
}
if (aIsActive) { #ifdef ACCESSIBILITY if (nsAccessibilityService* accService = GetAccService()) {
accService->PresShellActivated(this);
} #endif if (nsIFrame* rootFrame = GetRootFrame()) {
rootFrame->SchedulePaint();
}
}
}
Maybe<MobileViewportManager::ManagerType> UseMobileViewportManager(
PresShell* aPresShell, Document* aDocument) { // If we're not using APZ, we won't be able to zoom, so there is no // point in having an MVM. if (nsIWidget* widget = aPresShell->GetNearestWidget()) { if (!widget->AsyncPanZoomEnabled()) { return Nothing();
}
} if (nsLayoutUtils::ShouldHandleMetaViewport(aDocument)) { return Some(MobileViewportManager::ManagerType::VisualAndMetaViewport);
} if (nsLayoutUtils::AllowZoomingForDocument(aDocument)) { return Some(MobileViewportManager::ManagerType::VisualViewportOnly);
} return Nothing();
}
void PresShell::MaybeRecreateMobileViewportManager(bool aAfterInitialization) { // Determine if we require a MobileViewportManager, and what kind if so. We // need one any time we allow resolution zooming for a document, and any time // we want to obey <meta name="viewport"> tags for it.
Maybe<MobileViewportManager::ManagerType> mvmType =
UseMobileViewportManager(this, mDocument);
if (mvmType.isNothing() && !mMobileViewportManager) { // We don't need one and don't have it. So we're done. return;
} if (mvmType && mMobileViewportManager &&
*mvmType == mMobileViewportManager->GetManagerType()) { // We need one and we have one of the correct type, so we're done. return;
}
if (!mPresContext->IsRootContentDocumentCrossProcess()) {
MOZ_ASSERT(!mMobileViewportManager, "We never create MVMs for subframes"); return;
}
if (mMobileViewportManager) { // We have one, but we need to either destroy it completely to replace it // with another one of the correct type. So either way, let's destroy the // one we have.
mMobileViewportManager->Destroy();
mMobileViewportManager = nullptr;
mMVMContext = nullptr;
ResetVisualViewportSize();
}
if (mvmType) { // Let's create the MVM of the type that we need. At this point we shouldn't // have one.
MOZ_ASSERT(!mMobileViewportManager);
mMVMContext = MakeRefPtr<GeckoMVMContext>(mDocument, this);
mMobileViewportManager =
MakeRefPtr<MobileViewportManager>(mMVMContext, *mvmType); if (MOZ_UNLIKELY(
MOZ_LOG_TEST(MobileViewportManager::gLog, LogLevel::Debug))) {
nsIURI* uri = mDocument->GetDocumentURI();
MOZ_LOG(
MobileViewportManager::gLog, LogLevel::Debug,
("Created MVM %p (type %d) for URI %s", mMobileViewportManager.get(),
(int)*mvmType, uri ? uri->GetSpecOrDefault().get() : "(null)"));
} if (BrowserChild* browserChild = BrowserChild::GetFrom(this)) {
mMobileViewportManager->UpdateKeyboardHeight(
browserChild->GetKeyboardHeight());
}
}
if (aAfterInitialization) { // Setting the initial viewport will trigger a reflow. if (mMobileViewportManager) {
mMobileViewportManager->SetInitialViewport();
} else { // Force a reflow to our correct layout viewport size.
ForceResizeReflowWithCurrentDimensions();
} // After we clear out the MVM and the MVMContext, also reset the // resolution to 1.
SetResolutionAndScaleTo(1.0f, ResolutionChangeOrigin::MainThreadRestore);
}
}
/* *Determinesthecurrentimagelockingstate.Calledwhenoneofthe *dependentfactorschanges.
*/ void PresShell::UpdateImageLockingState() { // We're locked if we're both thawed and active. constbool locked = !mFrozen && mIsActive; if (locked == mDocument->GetLockingImages()) { return;
}
mDocument->SetLockingImages(locked); if (locked) { // Request decodes for visible image frames; we want to start decoding as // quickly as possible when we get foregrounded to minimize flashing. for (constauto& key : mApproximatelyVisibleFrames) { if (nsImageFrame* imageFrame = do_QueryFrame(key)) {
imageFrame->MaybeDecodeForPredictedSize();
}
}
}
}
nsIWidget* PresShell::GetRootWidget() const { if (!mPresContext) { return nullptr;
} for (nsPresContext* pc = mPresContext; pc; pc = pc->GetParentPresContext()) { if (auto* widget = pc->PresShell()->GetOwnWidget()) { return widget;
}
} return nullptr;
}
changed = changed || fontInflationWasEnabled != FontSizeInflationEnabled(); if (!changed) { return;
} if (nsCOMPtr<nsIDocShell> docShell = pc->GetDocShell()) {
nsTArray<nsCOMPtr<nsIDocumentViewer>> array;
AppendSubtree(docShell, array); for (uint32_t i = 0, iEnd = array.Length(); i < iEnd; ++i) {
nsCOMPtr<nsIDocumentViewer> viewer = array[i]; if (RefPtr<PresShell> descendantPresShell = viewer->GetPresShell()) {
nsIFrame* rootFrame = descendantPresShell->GetRootFrame(); if (rootFrame) {
descendantPresShell->FrameNeedsReflow(
rootFrame, IntrinsicDirty::FrameAncestorsAndDescendants,
NS_FRAME_IS_DIRTY);
}
}
}
}
}
void PresShell::CompleteChangeToVisualViewportSize() { // This can get called during reflow, if the caller wants to get the latest // visual viewport size after scrollbars have been added/removed. In such a // case, we don't need to mark things as dirty because the things that we // would mark dirty either just got updated (the root scrollframe's // scrollbars), or will be laid out later during this reflow cycle (fixed-pos // items). Callers that update the visual viewport during a reflow are // responsible for maintaining these invariants. if (!mIsReflowing) { if (ScrollContainerFrame* sf = GetRootScrollContainerFrame()) {
sf->MarkScrollbarsDirtyForReflow();
}
MarkFixedFramesForReflow();
}
MaybeReflowForInflationScreenSizeChange();
if (auto* window = nsGlobalWindowInner::Cast(mDocument->GetInnerWindow())) {
window->VisualViewport()->PostResizeEvent();
}
}
void PresShell::SetNeedsWindowPropertiesSync() { if (XRE_IsContentProcess() || !IsRoot()) { // Window properties are only relevant to top level widgets in the parent // process return;
}
mNeedsWindowPropertiesSync = true;
SchedulePaint();
}
nsSize PresShell::GetVisualViewportSize() const {
NS_ASSERTION(mVisualViewportSizeSet, "asking for visual viewport size when its not set?");
DynamicToolbarState state = GetDynamicToolbarState(); return (state == DynamicToolbarState::InTransition ||
state == DynamicToolbarState::Collapsed)
? GetVisualViewportSizeUpdatedByDynamicToolbar()
: mVisualViewportSize;
}
bool PresShell::SetVisualViewportOffset(const nsPoint& aScrollOffset, const nsPoint& aPrevLayoutScrollPos) {
nsPoint newOffset = aScrollOffset;
ScrollContainerFrame* rootScrollContainerFrame =
GetRootScrollContainerFrame(); if (rootScrollContainerFrame) { // See the comment in ScrollContainerFrame::Reflow above the call to // SetVisualViewportOffset for why we need to do this.
nsRect scrollRange =
rootScrollContainerFrame->GetScrollRangeForUserInputEvents(); if (!scrollRange.Contains(newOffset)) {
newOffset.x = std::min(newOffset.x, scrollRange.XMost());
newOffset.x = std::max(newOffset.x, scrollRange.x);
newOffset.y = std::min(newOffset.y, scrollRange.YMost());
newOffset.y = std::max(newOffset.y, scrollRange.y);
}
}
// Careful here not to call GetVisualViewportOffset to get the previous visual // viewport offset because if mVisualViewportOffset is nothing then we'll get // the layout scroll position directly from the scroll frame and it has likely // already been updated.
nsPoint prevOffset = aPrevLayoutScrollPos; if (mVisualViewportOffset.isSome()) {
prevOffset = *mVisualViewportOffset;
} if (prevOffset == newOffset) { returnfalse;
}
mVisualViewportOffset = Some(newOffset);
if (auto* window = nsGlobalWindowInner::Cast(mDocument->GetInnerWindow())) {
window->VisualViewport()->PostScrollEvent(prevOffset, aPrevLayoutScrollPos);
}
if (IsVisualViewportSizeSet() && rootScrollContainerFrame) {
rootScrollContainerFrame->Anchor()->UserScrolled();
}
if (gfxPlatform::UseDesktopZoomingScrollbars()) { if (rootScrollContainerFrame) {
rootScrollContainerFrame->UpdateScrollbarPosition();
}
}
void PresShell::RefreshViewportSize() { if (mMobileViewportManager) {
mMobileViewportManager->RefreshViewportSize(false);
}
}
void PresShell::ScrollToVisual(const nsPoint& aVisualViewportOffset,
ScrollOffsetUpdateType aUpdateType,
ScrollMode aMode) { if (aMode == ScrollMode::Smooth || aMode == ScrollMode::SmoothMsd) { if (ScrollContainerFrame* sf = GetRootScrollContainerFrame()) { if (sf->SmoothScrollVisual(aVisualViewportOffset, aUpdateType, aMode)) { return;
}
}
}
// If the caller asked for instant scroll, or if we failed // to do a smooth scroll, do an instant scroll.
SetPendingVisualScrollUpdate(aVisualViewportOffset, aUpdateType);
}
// The pending update is picked up during the next paint. // Schedule a paint to make sure one will happen. if (nsIFrame* rootFrame = GetRootFrame()) {
rootFrame->SchedulePaint();
}
}
nsPoint PresShell::GetLayoutViewportOffset() const {
nsPoint result; if (ScrollContainerFrame* sf = GetRootScrollContainerFrame()) {
result = sf->GetScrollPosition();
} return result;
}
nsSize PresShell::GetLayoutViewportSize() const {
nsSize result; if (ScrollContainerFrame* sf = GetRootScrollContainerFrame()) {
result = sf->GetScrollPortRect().Size();
} return result;
}
nsSize PresShell::GetInnerSize() const { if (ScrollContainerFrame* sf = GetRootScrollContainerFrame()) { return sf->GetSizeForWindowInnerSize();
} return mPresContext->GetVisibleArea().Size();
}
nsSize PresShell::GetVisualViewportSizeUpdatedByDynamicToolbar() const {
NS_ASSERTION(mVisualViewportSizeSet, "asking for visual viewport size when its not set?"); if (!mMobileViewportManager) { return mVisualViewportSize;
}
bool PresShell::DetermineFontSizeInflationState() {
MOZ_ASSERT(mPresContext, "our pres context should not be null"); if (mPresContext->IsChrome()) { returnfalse;
}
// Force-enabling font inflation always trumps the heuristics here. if (!FontSizeInflationForceEnabled()) { if (BrowserChild* tab = BrowserChild::GetFrom(this)) { // We're in a child process. Cancel inflation if we're not // async-pan zoomed. if (!tab->AsyncPanZoomEnabled()) { returnfalse;
}
}
}
Maybe<LayoutDeviceIntSize> displaySize; // The MVM already caches the top-level content viewer size and is therefore // the fastest way of getting that data. if (mPresContext->IsRootContentDocumentCrossProcess()) { if (mMobileViewportManager) {
displaySize = Some(mMobileViewportManager->DisplaySize());
}
} elseif (PresShell* rootPresShell = GetRootPresShell()) { // With any luck, we can get at the root content document without any cross- // process shenanigans. if (auto mvm = rootPresShell->GetMobileViewportManager()) {
displaySize = Some(mvm->DisplaySize());
}
}
if (!displaySize) { // Unfortunately, it looks like the root content document lives in a // different process. For consistency's sake it would be best to always use // the content viewer size of the root content document, but it's not worth // the effort, because this only makes a difference in the case of pages // with an explicitly sized viewport (neither "width=device-width" nor a // completely missing viewport tag) being loaded within a frame, which is // hopefully a relatively exotic case. // More to the point, these viewport size and zoom-based calculations don't // really make sense for frames anyway, so instead of creating a way to // access the content viewer size of the top level document cross-process, // we probably rather want frames to simply inherit the font inflation state // of their top-level parent and should therefore invest any time spent on // getting things to work cross-process into that (bug 1724311).
// Until we get around to that though, we just use the content viewer size // of however high we can get within the same process.
// (This also serves as a fallback code path if the MVM isn't available, // e.g. when debugging in non-e10s mode on Desktop.)
nsPresContext* topContext =
mPresContext->GetInProcessRootContentDocumentPresContext();
LayoutDeviceIntSize result; if (!nsLayoutUtils::GetDocumentViewerSize(topContext, result)) { returnfalse;
}
displaySize = Some(result);
}
// If aFrameForPresShell has already been reframed before this is called, // we cannot keep handling aGUIEvent. auto overrideClickTargetOrError = [&]() -> Result<nsIContent*, nsresult> { if (PointerEventHandler::ShouldDispatchClickEventOnCapturingElement() &&
aPointerCapturingContent) { return aGUIEvent->AsMouseEvent()->mInputSource ==
MouseEvent_Binding::MOZ_SOURCE_TOUCH // If the event is a compatibility mouse event of Touch Events, // `click` event target should be the element capturing the // touch (Note that eTouchStart caused implicit pointer capture // by default when the web app does not use the pointer capture // API). However, if the web app released the pointer capture, // the target should be the closest common ancestor of // ePointerDown and ePointerUp. These things will be handled // by EventStateManager::SetClickCount(). Therefore, we should // not override the click event target for a single tap here.
? nullptr // On the other hand, we want to use the pointer capturing // element as the target of `click` event caused by other input // devices.
: aPointerCapturingContent;
}
if (MOZ_UNLIKELY(!aFrameForPresShell)) { return Err(NS_ERROR_FAILURE);
}
// If we know the event, we can compute the target correctly. if (aGUIEvent) {
MOZ_ASSERT(mContent == mFrame->GetEventTargetContent(aGUIEvent)); return;
} // If clicking an image map, mFrame should be the image frame, but mContent // should be the area element which handles the event at the position. if (mContent->IsHTMLElement(nsGkAtoms::area)) {
MOZ_ASSERT(mContent->GetPrimaryFrame() == mFrame); return;
}
// Otherwise, we can check only whether mContent is an inclusive ancestor // element or not. if (!mContent->IsElement()) {
MOZ_ASSERT(mContent == mFrame->GetContent()); return;
} const Element* const closestInclusiveAncestorElement =
mFrame->GetContent()->GetInclusiveFlattenedTreeAncestorElement(); if (closestInclusiveAncestorElement == mContent) { return;
} if (closestInclusiveAncestorElement->IsInNativeAnonymousSubtree() &&
(mContent == closestInclusiveAncestorElement
->FindFirstNonChromeOnlyAccessContent())) { return;
}
NS_WARNING(nsPrintfCString("mContent=%s", ToString(*mContent).c_str()).get());
NS_WARNING(nsPrintfCString("mFrame->GetContent()=%s",
ToString(*mFrame->GetContent()).c_str())
.get());
MOZ_ASSERT(mContent == mFrame->GetContent()); #endif// #ifdef DEBUG
}
bool PresShell::EventHandler::EventTargetData::MaybeRetargetToActiveDocument(
WidgetGUIEvent* aGUIEvent) {
MOZ_ASSERT(aGUIEvent);
MOZ_ASSERT(mFrame);
MOZ_ASSERT(mPresShell);
MOZ_ASSERT(!mContent, "Doesn't support to retarget the content");
EventStateManager* activeESM =
EventStateManager::GetActiveEventStateManager(); if (!activeESM) { returnfalse;
}
if (aGUIEvent->mClass != ePointerEventClass &&
!aGUIEvent->HasMouseEventMessage()) { returnfalse;
}
if (activeESM == GetEventStateManager()) { returnfalse;
}
if (aGUIEvent->ShouldIgnoreCapturingContent()) { returnfalse;
}
nsPresContext* activePresContext = activeESM->GetPresContext(); if (!activePresContext) { returnfalse;
}
PresShell* activePresShell = activePresContext->GetPresShell(); if (!activePresShell) { returnfalse;
}
// Note, currently for backwards compatibility we don't forward mouse events // to the active document when mouse is over some subdocument. if (!nsContentUtils::ContentIsCrossDocDescendantOf(
activePresShell->GetDocument(), GetDocument())) { returnfalse;
}
// If there is no content for this frame, target it anyway. Some frames can // be targeted but do not have content, particularly windows with scrolling // off. if (!mContent) { returntrue;
}
// Bug 103055, bug 185889: mouse events apply to *elements*, not all nodes. // Thus we get the nearest element parent here. // XXX we leave the frame the same even if we find an element parent, so that // the text frame will receive the event (selection and friends are the ones // who care about that anyway) // // We use weak pointers because during this tight loop, the node // will *not* go away. And this happens on every mousemove.
mContent = mContent->GetInclusiveFlattenedTreeAncestorElement();
// If we found an element, target it. Otherwise, target *nothing*. return !!mContent;
}
// If the stored event target is removed, we will not get an event target // frame from the wheel transaction here.
nsIFrame* groupFrame = WheelTransaction::GetEventTargetFrame(); if (!groupFrame) { return;
}
// If we have a stored event target from the wheel transaction, override // the event target.
SetFrameAndComputePresShellAndContent(groupFrame, aGUIEvent);
}
PresShell* newPresShell = PresShell::GetShellForTouchEvent(aGUIEvent); if (!newPresShell) { return; // XXX Why don't we stop handling the event in this case?
}
// Touch events (except touchstart) are dispatching to the captured // element. Get correct shell from it.
mPresShell = newPresShell;
}
nsIFrame* topmostFrameWithContentHidden = nullptr; for (nsIFrame* cur = aFrame->GetInFlowParent(); cur;
cur = cur->GetInFlowParent()) { if (cur->HidesContent()) {
topmostFrameWithContentHidden = cur;
mHiddenContentInForcedLayout.Insert(cur->GetContent());
}
}
if (mHiddenContentInForcedLayout.IsEmpty()) { return;
}
// Queue and immediately flush a reflow for this node.
MOZ_ASSERT(topmostFrameWithContentHidden);
FrameNeedsReflow(topmostFrameWithContentHidden, IntrinsicDirty::None,
NS_FRAME_IS_DIRTY);
}
if (!XRE_IsParentProcess()) { // We trigger the fullscreen warning from the parent process. returnfalse;
}
if (aKeyboardEvent.mIsRepeat) { // Only when we are in tracking a long press of the escape key and haven't // shown the warning for the current long press yet. if (mFirstUnmatchedEscapeKeyDownForFullscreen &&
!mHasShownFullscreenWarningForCurrentEscapeKeyLongPress) { if ((aKeyboardEvent.mTimeStamp -
mFirstUnmatchedEscapeKeyDownForFullscreen) >=
TimeDuration::FromMilliseconds(
StaticPrefs::
dom_fullscreen_keyboard_lock_long_press_warning_interval())) {
mHasShownFullscreenWarningForCurrentEscapeKeyLongPress = true; returntrue;
}
} returnfalse;
}
constbool escapeKeyDownWithinWarnInterval =
previousEscapeKeyDownTime &&
(aKeyboardEvent.mTimeStamp - previousEscapeKeyDownTime) <=
TimeDuration::FromMilliseconds(
StaticPrefs::
dom_fullscreen_keyboard_lock_triple_click_warn_interval()); if (!escapeKeyDownWithinWarnInterval) { // If the escape keys were pressed outside of the warning interval, start // a new count.
mEscapeKeyDownCountForFullscreenKeyboardLockWarning = 1; returnfalse;
}
mEscapeKeyDownCountForFullscreenKeyboardLockWarning += 1; if (mEscapeKeyDownCountForFullscreenKeyboardLockWarning < 3) { returnfalse;
}
// If the escape key has been pressed 3 or more times within the warning // interval, trigger the warning and reset the counter and timestamp.
mEscapeKeyDownCountForFullscreenKeyboardLockWarning = 0;
mLastEscapeKeyDownTimeForFullscreenKeyboardLockWarning = TimeStamp(); returntrue;
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 1.534 Sekunden
(vorverarbeitet am 2026-09-29)
¤