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.cpp206
1 files changed, 206 insertions, 0 deletions
diff --git a/layout/forms/nsSelectsAreaFrame.cpp b/layout/forms/nsSelectsAreaFrame.cpp
new file mode 100644
index 000000000..dd613ae9f
--- /dev/null
+++ b/layout/forms/nsSelectsAreaFrame.cpp
@@ -0,0 +1,206 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "nsIContent.h"
+#include "nsListControlFrame.h"
+#include "nsDisplayList.h"
+#include "WritingModes.h"
+
+using namespace mozilla;
+
+nsContainerFrame*
+NS_NewSelectsAreaFrame(nsIPresShell* aShell, nsStyleContext* aContext, nsFrameState aFlags)
+{
+ nsSelectsAreaFrame* it = new (aShell) nsSelectsAreaFrame(aContext);
+
+ // 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)
+
+//---------------------------------------------------------
+/**
+ * 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;
+ }
+ 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 nsDisplayWrapper
+{
+public:
+ nsOptionEventGrabberWrapper() {}
+ virtual nsDisplayItem* WrapList(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayList* aList) {
+ return new (aBuilder) nsDisplayOptionEventGrabber(aBuilder, aFrame, aList);
+ }
+ virtual nsDisplayItem* WrapItem(nsDisplayListBuilder* aBuilder,
+ nsDisplayItem* aItem) {
+ return new (aBuilder) nsDisplayOptionEventGrabber(aBuilder, aItem->Frame(), aItem);
+ }
+};
+
+static nsListControlFrame* GetEnclosingListFrame(nsIFrame* aSelectsAreaFrame)
+{
+ nsIFrame* frame = aSelectsAreaFrame->GetParent();
+ while (frame) {
+ if (frame->GetType() == nsGkAtoms::listControlFrame)
+ return static_cast<nsListControlFrame*>(frame);
+ frame = frame->GetParent();
+ }
+ return nullptr;
+}
+
+class nsDisplayListFocus : public nsDisplayItem {
+public:
+ nsDisplayListFocus(nsDisplayListBuilder* aBuilder,
+ nsSelectsAreaFrame* aFrame) :
+ nsDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayListFocus);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayListFocus() {
+ MOZ_COUNT_DTOR(nsDisplayListFocus);
+ }
+#endif
+
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override {
+ *aSnap = false;
+ // override bounds because the list item focus ring may extend outside
+ // the nsSelectsAreaFrame
+ nsListControlFrame* listFrame = GetEnclosingListFrame(Frame());
+ return listFrame->GetVisualOverflowRectRelativeToSelf() +
+ listFrame->GetOffsetToCrossDoc(ReferenceFrame());
+ }
+ virtual void Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) override {
+ nsListControlFrame* listFrame = GetEnclosingListFrame(Frame());
+ // listFrame must be non-null or we wouldn't get called.
+ listFrame->PaintFocus(aCtx->GetDrawTarget(),
+ aBuilder->ToReferenceFrame(listFrame));
+ }
+ NS_DISPLAY_DECL_NAME("ListFocus", TYPE_LIST_FOCUS)
+};
+
+void
+nsSelectsAreaFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ if (!aBuilder->IsForEventDelivery()) {
+ BuildDisplayListInternal(aBuilder, aDirtyRect, aLists);
+ return;
+ }
+
+ nsDisplayListCollection set;
+ BuildDisplayListInternal(aBuilder, aDirtyRect, set);
+
+ nsOptionEventGrabberWrapper wrapper;
+ wrapper.WrapLists(aBuilder, this, set, aLists);
+}
+
+void
+nsSelectsAreaFrame::BuildDisplayListInternal(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ nsBlockFrame::BuildDisplayList(aBuilder, aDirtyRect, 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(new (aBuilder)
+ nsDisplayListFocus(aBuilder, this));
+ }
+}
+
+void
+nsSelectsAreaFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ nsListControlFrame* list = GetEnclosingListFrame(this);
+ NS_ASSERTION(list,
+ "Must have an nsListControlFrame! Frame constructor is "
+ "broken");
+
+ bool isInDropdownMode = list->IsInDropDownMode();
+
+ // See similar logic in nsListControlFrame::Reflow and
+ // nsListControlFrame::ReflowAsDropdown. We need to match it here.
+ WritingMode wm = aReflowInput.GetWritingMode();
+ nscoord oldBSize;
+ if (isInDropdownMode) {
+ // Store the block size now in case it changes during
+ // nsBlockFrame::Reflow for some odd reason.
+ if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
+ oldBSize = BSize(wm);
+ } else {
+ oldBSize = NS_UNCONSTRAINEDSIZE;
+ }
+ }
+
+ 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 ||
+ (isInDropdownMode && (oldBSize != aDesiredSize.BSize(wm) ||
+ oldBSize != BSize(wm)))) {
+ mBSizeOfARow = newBSizeOfARow;
+ list->SetSuppressScrollbarUpdate(true);
+ }
+ }
+}