michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: #include "nsCOMPtr.h" michael@0: #include "nsComboboxControlFrame.h" michael@0: #include "nsFocusManager.h" michael@0: #include "nsFormControlFrame.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsCSSAnonBoxes.h" michael@0: #include "nsHTMLParts.h" michael@0: #include "nsIFormControl.h" michael@0: #include "nsNameSpaceManager.h" michael@0: #include "nsIListControlFrame.h" michael@0: #include "nsPIDOMWindow.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsContentList.h" michael@0: #include "nsView.h" michael@0: #include "nsViewManager.h" michael@0: #include "nsIDOMEventListener.h" michael@0: #include "nsIDOMNode.h" michael@0: #include "nsISelectControlFrame.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsINodeInfo.h" michael@0: #include "nsIScrollableFrame.h" michael@0: #include "nsListControlFrame.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsStyleSet.h" michael@0: #include "nsNodeInfoManager.h" michael@0: #include "nsContentCreatorFunctions.h" michael@0: #include "nsLayoutUtils.h" michael@0: #include "nsDisplayList.h" michael@0: #include "nsITheme.h" michael@0: #include "nsRenderingContext.h" michael@0: #include "mozilla/Likely.h" michael@0: #include michael@0: #include "nsTextNode.h" michael@0: #include "mozilla/AsyncEventDispatcher.h" michael@0: #include "mozilla/EventStates.h" michael@0: #include "mozilla/LookAndFeel.h" michael@0: #include "mozilla/MouseEvents.h" michael@0: #include "mozilla/unused.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: NS_IMETHODIMP michael@0: nsComboboxControlFrame::RedisplayTextEvent::Run() michael@0: { michael@0: if (mControlFrame) michael@0: mControlFrame->HandleRedisplayTextEvent(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: class nsPresState; michael@0: michael@0: #define FIX_FOR_BUG_53259 michael@0: michael@0: // Drop down list event management. michael@0: // The combo box uses the following strategy for managing the drop-down list. michael@0: // If the combo box or its arrow button is clicked on the drop-down list is displayed michael@0: // If mouse exits the combo box with the drop-down list displayed the drop-down list michael@0: // is asked to capture events michael@0: // The drop-down list will capture all events including mouse down and up and will always michael@0: // return with ListWasSelected method call regardless of whether an item in the list was michael@0: // actually selected. michael@0: // The ListWasSelected code will turn off mouse-capture for the drop-down list. michael@0: // The drop-down list does not explicitly set capture when it is in the drop-down mode. michael@0: michael@0: michael@0: /** michael@0: * Helper class that listens to the combo boxes button. If the button is pressed the michael@0: * combo box is toggled to open or close. this is used by Accessibility which presses michael@0: * that button Programmatically. michael@0: */ michael@0: class nsComboButtonListener : public nsIDOMEventListener michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: NS_IMETHOD HandleEvent(nsIDOMEvent*) MOZ_OVERRIDE michael@0: { michael@0: mComboBox->ShowDropDown(!mComboBox->IsDroppedDown()); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsComboButtonListener(nsComboboxControlFrame* aCombobox) michael@0: { michael@0: mComboBox = aCombobox; michael@0: } michael@0: michael@0: virtual ~nsComboButtonListener() {} michael@0: michael@0: nsComboboxControlFrame* mComboBox; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(nsComboButtonListener, michael@0: nsIDOMEventListener) michael@0: michael@0: // static class data member for Bug 32920 michael@0: nsComboboxControlFrame* nsComboboxControlFrame::sFocused = nullptr; michael@0: michael@0: nsIFrame* michael@0: NS_NewComboboxControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, nsFrameState aStateFlags) michael@0: { michael@0: nsComboboxControlFrame* it = new (aPresShell) nsComboboxControlFrame(aContext); michael@0: michael@0: if (it) { michael@0: // set the state flags (if any are provided) michael@0: it->AddStateBits(aStateFlags); michael@0: } michael@0: michael@0: return it; michael@0: } michael@0: michael@0: NS_IMPL_FRAMEARENA_HELPERS(nsComboboxControlFrame) michael@0: michael@0: //----------------------------------------------------------- michael@0: // Reflow Debugging Macros michael@0: // These let us "see" how many reflow counts are happening michael@0: //----------------------------------------------------------- michael@0: #ifdef DO_REFLOW_COUNTER michael@0: michael@0: #define MAX_REFLOW_CNT 1024 michael@0: static int32_t gTotalReqs = 0;; michael@0: static int32_t gTotalReflows = 0;; michael@0: static int32_t gReflowControlCntRQ[MAX_REFLOW_CNT]; michael@0: static int32_t gReflowControlCnt[MAX_REFLOW_CNT]; michael@0: static int32_t gReflowInx = -1; michael@0: michael@0: #define REFLOW_COUNTER() \ michael@0: if (mReflowId > -1) \ michael@0: gReflowControlCnt[mReflowId]++; michael@0: michael@0: #define REFLOW_COUNTER_REQUEST() \ michael@0: if (mReflowId > -1) \ michael@0: gReflowControlCntRQ[mReflowId]++; michael@0: michael@0: #define REFLOW_COUNTER_DUMP(__desc) \ michael@0: if (mReflowId > -1) {\ michael@0: gTotalReqs += gReflowControlCntRQ[mReflowId];\ michael@0: gTotalReflows += gReflowControlCnt[mReflowId];\ michael@0: printf("** Id:%5d %s RF: %d RQ: %d %d/%d %5.2f\n", \ michael@0: mReflowId, (__desc), \ michael@0: gReflowControlCnt[mReflowId], \ michael@0: gReflowControlCntRQ[mReflowId],\ michael@0: gTotalReflows, gTotalReqs, float(gTotalReflows)/float(gTotalReqs)*100.0f);\ michael@0: } michael@0: michael@0: #define REFLOW_COUNTER_INIT() \ michael@0: if (gReflowInx < MAX_REFLOW_CNT) { \ michael@0: gReflowInx++; \ michael@0: mReflowId = gReflowInx; \ michael@0: gReflowControlCnt[mReflowId] = 0; \ michael@0: gReflowControlCntRQ[mReflowId] = 0; \ michael@0: } else { \ michael@0: mReflowId = -1; \ michael@0: } michael@0: michael@0: // reflow messages michael@0: #define REFLOW_DEBUG_MSG(_msg1) printf((_msg1)) michael@0: #define REFLOW_DEBUG_MSG2(_msg1, _msg2) printf((_msg1), (_msg2)) michael@0: #define REFLOW_DEBUG_MSG3(_msg1, _msg2, _msg3) printf((_msg1), (_msg2), (_msg3)) michael@0: #define REFLOW_DEBUG_MSG4(_msg1, _msg2, _msg3, _msg4) printf((_msg1), (_msg2), (_msg3), (_msg4)) michael@0: michael@0: #else //------------- michael@0: michael@0: #define REFLOW_COUNTER_REQUEST() michael@0: #define REFLOW_COUNTER() michael@0: #define REFLOW_COUNTER_DUMP(__desc) michael@0: #define REFLOW_COUNTER_INIT() michael@0: michael@0: #define REFLOW_DEBUG_MSG(_msg) michael@0: #define REFLOW_DEBUG_MSG2(_msg1, _msg2) michael@0: #define REFLOW_DEBUG_MSG3(_msg1, _msg2, _msg3) michael@0: #define REFLOW_DEBUG_MSG4(_msg1, _msg2, _msg3, _msg4) michael@0: michael@0: michael@0: #endif michael@0: michael@0: //------------------------------------------ michael@0: // This is for being VERY noisy michael@0: //------------------------------------------ michael@0: #ifdef DO_VERY_NOISY michael@0: #define REFLOW_NOISY_MSG(_msg1) printf((_msg1)) michael@0: #define REFLOW_NOISY_MSG2(_msg1, _msg2) printf((_msg1), (_msg2)) michael@0: #define REFLOW_NOISY_MSG3(_msg1, _msg2, _msg3) printf((_msg1), (_msg2), (_msg3)) michael@0: #define REFLOW_NOISY_MSG4(_msg1, _msg2, _msg3, _msg4) printf((_msg1), (_msg2), (_msg3), (_msg4)) michael@0: #else michael@0: #define REFLOW_NOISY_MSG(_msg) michael@0: #define REFLOW_NOISY_MSG2(_msg1, _msg2) michael@0: #define REFLOW_NOISY_MSG3(_msg1, _msg2, _msg3) michael@0: #define REFLOW_NOISY_MSG4(_msg1, _msg2, _msg3, _msg4) michael@0: #endif michael@0: michael@0: //------------------------------------------ michael@0: // Displays value in pixels or twips michael@0: //------------------------------------------ michael@0: #ifdef DO_PIXELS michael@0: #define PX(__v) __v / 15 michael@0: #else michael@0: #define PX(__v) __v michael@0: #endif michael@0: michael@0: //------------------------------------------------------ michael@0: //-- Done with macros michael@0: //------------------------------------------------------ michael@0: michael@0: nsComboboxControlFrame::nsComboboxControlFrame(nsStyleContext* aContext) michael@0: : nsBlockFrame(aContext) michael@0: , mDisplayFrame(nullptr) michael@0: , mButtonFrame(nullptr) michael@0: , mDropdownFrame(nullptr) michael@0: , mListControlFrame(nullptr) michael@0: , mDisplayWidth(0) michael@0: , mRecentSelectedIndex(NS_SKIP_NOTIFY_INDEX) michael@0: , mDisplayedIndex(-1) michael@0: , mLastDropDownAboveScreenY(nscoord_MIN) michael@0: , mLastDropDownBelowScreenY(nscoord_MIN) michael@0: , mDroppedDown(false) michael@0: , mInRedisplayText(false) michael@0: , mDelayedShowDropDown(false) michael@0: { michael@0: REFLOW_COUNTER_INIT() michael@0: } michael@0: michael@0: //-------------------------------------------------------------- michael@0: nsComboboxControlFrame::~nsComboboxControlFrame() michael@0: { michael@0: REFLOW_COUNTER_DUMP("nsCCF"); michael@0: } michael@0: michael@0: //-------------------------------------------------------------- michael@0: michael@0: NS_QUERYFRAME_HEAD(nsComboboxControlFrame) michael@0: NS_QUERYFRAME_ENTRY(nsIComboboxControlFrame) michael@0: NS_QUERYFRAME_ENTRY(nsIFormControlFrame) michael@0: NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator) michael@0: NS_QUERYFRAME_ENTRY(nsISelectControlFrame) michael@0: NS_QUERYFRAME_ENTRY(nsIStatefulFrame) michael@0: NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame) michael@0: michael@0: #ifdef ACCESSIBILITY michael@0: a11y::AccType michael@0: nsComboboxControlFrame::AccessibleType() michael@0: { michael@0: return a11y::eHTMLComboboxType; michael@0: } michael@0: #endif michael@0: michael@0: void michael@0: nsComboboxControlFrame::SetFocus(bool aOn, bool aRepaint) michael@0: { michael@0: nsWeakFrame weakFrame(this); michael@0: if (aOn) { michael@0: nsListControlFrame::ComboboxFocusSet(); michael@0: sFocused = this; michael@0: if (mDelayedShowDropDown) { michael@0: ShowDropDown(true); // might destroy us michael@0: if (!weakFrame.IsAlive()) { michael@0: return; michael@0: } michael@0: } michael@0: } else { michael@0: sFocused = nullptr; michael@0: mDelayedShowDropDown = false; michael@0: if (mDroppedDown) { michael@0: mListControlFrame->ComboboxFinish(mDisplayedIndex); // might destroy us michael@0: if (!weakFrame.IsAlive()) { michael@0: return; michael@0: } michael@0: } michael@0: // May delete |this|. michael@0: mListControlFrame->FireOnChange(); michael@0: } michael@0: michael@0: if (!weakFrame.IsAlive()) { michael@0: return; michael@0: } michael@0: michael@0: // This is needed on a temporary basis. It causes the focus michael@0: // rect to be drawn. This is much faster than ReResolvingStyle michael@0: // Bug 32920 michael@0: InvalidateFrame(); michael@0: } michael@0: michael@0: void michael@0: nsComboboxControlFrame::ShowPopup(bool aShowPopup) michael@0: { michael@0: nsView* view = mDropdownFrame->GetView(); michael@0: nsViewManager* viewManager = view->GetViewManager(); michael@0: michael@0: if (aShowPopup) { michael@0: nsRect rect = mDropdownFrame->GetRect(); michael@0: rect.x = rect.y = 0; michael@0: viewManager->ResizeView(view, rect); michael@0: viewManager->SetViewVisibility(view, nsViewVisibility_kShow); michael@0: } else { michael@0: viewManager->SetViewVisibility(view, nsViewVisibility_kHide); michael@0: nsRect emptyRect(0, 0, 0, 0); michael@0: viewManager->ResizeView(view, emptyRect); michael@0: } michael@0: michael@0: // fire a popup dom event michael@0: nsEventStatus status = nsEventStatus_eIgnore; michael@0: WidgetMouseEvent event(true, aShowPopup ? michael@0: NS_XUL_POPUP_SHOWING : NS_XUL_POPUP_HIDING, nullptr, michael@0: WidgetMouseEvent::eReal); michael@0: michael@0: nsCOMPtr shell = PresContext()->GetPresShell(); michael@0: if (shell) michael@0: shell->HandleDOMEventWithTarget(mContent, &event, &status); michael@0: } michael@0: michael@0: bool michael@0: nsComboboxControlFrame::ShowList(bool aShowList) michael@0: { michael@0: nsView* view = mDropdownFrame->GetView(); michael@0: if (aShowList) { michael@0: NS_ASSERTION(!view->HasWidget(), michael@0: "We shouldn't have a widget before we need to display the popup"); michael@0: michael@0: // Create the widget for the drop-down list michael@0: view->GetViewManager()->SetViewFloating(view, true); michael@0: michael@0: nsWidgetInitData widgetData; michael@0: widgetData.mWindowType = eWindowType_popup; michael@0: widgetData.mBorderStyle = eBorderStyle_default; michael@0: view->CreateWidgetForPopup(&widgetData); michael@0: } else { michael@0: nsIWidget* widget = view->GetWidget(); michael@0: if (widget) { michael@0: // We must do this before ShowPopup in case it destroys us (bug 813442). michael@0: widget->CaptureRollupEvents(this, false); michael@0: } michael@0: } michael@0: michael@0: nsWeakFrame weakFrame(this); michael@0: ShowPopup(aShowList); // might destroy us michael@0: if (!weakFrame.IsAlive()) { michael@0: return false; michael@0: } michael@0: michael@0: mDroppedDown = aShowList; michael@0: nsIWidget* widget = view->GetWidget(); michael@0: if (mDroppedDown) { michael@0: // The listcontrol frame will call back to the nsComboboxControlFrame's michael@0: // ListWasSelected which will stop the capture. michael@0: mListControlFrame->AboutToDropDown(); michael@0: mListControlFrame->CaptureMouseEvents(true); michael@0: if (widget) { michael@0: widget->CaptureRollupEvents(this, true); michael@0: } michael@0: } else { michael@0: if (widget) { michael@0: view->DestroyWidget(); michael@0: } michael@0: } michael@0: michael@0: return weakFrame.IsAlive(); michael@0: } michael@0: michael@0: class nsResizeDropdownAtFinalPosition michael@0: : public nsIReflowCallback, public nsRunnable michael@0: { michael@0: public: michael@0: nsResizeDropdownAtFinalPosition(nsComboboxControlFrame* aFrame) michael@0: : mFrame(aFrame) michael@0: { michael@0: MOZ_COUNT_CTOR(nsResizeDropdownAtFinalPosition); michael@0: } michael@0: ~nsResizeDropdownAtFinalPosition() michael@0: { michael@0: MOZ_COUNT_DTOR(nsResizeDropdownAtFinalPosition); michael@0: } michael@0: michael@0: virtual bool ReflowFinished() MOZ_OVERRIDE michael@0: { michael@0: Run(); michael@0: NS_RELEASE_THIS(); michael@0: return false; michael@0: } michael@0: michael@0: virtual void ReflowCallbackCanceled() MOZ_OVERRIDE michael@0: { michael@0: NS_RELEASE_THIS(); michael@0: } michael@0: michael@0: NS_IMETHODIMP Run() michael@0: { michael@0: if (mFrame.IsAlive()) { michael@0: static_cast(mFrame.GetFrame())-> michael@0: AbsolutelyPositionDropDown(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsWeakFrame mFrame; michael@0: }; michael@0: michael@0: nsresult michael@0: nsComboboxControlFrame::ReflowDropdown(nsPresContext* aPresContext, michael@0: const nsHTMLReflowState& aReflowState) michael@0: { michael@0: // All we want out of it later on, really, is the height of a row, so we michael@0: // don't even need to cache mDropdownFrame's ascent or anything. If we don't michael@0: // need to reflow it, just bail out here. michael@0: if (!aReflowState.ShouldReflowAllKids() && michael@0: !NS_SUBTREE_DIRTY(mDropdownFrame)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // XXXbz this will, for small-height dropdowns, have extra space on the right michael@0: // edge for the scrollbar we don't show... but that's the best we can do here michael@0: // for now. michael@0: nsSize availSize(aReflowState.AvailableWidth(), NS_UNCONSTRAINEDSIZE); michael@0: nsHTMLReflowState kidReflowState(aPresContext, aReflowState, mDropdownFrame, michael@0: availSize); michael@0: michael@0: // If the dropdown's intrinsic width is narrower than our specified width, michael@0: // then expand it out. We want our border-box width to end up the same as michael@0: // the dropdown's so account for both sets of mComputedBorderPadding. michael@0: nscoord forcedWidth = aReflowState.ComputedWidth() + michael@0: aReflowState.ComputedPhysicalBorderPadding().LeftRight() - michael@0: kidReflowState.ComputedPhysicalBorderPadding().LeftRight(); michael@0: kidReflowState.SetComputedWidth(std::max(kidReflowState.ComputedWidth(), michael@0: forcedWidth)); michael@0: michael@0: // ensure we start off hidden michael@0: if (GetStateBits() & NS_FRAME_FIRST_REFLOW) { michael@0: nsView* view = mDropdownFrame->GetView(); michael@0: nsViewManager* viewManager = view->GetViewManager(); michael@0: viewManager->SetViewVisibility(view, nsViewVisibility_kHide); michael@0: nsRect emptyRect(0, 0, 0, 0); michael@0: viewManager->ResizeView(view, emptyRect); michael@0: } michael@0: michael@0: // Allow the child to move/size/change-visibility its view if it's currently michael@0: // dropped down michael@0: int32_t flags = NS_FRAME_NO_MOVE_FRAME | NS_FRAME_NO_VISIBILITY | NS_FRAME_NO_SIZE_VIEW; michael@0: if (mDroppedDown) { michael@0: flags = 0; michael@0: } michael@0: nsRect rect = mDropdownFrame->GetRect(); michael@0: nsHTMLReflowMetrics desiredSize(aReflowState); michael@0: nsReflowStatus ignoredStatus; michael@0: nsresult rv = ReflowChild(mDropdownFrame, aPresContext, desiredSize, michael@0: kidReflowState, rect.x, rect.y, flags, michael@0: ignoredStatus); michael@0: michael@0: // Set the child's width and height to its desired size michael@0: FinishReflowChild(mDropdownFrame, aPresContext, desiredSize, michael@0: &kidReflowState, rect.x, rect.y, flags); michael@0: return rv; michael@0: } michael@0: michael@0: nsPoint michael@0: nsComboboxControlFrame::GetCSSTransformTranslation() michael@0: { michael@0: nsIFrame* frame = this; michael@0: bool is3DTransform = false; michael@0: gfxMatrix transform; michael@0: while (frame) { michael@0: nsIFrame* parent; michael@0: gfx3DMatrix ctm = frame->GetTransformMatrix(nullptr, &parent); michael@0: gfxMatrix matrix; michael@0: if (ctm.Is2D(&matrix)) { michael@0: transform = transform * matrix; michael@0: } else { michael@0: is3DTransform = true; michael@0: break; michael@0: } michael@0: frame = parent; michael@0: } michael@0: nsPoint translation; michael@0: if (!is3DTransform && !transform.HasNonTranslation()) { michael@0: nsPresContext* pc = PresContext(); michael@0: gfxPoint pixelTranslation = transform.GetTranslation(); michael@0: int32_t apd = pc->AppUnitsPerDevPixel(); michael@0: translation.x = NSFloatPixelsToAppUnits(float(pixelTranslation.x), apd); michael@0: translation.y = NSFloatPixelsToAppUnits(float(pixelTranslation.y), apd); michael@0: // To get the translation introduced only by transforms we subtract the michael@0: // regular non-transform translation. michael@0: nsRootPresContext* rootPC = pc->GetRootPresContext(); michael@0: if (rootPC) { michael@0: translation -= GetOffsetToCrossDoc(rootPC->PresShell()->GetRootFrame()); michael@0: } else { michael@0: translation.x = translation.y = 0; michael@0: } michael@0: } michael@0: return translation; michael@0: } michael@0: michael@0: class nsAsyncRollup : public nsRunnable michael@0: { michael@0: public: michael@0: nsAsyncRollup(nsComboboxControlFrame* aFrame) : mFrame(aFrame) {} michael@0: NS_IMETHODIMP Run() michael@0: { michael@0: if (mFrame.IsAlive()) { michael@0: static_cast(mFrame.GetFrame()) michael@0: ->RollupFromList(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: nsWeakFrame mFrame; michael@0: }; michael@0: michael@0: class nsAsyncResize : public nsRunnable michael@0: { michael@0: public: michael@0: nsAsyncResize(nsComboboxControlFrame* aFrame) : mFrame(aFrame) {} michael@0: NS_IMETHODIMP Run() michael@0: { michael@0: if (mFrame.IsAlive()) { michael@0: nsComboboxControlFrame* combo = michael@0: static_cast(mFrame.GetFrame()); michael@0: static_cast(combo->mDropdownFrame)-> michael@0: SetSuppressScrollbarUpdate(true); michael@0: nsCOMPtr shell = mFrame->PresContext()->PresShell(); michael@0: shell->FrameNeedsReflow(combo->mDropdownFrame, nsIPresShell::eResize, michael@0: NS_FRAME_IS_DIRTY); michael@0: shell->FlushPendingNotifications(Flush_Layout); michael@0: if (mFrame.IsAlive()) { michael@0: combo = static_cast(mFrame.GetFrame()); michael@0: static_cast(combo->mDropdownFrame)-> michael@0: SetSuppressScrollbarUpdate(false); michael@0: if (combo->mDelayedShowDropDown) { michael@0: combo->ShowDropDown(true); michael@0: } michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: nsWeakFrame mFrame; michael@0: }; michael@0: michael@0: void michael@0: nsComboboxControlFrame::GetAvailableDropdownSpace(nscoord* aAbove, michael@0: nscoord* aBelow, michael@0: nsPoint* aTranslation) michael@0: { michael@0: // Note: At first glance, it appears that you could simply get the absolute michael@0: // bounding box for the dropdown list by first getting its view, then getting michael@0: // the view's nsIWidget, then asking the nsIWidget for its AbsoluteBounds. michael@0: // The problem with this approach, is that the dropdown lists y location can michael@0: // change based on whether the dropdown is placed below or above the display michael@0: // frame. The approach, taken here is to get the absolute position of the michael@0: // display frame and use its location to determine if the dropdown will go michael@0: // offscreen. michael@0: michael@0: // Normal frame geometry (eg GetOffsetTo, mRect) doesn't include transforms. michael@0: // In the special case that our transform is only a 2D translation we michael@0: // introduce this hack so that the dropdown will show up in the right place. michael@0: *aTranslation = GetCSSTransformTranslation(); michael@0: *aAbove = 0; michael@0: *aBelow = 0; michael@0: michael@0: nsRect screen = nsFormControlFrame::GetUsableScreenRect(PresContext()); michael@0: if (mLastDropDownBelowScreenY == nscoord_MIN) { michael@0: nsRect thisScreenRect = GetScreenRectInAppUnits(); michael@0: mLastDropDownBelowScreenY = thisScreenRect.YMost() + aTranslation->y; michael@0: mLastDropDownAboveScreenY = thisScreenRect.y + aTranslation->y; michael@0: } michael@0: michael@0: nscoord minY; michael@0: nsPresContext* pc = PresContext()->GetToplevelContentDocumentPresContext(); michael@0: nsIFrame* root = pc ? pc->PresShell()->GetRootFrame() : nullptr; michael@0: if (root) { michael@0: minY = root->GetScreenRectInAppUnits().y; michael@0: if (mLastDropDownBelowScreenY < minY) { michael@0: // Don't allow the drop-down to be placed above the content area. michael@0: return; michael@0: } michael@0: } else { michael@0: minY = screen.y; michael@0: } michael@0: michael@0: nscoord below = screen.YMost() - mLastDropDownBelowScreenY; michael@0: nscoord above = mLastDropDownAboveScreenY - minY; michael@0: michael@0: // If the difference between the space above and below is less michael@0: // than a row-height, then we favor the space below. michael@0: if (above >= below) { michael@0: nsListControlFrame* lcf = static_cast(mDropdownFrame); michael@0: nscoord rowHeight = lcf->GetHeightOfARow(); michael@0: if (above < below + rowHeight) { michael@0: above -= rowHeight; michael@0: } michael@0: } michael@0: michael@0: *aBelow = below; michael@0: *aAbove = above; michael@0: } michael@0: michael@0: nsComboboxControlFrame::DropDownPositionState michael@0: nsComboboxControlFrame::AbsolutelyPositionDropDown() michael@0: { michael@0: nsPoint translation; michael@0: nscoord above, below; michael@0: mLastDropDownBelowScreenY = nscoord_MIN; michael@0: GetAvailableDropdownSpace(&above, &below, &translation); michael@0: if (above <= 0 && below <= 0) { michael@0: if (IsDroppedDown()) { michael@0: // Hide the view immediately to minimize flicker. michael@0: nsView* view = mDropdownFrame->GetView(); michael@0: view->GetViewManager()->SetViewVisibility(view, nsViewVisibility_kHide); michael@0: NS_DispatchToCurrentThread(new nsAsyncRollup(this)); michael@0: } michael@0: return eDropDownPositionSuppressed; michael@0: } michael@0: michael@0: nsSize dropdownSize = mDropdownFrame->GetSize(); michael@0: nscoord height = std::max(above, below); michael@0: nsListControlFrame* lcf = static_cast(mDropdownFrame); michael@0: if (height < dropdownSize.height) { michael@0: if (lcf->GetNumDisplayRows() > 1) { michael@0: // The drop-down doesn't fit and currently shows more than 1 row - michael@0: // schedule a resize to show fewer rows. michael@0: NS_DispatchToCurrentThread(new nsAsyncResize(this)); michael@0: return eDropDownPositionPendingResize; michael@0: } michael@0: } else if (height > (dropdownSize.height + lcf->GetHeightOfARow() * 1.5) && michael@0: lcf->GetDropdownCanGrow()) { michael@0: // The drop-down fits but there is room for at least 1.5 more rows - michael@0: // schedule a resize to show more rows if it has more rows to show. michael@0: // (1.5 rows for good measure to avoid any rounding issues that would michael@0: // lead to a loop of reflow requests) michael@0: NS_DispatchToCurrentThread(new nsAsyncResize(this)); michael@0: return eDropDownPositionPendingResize; michael@0: } michael@0: michael@0: // Position the drop-down below if there is room, otherwise place it above michael@0: // if there is room. If there is no room for it on either side then place michael@0: // it below (to avoid overlapping UI like the URL bar). michael@0: bool b = dropdownSize.height <= below || dropdownSize.height > above; michael@0: nsPoint dropdownPosition(0, b ? GetRect().height : -dropdownSize.height); michael@0: if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) { michael@0: // Align the right edge of the drop-down with the right edge of the control. michael@0: dropdownPosition.x = GetRect().width - dropdownSize.width; michael@0: } michael@0: michael@0: // Don't position the view unless the position changed since it might cause michael@0: // a call to NotifyGeometryChange() and an infinite loop here. michael@0: const nsPoint currentPos = mDropdownFrame->GetPosition(); michael@0: const nsPoint newPos = dropdownPosition + translation; michael@0: if (currentPos != newPos) { michael@0: mDropdownFrame->SetPosition(newPos); michael@0: nsContainerFrame::PositionFrameView(mDropdownFrame); michael@0: } michael@0: return eDropDownPositionFinal; michael@0: } michael@0: michael@0: void michael@0: nsComboboxControlFrame::NotifyGeometryChange() michael@0: { michael@0: // We don't need to resize if we're not dropped down since ShowDropDown michael@0: // does that, or if we're dirty then the reflow callback does it, michael@0: // or if we have a delayed ShowDropDown pending. michael@0: if (IsDroppedDown() && michael@0: !(GetStateBits() & NS_FRAME_IS_DIRTY) && michael@0: !mDelayedShowDropDown) { michael@0: // Async because we're likely in a middle of a scroll here so michael@0: // frame/view positions are in flux. michael@0: nsRefPtr resize = michael@0: new nsResizeDropdownAtFinalPosition(this); michael@0: NS_DispatchToCurrentThread(resize); michael@0: } michael@0: } michael@0: michael@0: //---------------------------------------------------------- michael@0: // michael@0: //---------------------------------------------------------- michael@0: #ifdef DO_REFLOW_DEBUG michael@0: static int myCounter = 0; michael@0: michael@0: static void printSize(char * aDesc, nscoord aSize) michael@0: { michael@0: printf(" %s: ", aDesc); michael@0: if (aSize == NS_UNCONSTRAINEDSIZE) { michael@0: printf("UC"); michael@0: } else { michael@0: printf("%d", PX(aSize)); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: //------------------------------------------------------------------- michael@0: //-- Main Reflow for the Combobox michael@0: //------------------------------------------------------------------- michael@0: michael@0: nscoord michael@0: nsComboboxControlFrame::GetIntrinsicWidth(nsRenderingContext* aRenderingContext, michael@0: nsLayoutUtils::IntrinsicWidthType aType) michael@0: { michael@0: // get the scrollbar width, we'll use this later michael@0: nscoord scrollbarWidth = 0; michael@0: nsPresContext* presContext = PresContext(); michael@0: if (mListControlFrame) { michael@0: nsIScrollableFrame* scrollable = do_QueryFrame(mListControlFrame); michael@0: NS_ASSERTION(scrollable, "List must be a scrollable frame"); michael@0: scrollbarWidth = scrollable->GetNondisappearingScrollbarWidth( michael@0: presContext, aRenderingContext); michael@0: } michael@0: michael@0: nscoord displayWidth = 0; michael@0: if (MOZ_LIKELY(mDisplayFrame)) { michael@0: displayWidth = nsLayoutUtils::IntrinsicForContainer(aRenderingContext, michael@0: mDisplayFrame, michael@0: aType); michael@0: } michael@0: michael@0: if (mDropdownFrame) { michael@0: nscoord dropdownContentWidth; michael@0: bool isUsingOverlayScrollbars = michael@0: LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0; michael@0: if (aType == nsLayoutUtils::MIN_WIDTH) { michael@0: dropdownContentWidth = mDropdownFrame->GetMinWidth(aRenderingContext); michael@0: if (isUsingOverlayScrollbars) { michael@0: dropdownContentWidth += scrollbarWidth; michael@0: } michael@0: } else { michael@0: NS_ASSERTION(aType == nsLayoutUtils::PREF_WIDTH, "Unexpected type"); michael@0: dropdownContentWidth = mDropdownFrame->GetPrefWidth(aRenderingContext); michael@0: if (isUsingOverlayScrollbars) { michael@0: dropdownContentWidth += scrollbarWidth; michael@0: } michael@0: } michael@0: dropdownContentWidth = NSCoordSaturatingSubtract(dropdownContentWidth, michael@0: scrollbarWidth, michael@0: nscoord_MAX); michael@0: michael@0: displayWidth = std::max(dropdownContentWidth, displayWidth); michael@0: } michael@0: michael@0: // add room for the dropmarker button if there is one michael@0: if (!IsThemed() || presContext->GetTheme()->ThemeNeedsComboboxDropmarker()) michael@0: displayWidth += scrollbarWidth; michael@0: michael@0: return displayWidth; michael@0: michael@0: } michael@0: michael@0: nscoord michael@0: nsComboboxControlFrame::GetMinWidth(nsRenderingContext *aRenderingContext) michael@0: { michael@0: nscoord minWidth; michael@0: DISPLAY_MIN_WIDTH(this, minWidth); michael@0: minWidth = GetIntrinsicWidth(aRenderingContext, nsLayoutUtils::MIN_WIDTH); michael@0: return minWidth; michael@0: } michael@0: michael@0: nscoord michael@0: nsComboboxControlFrame::GetPrefWidth(nsRenderingContext *aRenderingContext) michael@0: { michael@0: nscoord prefWidth; michael@0: DISPLAY_PREF_WIDTH(this, prefWidth); michael@0: prefWidth = GetIntrinsicWidth(aRenderingContext, nsLayoutUtils::PREF_WIDTH); michael@0: return prefWidth; michael@0: } michael@0: michael@0: nsresult michael@0: nsComboboxControlFrame::Reflow(nsPresContext* aPresContext, michael@0: nsHTMLReflowMetrics& aDesiredSize, michael@0: const nsHTMLReflowState& aReflowState, michael@0: nsReflowStatus& aStatus) michael@0: { michael@0: // Constraints we try to satisfy: michael@0: michael@0: // 1) Default width of button is the vertical scrollbar size michael@0: // 2) If the width of button is bigger than our width, set width of michael@0: // button to 0. michael@0: // 3) Default height of button is height of display area michael@0: // 4) Width of display area is whatever is left over from our width after michael@0: // allocating width for the button. michael@0: // 5) Height of display area is GetHeightOfARow() on the michael@0: // mListControlFrame. michael@0: michael@0: if (!mDisplayFrame || !mButtonFrame || !mDropdownFrame) { michael@0: NS_ERROR("Why did the frame constructor allow this to happen? Fix it!!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: // Make sure the displayed text is the same as the selected option, bug 297389. michael@0: int32_t selectedIndex; michael@0: nsAutoString selectedOptionText; michael@0: if (!mDroppedDown) { michael@0: selectedIndex = mListControlFrame->GetSelectedIndex(); michael@0: } michael@0: else { michael@0: // In dropped down mode the "selected index" is the hovered menu item, michael@0: // we want the last selected item which is |mDisplayedIndex| in this case. michael@0: selectedIndex = mDisplayedIndex; michael@0: } michael@0: if (selectedIndex != -1) { michael@0: mListControlFrame->GetOptionText(selectedIndex, selectedOptionText); michael@0: } michael@0: if (mDisplayedOptionText != selectedOptionText) { michael@0: RedisplayText(selectedIndex); michael@0: } michael@0: michael@0: // First reflow our dropdown so that we know how tall we should be. michael@0: ReflowDropdown(aPresContext, aReflowState); michael@0: nsRefPtr resize = michael@0: new nsResizeDropdownAtFinalPosition(this); michael@0: if (NS_SUCCEEDED(aPresContext->PresShell()->PostReflowCallback(resize))) { michael@0: // The reflow callback queue doesn't AddRef so we keep it alive until michael@0: // it's released in its ReflowFinished / ReflowCallbackCanceled. michael@0: unused << resize.forget(); michael@0: } michael@0: michael@0: // Get the width of the vertical scrollbar. That will be the width of the michael@0: // dropdown button. michael@0: nscoord buttonWidth; michael@0: const nsStyleDisplay *disp = StyleDisplay(); michael@0: if (IsThemed(disp) && !aPresContext->GetTheme()->ThemeNeedsComboboxDropmarker()) { michael@0: buttonWidth = 0; michael@0: } michael@0: else { michael@0: nsIScrollableFrame* scrollable = do_QueryFrame(mListControlFrame); michael@0: NS_ASSERTION(scrollable, "List must be a scrollable frame"); michael@0: buttonWidth = scrollable->GetNondisappearingScrollbarWidth( michael@0: PresContext(), aReflowState.rendContext); michael@0: if (buttonWidth > aReflowState.ComputedWidth()) { michael@0: buttonWidth = 0; michael@0: } michael@0: } michael@0: michael@0: mDisplayWidth = aReflowState.ComputedWidth() - buttonWidth; michael@0: michael@0: nsresult rv = nsBlockFrame::Reflow(aPresContext, aDesiredSize, aReflowState, michael@0: aStatus); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // The button should occupy the same space as a scrollbar michael@0: nsRect buttonRect = mButtonFrame->GetRect(); michael@0: michael@0: if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) { michael@0: buttonRect.x = aReflowState.ComputedPhysicalBorderPadding().left - michael@0: aReflowState.ComputedPhysicalPadding().left; michael@0: } michael@0: else { michael@0: buttonRect.x = aReflowState.ComputedPhysicalBorderPadding().LeftRight() + michael@0: mDisplayWidth - michael@0: (aReflowState.ComputedPhysicalBorderPadding().right - michael@0: aReflowState.ComputedPhysicalPadding().right); michael@0: } michael@0: buttonRect.width = buttonWidth; michael@0: michael@0: buttonRect.y = this->GetUsedBorder().top; michael@0: buttonRect.height = mDisplayFrame->GetRect().height + michael@0: this->GetUsedPadding().TopBottom(); michael@0: michael@0: mButtonFrame->SetRect(buttonRect); michael@0: michael@0: if (!NS_INLINE_IS_BREAK_BEFORE(aStatus) && michael@0: !NS_FRAME_IS_FULLY_COMPLETE(aStatus)) { michael@0: // This frame didn't fit inside a fragmentation container. Splitting michael@0: // a nsComboboxControlFrame makes no sense, so we override the status here. michael@0: aStatus = NS_FRAME_COMPLETE; michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: //-------------------------------------------------------------- michael@0: michael@0: nsIAtom* michael@0: nsComboboxControlFrame::GetType() const michael@0: { michael@0: return nsGkAtoms::comboboxControlFrame; michael@0: } michael@0: michael@0: #ifdef DEBUG_FRAME_DUMP michael@0: nsresult michael@0: nsComboboxControlFrame::GetFrameName(nsAString& aResult) const michael@0: { michael@0: return MakeFrameName(NS_LITERAL_STRING("ComboboxControl"), aResult); michael@0: } michael@0: #endif michael@0: michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // nsIComboboxControlFrame michael@0: //---------------------------------------------------------------------- michael@0: void michael@0: nsComboboxControlFrame::ShowDropDown(bool aDoDropDown) michael@0: { michael@0: mDelayedShowDropDown = false; michael@0: EventStates eventStates = mContent->AsElement()->State(); michael@0: if (aDoDropDown && eventStates.HasState(NS_EVENT_STATE_DISABLED)) { michael@0: return; michael@0: } michael@0: michael@0: if (!mDroppedDown && aDoDropDown) { michael@0: nsFocusManager* fm = nsFocusManager::GetFocusManager(); michael@0: if (!fm || fm->GetFocusedContent() == GetContent()) { michael@0: DropDownPositionState state = AbsolutelyPositionDropDown(); michael@0: if (state == eDropDownPositionFinal) { michael@0: ShowList(aDoDropDown); // might destroy us michael@0: } else if (state == eDropDownPositionPendingResize) { michael@0: // Delay until after the resize reflow, see nsAsyncResize. michael@0: mDelayedShowDropDown = true; michael@0: } michael@0: } else { michael@0: // Delay until we get focus, see SetFocus(). michael@0: mDelayedShowDropDown = true; michael@0: } michael@0: } else if (mDroppedDown && !aDoDropDown) { michael@0: ShowList(aDoDropDown); // might destroy us michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsComboboxControlFrame::SetDropDown(nsIFrame* aDropDownFrame) michael@0: { michael@0: mDropdownFrame = aDropDownFrame; michael@0: mListControlFrame = do_QueryFrame(mDropdownFrame); michael@0: } michael@0: michael@0: nsIFrame* michael@0: nsComboboxControlFrame::GetDropDown() michael@0: { michael@0: return mDropdownFrame; michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////// michael@0: michael@0: NS_IMETHODIMP michael@0: nsComboboxControlFrame::RedisplaySelectedText() michael@0: { michael@0: nsAutoScriptBlocker scriptBlocker; michael@0: return RedisplayText(mListControlFrame->GetSelectedIndex()); michael@0: } michael@0: michael@0: nsresult michael@0: nsComboboxControlFrame::RedisplayText(int32_t aIndex) michael@0: { michael@0: // Get the text to display michael@0: if (aIndex != -1) { michael@0: mListControlFrame->GetOptionText(aIndex, mDisplayedOptionText); michael@0: } else { michael@0: mDisplayedOptionText.Truncate(); michael@0: } michael@0: mDisplayedIndex = aIndex; michael@0: michael@0: REFLOW_DEBUG_MSG2("RedisplayText \"%s\"\n", michael@0: NS_LossyConvertUTF16toASCII(mDisplayedOptionText).get()); michael@0: michael@0: // Send reflow command because the new text maybe larger michael@0: nsresult rv = NS_OK; michael@0: if (mDisplayContent) { michael@0: // Don't call ActuallyDisplayText(true) directly here since that michael@0: // could cause recursive frame construction. See bug 283117 and the comment in michael@0: // HandleRedisplayTextEvent() below. michael@0: michael@0: // Revoke outstanding events to avoid out-of-order events which could mean michael@0: // displaying the wrong text. michael@0: mRedisplayTextEvent.Revoke(); michael@0: michael@0: NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(), michael@0: "If we happen to run our redisplay event now, we might kill " michael@0: "ourselves!"); michael@0: michael@0: nsRefPtr event = new RedisplayTextEvent(this); michael@0: mRedisplayTextEvent = event; michael@0: if (!nsContentUtils::AddScriptRunner(event)) michael@0: mRedisplayTextEvent.Forget(); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: void michael@0: nsComboboxControlFrame::HandleRedisplayTextEvent() michael@0: { michael@0: // First, make sure that the content model is up to date and we've michael@0: // constructed the frames for all our content in the right places. michael@0: // Otherwise they'll end up under the wrong insertion frame when we michael@0: // ActuallyDisplayText, since that flushes out the content sink by michael@0: // calling SetText on a DOM node with aNotify set to true. See bug michael@0: // 289730. michael@0: nsWeakFrame weakThis(this); michael@0: PresContext()->Document()-> michael@0: FlushPendingNotifications(Flush_ContentAndNotify); michael@0: if (!weakThis.IsAlive()) michael@0: return; michael@0: michael@0: // Redirect frame insertions during this method (see GetContentInsertionFrame()) michael@0: // so that any reframing that the frame constructor forces upon us is inserted michael@0: // into the correct parent (mDisplayFrame). See bug 282607. michael@0: NS_PRECONDITION(!mInRedisplayText, "Nested RedisplayText"); michael@0: mInRedisplayText = true; michael@0: mRedisplayTextEvent.Forget(); michael@0: michael@0: ActuallyDisplayText(true); michael@0: // XXXbz This should perhaps be eResize. Check. michael@0: PresContext()->PresShell()->FrameNeedsReflow(mDisplayFrame, michael@0: nsIPresShell::eStyleChange, michael@0: NS_FRAME_IS_DIRTY); michael@0: michael@0: mInRedisplayText = false; michael@0: } michael@0: michael@0: void michael@0: nsComboboxControlFrame::ActuallyDisplayText(bool aNotify) michael@0: { michael@0: if (mDisplayedOptionText.IsEmpty()) { michael@0: // Have to use a non-breaking space for line-height calculations michael@0: // to be right michael@0: static const char16_t space = 0xA0; michael@0: mDisplayContent->SetText(&space, 1, aNotify); michael@0: } else { michael@0: mDisplayContent->SetText(mDisplayedOptionText, aNotify); michael@0: } michael@0: } michael@0: michael@0: int32_t michael@0: nsComboboxControlFrame::GetIndexOfDisplayArea() michael@0: { michael@0: return mDisplayedIndex; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // nsISelectControlFrame michael@0: //---------------------------------------------------------------------- michael@0: NS_IMETHODIMP michael@0: nsComboboxControlFrame::DoneAddingChildren(bool aIsDone) michael@0: { michael@0: nsISelectControlFrame* listFrame = do_QueryFrame(mDropdownFrame); michael@0: if (!listFrame) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: return listFrame->DoneAddingChildren(aIsDone); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsComboboxControlFrame::AddOption(int32_t aIndex) michael@0: { michael@0: if (aIndex <= mDisplayedIndex) { michael@0: ++mDisplayedIndex; michael@0: } michael@0: michael@0: nsListControlFrame* lcf = static_cast(mDropdownFrame); michael@0: return lcf->AddOption(aIndex); michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsComboboxControlFrame::RemoveOption(int32_t aIndex) michael@0: { michael@0: nsWeakFrame weakThis(this); michael@0: if (mListControlFrame->GetNumberOfOptions() > 0) { michael@0: if (aIndex < mDisplayedIndex) { michael@0: --mDisplayedIndex; michael@0: } else if (aIndex == mDisplayedIndex) { michael@0: mDisplayedIndex = 0; // IE6 compat michael@0: RedisplayText(mDisplayedIndex); michael@0: } michael@0: } michael@0: else { michael@0: // If we removed the last option, we need to blank things out michael@0: RedisplayText(-1); michael@0: } michael@0: michael@0: if (!weakThis.IsAlive()) michael@0: return NS_OK; michael@0: michael@0: nsListControlFrame* lcf = static_cast(mDropdownFrame); michael@0: return lcf->RemoveOption(aIndex); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsComboboxControlFrame::OnSetSelectedIndex(int32_t aOldIndex, int32_t aNewIndex) michael@0: { michael@0: nsAutoScriptBlocker scriptBlocker; michael@0: RedisplayText(aNewIndex); michael@0: NS_ASSERTION(mDropdownFrame, "No dropdown frame!"); michael@0: michael@0: nsISelectControlFrame* listFrame = do_QueryFrame(mDropdownFrame); michael@0: NS_ASSERTION(listFrame, "No list frame!"); michael@0: michael@0: return listFrame->OnSetSelectedIndex(aOldIndex, aNewIndex); michael@0: } michael@0: michael@0: // End nsISelectControlFrame michael@0: //---------------------------------------------------------------------- michael@0: michael@0: nsresult michael@0: nsComboboxControlFrame::HandleEvent(nsPresContext* aPresContext, michael@0: WidgetGUIEvent* aEvent, michael@0: nsEventStatus* aEventStatus) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aEventStatus); michael@0: michael@0: if (nsEventStatus_eConsumeNoDefault == *aEventStatus) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: EventStates eventStates = mContent->AsElement()->State(); michael@0: if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // If we have style that affects how we are selected, feed event down to michael@0: // nsFrame::HandleEvent so that selection takes place when appropriate. michael@0: const nsStyleUserInterface* uiStyle = StyleUserInterface(); michael@0: if (uiStyle->mUserInput == NS_STYLE_USER_INPUT_NONE || uiStyle->mUserInput == NS_STYLE_USER_INPUT_DISABLED) michael@0: return nsBlockFrame::HandleEvent(aPresContext, aEvent, aEventStatus); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsComboboxControlFrame::SetFormProperty(nsIAtom* aName, const nsAString& aValue) michael@0: { michael@0: nsIFormControlFrame* fcFrame = do_QueryFrame(mDropdownFrame); michael@0: if (!fcFrame) { michael@0: return NS_NOINTERFACE; michael@0: } michael@0: michael@0: return fcFrame->SetFormProperty(aName, aValue); michael@0: } michael@0: michael@0: nsIFrame* michael@0: nsComboboxControlFrame::GetContentInsertionFrame() { michael@0: return mInRedisplayText ? mDisplayFrame : mDropdownFrame->GetContentInsertionFrame(); michael@0: } michael@0: michael@0: nsresult michael@0: nsComboboxControlFrame::CreateAnonymousContent(nsTArray& aElements) michael@0: { michael@0: // The frames used to display the combo box and the button used to popup the dropdown list michael@0: // are created through anonymous content. The dropdown list is not created through anonymous michael@0: // content because its frame is initialized specifically for the drop-down case and it is placed michael@0: // a special list referenced through NS_COMBO_FRAME_POPUP_LIST_INDEX to keep separate from the michael@0: // layout of the display and button. michael@0: // michael@0: // Note: The value attribute of the display content is set when an item is selected in the dropdown list. michael@0: // If the content specified below does not honor the value attribute than nothing will be displayed. michael@0: michael@0: // For now the content that is created corresponds to two input buttons. It would be better to create the michael@0: // tag as something other than input, but then there isn't any way to create a button frame since it michael@0: // isn't possible to set the display type in CSS2 to create a button frame. michael@0: michael@0: // create content used for display michael@0: //nsIAtom* tag = NS_NewAtom("mozcombodisplay"); michael@0: michael@0: // Add a child text content node for the label michael@0: michael@0: nsNodeInfoManager *nimgr = mContent->NodeInfo()->NodeInfoManager(); michael@0: michael@0: mDisplayContent = new nsTextNode(nimgr); michael@0: michael@0: // set the value of the text node michael@0: mDisplayedIndex = mListControlFrame->GetSelectedIndex(); michael@0: if (mDisplayedIndex != -1) { michael@0: mListControlFrame->GetOptionText(mDisplayedIndex, mDisplayedOptionText); michael@0: } michael@0: ActuallyDisplayText(false); michael@0: michael@0: if (!aElements.AppendElement(mDisplayContent)) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: mButtonContent = mContent->OwnerDoc()->CreateHTMLElement(nsGkAtoms::button); michael@0: if (!mButtonContent) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: // make someone to listen to the button. If its pressed by someone like Accessibility michael@0: // then open or close the combo box. michael@0: mButtonListener = new nsComboButtonListener(this); michael@0: mButtonContent->AddEventListener(NS_LITERAL_STRING("click"), mButtonListener, michael@0: false, false); michael@0: michael@0: mButtonContent->SetAttr(kNameSpaceID_None, nsGkAtoms::type, michael@0: NS_LITERAL_STRING("button"), false); michael@0: // Set tabindex="-1" so that the button is not tabbable michael@0: mButtonContent->SetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, michael@0: NS_LITERAL_STRING("-1"), false); michael@0: michael@0: if (!aElements.AppendElement(mButtonContent)) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsComboboxControlFrame::AppendAnonymousContentTo(nsBaseContentList& aElements, michael@0: uint32_t aFilter) michael@0: { michael@0: aElements.MaybeAppendElement(mDisplayContent); michael@0: aElements.MaybeAppendElement(mButtonContent); michael@0: } michael@0: michael@0: // XXXbz this is a for-now hack. Now that display:inline-block works, michael@0: // need to revisit this. michael@0: class nsComboboxDisplayFrame : public nsBlockFrame { michael@0: public: michael@0: NS_DECL_FRAMEARENA_HELPERS michael@0: michael@0: nsComboboxDisplayFrame (nsStyleContext* aContext, michael@0: nsComboboxControlFrame* aComboBox) michael@0: : nsBlockFrame(aContext), michael@0: mComboBox(aComboBox) michael@0: {} michael@0: michael@0: // Need this so that line layout knows that this block's width michael@0: // depends on the available width. michael@0: virtual nsIAtom* GetType() const MOZ_OVERRIDE; michael@0: michael@0: virtual bool IsFrameOfType(uint32_t aFlags) const MOZ_OVERRIDE michael@0: { michael@0: return nsBlockFrame::IsFrameOfType(aFlags & michael@0: ~(nsIFrame::eReplacedContainsBlock)); michael@0: } michael@0: michael@0: virtual nsresult Reflow(nsPresContext* aPresContext, michael@0: nsHTMLReflowMetrics& aDesiredSize, michael@0: const nsHTMLReflowState& aReflowState, michael@0: nsReflowStatus& aStatus) MOZ_OVERRIDE; michael@0: michael@0: virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, michael@0: const nsRect& aDirtyRect, michael@0: const nsDisplayListSet& aLists) MOZ_OVERRIDE; michael@0: michael@0: protected: michael@0: nsComboboxControlFrame* mComboBox; michael@0: }; michael@0: michael@0: NS_IMPL_FRAMEARENA_HELPERS(nsComboboxDisplayFrame) michael@0: michael@0: nsIAtom* michael@0: nsComboboxDisplayFrame::GetType() const michael@0: { michael@0: return nsGkAtoms::comboboxDisplayFrame; michael@0: } michael@0: michael@0: nsresult michael@0: nsComboboxDisplayFrame::Reflow(nsPresContext* aPresContext, michael@0: nsHTMLReflowMetrics& aDesiredSize, michael@0: const nsHTMLReflowState& aReflowState, michael@0: nsReflowStatus& aStatus) michael@0: { michael@0: nsHTMLReflowState state(aReflowState); michael@0: if (state.ComputedHeight() == NS_INTRINSICSIZE) { michael@0: // Note that the only way we can have a computed height here is if the michael@0: // combobox had a specified height. If it didn't, size based on what our michael@0: // rows look like, for lack of anything better. michael@0: state.SetComputedHeight(mComboBox->mListControlFrame->GetHeightOfARow()); michael@0: } michael@0: nscoord computedWidth = mComboBox->mDisplayWidth - michael@0: state.ComputedPhysicalBorderPadding().LeftRight(); michael@0: if (computedWidth < 0) { michael@0: computedWidth = 0; michael@0: } michael@0: state.SetComputedWidth(computedWidth); michael@0: michael@0: return nsBlockFrame::Reflow(aPresContext, aDesiredSize, state, aStatus); michael@0: } michael@0: michael@0: void michael@0: nsComboboxDisplayFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, michael@0: const nsRect& aDirtyRect, michael@0: const nsDisplayListSet& aLists) michael@0: { michael@0: nsDisplayListCollection set; michael@0: nsBlockFrame::BuildDisplayList(aBuilder, aDirtyRect, set); michael@0: michael@0: // remove background items if parent frame is themed michael@0: if (mComboBox->IsThemed()) { michael@0: set.BorderBackground()->DeleteAll(); michael@0: } michael@0: michael@0: set.MoveTo(aLists); michael@0: } michael@0: michael@0: nsIFrame* michael@0: nsComboboxControlFrame::CreateFrameFor(nsIContent* aContent) michael@0: { michael@0: NS_PRECONDITION(nullptr != aContent, "null ptr"); michael@0: michael@0: NS_ASSERTION(mDisplayContent, "mDisplayContent can't be null!"); michael@0: michael@0: if (mDisplayContent != aContent) { michael@0: // We only handle the frames for mDisplayContent here michael@0: return nullptr; michael@0: } michael@0: michael@0: // Get PresShell michael@0: nsIPresShell *shell = PresContext()->PresShell(); michael@0: nsStyleSet *styleSet = shell->StyleSet(); michael@0: michael@0: // create the style contexts for the anonymous block frame and text frame michael@0: nsRefPtr styleContext; michael@0: styleContext = styleSet-> michael@0: ResolveAnonymousBoxStyle(nsCSSAnonBoxes::mozDisplayComboboxControlFrame, michael@0: mStyleContext); michael@0: michael@0: nsRefPtr textStyleContext; michael@0: textStyleContext = styleSet->ResolveStyleForNonElement(mStyleContext); michael@0: michael@0: // Start by creating our anonymous block frame michael@0: mDisplayFrame = new (shell) nsComboboxDisplayFrame(styleContext, this); michael@0: mDisplayFrame->Init(mContent, this, nullptr); michael@0: michael@0: // Create a text frame and put it inside the block frame michael@0: nsIFrame* textFrame = NS_NewTextFrame(shell, textStyleContext); michael@0: michael@0: // initialize the text frame michael@0: textFrame->Init(aContent, mDisplayFrame, nullptr); michael@0: mDisplayContent->SetPrimaryFrame(textFrame); michael@0: michael@0: nsFrameList textList(textFrame, textFrame); michael@0: mDisplayFrame->SetInitialChildList(kPrincipalList, textList); michael@0: return mDisplayFrame; michael@0: } michael@0: michael@0: void michael@0: nsComboboxControlFrame::DestroyFrom(nsIFrame* aDestructRoot) michael@0: { michael@0: // Revoke any pending RedisplayTextEvent michael@0: mRedisplayTextEvent.Revoke(); michael@0: michael@0: nsFormControlFrame::RegUnRegAccessKey(static_cast(this), false); michael@0: michael@0: if (mDroppedDown) { michael@0: MOZ_ASSERT(mDropdownFrame, "mDroppedDown without frame"); michael@0: nsView* view = mDropdownFrame->GetView(); michael@0: MOZ_ASSERT(view); michael@0: nsIWidget* widget = view->GetWidget(); michael@0: if (widget) { michael@0: widget->CaptureRollupEvents(this, false); michael@0: } michael@0: } michael@0: michael@0: // Cleanup frames in popup child list michael@0: mPopupFrames.DestroyFramesFrom(aDestructRoot); michael@0: nsContentUtils::DestroyAnonymousContent(&mDisplayContent); michael@0: nsContentUtils::DestroyAnonymousContent(&mButtonContent); michael@0: nsBlockFrame::DestroyFrom(aDestructRoot); michael@0: } michael@0: michael@0: const nsFrameList& michael@0: nsComboboxControlFrame::GetChildList(ChildListID aListID) const michael@0: { michael@0: if (kSelectPopupList == aListID) { michael@0: return mPopupFrames; michael@0: } michael@0: return nsBlockFrame::GetChildList(aListID); michael@0: } michael@0: michael@0: void michael@0: nsComboboxControlFrame::GetChildLists(nsTArray* aLists) const michael@0: { michael@0: nsBlockFrame::GetChildLists(aLists); michael@0: mPopupFrames.AppendIfNonempty(aLists, kSelectPopupList); michael@0: } michael@0: michael@0: nsresult michael@0: nsComboboxControlFrame::SetInitialChildList(ChildListID aListID, michael@0: nsFrameList& aChildList) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: if (kSelectPopupList == aListID) { michael@0: mPopupFrames.SetFrames(aChildList); michael@0: } else { michael@0: for (nsFrameList::Enumerator e(aChildList); !e.AtEnd(); e.Next()) { michael@0: nsCOMPtr formControl = michael@0: do_QueryInterface(e.get()->GetContent()); michael@0: if (formControl && formControl->GetType() == NS_FORM_BUTTON_BUTTON) { michael@0: mButtonFrame = e.get(); michael@0: break; michael@0: } michael@0: } michael@0: NS_ASSERTION(mButtonFrame, "missing button frame in initial child list"); michael@0: rv = nsBlockFrame::SetInitialChildList(aListID, aChildList); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: //nsIRollupListener michael@0: //---------------------------------------------------------------------- michael@0: bool michael@0: nsComboboxControlFrame::Rollup(uint32_t aCount, const nsIntPoint* pos, nsIContent** aLastRolledUp) michael@0: { michael@0: if (!mDroppedDown) michael@0: return false; michael@0: michael@0: nsWeakFrame weakFrame(this); michael@0: mListControlFrame->AboutToRollup(); // might destroy us michael@0: if (!weakFrame.IsAlive()) michael@0: return true; michael@0: ShowDropDown(false); // might destroy us michael@0: if (weakFrame.IsAlive()) { michael@0: mListControlFrame->CaptureMouseEvents(false); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: nsIWidget* michael@0: nsComboboxControlFrame::GetRollupWidget() michael@0: { michael@0: nsView* view = mDropdownFrame->GetView(); michael@0: MOZ_ASSERT(view); michael@0: return view->GetWidget(); michael@0: } michael@0: michael@0: void michael@0: nsComboboxControlFrame::RollupFromList() michael@0: { michael@0: if (ShowList(false)) michael@0: mListControlFrame->CaptureMouseEvents(false); michael@0: } michael@0: michael@0: int32_t michael@0: nsComboboxControlFrame::UpdateRecentIndex(int32_t aIndex) michael@0: { michael@0: int32_t index = mRecentSelectedIndex; michael@0: if (mRecentSelectedIndex == NS_SKIP_NOTIFY_INDEX || aIndex == NS_SKIP_NOTIFY_INDEX) michael@0: mRecentSelectedIndex = aIndex; michael@0: return index; michael@0: } michael@0: michael@0: class nsDisplayComboboxFocus : public nsDisplayItem { michael@0: public: michael@0: nsDisplayComboboxFocus(nsDisplayListBuilder* aBuilder, michael@0: nsComboboxControlFrame* aFrame) michael@0: : nsDisplayItem(aBuilder, aFrame) { michael@0: MOZ_COUNT_CTOR(nsDisplayComboboxFocus); michael@0: } michael@0: #ifdef NS_BUILD_REFCNT_LOGGING michael@0: virtual ~nsDisplayComboboxFocus() { michael@0: MOZ_COUNT_DTOR(nsDisplayComboboxFocus); michael@0: } michael@0: #endif michael@0: michael@0: virtual void Paint(nsDisplayListBuilder* aBuilder, michael@0: nsRenderingContext* aCtx) MOZ_OVERRIDE; michael@0: NS_DISPLAY_DECL_NAME("ComboboxFocus", TYPE_COMBOBOX_FOCUS) michael@0: }; michael@0: michael@0: void nsDisplayComboboxFocus::Paint(nsDisplayListBuilder* aBuilder, michael@0: nsRenderingContext* aCtx) michael@0: { michael@0: static_cast(mFrame) michael@0: ->PaintFocus(*aCtx, ToReferenceFrame()); michael@0: } michael@0: michael@0: void michael@0: nsComboboxControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, michael@0: const nsRect& aDirtyRect, michael@0: const nsDisplayListSet& aLists) michael@0: { michael@0: #ifdef NOISY michael@0: printf("%p paint at (%d, %d, %d, %d)\n", this, michael@0: aDirtyRect.x, aDirtyRect.y, aDirtyRect.width, aDirtyRect.height); michael@0: #endif michael@0: michael@0: if (aBuilder->IsForEventDelivery()) { michael@0: // Don't allow children to receive events. michael@0: // REVIEW: following old GetFrameForPoint michael@0: DisplayBorderBackgroundOutline(aBuilder, aLists); michael@0: } else { michael@0: // REVIEW: Our in-flow child frames are inline-level so they will paint in our michael@0: // content list, so we don't need to mess with layers. michael@0: nsBlockFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists); michael@0: } michael@0: michael@0: // draw a focus indicator only when focus rings should be drawn michael@0: nsIDocument* doc = mContent->GetCurrentDoc(); michael@0: if (doc) { michael@0: nsPIDOMWindow* window = doc->GetWindow(); michael@0: if (window && window->ShouldShowFocusRing()) { michael@0: nsPresContext *presContext = PresContext(); michael@0: const nsStyleDisplay *disp = StyleDisplay(); michael@0: if ((!IsThemed(disp) || michael@0: !presContext->GetTheme()->ThemeDrawsFocusForWidget(disp->mAppearance)) && michael@0: mDisplayFrame && IsVisibleForPainting(aBuilder)) { michael@0: aLists.Content()->AppendNewToTop( michael@0: new (aBuilder) nsDisplayComboboxFocus(aBuilder, this)); michael@0: } michael@0: } michael@0: } michael@0: michael@0: DisplaySelectionOverlay(aBuilder, aLists.Content()); michael@0: } michael@0: michael@0: void nsComboboxControlFrame::PaintFocus(nsRenderingContext& aRenderingContext, michael@0: nsPoint aPt) michael@0: { michael@0: /* Do we need to do anything? */ michael@0: EventStates eventStates = mContent->AsElement()->State(); michael@0: if (eventStates.HasState(NS_EVENT_STATE_DISABLED) || sFocused != this) michael@0: return; michael@0: michael@0: aRenderingContext.PushState(); michael@0: nsRect clipRect = mDisplayFrame->GetRect() + aPt; michael@0: aRenderingContext.IntersectClip(clipRect); michael@0: michael@0: // REVIEW: Why does the old code paint mDisplayFrame again? We've michael@0: // already painted it in the children above. So clipping it here won't do michael@0: // us much good. michael@0: michael@0: ///////////////////// michael@0: // draw focus michael@0: michael@0: aRenderingContext.SetLineStyle(nsLineStyle_kDotted); michael@0: aRenderingContext.SetColor(StyleColor()->mColor); michael@0: michael@0: //aRenderingContext.DrawRect(clipRect); michael@0: michael@0: nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1); michael@0: clipRect.width -= onePixel; michael@0: clipRect.height -= onePixel; michael@0: aRenderingContext.DrawLine(clipRect.TopLeft(), clipRect.TopRight()); michael@0: aRenderingContext.DrawLine(clipRect.TopRight(), clipRect.BottomRight()); michael@0: aRenderingContext.DrawLine(clipRect.BottomRight(), clipRect.BottomLeft()); michael@0: aRenderingContext.DrawLine(clipRect.BottomLeft(), clipRect.TopLeft()); michael@0: michael@0: aRenderingContext.PopState(); michael@0: } michael@0: michael@0: //--------------------------------------------------------- michael@0: // gets the content (an option) by index and then set it as michael@0: // being selected or not selected michael@0: //--------------------------------------------------------- michael@0: NS_IMETHODIMP michael@0: nsComboboxControlFrame::OnOptionSelected(int32_t aIndex, bool aSelected) michael@0: { michael@0: if (mDroppedDown) { michael@0: nsISelectControlFrame *selectFrame = do_QueryFrame(mListControlFrame); michael@0: if (selectFrame) { michael@0: selectFrame->OnOptionSelected(aIndex, aSelected); michael@0: } michael@0: } else { michael@0: if (aSelected) { michael@0: nsAutoScriptBlocker blocker; michael@0: RedisplayText(aIndex); michael@0: } else { michael@0: nsWeakFrame weakFrame(this); michael@0: RedisplaySelectedText(); michael@0: if (weakFrame.IsAlive()) { michael@0: FireValueChangeEvent(); // Fire after old option is unselected michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void nsComboboxControlFrame::FireValueChangeEvent() michael@0: { michael@0: // Fire ValueChange event to indicate data value of combo box has changed michael@0: nsContentUtils::AddScriptRunner( michael@0: new AsyncEventDispatcher(mContent, NS_LITERAL_STRING("ValueChange"), true, michael@0: false)); michael@0: } michael@0: michael@0: void michael@0: nsComboboxControlFrame::OnContentReset() michael@0: { michael@0: if (mListControlFrame) { michael@0: mListControlFrame->OnContentReset(); michael@0: } michael@0: } michael@0: michael@0: michael@0: //-------------------------------------------------------- michael@0: // nsIStatefulFrame michael@0: //-------------------------------------------------------- michael@0: NS_IMETHODIMP michael@0: nsComboboxControlFrame::SaveState(nsPresState** aState) michael@0: { michael@0: if (!mListControlFrame) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsIStatefulFrame* stateful = do_QueryFrame(mListControlFrame); michael@0: return stateful->SaveState(aState); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsComboboxControlFrame::RestoreState(nsPresState* aState) michael@0: { michael@0: if (!mListControlFrame) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsIStatefulFrame* stateful = do_QueryFrame(mListControlFrame); michael@0: NS_ASSERTION(stateful, "Must implement nsIStatefulFrame"); michael@0: return stateful->RestoreState(aState); michael@0: } michael@0: michael@0: michael@0: // michael@0: // Camino uses a native widget for the combobox michael@0: // popup, which affects drawing and event michael@0: // handling here and in nsListControlFrame. michael@0: // michael@0: // Also, Fennec use a custom combobox built-in widget michael@0: // michael@0: michael@0: /* static */ michael@0: bool michael@0: nsComboboxControlFrame::ToolkitHasNativePopup() michael@0: { michael@0: #ifdef MOZ_USE_NATIVE_POPUP_WINDOWS michael@0: return true; michael@0: #else michael@0: return false; michael@0: #endif /* MOZ_USE_NATIVE_POPUP_WINDOWS */ michael@0: } michael@0: