author Cosmin Sabou <>
Thu, 02 Aug 2018 19:59:53 +0300
changeset 484986 f7f4ba3486b5e44b6ccdc3b150f5aa0aab76ffb7
parent 463054 0016368787a44bdf5deb4c9b73f3c0b59d2bc27a
child 505383 6f3709b3878117466168c40affa7bca0b60cf75b
permissions -rw-r--r--
Backed out changeset 5950c9d63c3b (bug 1090497) for build bustages on several files. CLOSED TREE

/* -*- 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 "nsIContent.h"
#include "nsListControlFrame.h"
#include "nsDisplayList.h"
#include "WritingModes.h"

using namespace mozilla;

NS_NewSelectsAreaFrame(nsIPresShell* aShell, ComputedStyle* aStyle, nsFrameState aFlags)
  nsSelectsAreaFrame* it = new (aShell) nsSelectsAreaFrame(aStyle);

  // 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() {}
  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);

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 nsDisplayItem {
  nsDisplayListFocus(nsDisplayListBuilder* aBuilder,
                     nsSelectsAreaFrame* aFrame) :
    nsDisplayItem(aBuilder, aFrame) {
  virtual ~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->GetVisualOverflowRectRelativeToSelf() +
  virtual void Paint(nsDisplayListBuilder* aBuilder,
                     gfxContext* aCtx) override {
    nsListControlFrame* listFrame = GetEnclosingListFrame(Frame());
    // listFrame must be non-null or we wouldn't get called.

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);

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).
      MakeDisplayItem<nsDisplayListFocus>(aBuilder, this));

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 (!(GetStateBits() & 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;