1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "nsComboboxControlFrame.h"
8 
9 #include "gfxContext.h"
10 #include "gfxUtils.h"
11 #include "mozilla/gfx/2D.h"
12 #include "mozilla/gfx/PathHelpers.h"
13 #include "nsCOMPtr.h"
14 #include "nsDeviceContext.h"
15 #include "nsFocusManager.h"
16 #include "nsCheckboxRadioFrame.h"
17 #include "nsGkAtoms.h"
18 #include "nsCSSAnonBoxes.h"
19 #include "nsHTMLParts.h"
20 #include "nsIFormControl.h"
21 #include "nsILayoutHistoryState.h"
22 #include "nsNameSpaceManager.h"
23 #include "nsListControlFrame.h"
24 #include "nsPIDOMWindow.h"
25 #include "mozilla/PresState.h"
26 #include "nsView.h"
27 #include "nsViewManager.h"
28 #include "nsIContentInlines.h"
29 #include "nsIDOMEventListener.h"
30 #include "nsISelectControlFrame.h"
31 #include "nsContentUtils.h"
32 #include "mozilla/dom/Event.h"
33 #include "mozilla/dom/HTMLSelectElement.h"
34 #include "mozilla/dom/Document.h"
35 #include "nsIScrollableFrame.h"
36 #include "mozilla/ServoStyleSet.h"
37 #include "nsNodeInfoManager.h"
38 #include "nsContentCreatorFunctions.h"
39 #include "nsLayoutUtils.h"
40 #include "nsDisplayList.h"
41 #include "nsITheme.h"
42 #include "nsStyleConsts.h"
43 #include "nsTextFrameUtils.h"
44 #include "nsTextRunTransformations.h"
45 #include "HTMLSelectEventListener.h"
46 #include "mozilla/Likely.h"
47 #include <algorithm>
48 #include "nsTextNode.h"
49 #include "mozilla/AsyncEventDispatcher.h"
50 #include "mozilla/EventStates.h"
51 #include "mozilla/LookAndFeel.h"
52 #include "mozilla/MouseEvents.h"
53 #include "mozilla/PresShell.h"
54 #include "mozilla/PresShellInlines.h"
55 #include "mozilla/Unused.h"
56 #include "gfx2DGlue.h"
57 #include "mozilla/widget/nsAutoRollup.h"
58 
59 using namespace mozilla;
60 using namespace mozilla::gfx;
61 
62 NS_IMETHODIMP
Run()63 nsComboboxControlFrame::RedisplayTextEvent::Run() {
64   if (mControlFrame) mControlFrame->HandleRedisplayTextEvent();
65   return NS_OK;
66 }
67 
68 // Drop down list event management.
69 // The combo box uses the following strategy for managing the drop-down list.
70 // If the combo box or its arrow button is clicked on the drop-down list is
71 // displayed If mouse exits the combo box with the drop-down list displayed the
72 // drop-down list is asked to capture events The drop-down list will capture all
73 // events including mouse down and up and will always return with
74 // ListWasSelected method call regardless of whether an item in the list was
75 // actually selected.
76 // The ListWasSelected code will turn off mouse-capture for the drop-down list.
77 // The drop-down list does not explicitly set capture when it is in the
78 // drop-down mode.
79 
NS_NewComboboxControlFrame(PresShell * aPresShell,ComputedStyle * aStyle,nsFrameState aStateFlags)80 nsComboboxControlFrame* NS_NewComboboxControlFrame(PresShell* aPresShell,
81                                                    ComputedStyle* aStyle,
82                                                    nsFrameState aStateFlags) {
83   nsComboboxControlFrame* it = new (aPresShell)
84       nsComboboxControlFrame(aStyle, aPresShell->GetPresContext());
85 
86   if (it) {
87     // set the state flags (if any are provided)
88     it->AddStateBits(aStateFlags);
89   }
90 
91   return it;
92 }
93 
94 NS_IMPL_FRAMEARENA_HELPERS(nsComboboxControlFrame)
95 
96 //-----------------------------------------------------------
97 // Reflow Debugging Macros
98 // These let us "see" how many reflow counts are happening
99 //-----------------------------------------------------------
100 #ifdef DO_REFLOW_COUNTER
101 
102 #  define MAX_REFLOW_CNT 1024
103 static int32_t gTotalReqs = 0;
104 ;
105 static int32_t gTotalReflows = 0;
106 ;
107 static int32_t gReflowControlCntRQ[MAX_REFLOW_CNT];
108 static int32_t gReflowControlCnt[MAX_REFLOW_CNT];
109 static int32_t gReflowInx = -1;
110 
111 #  define REFLOW_COUNTER() \
112     if (mReflowId > -1) gReflowControlCnt[mReflowId]++;
113 
114 #  define REFLOW_COUNTER_REQUEST() \
115     if (mReflowId > -1) gReflowControlCntRQ[mReflowId]++;
116 
117 #  define REFLOW_COUNTER_DUMP(__desc)                                   \
118     if (mReflowId > -1) {                                               \
119       gTotalReqs += gReflowControlCntRQ[mReflowId];                     \
120       gTotalReflows += gReflowControlCnt[mReflowId];                    \
121       printf("** Id:%5d %s RF: %d RQ: %d   %d/%d  %5.2f\n", mReflowId,  \
122              (__desc), gReflowControlCnt[mReflowId],                    \
123              gReflowControlCntRQ[mReflowId], gTotalReflows, gTotalReqs, \
124              float(gTotalReflows) / float(gTotalReqs) * 100.0f);        \
125     }
126 
127 #  define REFLOW_COUNTER_INIT()           \
128     if (gReflowInx < MAX_REFLOW_CNT) {    \
129       gReflowInx++;                       \
130       mReflowId = gReflowInx;             \
131       gReflowControlCnt[mReflowId] = 0;   \
132       gReflowControlCntRQ[mReflowId] = 0; \
133     } else {                              \
134       mReflowId = -1;                     \
135     }
136 
137 // reflow messages
138 #  define REFLOW_DEBUG_MSG(_msg1) printf((_msg1))
139 #  define REFLOW_DEBUG_MSG2(_msg1, _msg2) printf((_msg1), (_msg2))
140 #  define REFLOW_DEBUG_MSG3(_msg1, _msg2, _msg3) \
141     printf((_msg1), (_msg2), (_msg3))
142 #  define REFLOW_DEBUG_MSG4(_msg1, _msg2, _msg3, _msg4) \
143     printf((_msg1), (_msg2), (_msg3), (_msg4))
144 
145 #else  //-------------
146 
147 #  define REFLOW_COUNTER_REQUEST()
148 #  define REFLOW_COUNTER()
149 #  define REFLOW_COUNTER_DUMP(__desc)
150 #  define REFLOW_COUNTER_INIT()
151 
152 #  define REFLOW_DEBUG_MSG(_msg)
153 #  define REFLOW_DEBUG_MSG2(_msg1, _msg2)
154 #  define REFLOW_DEBUG_MSG3(_msg1, _msg2, _msg3)
155 #  define REFLOW_DEBUG_MSG4(_msg1, _msg2, _msg3, _msg4)
156 
157 #endif
158 
159 //------------------------------------------
160 // This is for being VERY noisy
161 //------------------------------------------
162 #ifdef DO_VERY_NOISY
163 #  define REFLOW_NOISY_MSG(_msg1) printf((_msg1))
164 #  define REFLOW_NOISY_MSG2(_msg1, _msg2) printf((_msg1), (_msg2))
165 #  define REFLOW_NOISY_MSG3(_msg1, _msg2, _msg3) \
166     printf((_msg1), (_msg2), (_msg3))
167 #  define REFLOW_NOISY_MSG4(_msg1, _msg2, _msg3, _msg4) \
168     printf((_msg1), (_msg2), (_msg3), (_msg4))
169 #else
170 #  define REFLOW_NOISY_MSG(_msg)
171 #  define REFLOW_NOISY_MSG2(_msg1, _msg2)
172 #  define REFLOW_NOISY_MSG3(_msg1, _msg2, _msg3)
173 #  define REFLOW_NOISY_MSG4(_msg1, _msg2, _msg3, _msg4)
174 #endif
175 
176 //------------------------------------------
177 // Displays value in pixels or twips
178 //------------------------------------------
179 #ifdef DO_PIXELS
180 #  define PX(__v) __v / 15
181 #else
182 #  define PX(__v) __v
183 #endif
184 
185 //------------------------------------------------------
186 //-- Done with macros
187 //------------------------------------------------------
188 
nsComboboxControlFrame(ComputedStyle * aStyle,nsPresContext * aPresContext)189 nsComboboxControlFrame::nsComboboxControlFrame(ComputedStyle* aStyle,
190                                                nsPresContext* aPresContext)
191     : nsBlockFrame(aStyle, aPresContext, kClassID),
192       mDisplayFrame(nullptr),
193       mButtonFrame(nullptr),
194       mDisplayISize(0),
195       mMaxDisplayISize(0),
196       mRecentSelectedIndex(NS_SKIP_NOTIFY_INDEX),
197       mDisplayedIndex(-1),
198       mInRedisplayText(false),
199       mIsOpenInParentProcess(false){REFLOW_COUNTER_INIT()}
200 
201       //--------------------------------------------------------------
~nsComboboxControlFrame()202       nsComboboxControlFrame::~nsComboboxControlFrame() {
203   REFLOW_COUNTER_DUMP("nsCCF");
204 }
205 
206 //--------------------------------------------------------------
207 
208 NS_QUERYFRAME_HEAD(nsComboboxControlFrame)
NS_QUERYFRAME_ENTRY(nsComboboxControlFrame)209   NS_QUERYFRAME_ENTRY(nsComboboxControlFrame)
210   NS_QUERYFRAME_ENTRY(nsIFormControlFrame)
211   NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
212   NS_QUERYFRAME_ENTRY(nsISelectControlFrame)
213 NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame)
214 
215 #ifdef ACCESSIBILITY
216 a11y::AccType nsComboboxControlFrame::AccessibleType() {
217   return a11y::eHTMLComboboxType;
218 }
219 #endif
220 
SetFocus(bool aOn,bool aRepaint)221 void nsComboboxControlFrame::SetFocus(bool aOn, bool aRepaint) {
222   // This is needed on a temporary basis. It causes the focus
223   // rect to be drawn. This is much faster than ReResolvingStyle
224   // Bug 32920
225   InvalidateFrame();
226 }
227 
GetCSSTransformTranslation()228 nsPoint nsComboboxControlFrame::GetCSSTransformTranslation() {
229   nsIFrame* frame = this;
230   bool is3DTransform = false;
231   Matrix transform;
232   while (frame) {
233     nsIFrame* parent;
234     Matrix4x4Flagged ctm = frame->GetTransformMatrix(
235         ViewportType::Layout, RelativeTo{nullptr}, &parent);
236     Matrix matrix;
237     if (ctm.Is2D(&matrix)) {
238       transform = transform * matrix;
239     } else {
240       is3DTransform = true;
241       break;
242     }
243     frame = parent;
244   }
245   nsPoint translation;
246   if (!is3DTransform && !transform.HasNonTranslation()) {
247     nsPresContext* pc = PresContext();
248     // To get the translation introduced only by transforms we subtract the
249     // regular non-transform translation.
250     nsRootPresContext* rootPC = pc->GetRootPresContext();
251     if (rootPC) {
252       int32_t apd = pc->AppUnitsPerDevPixel();
253       translation.x = NSFloatPixelsToAppUnits(transform._31, apd);
254       translation.y = NSFloatPixelsToAppUnits(transform._32, apd);
255       translation -= GetOffsetToCrossDoc(rootPC->PresShell()->GetRootFrame());
256     }
257   }
258   return translation;
259 }
260 
261 //----------------------------------------------------------
262 //
263 //----------------------------------------------------------
264 #ifdef DO_REFLOW_DEBUG
265 static int myCounter = 0;
266 
printSize(char * aDesc,nscoord aSize)267 static void printSize(char* aDesc, nscoord aSize) {
268   printf(" %s: ", aDesc);
269   if (aSize == NS_UNCONSTRAINEDSIZE) {
270     printf("UC");
271   } else {
272     printf("%d", PX(aSize));
273   }
274 }
275 #endif
276 
277 //-------------------------------------------------------------------
278 //-- Main Reflow for the Combobox
279 //-------------------------------------------------------------------
280 
HasDropDownButton() const281 bool nsComboboxControlFrame::HasDropDownButton() const {
282   const nsStyleDisplay* disp = StyleDisplay();
283   // FIXME(emilio): Blink also shows this for menulist-button and such... Seems
284   // more similar to our mac / linux implementation.
285   return disp->EffectiveAppearance() == StyleAppearance::Menulist &&
286          (!IsThemed(disp) ||
287           PresContext()->Theme()->ThemeNeedsComboboxDropmarker());
288 }
289 
DropDownButtonISize()290 nscoord nsComboboxControlFrame::DropDownButtonISize() {
291   if (!HasDropDownButton()) {
292     return 0;
293   }
294 
295   LayoutDeviceIntSize dropdownButtonSize;
296   bool canOverride = true;
297   nsPresContext* presContext = PresContext();
298   presContext->Theme()->GetMinimumWidgetSize(
299       presContext, this, StyleAppearance::MozMenulistArrowButton,
300       &dropdownButtonSize, &canOverride);
301 
302   return presContext->DevPixelsToAppUnits(dropdownButtonSize.width);
303 }
304 
CharCountOfLargestOptionForInflation() const305 int32_t nsComboboxControlFrame::CharCountOfLargestOptionForInflation() const {
306   uint32_t maxLength = 0;
307   nsAutoString label;
308   for (auto i : IntegerRange(Select().Options()->Length())) {
309     GetOptionText(i, label);
310     maxLength = std::max(
311         maxLength,
312         nsTextFrameUtils::ComputeApproximateLengthWithWhitespaceCompression(
313             label, StyleText()));
314   }
315   if (MOZ_UNLIKELY(maxLength > uint32_t(INT32_MAX))) {
316     return INT32_MAX;
317   }
318   return int32_t(maxLength);
319 }
320 
GetIntrinsicISize(gfxContext * aRenderingContext,IntrinsicISizeType aType)321 nscoord nsComboboxControlFrame::GetIntrinsicISize(gfxContext* aRenderingContext,
322                                                   IntrinsicISizeType aType) {
323   nscoord displayISize = mDisplayFrame->IntrinsicISizeOffsets().padding;
324 
325   if (!StyleDisplay()->IsContainSize() && !StyleContent()->mContent.IsNone()) {
326     // Compute the width of each option's (potentially text-transformed) text,
327     // and use the widest one as part of our intrinsic size.
328     nscoord maxOptionSize = 0;
329     nsAutoString label;
330     nsAutoString transformedLabel;
331     RefPtr<nsFontMetrics> fm =
332         nsLayoutUtils::GetInflatedFontMetricsForFrame(this);
333     auto textTransform = StyleText()->mTextTransform.IsNone()
334                              ? Nothing()
335                              : Some(StyleText()->mTextTransform);
336     nsAtom* language = StyleFont()->mLanguage;
337     AutoTArray<bool, 50> charsToMergeArray;
338     AutoTArray<bool, 50> deletedCharsArray;
339     for (auto i : IntegerRange(Select().Options()->Length())) {
340       GetOptionText(i, label);
341       const nsAutoString* stringToUse = &label;
342       if (textTransform) {
343         transformedLabel.Truncate();
344         charsToMergeArray.SetLengthAndRetainStorage(0);
345         deletedCharsArray.SetLengthAndRetainStorage(0);
346         nsCaseTransformTextRunFactory::TransformString(
347             label, transformedLabel, textTransform,
348             /* aCaseTransformsOnly = */ false, language, charsToMergeArray,
349             deletedCharsArray);
350         stringToUse = &transformedLabel;
351       }
352       maxOptionSize = std::max(
353           maxOptionSize, nsLayoutUtils::AppUnitWidthOfStringBidi(
354                              *stringToUse, this, *fm, *aRenderingContext));
355     }
356 
357     displayISize += maxOptionSize;
358   }
359 
360   // Add room for the dropmarker button (if there is one) and scrollbar on the
361   // popup.
362   displayISize += DropDownButtonISize();
363   nsPresContext* pc = PresContext();
364   if (!pc->UseOverlayScrollbars()) {
365     displayISize += nsIScrollableFrame::GetNondisappearingScrollbarWidth(
366         pc, GetWritingMode());
367   }
368 
369   return displayISize;
370 }
371 
GetMinISize(gfxContext * aRenderingContext)372 nscoord nsComboboxControlFrame::GetMinISize(gfxContext* aRenderingContext) {
373   nscoord minISize;
374   DISPLAY_MIN_INLINE_SIZE(this, minISize);
375   minISize = GetIntrinsicISize(aRenderingContext, IntrinsicISizeType::MinISize);
376   return minISize;
377 }
378 
GetPrefISize(gfxContext * aRenderingContext)379 nscoord nsComboboxControlFrame::GetPrefISize(gfxContext* aRenderingContext) {
380   nscoord prefISize;
381   DISPLAY_PREF_INLINE_SIZE(this, prefISize);
382   prefISize =
383       GetIntrinsicISize(aRenderingContext, IntrinsicISizeType::PrefISize);
384   return prefISize;
385 }
386 
Select() const387 dom::HTMLSelectElement& nsComboboxControlFrame::Select() const {
388   return *static_cast<dom::HTMLSelectElement*>(GetContent());
389 }
390 
GetOptionText(uint32_t aIndex,nsAString & aText) const391 void nsComboboxControlFrame::GetOptionText(uint32_t aIndex,
392                                            nsAString& aText) const {
393   aText.Truncate();
394   if (Element* el = Select().Options()->GetElementAt(aIndex)) {
395     static_cast<dom::HTMLOptionElement*>(el)->GetRenderedLabel(aText);
396   }
397 }
398 
Reflow(nsPresContext * aPresContext,ReflowOutput & aDesiredSize,const ReflowInput & aReflowInput,nsReflowStatus & aStatus)399 void nsComboboxControlFrame::Reflow(nsPresContext* aPresContext,
400                                     ReflowOutput& aDesiredSize,
401                                     const ReflowInput& aReflowInput,
402                                     nsReflowStatus& aStatus) {
403   MarkInReflow();
404   MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
405   // Constraints we try to satisfy:
406 
407   // 1) Default inline size of button is the vertical scrollbar size
408   // 2) If the inline size of button is bigger than our inline size, set
409   //    inline size of button to 0.
410   // 3) Default block size of button is block size of display area
411   // 4) Inline size of display area is whatever is left over from our
412   //    inline size after allocating inline size for the button.
413 
414   if (!mDisplayFrame || !mButtonFrame) {
415     NS_ERROR("Why did the frame constructor allow this to happen?  Fix it!!");
416     return;
417   }
418 
419   // Make sure the displayed text is the same as the selected option,
420   // bug 297389.
421   mDisplayedIndex = Select().SelectedIndex();
422 
423   // In dropped down mode the "selected index" is the hovered menu item,
424   // we want the last selected item which is |mDisplayedIndex| in this case.
425   RedisplayText();
426 
427   WritingMode wm = aReflowInput.GetWritingMode();
428 
429   // Check if the theme specifies a minimum size for the dropdown button
430   // first.
431   const nscoord buttonISize = DropDownButtonISize();
432   const auto borderPadding = aReflowInput.ComputedLogicalBorderPadding(wm);
433   const auto padding = aReflowInput.ComputedLogicalPadding(wm);
434   const auto border = borderPadding - padding;
435 
436   mDisplayISize = aReflowInput.ComputedISize() - buttonISize;
437   mMaxDisplayISize = mDisplayISize + padding.IEnd(wm);
438 
439   nsBlockFrame::Reflow(aPresContext, aDesiredSize, aReflowInput, aStatus);
440 
441   // The button should occupy the same space as a scrollbar, and its position
442   // starts from the border edge.
443   LogicalRect buttonRect(wm);
444   buttonRect.IStart(wm) = borderPadding.IStart(wm) + mMaxDisplayISize;
445   buttonRect.BStart(wm) = border.BStart(wm);
446 
447   buttonRect.ISize(wm) = buttonISize;
448   buttonRect.BSize(wm) = mDisplayFrame->BSize(wm) + padding.BStartEnd(wm);
449 
450   const nsSize containerSize = aDesiredSize.PhysicalSize();
451   mButtonFrame->SetRect(buttonRect, containerSize);
452 
453   if (!aStatus.IsInlineBreakBefore() && !aStatus.IsFullyComplete()) {
454     // This frame didn't fit inside a fragmentation container.  Splitting
455     // a nsComboboxControlFrame makes no sense, so we override the status here.
456     aStatus.Reset();
457   }
458 }
459 
Init(nsIContent * aContent,nsContainerFrame * aParent,nsIFrame * aPrevInFlow)460 void nsComboboxControlFrame::Init(nsIContent* aContent,
461                                   nsContainerFrame* aParent,
462                                   nsIFrame* aPrevInFlow) {
463   nsBlockFrame::Init(aContent, aParent, aPrevInFlow);
464 
465   mEventListener = new HTMLSelectEventListener(
466       Select(), HTMLSelectEventListener::SelectType::Combobox);
467 }
468 
469 #ifdef DEBUG_FRAME_DUMP
GetFrameName(nsAString & aResult) const470 nsresult nsComboboxControlFrame::GetFrameName(nsAString& aResult) const {
471   return MakeFrameName(u"ComboboxControl"_ns, aResult);
472 }
473 #endif
474 
475 ///////////////////////////////////////////////////////////////
476 
RedisplaySelectedText()477 nsresult nsComboboxControlFrame::RedisplaySelectedText() {
478   nsAutoScriptBlocker scriptBlocker;
479   mDisplayedIndex = Select().SelectedIndex();
480   return RedisplayText();
481 }
482 
RedisplayText()483 nsresult nsComboboxControlFrame::RedisplayText() {
484   nsString previewValue;
485   nsString previousText(mDisplayedOptionTextOrPreview);
486 
487   Select().GetPreviewValue(previewValue);
488   // Get the text to display
489   if (!previewValue.IsEmpty()) {
490     mDisplayedOptionTextOrPreview = previewValue;
491   } else if (mDisplayedIndex != -1 && !StyleContent()->mContent.IsNone()) {
492     GetOptionText(mDisplayedIndex, mDisplayedOptionTextOrPreview);
493   } else {
494     mDisplayedOptionTextOrPreview.Truncate();
495   }
496 
497   REFLOW_DEBUG_MSG2(
498       "RedisplayText \"%s\"\n",
499       NS_LossyConvertUTF16toASCII(mDisplayedOptionTextOrPreview).get());
500 
501   // Send reflow command because the new text maybe larger
502   nsresult rv = NS_OK;
503   if (mDisplayContent && !previousText.Equals(mDisplayedOptionTextOrPreview)) {
504     // Don't call ActuallyDisplayText(true) directly here since that
505     // could cause recursive frame construction. See bug 283117 and the comment
506     // in HandleRedisplayTextEvent() below.
507 
508     // Revoke outstanding events to avoid out-of-order events which could mean
509     // displaying the wrong text.
510     mRedisplayTextEvent.Revoke();
511 
512     NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
513                  "If we happen to run our redisplay event now, we might kill "
514                  "ourselves!");
515 
516     mRedisplayTextEvent = new RedisplayTextEvent(this);
517     nsContentUtils::AddScriptRunner(mRedisplayTextEvent.get());
518   }
519   return rv;
520 }
521 
HandleRedisplayTextEvent()522 void nsComboboxControlFrame::HandleRedisplayTextEvent() {
523   // First, make sure that the content model is up to date and we've
524   // constructed the frames for all our content in the right places.
525   // Otherwise they'll end up under the wrong insertion frame when we
526   // ActuallyDisplayText, since that flushes out the content sink by
527   // calling SetText on a DOM node with aNotify set to true.  See bug
528   // 289730.
529   AutoWeakFrame weakThis(this);
530   PresContext()->Document()->FlushPendingNotifications(
531       FlushType::ContentAndNotify);
532   if (!weakThis.IsAlive()) return;
533 
534   // Redirect frame insertions during this method (see
535   // GetContentInsertionFrame()) so that any reframing that the frame
536   // constructor forces upon us is inserted into the correct parent
537   // (mDisplayFrame). See bug 282607.
538   MOZ_ASSERT(!mInRedisplayText, "Nested RedisplayText");
539   mInRedisplayText = true;
540   mRedisplayTextEvent.Forget();
541 
542   ActuallyDisplayText(true);
543   if (!weakThis.IsAlive()) {
544     return;
545   }
546 
547   // XXXbz This should perhaps be eResize.  Check.
548   PresShell()->FrameNeedsReflow(mDisplayFrame, IntrinsicDirty::StyleChange,
549                                 NS_FRAME_IS_DIRTY);
550 
551   mInRedisplayText = false;
552 }
553 
ActuallyDisplayText(bool aNotify)554 void nsComboboxControlFrame::ActuallyDisplayText(bool aNotify) {
555   RefPtr<nsTextNode> displayContent = mDisplayContent;
556   if (mDisplayedOptionTextOrPreview.IsEmpty()) {
557     // Have to use a space character of some sort for line-block-size
558     // calculations to be right. Also, the space character must be zero-width
559     // in order for the the inline-size calculations to be consistent between
560     // size-contained comboboxes vs. empty comboboxes.
561     //
562     // XXXdholbert Does this space need to be "non-breaking"? I'm not sure
563     // if it matters, but we previously had a comment here (added in 2002)
564     // saying "Have to use a non-breaking space for line-height calculations
565     // to be right". So I'll stick with a non-breaking space for now...
566     static const char16_t space = 0xFEFF;
567     displayContent->SetText(&space, 1, aNotify);
568   } else {
569     displayContent->SetText(mDisplayedOptionTextOrPreview, aNotify);
570   }
571 }
572 
GetIndexOfDisplayArea()573 int32_t nsComboboxControlFrame::GetIndexOfDisplayArea() {
574   return mDisplayedIndex;
575 }
576 
IsDroppedDown() const577 bool nsComboboxControlFrame::IsDroppedDown() const {
578   return Select().OpenInParentProcess();
579 }
580 
581 //----------------------------------------------------------------------
582 // nsISelectControlFrame
583 //----------------------------------------------------------------------
584 NS_IMETHODIMP
DoneAddingChildren(bool aIsDone)585 nsComboboxControlFrame::DoneAddingChildren(bool aIsDone) { return NS_OK; }
586 
587 NS_IMETHODIMP
AddOption(int32_t aIndex)588 nsComboboxControlFrame::AddOption(int32_t aIndex) {
589   if (aIndex <= mDisplayedIndex) {
590     ++mDisplayedIndex;
591   }
592 
593   return NS_OK;
594 }
595 
596 NS_IMETHODIMP
RemoveOption(int32_t aIndex)597 nsComboboxControlFrame::RemoveOption(int32_t aIndex) {
598   if (Select().Options()->Length()) {
599     if (aIndex < mDisplayedIndex) {
600       --mDisplayedIndex;
601     } else if (aIndex == mDisplayedIndex) {
602       mDisplayedIndex = 0;  // IE6 compat
603       RedisplayText();
604     }
605   } else {
606     // If we removed the last option, we need to blank things out
607     mDisplayedIndex = -1;
608     RedisplayText();
609   }
610   return NS_OK;
611 }
612 
NS_IMETHODIMP_(void)613 NS_IMETHODIMP_(void)
614 nsComboboxControlFrame::OnSetSelectedIndex(int32_t aOldIndex,
615                                            int32_t aNewIndex) {
616   nsAutoScriptBlocker scriptBlocker;
617   mDisplayedIndex = aNewIndex;
618   RedisplayText();
619 }
620 
621 // End nsISelectControlFrame
622 //----------------------------------------------------------------------
623 
HandleEvent(nsPresContext * aPresContext,WidgetGUIEvent * aEvent,nsEventStatus * aEventStatus)624 nsresult nsComboboxControlFrame::HandleEvent(nsPresContext* aPresContext,
625                                              WidgetGUIEvent* aEvent,
626                                              nsEventStatus* aEventStatus) {
627   NS_ENSURE_ARG_POINTER(aEventStatus);
628 
629   if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
630     return NS_OK;
631   }
632 
633   EventStates eventStates = mContent->AsElement()->State();
634   if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
635     return NS_OK;
636   }
637 
638   // If we have style that affects how we are selected, feed event down to
639   // nsIFrame::HandleEvent so that selection takes place when appropriate.
640   if (IsContentDisabled()) {
641     return nsBlockFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
642   }
643   return NS_OK;
644 }
645 
GetContentInsertionFrame()646 nsContainerFrame* nsComboboxControlFrame::GetContentInsertionFrame() {
647   return mInRedisplayText ? mDisplayFrame : nullptr;
648 }
649 
AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox> & aResult)650 void nsComboboxControlFrame::AppendDirectlyOwnedAnonBoxes(
651     nsTArray<OwnedAnonBox>& aResult) {
652   aResult.AppendElement(OwnedAnonBox(mDisplayFrame));
653 }
654 
CreateAnonymousContent(nsTArray<ContentInfo> & aElements)655 nsresult nsComboboxControlFrame::CreateAnonymousContent(
656     nsTArray<ContentInfo>& aElements) {
657   // The frames used to display the combo box and the button used to popup the
658   // dropdown list are created through anonymous content. The dropdown list is
659   // not created through anonymous content because its frame is initialized
660   // specifically for the drop-down case and it is placed a special list
661   // referenced through NS_COMBO_FRAME_POPUP_LIST_INDEX to keep separate from
662   // the layout of the display and button.
663   //
664   // Note: The value attribute of the display content is set when an item is
665   // selected in the dropdown list. If the content specified below does not
666   // honor the value attribute than nothing will be displayed.
667 
668   // For now the content that is created corresponds to two input buttons. It
669   // would be better to create the tag as something other than input, but then
670   // there isn't any way to create a button frame since it isn't possible to set
671   // the display type in CSS2 to create a button frame.
672 
673   // create content used for display
674   // nsAtom* tag = NS_Atomize("mozcombodisplay");
675 
676   // Add a child text content node for the label
677 
678   nsNodeInfoManager* nimgr = mContent->NodeInfo()->NodeInfoManager();
679 
680   mDisplayContent = new (nimgr) nsTextNode(nimgr);
681 
682   // set the value of the text node
683   mDisplayedIndex = Select().SelectedIndex();
684   if (mDisplayedIndex != -1) {
685     GetOptionText(mDisplayedIndex, mDisplayedOptionTextOrPreview);
686   }
687   ActuallyDisplayText(false);
688 
689   // XXX(Bug 1631371) Check if this should use a fallible operation as it
690   // pretended earlier.
691   aElements.AppendElement(mDisplayContent);
692 
693   mButtonContent = mContent->OwnerDoc()->CreateHTMLElement(nsGkAtoms::button);
694   if (!mButtonContent) return NS_ERROR_OUT_OF_MEMORY;
695 
696   // make someone to listen to the button.
697   mButtonContent->SetAttr(kNameSpaceID_None, nsGkAtoms::type, u"button"_ns,
698                           false);
699   // Set tabindex="-1" so that the button is not tabbable
700   mButtonContent->SetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, u"-1"_ns,
701                           false);
702 
703   WritingMode wm = GetWritingMode();
704   if (wm.IsVertical()) {
705     mButtonContent->SetAttr(kNameSpaceID_None, nsGkAtoms::orientation,
706                             wm.IsVerticalRL() ? u"left"_ns : u"right"_ns,
707                             false);
708   }
709 
710   // XXX(Bug 1631371) Check if this should use a fallible operation as it
711   // pretended earlier.
712   aElements.AppendElement(mButtonContent);
713 
714   return NS_OK;
715 }
716 
AppendAnonymousContentTo(nsTArray<nsIContent * > & aElements,uint32_t aFilter)717 void nsComboboxControlFrame::AppendAnonymousContentTo(
718     nsTArray<nsIContent*>& aElements, uint32_t aFilter) {
719   if (mDisplayContent) {
720     aElements.AppendElement(mDisplayContent);
721   }
722 
723   if (mButtonContent) {
724     aElements.AppendElement(mButtonContent);
725   }
726 }
727 
GetDisplayNode() const728 nsIContent* nsComboboxControlFrame::GetDisplayNode() const {
729   return mDisplayContent;
730 }
731 
732 // XXXbz this is a for-now hack.  Now that display:inline-block works,
733 // need to revisit this.
734 class nsComboboxDisplayFrame final : public nsBlockFrame {
735  public:
736   NS_DECL_FRAMEARENA_HELPERS(nsComboboxDisplayFrame)
737 
nsComboboxDisplayFrame(ComputedStyle * aStyle,nsComboboxControlFrame * aComboBox)738   nsComboboxDisplayFrame(ComputedStyle* aStyle,
739                          nsComboboxControlFrame* aComboBox)
740       : nsBlockFrame(aStyle, aComboBox->PresContext(), kClassID),
741         mComboBox(aComboBox) {}
742 
743 #ifdef DEBUG_FRAME_DUMP
GetFrameName(nsAString & aResult) const744   nsresult GetFrameName(nsAString& aResult) const final {
745     return MakeFrameName(u"ComboboxDisplay"_ns, aResult);
746   }
747 #endif
748 
IsFrameOfType(uint32_t aFlags) const749   bool IsFrameOfType(uint32_t aFlags) const final {
750     return nsBlockFrame::IsFrameOfType(aFlags &
751                                        ~(nsIFrame::eReplacedContainsBlock));
752   }
753 
754   void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
755               const ReflowInput& aReflowInput, nsReflowStatus& aStatus) final;
756 
757   void BuildDisplayList(nsDisplayListBuilder* aBuilder,
758                         const nsDisplayListSet& aLists) final;
759 
760  protected:
761   nsComboboxControlFrame* mComboBox;
762 };
763 
NS_IMPL_FRAMEARENA_HELPERS(nsComboboxDisplayFrame)764 NS_IMPL_FRAMEARENA_HELPERS(nsComboboxDisplayFrame)
765 
766 void nsComboboxDisplayFrame::Reflow(nsPresContext* aPresContext,
767                                     ReflowOutput& aDesiredSize,
768                                     const ReflowInput& aReflowInput,
769                                     nsReflowStatus& aStatus) {
770   MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
771   MOZ_ASSERT(aReflowInput.mParentReflowInput &&
772                  aReflowInput.mParentReflowInput->mFrame == mComboBox,
773              "Combobox's frame tree is wrong!");
774 
775   ReflowInput state(aReflowInput);
776   if (state.ComputedBSize() == NS_UNCONSTRAINEDSIZE) {
777     state.SetLineHeight(state.mParentReflowInput->GetLineHeight());
778   }
779   const WritingMode wm = aReflowInput.GetWritingMode();
780   const LogicalMargin bp = state.ComputedLogicalBorderPadding(wm);
781   MOZ_ASSERT(bp.BStartEnd(wm) == 0,
782              "We shouldn't have border and padding in the block axis in UA!");
783   nscoord inlineBp = bp.IStartEnd(wm);
784   nscoord computedISize = mComboBox->mDisplayISize - inlineBp;
785 
786   // Other UAs ignore padding in some (but not all) platforms for (themed only)
787   // comboboxes. Instead of doing that, we prevent that padding if present from
788   // clipping the display text, by enforcing the display text minimum size in
789   // that situation.
790   const bool shouldHonorMinISize =
791       mComboBox->StyleDisplay()->EffectiveAppearance() ==
792       StyleAppearance::Menulist;
793   if (shouldHonorMinISize) {
794     computedISize = std::max(state.ComputedMinISize(), computedISize);
795     // Don't let this size go over mMaxDisplayISize, since that'd be
796     // observable via clientWidth / scrollWidth.
797     computedISize =
798         std::min(computedISize, mComboBox->mMaxDisplayISize - inlineBp);
799   }
800 
801   state.SetComputedISize(std::max(0, computedISize));
802   nsBlockFrame::Reflow(aPresContext, aDesiredSize, state, aStatus);
803   aStatus.Reset();  // this type of frame can't be split
804 }
805 
BuildDisplayList(nsDisplayListBuilder * aBuilder,const nsDisplayListSet & aLists)806 void nsComboboxDisplayFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
807                                               const nsDisplayListSet& aLists) {
808   nsDisplayListCollection set(aBuilder);
809   nsBlockFrame::BuildDisplayList(aBuilder, set);
810 
811   // remove background items if parent frame is themed
812   if (mComboBox->IsThemed()) {
813     set.BorderBackground()->DeleteAll(aBuilder);
814   }
815 
816   set.MoveTo(aLists);
817 }
818 
CreateFrameForDisplayNode()819 nsIFrame* nsComboboxControlFrame::CreateFrameForDisplayNode() {
820   MOZ_ASSERT(mDisplayContent);
821 
822   // Get PresShell
823   mozilla::PresShell* ps = PresShell();
824   ServoStyleSet* styleSet = ps->StyleSet();
825 
826   // create the ComputedStyle for the anonymous block frame and text frame
827   RefPtr<ComputedStyle> computedStyle =
828       styleSet->ResolveInheritingAnonymousBoxStyle(
829           PseudoStyleType::mozDisplayComboboxControlFrame, mComputedStyle);
830 
831   RefPtr<ComputedStyle> textComputedStyle =
832       styleSet->ResolveStyleForText(mDisplayContent, mComputedStyle);
833 
834   // Start by creating our anonymous block frame
835   mDisplayFrame = new (ps) nsComboboxDisplayFrame(computedStyle, this);
836   mDisplayFrame->Init(mContent, this, nullptr);
837 
838   // Create a text frame and put it inside the block frame
839   nsIFrame* textFrame = NS_NewTextFrame(ps, textComputedStyle);
840 
841   // initialize the text frame
842   textFrame->Init(mDisplayContent, mDisplayFrame, nullptr);
843   mDisplayContent->SetPrimaryFrame(textFrame);
844 
845   nsFrameList textList(textFrame, textFrame);
846   mDisplayFrame->SetInitialChildList(kPrincipalList, textList);
847   return mDisplayFrame;
848 }
849 
DestroyFrom(nsIFrame * aDestructRoot,PostDestroyData & aPostDestroyData)850 void nsComboboxControlFrame::DestroyFrom(nsIFrame* aDestructRoot,
851                                          PostDestroyData& aPostDestroyData) {
852   // Revoke any pending RedisplayTextEvent
853   mRedisplayTextEvent.Revoke();
854 
855   mEventListener->Detach();
856 
857   // Cleanup frames in popup child list
858   mPopupFrames.DestroyFramesFrom(aDestructRoot, aPostDestroyData);
859   aPostDestroyData.AddAnonymousContent(mDisplayContent.forget());
860   aPostDestroyData.AddAnonymousContent(mButtonContent.forget());
861   nsBlockFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
862 }
863 
GetChildList(ChildListID aListID) const864 const nsFrameList& nsComboboxControlFrame::GetChildList(
865     ChildListID aListID) const {
866   if (kSelectPopupList == aListID) {
867     return mPopupFrames;
868   }
869   return nsBlockFrame::GetChildList(aListID);
870 }
871 
GetChildLists(nsTArray<ChildList> * aLists) const872 void nsComboboxControlFrame::GetChildLists(nsTArray<ChildList>* aLists) const {
873   nsBlockFrame::GetChildLists(aLists);
874   mPopupFrames.AppendIfNonempty(aLists, kSelectPopupList);
875 }
876 
SetInitialChildList(ChildListID aListID,nsFrameList & aChildList)877 void nsComboboxControlFrame::SetInitialChildList(ChildListID aListID,
878                                                  nsFrameList& aChildList) {
879 #ifdef DEBUG
880   for (nsIFrame* f : aChildList) {
881     MOZ_ASSERT(f->GetParent() == this, "Unexpected parent");
882   }
883 #endif
884   if (kSelectPopupList == aListID) {
885     mPopupFrames.SetFrames(aChildList);
886   } else {
887     for (nsFrameList::Enumerator e(aChildList); !e.AtEnd(); e.Next()) {
888       nsCOMPtr<nsIFormControl> formControl =
889           do_QueryInterface(e.get()->GetContent());
890       if (formControl &&
891           formControl->ControlType() == FormControlType::ButtonButton) {
892         mButtonFrame = e.get();
893         break;
894       }
895     }
896     NS_ASSERTION(mButtonFrame, "missing button frame in initial child list");
897     nsBlockFrame::SetInitialChildList(aListID, aChildList);
898   }
899 }
900 
901 namespace mozilla {
902 
903 class nsDisplayComboboxFocus : public nsPaintedDisplayItem {
904  public:
nsDisplayComboboxFocus(nsDisplayListBuilder * aBuilder,nsComboboxControlFrame * aFrame)905   nsDisplayComboboxFocus(nsDisplayListBuilder* aBuilder,
906                          nsComboboxControlFrame* aFrame)
907       : nsPaintedDisplayItem(aBuilder, aFrame) {
908     MOZ_COUNT_CTOR(nsDisplayComboboxFocus);
909   }
910   MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayComboboxFocus)
911 
912   void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
913   NS_DISPLAY_DECL_NAME("ComboboxFocus", TYPE_COMBOBOX_FOCUS)
914 };
915 
Paint(nsDisplayListBuilder * aBuilder,gfxContext * aCtx)916 void nsDisplayComboboxFocus::Paint(nsDisplayListBuilder* aBuilder,
917                                    gfxContext* aCtx) {
918   static_cast<nsComboboxControlFrame*>(mFrame)->PaintFocus(
919       *aCtx->GetDrawTarget(), ToReferenceFrame());
920 }
921 
922 }  // namespace mozilla
923 
BuildDisplayList(nsDisplayListBuilder * aBuilder,const nsDisplayListSet & aLists)924 void nsComboboxControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
925                                               const nsDisplayListSet& aLists) {
926   if (aBuilder->IsForEventDelivery()) {
927     // Don't allow children to receive events.
928     // REVIEW: following old GetFrameForPoint
929     DisplayBorderBackgroundOutline(aBuilder, aLists);
930   } else {
931     // REVIEW: Our in-flow child frames are inline-level so they will paint in
932     // our content list, so we don't need to mess with layers.
933     nsBlockFrame::BuildDisplayList(aBuilder, aLists);
934   }
935 
936   // draw a focus indicator only when focus rings should be drawn
937   if (mContent->AsElement()->State().HasState(NS_EVENT_STATE_FOCUSRING)) {
938     nsPresContext* pc = PresContext();
939     const nsStyleDisplay* disp = StyleDisplay();
940     if (IsThemed(disp) &&
941         pc->Theme()->ThemeWantsButtonInnerFocusRing(
942             this, disp->EffectiveAppearance()) &&
943         mDisplayFrame && IsVisibleForPainting()) {
944       aLists.Content()->AppendNewToTop<nsDisplayComboboxFocus>(aBuilder, this);
945     }
946   }
947 
948   DisplaySelectionOverlay(aBuilder, aLists.Content());
949 }
950 
PaintFocus(DrawTarget & aDrawTarget,nsPoint aPt)951 void nsComboboxControlFrame::PaintFocus(DrawTarget& aDrawTarget, nsPoint aPt) {
952   /* Do we need to do anything? */
953   EventStates eventStates = mContent->AsElement()->State();
954   if (eventStates.HasState(NS_EVENT_STATE_DISABLED) ||
955       !eventStates.HasState(NS_EVENT_STATE_FOCUS)) {
956     return;
957   }
958 
959   int32_t appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel();
960 
961   nsRect clipRect = mDisplayFrame->GetRect() + aPt;
962   aDrawTarget.PushClipRect(
963       NSRectToSnappedRect(clipRect, appUnitsPerDevPixel, aDrawTarget));
964 
965   StrokeOptions strokeOptions;
966   nsLayoutUtils::InitDashPattern(strokeOptions, StyleBorderStyle::Dotted);
967   ColorPattern color(ToDeviceColor(StyleText()->mColor));
968   nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1);
969   clipRect.width -= onePixel;
970   clipRect.height -= onePixel;
971   Rect r = ToRect(nsLayoutUtils::RectToGfxRect(clipRect, appUnitsPerDevPixel));
972   StrokeSnappedEdgesOfRect(r, aDrawTarget, color, strokeOptions);
973 
974   aDrawTarget.PopClip();
975 }
976 
977 //---------------------------------------------------------
978 // gets the content (an option) by index and then set it as
979 // being selected or not selected
980 //---------------------------------------------------------
981 NS_IMETHODIMP
OnOptionSelected(int32_t aIndex,bool aSelected)982 nsComboboxControlFrame::OnOptionSelected(int32_t aIndex, bool aSelected) {
983   if (aSelected) {
984     nsAutoScriptBlocker blocker;
985     mDisplayedIndex = aIndex;
986     RedisplayText();
987   } else {
988     AutoWeakFrame weakFrame(this);
989     RedisplaySelectedText();
990     if (weakFrame.IsAlive()) {
991       FireValueChangeEvent();  // Fire after old option is unselected
992     }
993   }
994   return NS_OK;
995 }
996 
FireValueChangeEvent()997 void nsComboboxControlFrame::FireValueChangeEvent() {
998   // Fire ValueChange event to indicate data value of combo box has changed
999   nsContentUtils::AddScriptRunner(new AsyncEventDispatcher(
1000       mContent, u"ValueChange"_ns, CanBubble::eYes, ChromeOnlyDispatch::eNo));
1001 }
1002