fune/layout/xul/nsRootBoxFrame.cpp
Emilio Cobos Álvarez 4e978b56b5 Bug 1689816 - Implement <tabpanels> and <deck> without XUL layout. r=Gijs,Jamie,morgan,preferences-reviewers,mconley,TYLin
Gijs for front-end bits, layout for the new CSS properties and the
removal of nsDeckFrame / nsStackLayout, Jamie and Morgan for the a11y
changes.

As discussed in the bug, the main tricky part here is handling a11y
correctly. For <deck>, that's trivial (just use `visibility: hidden` to
hide the panels visually, while removing the unselected panels from the
a11y tree).

For <tabpanels> however we need to do something special. We do want to
hide stuff visually, but we want to preserve the contents in the a11y
tree.

For that, the easiest fix is introducing a new privileged CSS property
(-moz-subtree-hidden-only-visually), which takes care of not painting
the frame, but marks stuff offscreen in the accessibility tree. This is
not intended to be a property used widely.

Other than that, the changes are relatively straight-forward, though
some of the accessible/mac changes I could get a sanity-check on.

Differential Revision: https://phabricator.services.mozilla.com/D157875
2022-09-27 04:18:16 +00:00

214 lines
7.8 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsHTMLParts.h"
#include "nsStyleConsts.h"
#include "nsGkAtoms.h"
#include "nsBoxFrame.h"
#include "nsDisplayList.h"
#include "nsIPopupContainer.h"
#include "nsIContent.h"
#include "nsFrameManager.h"
#include "nsLayoutUtils.h"
#include "mozilla/BasicEvents.h"
#include "mozilla/DisplayPortUtils.h"
#include "mozilla/PresShell.h"
using namespace mozilla;
// Interface IDs
// static
nsIPopupContainer* nsIPopupContainer::GetPopupContainer(PresShell* aPresShell) {
if (!aPresShell) {
return nullptr;
}
nsIFrame* rootFrame = aPresShell->GetRootFrame();
if (!rootFrame) {
return nullptr;
}
if (rootFrame) {
rootFrame = rootFrame->PrincipalChildList().FirstChild();
}
nsIPopupContainer* rootBox = do_QueryFrame(rootFrame);
// If the rootBox was not found yet this may be a top level non-XUL document.
if (rootFrame && !rootBox) {
// In a non-XUL document the rootFrame here will be a nsHTMLScrollFrame,
// get the nsCanvasFrame (which is the popup container) from it.
rootFrame = rootFrame->GetContentInsertionFrame();
rootBox = do_QueryFrame(rootFrame);
}
return rootBox;
}
class nsRootBoxFrame final : public nsBoxFrame, public nsIPopupContainer {
public:
friend nsIFrame* NS_NewBoxFrame(mozilla::PresShell* aPresShell,
ComputedStyle* aStyle);
explicit nsRootBoxFrame(ComputedStyle* aStyle, nsPresContext* aPresContext);
NS_DECL_QUERYFRAME
NS_DECL_FRAMEARENA_HELPERS(nsRootBoxFrame)
virtual nsPopupSetFrame* GetPopupSetFrame() override;
virtual void SetPopupSetFrame(nsPopupSetFrame* aPopupSet) override;
virtual dom::Element* GetDefaultTooltip() override;
virtual void SetDefaultTooltip(dom::Element* aTooltip) override;
virtual void AppendFrames(ChildListID aListID,
nsFrameList& aFrameList) override;
virtual void InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
const nsLineList::iterator* aPrevFrameLine,
nsFrameList& aFrameList) override;
virtual void RemoveFrame(ChildListID aListID, nsIFrame* aOldFrame) override;
virtual void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
const ReflowInput& aReflowInput,
nsReflowStatus& aStatus) override;
virtual nsresult HandleEvent(nsPresContext* aPresContext,
WidgetGUIEvent* aEvent,
nsEventStatus* aEventStatus) override;
virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
const nsDisplayListSet& aLists) override;
virtual bool IsFrameOfType(uint32_t aFlags) const override {
// Override bogus IsFrameOfType in nsBoxFrame.
if (aFlags & (nsIFrame::eReplacedContainsBlock | nsIFrame::eReplaced))
return false;
return nsBoxFrame::IsFrameOfType(aFlags);
}
#ifdef DEBUG_FRAME_DUMP
virtual nsresult GetFrameName(nsAString& aResult) const override;
#endif
nsPopupSetFrame* mPopupSetFrame;
protected:
dom::Element* mDefaultTooltip;
};
//----------------------------------------------------------------------
nsContainerFrame* NS_NewRootBoxFrame(PresShell* aPresShell,
ComputedStyle* aStyle) {
return new (aPresShell) nsRootBoxFrame(aStyle, aPresShell->GetPresContext());
}
NS_IMPL_FRAMEARENA_HELPERS(nsRootBoxFrame)
nsRootBoxFrame::nsRootBoxFrame(ComputedStyle* aStyle,
nsPresContext* aPresContext)
: nsBoxFrame(aStyle, aPresContext, kClassID, true),
mPopupSetFrame(nullptr),
mDefaultTooltip(nullptr) {}
void nsRootBoxFrame::AppendFrames(ChildListID aListID,
nsFrameList& aFrameList) {
MOZ_ASSERT(aListID == kPrincipalList, "unexpected child list ID");
MOZ_ASSERT(mFrames.IsEmpty(), "already have a child frame");
nsBoxFrame::AppendFrames(aListID, aFrameList);
}
void nsRootBoxFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
const nsLineList::iterator* aPrevFrameLine,
nsFrameList& aFrameList) {
// Because we only support a single child frame inserting is the same
// as appending.
MOZ_ASSERT(!aPrevFrame, "unexpected previous sibling frame");
AppendFrames(aListID, aFrameList);
}
void nsRootBoxFrame::RemoveFrame(ChildListID aListID, nsIFrame* aOldFrame) {
NS_ASSERTION(aListID == kPrincipalList, "unexpected child list ID");
if (aOldFrame == mFrames.FirstChild()) {
nsBoxFrame::RemoveFrame(aListID, aOldFrame);
} else {
MOZ_CRASH("unknown aOldFrame");
}
}
void nsRootBoxFrame::Reflow(nsPresContext* aPresContext,
ReflowOutput& aDesiredSize,
const ReflowInput& aReflowInput,
nsReflowStatus& aStatus) {
DO_GLOBAL_REFLOW_COUNT("nsRootBoxFrame");
MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
return nsBoxFrame::Reflow(aPresContext, aDesiredSize, aReflowInput, aStatus);
}
void nsRootBoxFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
const nsDisplayListSet& aLists) {
if (mContent && mContent->GetProperty(nsGkAtoms::DisplayPortMargins)) {
// The XUL document's root element may have displayport margins set in
// ChromeProcessController::InitializeRoot, and we should to supply the
// base rect.
nsRect displayPortBase =
aBuilder->GetVisibleRect().Intersect(nsRect(nsPoint(0, 0), GetSize()));
DisplayPortUtils::SetDisplayPortBase(mContent, displayPortBase);
}
// root boxes don't need a debug border/outline or a selection overlay...
// They *may* have a background propagated to them, so force creation
// of a background display list element.
DisplayBorderBackgroundOutline(aBuilder, aLists, true);
BuildDisplayListForChildren(aBuilder, aLists);
}
nsresult nsRootBoxFrame::HandleEvent(nsPresContext* aPresContext,
WidgetGUIEvent* aEvent,
nsEventStatus* aEventStatus) {
NS_ENSURE_ARG_POINTER(aEventStatus);
if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
return NS_OK;
}
if (aEvent->mMessage == eMouseUp) {
nsIFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
}
return NS_OK;
}
nsPopupSetFrame* nsRootBoxFrame::GetPopupSetFrame() { return mPopupSetFrame; }
void nsRootBoxFrame::SetPopupSetFrame(nsPopupSetFrame* aPopupSet) {
// Under normal conditions this should only be called once. However,
// if something triggers ReconstructDocElementHierarchy, we will
// destroy this frame's child (the nsDocElementBoxFrame), but not this
// frame. This will cause the popupset to remove itself by calling
// |SetPopupSetFrame(nullptr)|, and then we'll be able to accept a new
// popupset. Since the anonymous content is associated with the
// nsDocElementBoxFrame, we'll get a new popupset when the new doc
// element box frame is created.
MOZ_ASSERT(!aPopupSet || !mPopupSetFrame,
"Popup set is already defined! Only 1 allowed.");
mPopupSetFrame = aPopupSet;
}
dom::Element* nsRootBoxFrame::GetDefaultTooltip() { return mDefaultTooltip; }
void nsRootBoxFrame::SetDefaultTooltip(dom::Element* aTooltip) {
mDefaultTooltip = aTooltip;
}
NS_QUERYFRAME_HEAD(nsRootBoxFrame)
NS_QUERYFRAME_ENTRY(nsIPopupContainer)
NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
#ifdef DEBUG_FRAME_DUMP
nsresult nsRootBoxFrame::GetFrameName(nsAString& aResult) const {
return MakeFrameName(u"RootBox"_ns, aResult);
}
#endif