summaryrefslogtreecommitdiffstats
path: root/layout/forms/nsSelectsAreaFrame.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/forms/nsSelectsAreaFrame.cpp')
-rw-r--r--layout/forms/nsSelectsAreaFrame.cpp191
1 files changed, 191 insertions, 0 deletions
diff --git a/layout/forms/nsSelectsAreaFrame.cpp b/layout/forms/nsSelectsAreaFrame.cpp
new file mode 100644
index 0000000000..64fcdbd029
--- /dev/null
+++ b/layout/forms/nsSelectsAreaFrame.cpp
@@ -0,0 +1,191 @@
+/* -*- 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);
+ }
+}