mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 05:41:12 +00:00
b651bfe99a
With this patch on its own we get some a11y tests failures, but those are fixed on a later patch. Combobox select no longer creates frames for its <options>, nor an nsListControlFrame. Instead, it computes its right intrinsic size using the largest size of the options. This is better, because we render the option text using the select style so if the select and option styles are mismatched it'd cause changes in the size of the select when text changes. See the following in a build without the patch, for example: <select> <option>ABC</option> <option style="font-size: 1px">Something long</option> </select> This seems like a rather obscure case, but it's important to get it right, see bug 1741888. With this patch we use the same setup in content and parent processes (this needs bug 1596852 and bug 1744152). This means we can remove a bunch of the native view and popup code in nsListControlFrame. A couple browser_* tests are affected by this change and have been tweaked appropriately (the changes there are trivial). Not creating an nsListControlFrame for dropdown select means that we need to move a bunch of the event handling code from nsListControlFrame to a common place that nsComboboxControlFrame can also use. That place is HTMLSelectEventListener, and I think the setup is much nicer than having the code intertwined with nsListControlFrame. It should be relatively straight-forward to review, mostly moving code from one part to another. Another thing that we need to do in HTMLSelectEventListener that we didn't use to do is listening for DOM mutations on the dropdown. Before, we were relying on changes like text mutations triggering a reflow of the listcontrolframe, which also triggered a reflow of the comboboxcontrolframe, which in turn updated the text of the anonymous content. Now we need to trigger that reflow manually. There are some further simplifications that can be done after this lands (cleanup naming of openInParentProcess and so on, among others), but I'd rather land this first (after the merge of course) and work on them separately. Differential Revision: https://phabricator.services.mozilla.com/D132719
192 lines
7.4 KiB
C++
192 lines
7.4 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 "nsSelectsAreaFrame.h"
|
|
|
|
#include "mozilla/PresShell.h"
|
|
#include "nsIContent.h"
|
|
#include "nsListControlFrame.h"
|
|
#include "nsDisplayList.h"
|
|
#include "WritingModes.h"
|
|
|
|
using namespace mozilla;
|
|
|
|
nsContainerFrame* NS_NewSelectsAreaFrame(PresShell* aShell,
|
|
ComputedStyle* aStyle,
|
|
nsFrameState aFlags) {
|
|
nsSelectsAreaFrame* it =
|
|
new (aShell) nsSelectsAreaFrame(aStyle, aShell->GetPresContext());
|
|
|
|
// We need NS_BLOCK_FLOAT_MGR to ensure that the options inside the select
|
|
// aren't expanded by right floats outside the select.
|
|
it->AddStateBits(aFlags | NS_BLOCK_FLOAT_MGR);
|
|
|
|
return it;
|
|
}
|
|
|
|
NS_IMPL_FRAMEARENA_HELPERS(nsSelectsAreaFrame)
|
|
|
|
static nsListControlFrame* GetEnclosingListFrame(nsIFrame* aSelectsAreaFrame) {
|
|
nsIFrame* frame = aSelectsAreaFrame->GetParent();
|
|
while (frame) {
|
|
if (frame->IsListControlFrame())
|
|
return static_cast<nsListControlFrame*>(frame);
|
|
frame = frame->GetParent();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void nsSelectsAreaFrame::Reflow(nsPresContext* aPresContext,
|
|
ReflowOutput& aDesiredSize,
|
|
const ReflowInput& aReflowInput,
|
|
nsReflowStatus& aStatus) {
|
|
MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
|
|
|
|
nsListControlFrame* list = GetEnclosingListFrame(this);
|
|
NS_ASSERTION(list,
|
|
"Must have an nsListControlFrame! Frame constructor is "
|
|
"broken");
|
|
|
|
nsBlockFrame::Reflow(aPresContext, aDesiredSize, aReflowInput, aStatus);
|
|
|
|
// Check whether we need to suppress scrollbar updates. We want to do
|
|
// that if we're in a possible first pass and our block size of a row
|
|
// has changed.
|
|
if (list->MightNeedSecondPass()) {
|
|
nscoord newBSizeOfARow = list->CalcBSizeOfARow();
|
|
// We'll need a second pass if our block size of a row changed. For
|
|
// comboboxes, we'll also need it if our block size changed. If
|
|
// we're going to do a second pass, suppress scrollbar updates for
|
|
// this pass.
|
|
if (newBSizeOfARow != mBSizeOfARow) {
|
|
mBSizeOfARow = newBSizeOfARow;
|
|
list->SetSuppressScrollbarUpdate(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace mozilla {
|
|
/**
|
|
* This wrapper class lets us redirect mouse hits from the child frame of
|
|
* an option element to the element's own frame.
|
|
* REVIEW: This is what nsSelectsAreaFrame::GetFrameForPoint used to do
|
|
*/
|
|
class nsDisplayOptionEventGrabber : public nsDisplayWrapList {
|
|
public:
|
|
nsDisplayOptionEventGrabber(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
|
|
nsDisplayItem* aItem)
|
|
: nsDisplayWrapList(aBuilder, aFrame, aItem) {}
|
|
nsDisplayOptionEventGrabber(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
|
|
nsDisplayList* aList)
|
|
: nsDisplayWrapList(aBuilder, aFrame, aList) {}
|
|
virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
|
|
HitTestState* aState,
|
|
nsTArray<nsIFrame*>* aOutFrames) override;
|
|
virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
|
|
return false;
|
|
}
|
|
void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override {
|
|
GetChildren()->Paint(aBuilder, aCtx,
|
|
mFrame->PresContext()->AppUnitsPerDevPixel());
|
|
}
|
|
NS_DISPLAY_DECL_NAME("OptionEventGrabber", TYPE_OPTION_EVENT_GRABBER)
|
|
};
|
|
|
|
void nsDisplayOptionEventGrabber::HitTest(nsDisplayListBuilder* aBuilder,
|
|
const nsRect& aRect,
|
|
HitTestState* aState,
|
|
nsTArray<nsIFrame*>* aOutFrames) {
|
|
nsTArray<nsIFrame*> outFrames;
|
|
mList.HitTest(aBuilder, aRect, aState, &outFrames);
|
|
|
|
for (uint32_t i = 0; i < outFrames.Length(); i++) {
|
|
nsIFrame* selectedFrame = outFrames.ElementAt(i);
|
|
while (selectedFrame &&
|
|
!(selectedFrame->GetContent() &&
|
|
selectedFrame->GetContent()->IsHTMLElement(nsGkAtoms::option))) {
|
|
selectedFrame = selectedFrame->GetParent();
|
|
}
|
|
if (selectedFrame) {
|
|
aOutFrames->AppendElement(selectedFrame);
|
|
} else {
|
|
// keep the original result, which could be this frame
|
|
aOutFrames->AppendElement(outFrames.ElementAt(i));
|
|
}
|
|
}
|
|
}
|
|
|
|
class nsOptionEventGrabberWrapper : public nsDisplayItemWrapper {
|
|
public:
|
|
nsOptionEventGrabberWrapper() = default;
|
|
virtual nsDisplayItem* WrapList(nsDisplayListBuilder* aBuilder,
|
|
nsIFrame* aFrame,
|
|
nsDisplayList* aList) override {
|
|
return MakeDisplayItem<nsDisplayOptionEventGrabber>(aBuilder, aFrame,
|
|
aList);
|
|
}
|
|
virtual nsDisplayItem* WrapItem(nsDisplayListBuilder* aBuilder,
|
|
nsDisplayItem* aItem) override {
|
|
return MakeDisplayItem<nsDisplayOptionEventGrabber>(aBuilder,
|
|
aItem->Frame(), aItem);
|
|
}
|
|
};
|
|
|
|
class nsDisplayListFocus : public nsPaintedDisplayItem {
|
|
public:
|
|
nsDisplayListFocus(nsDisplayListBuilder* aBuilder, nsSelectsAreaFrame* aFrame)
|
|
: nsPaintedDisplayItem(aBuilder, aFrame) {
|
|
MOZ_COUNT_CTOR(nsDisplayListFocus);
|
|
}
|
|
MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayListFocus)
|
|
|
|
virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
|
|
bool* aSnap) const override {
|
|
*aSnap = false;
|
|
// override bounds because the list item focus ring may extend outside
|
|
// the nsSelectsAreaFrame
|
|
nsListControlFrame* listFrame = GetEnclosingListFrame(Frame());
|
|
return listFrame->InkOverflowRectRelativeToSelf() +
|
|
listFrame->GetOffsetToCrossDoc(Frame()) + ToReferenceFrame();
|
|
}
|
|
virtual void Paint(nsDisplayListBuilder* aBuilder,
|
|
gfxContext* aCtx) override {
|
|
nsListControlFrame* listFrame = GetEnclosingListFrame(Frame());
|
|
// listFrame must be non-null or we wouldn't get called.
|
|
listFrame->PaintFocus(
|
|
aCtx->GetDrawTarget(),
|
|
listFrame->GetOffsetToCrossDoc(Frame()) + ToReferenceFrame());
|
|
}
|
|
NS_DISPLAY_DECL_NAME("ListFocus", TYPE_LIST_FOCUS)
|
|
};
|
|
|
|
} // namespace mozilla
|
|
|
|
void nsSelectsAreaFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
|
|
const nsDisplayListSet& aLists) {
|
|
if (!aBuilder->IsForEventDelivery()) {
|
|
BuildDisplayListInternal(aBuilder, aLists);
|
|
return;
|
|
}
|
|
|
|
nsDisplayListCollection set(aBuilder);
|
|
BuildDisplayListInternal(aBuilder, set);
|
|
|
|
nsOptionEventGrabberWrapper wrapper;
|
|
wrapper.WrapLists(aBuilder, this, set, aLists);
|
|
}
|
|
|
|
void nsSelectsAreaFrame::BuildDisplayListInternal(
|
|
nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists) {
|
|
nsBlockFrame::BuildDisplayList(aBuilder, aLists);
|
|
|
|
nsListControlFrame* listFrame = GetEnclosingListFrame(this);
|
|
if (listFrame && listFrame->IsFocused()) {
|
|
// we can't just associate the display item with the list frame,
|
|
// because then the list's scrollframe won't clip it (the scrollframe
|
|
// only clips contained descendants).
|
|
aLists.Outlines()->AppendNewToTop<nsDisplayListFocus>(aBuilder, this);
|
|
}
|
|
}
|