Source code

Revision control

Other Tools

/* -*- 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 */
#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;
* 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 {
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 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) {
} else {
// keep the original result, which could be this frame
class nsOptionEventGrabberWrapper : public nsDisplayWrapper {
nsOptionEventGrabberWrapper() = default;
virtual nsDisplayItem* WrapList(nsDisplayListBuilder* aBuilder,
nsIFrame* aFrame,
nsDisplayList* aList) override {
return MakeDisplayItem<nsDisplayOptionEventGrabber>(aBuilder, aFrame,
virtual nsDisplayItem* WrapItem(nsDisplayListBuilder* aBuilder,
nsDisplayItem* aItem) override {
return MakeDisplayItem<nsDisplayOptionEventGrabber>(aBuilder,
aItem->Frame(), aItem);
static nsListControlFrame* GetEnclosingListFrame(nsIFrame* aSelectsAreaFrame) {
nsIFrame* frame = aSelectsAreaFrame->GetParent();
while (frame) {
if (frame->IsListControlFrame())
return static_cast<nsListControlFrame*>(frame);
frame = frame->GetParent();
return nullptr;
class nsDisplayListFocus : public nsPaintedDisplayItem {
nsDisplayListFocus(nsDisplayListBuilder* aBuilder, nsSelectsAreaFrame* aFrame)
: nsPaintedDisplayItem(aBuilder, aFrame) {
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() +
virtual void Paint(nsDisplayListBuilder* aBuilder,
gfxContext* aCtx) override {
nsListControlFrame* listFrame = GetEnclosingListFrame(Frame());
// listFrame must be non-null or we wouldn't get called.
void nsSelectsAreaFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
const nsDisplayListSet& aLists) {
if (!aBuilder->IsForEventDelivery()) {
BuildDisplayListInternal(aBuilder, aLists);
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);
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);
"Must have an nsListControlFrame! Frame constructor is "
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 (!HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
oldBSize = BSize(wm);
} else {
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;