Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/layout/forms/nsComboboxControlFrame.cpp
Line
Count
Source (jump to first uncovered line)
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 "nsFocusManager.h"
15
#include "nsCheckboxRadioFrame.h"
16
#include "nsGkAtoms.h"
17
#include "nsCSSAnonBoxes.h"
18
#include "nsHTMLParts.h"
19
#include "nsIFormControl.h"
20
#include "nsNameSpaceManager.h"
21
#include "nsIListControlFrame.h"
22
#include "nsPIDOMWindow.h"
23
#include "nsIPresShell.h"
24
#include "mozilla/PresState.h"
25
#include "nsView.h"
26
#include "nsViewManager.h"
27
#include "nsIContentInlines.h"
28
#include "nsIDOMEventListener.h"
29
#include "nsISelectControlFrame.h"
30
#include "nsContentUtils.h"
31
#include "mozilla/dom/Event.h"
32
#include "mozilla/dom/HTMLSelectElement.h"
33
#include "nsIDocument.h"
34
#include "nsIScrollableFrame.h"
35
#include "nsListControlFrame.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 "mozilla/Likely.h"
44
#include <algorithm>
45
#include "nsTextNode.h"
46
#include "mozilla/AsyncEventDispatcher.h"
47
#include "mozilla/EventStates.h"
48
#include "mozilla/LookAndFeel.h"
49
#include "mozilla/MouseEvents.h"
50
#include "mozilla/Unused.h"
51
#include "gfx2DGlue.h"
52
#include "mozilla/widget/nsAutoRollup.h"
53
#include "nsILayoutHistoryState.h"
54
55
#ifdef XP_WIN
56
#define COMBOBOX_ROLLUP_CONSUME_EVENT 0
57
#else
58
0
#define COMBOBOX_ROLLUP_CONSUME_EVENT 1
59
#endif
60
61
using namespace mozilla;
62
using namespace mozilla::gfx;
63
64
NS_IMETHODIMP
65
nsComboboxControlFrame::RedisplayTextEvent::Run()
66
0
{
67
0
  if (mControlFrame)
68
0
    mControlFrame->HandleRedisplayTextEvent();
69
0
  return NS_OK;
70
0
}
71
72
73
#define FIX_FOR_BUG_53259
74
75
// Drop down list event management.
76
// The combo box uses the following strategy for managing the drop-down list.
77
// If the combo box or its arrow button is clicked on the drop-down list is displayed
78
// If mouse exits the combo box with the drop-down list displayed the drop-down list
79
// is asked to capture events
80
// The drop-down list will capture all events including mouse down and up and will always
81
// return with ListWasSelected method call regardless of whether an item in the list was
82
// actually selected.
83
// The ListWasSelected code will turn off mouse-capture for the drop-down list.
84
// The drop-down list does not explicitly set capture when it is in the drop-down mode.
85
86
87
/**
88
 * Helper class that listens to the combo boxes button. If the button is pressed the
89
 * combo box is toggled to open or close. this is used by Accessibility which presses
90
 * that button Programmatically.
91
 */
92
class nsComboButtonListener final : public nsIDOMEventListener
93
{
94
private:
95
0
  virtual ~nsComboButtonListener() {}
96
97
public:
98
  NS_DECL_ISUPPORTS
99
100
  NS_IMETHOD HandleEvent(dom::Event*) override
101
0
  {
102
0
    mComboBox->ShowDropDown(!mComboBox->IsDroppedDown());
103
0
    return NS_OK;
104
0
  }
105
106
  explicit nsComboButtonListener(nsComboboxControlFrame* aCombobox)
107
0
  {
108
0
    mComboBox = aCombobox;
109
0
  }
110
111
  nsComboboxControlFrame* mComboBox;
112
};
113
114
NS_IMPL_ISUPPORTS(nsComboButtonListener,
115
                  nsIDOMEventListener)
116
117
// static class data member for Bug 32920
118
nsComboboxControlFrame* nsComboboxControlFrame::sFocused = nullptr;
119
120
nsComboboxControlFrame*
121
NS_NewComboboxControlFrame(nsIPresShell* aPresShell, ComputedStyle* aStyle, nsFrameState aStateFlags)
122
0
{
123
0
  nsComboboxControlFrame* it = new (aPresShell) nsComboboxControlFrame(aStyle);
124
0
125
0
  if (it) {
126
0
    // set the state flags (if any are provided)
127
0
    it->AddStateBits(aStateFlags);
128
0
  }
129
0
130
0
  return it;
131
0
}
132
133
NS_IMPL_FRAMEARENA_HELPERS(nsComboboxControlFrame)
134
135
//-----------------------------------------------------------
136
// Reflow Debugging Macros
137
// These let us "see" how many reflow counts are happening
138
//-----------------------------------------------------------
139
#ifdef DO_REFLOW_COUNTER
140
141
#define MAX_REFLOW_CNT 1024
142
static int32_t gTotalReqs    = 0;;
143
static int32_t gTotalReflows = 0;;
144
static int32_t gReflowControlCntRQ[MAX_REFLOW_CNT];
145
static int32_t gReflowControlCnt[MAX_REFLOW_CNT];
146
static int32_t gReflowInx = -1;
147
148
#define REFLOW_COUNTER() \
149
  if (mReflowId > -1) \
150
    gReflowControlCnt[mReflowId]++;
151
152
#define REFLOW_COUNTER_REQUEST() \
153
  if (mReflowId > -1) \
154
    gReflowControlCntRQ[mReflowId]++;
155
156
#define REFLOW_COUNTER_DUMP(__desc) \
157
  if (mReflowId > -1) {\
158
    gTotalReqs    += gReflowControlCntRQ[mReflowId];\
159
    gTotalReflows += gReflowControlCnt[mReflowId];\
160
    printf("** Id:%5d %s RF: %d RQ: %d   %d/%d  %5.2f\n", \
161
           mReflowId, (__desc), \
162
           gReflowControlCnt[mReflowId], \
163
           gReflowControlCntRQ[mReflowId],\
164
           gTotalReflows, gTotalReqs, float(gTotalReflows)/float(gTotalReqs)*100.0f);\
165
  }
166
167
#define REFLOW_COUNTER_INIT() \
168
  if (gReflowInx < MAX_REFLOW_CNT) { \
169
    gReflowInx++; \
170
    mReflowId = gReflowInx; \
171
    gReflowControlCnt[mReflowId] = 0; \
172
    gReflowControlCntRQ[mReflowId] = 0; \
173
  } else { \
174
    mReflowId = -1; \
175
  }
176
177
// reflow messages
178
#define REFLOW_DEBUG_MSG(_msg1) printf((_msg1))
179
#define REFLOW_DEBUG_MSG2(_msg1, _msg2) printf((_msg1), (_msg2))
180
#define REFLOW_DEBUG_MSG3(_msg1, _msg2, _msg3) printf((_msg1), (_msg2), (_msg3))
181
#define REFLOW_DEBUG_MSG4(_msg1, _msg2, _msg3, _msg4) printf((_msg1), (_msg2), (_msg3), (_msg4))
182
183
#else //-------------
184
185
#define REFLOW_COUNTER_REQUEST()
186
#define REFLOW_COUNTER()
187
#define REFLOW_COUNTER_DUMP(__desc)
188
#define REFLOW_COUNTER_INIT()
189
190
#define REFLOW_DEBUG_MSG(_msg)
191
#define REFLOW_DEBUG_MSG2(_msg1, _msg2)
192
#define REFLOW_DEBUG_MSG3(_msg1, _msg2, _msg3)
193
#define REFLOW_DEBUG_MSG4(_msg1, _msg2, _msg3, _msg4)
194
195
196
#endif
197
198
//------------------------------------------
199
// This is for being VERY noisy
200
//------------------------------------------
201
#ifdef DO_VERY_NOISY
202
#define REFLOW_NOISY_MSG(_msg1) printf((_msg1))
203
#define REFLOW_NOISY_MSG2(_msg1, _msg2) printf((_msg1), (_msg2))
204
#define REFLOW_NOISY_MSG3(_msg1, _msg2, _msg3) printf((_msg1), (_msg2), (_msg3))
205
#define REFLOW_NOISY_MSG4(_msg1, _msg2, _msg3, _msg4) printf((_msg1), (_msg2), (_msg3), (_msg4))
206
#else
207
#define REFLOW_NOISY_MSG(_msg)
208
#define REFLOW_NOISY_MSG2(_msg1, _msg2)
209
#define REFLOW_NOISY_MSG3(_msg1, _msg2, _msg3)
210
#define REFLOW_NOISY_MSG4(_msg1, _msg2, _msg3, _msg4)
211
#endif
212
213
//------------------------------------------
214
// Displays value in pixels or twips
215
//------------------------------------------
216
#ifdef DO_PIXELS
217
#define PX(__v) __v / 15
218
#else
219
#define PX(__v) __v
220
#endif
221
222
//------------------------------------------------------
223
//-- Done with macros
224
//------------------------------------------------------
225
226
nsComboboxControlFrame::nsComboboxControlFrame(ComputedStyle* aStyle)
227
  : nsBlockFrame(aStyle, kClassID)
228
  , mDisplayFrame(nullptr)
229
  , mButtonFrame(nullptr)
230
  , mDropdownFrame(nullptr)
231
  , mListControlFrame(nullptr)
232
  , mDisplayISize(0)
233
  , mRecentSelectedIndex(NS_SKIP_NOTIFY_INDEX)
234
  , mDisplayedIndex(-1)
235
  , mLastDropDownBeforeScreenBCoord(nscoord_MIN)
236
  , mLastDropDownAfterScreenBCoord(nscoord_MIN)
237
  , mDroppedDown(false)
238
  , mInRedisplayText(false)
239
  , mDelayedShowDropDown(false)
240
  , mIsOpenInParentProcess(false)
241
0
{
242
0
  REFLOW_COUNTER_INIT()
243
0
}
244
245
//--------------------------------------------------------------
246
nsComboboxControlFrame::~nsComboboxControlFrame()
247
0
{
248
0
  REFLOW_COUNTER_DUMP("nsCCF");
249
0
}
250
251
//--------------------------------------------------------------
252
253
0
NS_QUERYFRAME_HEAD(nsComboboxControlFrame)
254
0
  NS_QUERYFRAME_ENTRY(nsComboboxControlFrame)
255
0
  NS_QUERYFRAME_ENTRY(nsIComboboxControlFrame)
256
0
  NS_QUERYFRAME_ENTRY(nsIFormControlFrame)
257
0
  NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
258
0
  NS_QUERYFRAME_ENTRY(nsISelectControlFrame)
259
0
  NS_QUERYFRAME_ENTRY(nsIStatefulFrame)
260
0
NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame)
261
262
#ifdef ACCESSIBILITY
263
a11y::AccType
264
nsComboboxControlFrame::AccessibleType()
265
0
{
266
0
  return a11y::eHTMLComboboxType;
267
0
}
268
#endif
269
270
void
271
nsComboboxControlFrame::SetFocus(bool aOn, bool aRepaint)
272
0
{
273
0
  AutoWeakFrame weakFrame(this);
274
0
  if (aOn) {
275
0
    nsListControlFrame::ComboboxFocusSet();
276
0
    sFocused = this;
277
0
    if (mDelayedShowDropDown) {
278
0
      ShowDropDown(true); // might destroy us
279
0
      if (!weakFrame.IsAlive()) {
280
0
        return;
281
0
      }
282
0
    }
283
0
  } else {
284
0
    sFocused = nullptr;
285
0
    mDelayedShowDropDown = false;
286
0
    if (mDroppedDown) {
287
0
      mListControlFrame->ComboboxFinish(mDisplayedIndex); // might destroy us
288
0
      if (!weakFrame.IsAlive()) {
289
0
        return;
290
0
      }
291
0
    }
292
0
    // May delete |this|.
293
0
    mListControlFrame->FireOnInputAndOnChange();
294
0
  }
295
0
296
0
  if (!weakFrame.IsAlive()) {
297
0
    return;
298
0
  }
299
0
300
0
  // This is needed on a temporary basis. It causes the focus
301
0
  // rect to be drawn. This is much faster than ReResolvingStyle
302
0
  // Bug 32920
303
0
  InvalidateFrame();
304
0
}
305
306
void
307
nsComboboxControlFrame::ShowPopup(bool aShowPopup)
308
0
{
309
0
  // TODO(kuoe0) Remove this function when content-select is enabled.
310
0
311
0
  nsView* view = mDropdownFrame->GetView();
312
0
  nsViewManager* viewManager = view->GetViewManager();
313
0
314
0
  if (aShowPopup) {
315
0
    nsRect rect = mDropdownFrame->GetRect();
316
0
    rect.x = rect.y = 0;
317
0
    viewManager->ResizeView(view, rect);
318
0
    viewManager->SetViewVisibility(view, nsViewVisibility_kShow);
319
0
  } else {
320
0
    viewManager->SetViewVisibility(view, nsViewVisibility_kHide);
321
0
    nsRect emptyRect(0, 0, 0, 0);
322
0
    viewManager->ResizeView(view, emptyRect);
323
0
  }
324
0
325
0
  // fire a popup dom event if it is safe to do so
326
0
  nsCOMPtr<nsIPresShell> shell = PresContext()->GetPresShell();
327
0
  if (shell && nsContentUtils::IsSafeToRunScript()) {
328
0
    nsEventStatus status = nsEventStatus_eIgnore;
329
0
    WidgetMouseEvent event(true, aShowPopup ? eXULPopupShowing : eXULPopupHiding,
330
0
                           nullptr, WidgetMouseEvent::eReal);
331
0
332
0
    shell->HandleDOMEventWithTarget(mContent, &event, &status);
333
0
  }
334
0
}
335
336
bool
337
nsComboboxControlFrame::ShowList(bool aShowList)
338
0
{
339
0
340
0
  // TODO(kuoe0) Remove this function when content-select is enabled.
341
0
  //
342
0
  // This function is used to handle the widget/view stuff, so we just return
343
0
  // when content-select is enabled. And the following callee, ShowPopup(), will
344
0
  // also be ignored, it is only used to show and hide the widget.
345
0
  if (nsLayoutUtils::IsContentSelectEnabled()) {
346
0
    return true;
347
0
  }
348
0
349
0
  nsView* view = mDropdownFrame->GetView();
350
0
  if (aShowList) {
351
0
    NS_ASSERTION(!view->HasWidget(),
352
0
                 "We shouldn't have a widget before we need to display the popup");
353
0
354
0
    // Create the widget for the drop-down list
355
0
    view->GetViewManager()->SetViewFloating(view, true);
356
0
357
0
    nsWidgetInitData widgetData;
358
0
    widgetData.mWindowType  = eWindowType_popup;
359
0
    widgetData.mBorderStyle = eBorderStyle_default;
360
0
    view->CreateWidgetForPopup(&widgetData);
361
0
  } else {
362
0
    nsIWidget* widget = view->GetWidget();
363
0
    if (widget) {
364
0
      // We must do this before ShowPopup in case it destroys us (bug 813442).
365
0
      widget->CaptureRollupEvents(this, false);
366
0
    }
367
0
  }
368
0
369
0
  AutoWeakFrame weakFrame(this);
370
0
  ShowPopup(aShowList);  // might destroy us
371
0
  if (!weakFrame.IsAlive()) {
372
0
    return false;
373
0
  }
374
0
375
0
  mDroppedDown = aShowList;
376
0
  nsIWidget* widget = view->GetWidget();
377
0
  if (mDroppedDown) {
378
0
    // The listcontrol frame will call back to the nsComboboxControlFrame's
379
0
    // ListWasSelected which will stop the capture.
380
0
    mListControlFrame->AboutToDropDown();
381
0
    mListControlFrame->CaptureMouseEvents(true);
382
0
    if (widget) {
383
0
      widget->CaptureRollupEvents(this, true);
384
0
    }
385
0
  } else {
386
0
    if (widget) {
387
0
      view->DestroyWidget();
388
0
    }
389
0
  }
390
0
391
0
  return weakFrame.IsAlive();
392
0
}
393
394
class nsResizeDropdownAtFinalPosition final
395
  : public nsIReflowCallback, public Runnable
396
{
397
public:
398
  explicit nsResizeDropdownAtFinalPosition(nsComboboxControlFrame* aFrame)
399
    : mozilla::Runnable("nsResizeDropdownAtFinalPosition")
400
    , mFrame(aFrame)
401
0
  {
402
0
  }
403
404
protected:
405
  ~nsResizeDropdownAtFinalPosition()
406
0
  {
407
0
  }
408
409
public:
410
  virtual bool ReflowFinished() override
411
0
  {
412
0
    Run();
413
0
    NS_RELEASE_THIS();
414
0
    return false;
415
0
  }
416
417
  virtual void ReflowCallbackCanceled() override
418
0
  {
419
0
    NS_RELEASE_THIS();
420
0
  }
421
422
  NS_IMETHOD Run() override
423
0
  {
424
0
    if (mFrame.IsAlive()) {
425
0
      static_cast<nsComboboxControlFrame*>(mFrame.GetFrame())->
426
0
        AbsolutelyPositionDropDown();
427
0
    }
428
0
    return NS_OK;
429
0
  }
430
431
  WeakFrame mFrame;
432
};
433
434
void
435
nsComboboxControlFrame::ReflowDropdown(nsPresContext*  aPresContext,
436
                                       const ReflowInput& aReflowInput)
437
0
{
438
0
  // All we want out of it later on, really, is the block size of a row, so we
439
0
  // don't even need to cache mDropdownFrame's ascent or anything.  If we don't
440
0
  // need to reflow it, just bail out here.
441
0
  if (!aReflowInput.ShouldReflowAllKids() &&
442
0
      !NS_SUBTREE_DIRTY(mDropdownFrame)) {
443
0
    return;
444
0
  }
445
0
446
0
  // XXXbz this will, for small-block-size dropdowns, have extra space
447
0
  // on the appropriate edge for the scrollbar we don't show... but
448
0
  // that's the best we can do here for now.
449
0
  WritingMode wm = mDropdownFrame->GetWritingMode();
450
0
  LogicalSize availSize = aReflowInput.AvailableSize(wm);
451
0
  availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
452
0
  ReflowInput kidReflowInput(aPresContext, aReflowInput, mDropdownFrame,
453
0
                                   availSize);
454
0
455
0
  // If the dropdown's intrinsic inline size is narrower than our
456
0
  // specified inline size, then expand it out.  We want our border-box
457
0
  // inline size to end up the same as the dropdown's so account for
458
0
  // both sets of mComputedBorderPadding.
459
0
  nscoord forcedISize = aReflowInput.ComputedISize() +
460
0
    aReflowInput.ComputedLogicalBorderPadding().IStartEnd(wm) -
461
0
    kidReflowInput.ComputedLogicalBorderPadding().IStartEnd(wm);
462
0
  kidReflowInput.SetComputedISize(std::max(kidReflowInput.ComputedISize(),
463
0
                                         forcedISize));
464
0
465
0
  // ensure we start off hidden
466
0
  if (!nsLayoutUtils::IsContentSelectEnabled() &&
467
0
      !mDroppedDown && GetStateBits() & NS_FRAME_FIRST_REFLOW) {
468
0
    nsView* view = mDropdownFrame->GetView();
469
0
    nsViewManager* viewManager = view->GetViewManager();
470
0
    viewManager->SetViewVisibility(view, nsViewVisibility_kHide);
471
0
    nsRect emptyRect(0, 0, 0, 0);
472
0
    viewManager->ResizeView(view, emptyRect);
473
0
  }
474
0
475
0
  // Allow the child to move/size/change-visibility its view if it's currently
476
0
  // dropped down
477
0
  int32_t flags = mDroppedDown ? 0
478
0
                               : NS_FRAME_NO_MOVE_FRAME |
479
0
                                 NS_FRAME_NO_VISIBILITY |
480
0
                                 NS_FRAME_NO_SIZE_VIEW;
481
0
482
0
  //XXX Can this be different from the dropdown's writing mode?
483
0
  // That would be odd!
484
0
  // Note that we don't need to pass the true frame position or container size
485
0
  // to ReflowChild or FinishReflowChild here; it will be positioned as needed
486
0
  // by AbsolutelyPositionDropDown().
487
0
  WritingMode outerWM = GetWritingMode();
488
0
  const nsSize dummyContainerSize;
489
0
  ReflowOutput desiredSize(aReflowInput);
490
0
  nsReflowStatus ignoredStatus;
491
0
  ReflowChild(mDropdownFrame, aPresContext, desiredSize,
492
0
              kidReflowInput, outerWM, LogicalPoint(outerWM),
493
0
              dummyContainerSize, flags, ignoredStatus);
494
0
495
0
   // Set the child's width and height to its desired size
496
0
  FinishReflowChild(mDropdownFrame, aPresContext, desiredSize, &kidReflowInput,
497
0
                    outerWM, LogicalPoint(outerWM), dummyContainerSize, flags);
498
0
}
499
500
nsPoint
501
nsComboboxControlFrame::GetCSSTransformTranslation()
502
0
{
503
0
  nsIFrame* frame = this;
504
0
  bool is3DTransform = false;
505
0
  Matrix transform;
506
0
  while (frame) {
507
0
    nsIFrame* parent;
508
0
    Matrix4x4Flagged ctm = frame->GetTransformMatrix(nullptr, &parent);
509
0
    Matrix matrix;
510
0
    if (ctm.Is2D(&matrix)) {
511
0
      transform = transform * matrix;
512
0
    } else {
513
0
      is3DTransform = true;
514
0
      break;
515
0
    }
516
0
    frame = parent;
517
0
  }
518
0
  nsPoint translation;
519
0
  if (!is3DTransform && !transform.HasNonTranslation()) {
520
0
    nsPresContext* pc = PresContext();
521
0
    // To get the translation introduced only by transforms we subtract the
522
0
    // regular non-transform translation.
523
0
    nsRootPresContext* rootPC = pc->GetRootPresContext();
524
0
    if (rootPC) {
525
0
      int32_t apd = pc->AppUnitsPerDevPixel();
526
0
      translation.x = NSFloatPixelsToAppUnits(transform._31, apd);
527
0
      translation.y = NSFloatPixelsToAppUnits(transform._32, apd);
528
0
      translation -= GetOffsetToCrossDoc(rootPC->PresShell()->GetRootFrame());
529
0
    }
530
0
  }
531
0
  return translation;
532
0
}
533
534
class nsAsyncRollup : public Runnable
535
{
536
public:
537
  explicit nsAsyncRollup(nsComboboxControlFrame* aFrame)
538
    : mozilla::Runnable("nsAsyncRollup")
539
    , mFrame(aFrame)
540
0
  {
541
0
  }
542
  NS_IMETHOD Run() override
543
0
  {
544
0
    if (mFrame.IsAlive()) {
545
0
      static_cast<nsComboboxControlFrame*>(mFrame.GetFrame())
546
0
        ->RollupFromList();
547
0
    }
548
0
    return NS_OK;
549
0
  }
550
  WeakFrame mFrame;
551
};
552
553
class nsAsyncResize : public Runnable
554
{
555
public:
556
  explicit nsAsyncResize(nsComboboxControlFrame* aFrame)
557
    : mozilla::Runnable("nsAsyncResize")
558
    , mFrame(aFrame)
559
0
  {
560
0
  }
561
  NS_IMETHOD Run() override
562
0
  {
563
0
    if (mFrame.IsAlive()) {
564
0
      nsComboboxControlFrame* combo =
565
0
        static_cast<nsComboboxControlFrame*>(mFrame.GetFrame());
566
0
      static_cast<nsListControlFrame*>(combo->mDropdownFrame)->
567
0
        SetSuppressScrollbarUpdate(true);
568
0
      nsCOMPtr<nsIPresShell> shell = mFrame->PresShell();
569
0
      shell->FrameNeedsReflow(combo->mDropdownFrame, nsIPresShell::eResize,
570
0
                              NS_FRAME_IS_DIRTY);
571
0
      shell->FlushPendingNotifications(FlushType::Layout);
572
0
      if (mFrame.IsAlive()) {
573
0
        combo = static_cast<nsComboboxControlFrame*>(mFrame.GetFrame());
574
0
        static_cast<nsListControlFrame*>(combo->mDropdownFrame)->
575
0
          SetSuppressScrollbarUpdate(false);
576
0
        if (combo->mDelayedShowDropDown) {
577
0
          combo->ShowDropDown(true);
578
0
        }
579
0
      }
580
0
    }
581
0
    return NS_OK;
582
0
  }
583
  WeakFrame mFrame;
584
};
585
586
void
587
nsComboboxControlFrame::GetAvailableDropdownSpace(WritingMode aWM,
588
                                                  nscoord* aBefore,
589
                                                  nscoord* aAfter,
590
                                                  LogicalPoint* aTranslation)
591
0
{
592
0
  MOZ_ASSERT(!XRE_IsContentProcess());
593
0
  // Note: At first glance, it appears that you could simply get the
594
0
  // absolute bounding box for the dropdown list by first getting its
595
0
  // view, then getting the view's nsIWidget, then asking the nsIWidget
596
0
  // for its AbsoluteBounds.
597
0
  // The problem with this approach, is that the dropdown list's bcoord
598
0
  // location can change based on whether the dropdown is placed after
599
0
  // or before the display frame.  The approach taken here is to get the
600
0
  // absolute position of the display frame and use its location to
601
0
  // determine if the dropdown will go offscreen.
602
0
603
0
  // Normal frame geometry (eg GetOffsetTo, mRect) doesn't include transforms.
604
0
  // In the special case that our transform is only a 2D translation we
605
0
  // introduce this hack so that the dropdown will show up in the right place.
606
0
  // Use null container size when converting a vector from logical to physical.
607
0
  const nsSize nullContainerSize;
608
0
  *aTranslation = LogicalPoint(aWM, GetCSSTransformTranslation(),
609
0
                               nullContainerSize);
610
0
  *aBefore = 0;
611
0
  *aAfter = 0;
612
0
613
0
  nsRect screen = nsCheckboxRadioFrame::GetUsableScreenRect(PresContext());
614
0
  nsSize containerSize = screen.Size();
615
0
  LogicalRect logicalScreen(aWM, screen, containerSize);
616
0
  if (mLastDropDownAfterScreenBCoord == nscoord_MIN) {
617
0
    LogicalRect thisScreenRect(aWM, GetScreenRectInAppUnits(),
618
0
                               containerSize);
619
0
    mLastDropDownAfterScreenBCoord = thisScreenRect.BEnd(aWM) +
620
0
                                     aTranslation->B(aWM);
621
0
    mLastDropDownBeforeScreenBCoord = thisScreenRect.BStart(aWM) +
622
0
                                      aTranslation->B(aWM);
623
0
  }
624
0
625
0
  nscoord minBCoord;
626
0
  nsPresContext* pc = PresContext()->GetToplevelContentDocumentPresContext();
627
0
  nsIFrame* root = pc ? pc->PresShell()->GetRootFrame() : nullptr;
628
0
  if (root) {
629
0
    minBCoord = LogicalRect(aWM,
630
0
                            root->GetScreenRectInAppUnits(),
631
0
                            containerSize).BStart(aWM);
632
0
    if (mLastDropDownAfterScreenBCoord < minBCoord) {
633
0
      // Don't allow the drop-down to be placed before the content area.
634
0
      return;
635
0
    }
636
0
  } else {
637
0
    minBCoord = logicalScreen.BStart(aWM);
638
0
  }
639
0
640
0
  nscoord after = logicalScreen.BEnd(aWM) - mLastDropDownAfterScreenBCoord;
641
0
  nscoord before = mLastDropDownBeforeScreenBCoord - minBCoord;
642
0
643
0
  // If the difference between the space before and after is less
644
0
  // than a row-block-size, then we favor the space after.
645
0
  if (before >= after) {
646
0
    nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame);
647
0
    nscoord rowBSize = lcf->GetBSizeOfARow();
648
0
    if (before < after + rowBSize) {
649
0
      before -= rowBSize;
650
0
    }
651
0
  }
652
0
653
0
  *aAfter = after;
654
0
  *aBefore = before;
655
0
}
656
657
nsComboboxControlFrame::DropDownPositionState
658
nsComboboxControlFrame::AbsolutelyPositionDropDown()
659
0
{
660
0
  if (XRE_IsContentProcess()) {
661
0
    return eDropDownPositionSuppressed;
662
0
  }
663
0
664
0
  WritingMode wm = GetWritingMode();
665
0
  LogicalPoint translation(wm);
666
0
  nscoord before, after;
667
0
  mLastDropDownAfterScreenBCoord = nscoord_MIN;
668
0
  GetAvailableDropdownSpace(wm, &before, &after, &translation);
669
0
  if (before <= 0 && after <= 0) {
670
0
    if (!nsLayoutUtils::IsContentSelectEnabled() && IsDroppedDown()) {
671
0
      // Hide the view immediately to minimize flicker.
672
0
      nsView* view = mDropdownFrame->GetView();
673
0
      view->GetViewManager()->SetViewVisibility(view, nsViewVisibility_kHide);
674
0
      NS_DispatchToCurrentThread(new nsAsyncRollup(this));
675
0
    }
676
0
    return eDropDownPositionSuppressed;
677
0
  }
678
0
679
0
  LogicalSize dropdownSize = mDropdownFrame->GetLogicalSize(wm);
680
0
  nscoord bSize = std::max(before, after);
681
0
  nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame);
682
0
  if (bSize < dropdownSize.BSize(wm)) {
683
0
    if (lcf->GetNumDisplayRows() > 1) {
684
0
      // The drop-down doesn't fit and currently shows more than 1 row -
685
0
      // schedule a resize to show fewer rows.
686
0
      NS_DispatchToCurrentThread(new nsAsyncResize(this));
687
0
      return eDropDownPositionPendingResize;
688
0
    }
689
0
  } else if (bSize > (dropdownSize.BSize(wm) + lcf->GetBSizeOfARow() * 1.5) &&
690
0
             lcf->GetDropdownCanGrow()) {
691
0
    // The drop-down fits but there is room for at least 1.5 more rows -
692
0
    // schedule a resize to show more rows if it has more rows to show.
693
0
    // (1.5 rows for good measure to avoid any rounding issues that would
694
0
    // lead to a loop of reflow requests)
695
0
    NS_DispatchToCurrentThread(new nsAsyncResize(this));
696
0
    return eDropDownPositionPendingResize;
697
0
  }
698
0
699
0
  // Position the drop-down after if there is room, otherwise place it before
700
0
  // if there is room.  If there is no room for it on either side then place
701
0
  // it after (to avoid overlapping UI like the URL bar).
702
0
  bool b = dropdownSize.BSize(wm)<= after || dropdownSize.BSize(wm) > before;
703
0
  LogicalPoint dropdownPosition(wm, 0, b ? BSize(wm) : -dropdownSize.BSize(wm));
704
0
705
0
  // Don't position the view unless the position changed since it might cause
706
0
  // a call to NotifyGeometryChange() and an infinite loop here.
707
0
  nsSize containerSize = GetSize();
708
0
  const LogicalPoint currentPos =
709
0
    mDropdownFrame->GetLogicalPosition(containerSize);
710
0
  const LogicalPoint newPos = dropdownPosition + translation;
711
0
  if (currentPos != newPos) {
712
0
    mDropdownFrame->SetPosition(wm, newPos, containerSize);
713
0
    nsContainerFrame::PositionFrameView(mDropdownFrame);
714
0
  }
715
0
  return eDropDownPositionFinal;
716
0
}
717
718
void
719
nsComboboxControlFrame::NotifyGeometryChange()
720
0
{
721
0
  if (XRE_IsContentProcess()) {
722
0
    return;
723
0
  }
724
0
725
0
  // We don't need to resize if we're not dropped down since ShowDropDown
726
0
  // does that, or if we're dirty then the reflow callback does it,
727
0
  // or if we have a delayed ShowDropDown pending.
728
0
  if (IsDroppedDown() &&
729
0
      !(GetStateBits() & NS_FRAME_IS_DIRTY) &&
730
0
      !mDelayedShowDropDown) {
731
0
    // Async because we're likely in a middle of a scroll here so
732
0
    // frame/view positions are in flux.
733
0
    RefPtr<nsResizeDropdownAtFinalPosition> resize =
734
0
      new nsResizeDropdownAtFinalPosition(this);
735
0
    NS_DispatchToCurrentThread(resize);
736
0
  }
737
0
}
738
739
//----------------------------------------------------------
740
//
741
//----------------------------------------------------------
742
#ifdef DO_REFLOW_DEBUG
743
static int myCounter = 0;
744
745
static void printSize(char * aDesc, nscoord aSize)
746
{
747
  printf(" %s: ", aDesc);
748
  if (aSize == NS_UNCONSTRAINEDSIZE) {
749
    printf("UC");
750
  } else {
751
    printf("%d", PX(aSize));
752
  }
753
}
754
#endif
755
756
//-------------------------------------------------------------------
757
//-- Main Reflow for the Combobox
758
//-------------------------------------------------------------------
759
760
bool
761
nsComboboxControlFrame::HasDropDownButton() const
762
0
{
763
0
  const nsStyleDisplay* disp = StyleDisplay();
764
0
  return disp->mAppearance == StyleAppearance::Menulist &&
765
0
    (!IsThemed(disp) ||
766
0
     PresContext()->GetTheme()->ThemeNeedsComboboxDropmarker());
767
0
}
768
769
nscoord
770
nsComboboxControlFrame::GetIntrinsicISize(gfxContext* aRenderingContext,
771
                                          nsLayoutUtils::IntrinsicISizeType aType)
772
0
{
773
0
  // get the scrollbar width, we'll use this later
774
0
  nscoord scrollbarWidth = 0;
775
0
  nsPresContext* presContext = PresContext();
776
0
  if (mListControlFrame) {
777
0
    nsIScrollableFrame* scrollable = do_QueryFrame(mListControlFrame);
778
0
    NS_ASSERTION(scrollable, "List must be a scrollable frame");
779
0
    scrollbarWidth = scrollable->GetNondisappearingScrollbarWidth(
780
0
      presContext, aRenderingContext, GetWritingMode());
781
0
  }
782
0
783
0
  nscoord displayISize = 0;
784
0
  if (MOZ_LIKELY(mDisplayFrame)) {
785
0
    displayISize = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
786
0
                                                        mDisplayFrame,
787
0
                                                        aType);
788
0
  }
789
0
790
0
  if (mDropdownFrame) {
791
0
    nscoord dropdownContentISize;
792
0
    bool isUsingOverlayScrollbars =
793
0
      LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0;
794
0
    if (aType == nsLayoutUtils::MIN_ISIZE) {
795
0
      dropdownContentISize = mDropdownFrame->GetMinISize(aRenderingContext);
796
0
      if (isUsingOverlayScrollbars) {
797
0
        dropdownContentISize += scrollbarWidth;
798
0
      }
799
0
    } else {
800
0
      NS_ASSERTION(aType == nsLayoutUtils::PREF_ISIZE, "Unexpected type");
801
0
      dropdownContentISize = mDropdownFrame->GetPrefISize(aRenderingContext);
802
0
      if (isUsingOverlayScrollbars) {
803
0
        dropdownContentISize += scrollbarWidth;
804
0
      }
805
0
    }
806
0
    dropdownContentISize = NSCoordSaturatingSubtract(dropdownContentISize,
807
0
                                                     scrollbarWidth,
808
0
                                                     nscoord_MAX);
809
0
810
0
    displayISize = std::max(dropdownContentISize, displayISize);
811
0
  }
812
0
813
0
  // add room for the dropmarker button if there is one
814
0
  if (HasDropDownButton()) {
815
0
    displayISize += scrollbarWidth;
816
0
  }
817
0
818
0
  return displayISize;
819
0
820
0
}
821
822
nscoord
823
nsComboboxControlFrame::GetMinISize(gfxContext *aRenderingContext)
824
0
{
825
0
  nscoord minISize;
826
0
  DISPLAY_MIN_INLINE_SIZE(this, minISize);
827
0
  minISize = GetIntrinsicISize(aRenderingContext, nsLayoutUtils::MIN_ISIZE);
828
0
  return minISize;
829
0
}
830
831
nscoord
832
nsComboboxControlFrame::GetPrefISize(gfxContext *aRenderingContext)
833
0
{
834
0
  nscoord prefISize;
835
0
  DISPLAY_PREF_INLINE_SIZE(this, prefISize);
836
0
  prefISize = GetIntrinsicISize(aRenderingContext, nsLayoutUtils::PREF_ISIZE);
837
0
  return prefISize;
838
0
}
839
840
void
841
nsComboboxControlFrame::Reflow(nsPresContext*          aPresContext,
842
                               ReflowOutput&     aDesiredSize,
843
                               const ReflowInput& aReflowInput,
844
                               nsReflowStatus&          aStatus)
845
0
{
846
0
  MarkInReflow();
847
0
  MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
848
0
  // Constraints we try to satisfy:
849
0
850
0
  // 1) Default inline size of button is the vertical scrollbar size
851
0
  // 2) If the inline size of button is bigger than our inline size, set
852
0
  //    inline size of button to 0.
853
0
  // 3) Default block size of button is block size of display area
854
0
  // 4) Inline size of display area is whatever is left over from our
855
0
  //    inline size after allocating inline size for the button.
856
0
  // 5) Block Size of display area is GetBSizeOfARow() on the
857
0
  //    mListControlFrame.
858
0
859
0
  if (!mDisplayFrame || !mButtonFrame || !mDropdownFrame) {
860
0
    NS_ERROR("Why did the frame constructor allow this to happen?  Fix it!!");
861
0
    return;
862
0
  }
863
0
864
0
  // Make sure the displayed text is the same as the selected option, bug 297389.
865
0
  if (!mDroppedDown) {
866
0
    mDisplayedIndex = mListControlFrame->GetSelectedIndex();
867
0
  }
868
0
  // In dropped down mode the "selected index" is the hovered menu item,
869
0
  // we want the last selected item which is |mDisplayedIndex| in this case.
870
0
  RedisplayText();
871
0
872
0
  // First reflow our dropdown so that we know how tall we should be.
873
0
  ReflowDropdown(aPresContext, aReflowInput);
874
0
  RefPtr<nsResizeDropdownAtFinalPosition> resize =
875
0
    new nsResizeDropdownAtFinalPosition(this);
876
0
  if (NS_SUCCEEDED(aPresContext->PresShell()->PostReflowCallback(resize))) {
877
0
    // The reflow callback queue doesn't AddRef so we keep it alive until
878
0
    // it's released in its ReflowFinished / ReflowCallbackCanceled.
879
0
    Unused << resize.forget();
880
0
  }
881
0
882
0
  // Get the width of the vertical scrollbar.  That will be the inline
883
0
  // size of the dropdown button.
884
0
  WritingMode wm = aReflowInput.GetWritingMode();
885
0
  nscoord buttonISize;
886
0
  if (!HasDropDownButton()) {
887
0
    buttonISize = 0;
888
0
  }
889
0
  else {
890
0
    nsIScrollableFrame* scrollable = do_QueryFrame(mListControlFrame);
891
0
    NS_ASSERTION(scrollable, "List must be a scrollable frame");
892
0
    buttonISize = scrollable->GetNondisappearingScrollbarWidth(
893
0
      PresContext(), aReflowInput.mRenderingContext, wm);
894
0
    if (buttonISize > aReflowInput.ComputedISize()) {
895
0
      buttonISize = 0;
896
0
    }
897
0
  }
898
0
899
0
  mDisplayISize = aReflowInput.ComputedISize() - buttonISize;
900
0
901
0
  nsBlockFrame::Reflow(aPresContext, aDesiredSize, aReflowInput, aStatus);
902
0
903
0
  // The button should occupy the same space as a scrollbar
904
0
  nsSize containerSize = aDesiredSize.PhysicalSize();
905
0
  LogicalRect buttonRect = mButtonFrame->GetLogicalRect(containerSize);
906
0
907
0
  buttonRect.IStart(wm) =
908
0
    aReflowInput.ComputedLogicalBorderPadding().IStartEnd(wm) +
909
0
    mDisplayISize -
910
0
    (aReflowInput.ComputedLogicalBorderPadding().IEnd(wm) -
911
0
     aReflowInput.ComputedLogicalPadding().IEnd(wm));
912
0
  buttonRect.ISize(wm) = buttonISize;
913
0
914
0
  buttonRect.BStart(wm) = this->GetLogicalUsedBorder(wm).BStart(wm);
915
0
  buttonRect.BSize(wm) = mDisplayFrame->BSize(wm) +
916
0
                         this->GetLogicalUsedPadding(wm).BStartEnd(wm);
917
0
918
0
  mButtonFrame->SetRect(buttonRect, containerSize);
919
0
920
0
  if (!aStatus.IsInlineBreakBefore() &&
921
0
      !aStatus.IsFullyComplete()) {
922
0
    // This frame didn't fit inside a fragmentation container.  Splitting
923
0
    // a nsComboboxControlFrame makes no sense, so we override the status here.
924
0
    aStatus.Reset();
925
0
  }
926
0
}
927
928
//--------------------------------------------------------------
929
930
#ifdef DEBUG_FRAME_DUMP
931
nsresult
932
nsComboboxControlFrame::GetFrameName(nsAString& aResult) const
933
{
934
  return MakeFrameName(NS_LITERAL_STRING("ComboboxControl"), aResult);
935
}
936
#endif
937
938
939
//----------------------------------------------------------------------
940
// nsIComboboxControlFrame
941
//----------------------------------------------------------------------
942
void
943
nsComboboxControlFrame::ShowDropDown(bool aDoDropDown)
944
0
{
945
0
  if (!nsLayoutUtils::IsContentSelectEnabled()) {
946
0
    // TODO(kuoe0) remove this assertion after content-select is enabled
947
0
    MOZ_ASSERT(!XRE_IsContentProcess());
948
0
  }
949
0
  mDelayedShowDropDown = false;
950
0
  EventStates eventStates = mContent->AsElement()->State();
951
0
  if (aDoDropDown && eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
952
0
    return;
953
0
  }
954
0
955
0
  if (!mDroppedDown && aDoDropDown) {
956
0
    nsFocusManager* fm = nsFocusManager::GetFocusManager();
957
0
    if (!fm || fm->GetFocusedElement() == GetContent()) {
958
0
      DropDownPositionState state = AbsolutelyPositionDropDown();
959
0
      if (state == eDropDownPositionFinal) {
960
0
        ShowList(aDoDropDown); // might destroy us
961
0
      } else if (state == eDropDownPositionPendingResize) {
962
0
        // Delay until after the resize reflow, see nsAsyncResize.
963
0
        mDelayedShowDropDown = true;
964
0
      }
965
0
    } else {
966
0
      // Delay until we get focus, see SetFocus().
967
0
      mDelayedShowDropDown = true;
968
0
    }
969
0
  } else if (mDroppedDown && !aDoDropDown) {
970
0
    ShowList(aDoDropDown); // might destroy us
971
0
  }
972
0
}
973
974
void
975
nsComboboxControlFrame::SetDropDown(nsIFrame* aDropDownFrame)
976
0
{
977
0
  mDropdownFrame = aDropDownFrame;
978
0
  mListControlFrame = do_QueryFrame(mDropdownFrame);
979
0
  if (!sFocused && nsContentUtils::IsFocusedContent(GetContent())) {
980
0
    sFocused = this;
981
0
    nsListControlFrame::ComboboxFocusSet();
982
0
  }
983
0
}
984
985
nsIFrame*
986
nsComboboxControlFrame::GetDropDown()
987
0
{
988
0
  return mDropdownFrame;
989
0
}
990
991
///////////////////////////////////////////////////////////////
992
993
994
NS_IMETHODIMP
995
nsComboboxControlFrame::RedisplaySelectedText()
996
0
{
997
0
  nsAutoScriptBlocker scriptBlocker;
998
0
  mDisplayedIndex = mListControlFrame->GetSelectedIndex();
999
0
  return RedisplayText();
1000
0
}
1001
1002
1003
nsresult
1004
nsComboboxControlFrame::RedisplayText()
1005
0
{
1006
0
  nsString previewValue;
1007
0
  nsString previousText(mDisplayedOptionTextOrPreview);
1008
0
1009
0
  auto* selectElement = static_cast<dom::HTMLSelectElement*>(GetContent());
1010
0
  selectElement->GetPreviewValue(previewValue);
1011
0
  // Get the text to display
1012
0
  if (!previewValue.IsEmpty()) {
1013
0
    mDisplayedOptionTextOrPreview = previewValue;
1014
0
  } else if (mDisplayedIndex != -1) {
1015
0
    mListControlFrame->GetOptionText(mDisplayedIndex, mDisplayedOptionTextOrPreview);
1016
0
  } else {
1017
0
    mDisplayedOptionTextOrPreview.Truncate();
1018
0
  }
1019
0
1020
0
  REFLOW_DEBUG_MSG2("RedisplayText \"%s\"\n",
1021
0
                    NS_LossyConvertUTF16toASCII(mDisplayedOptionTextOrPreview).get());
1022
0
1023
0
  // Send reflow command because the new text maybe larger
1024
0
  nsresult rv = NS_OK;
1025
0
  if (mDisplayContent &&
1026
0
      !previousText.Equals(mDisplayedOptionTextOrPreview)) {
1027
0
    // Don't call ActuallyDisplayText(true) directly here since that
1028
0
    // could cause recursive frame construction. See bug 283117 and the comment in
1029
0
    // HandleRedisplayTextEvent() below.
1030
0
1031
0
    // Revoke outstanding events to avoid out-of-order events which could mean
1032
0
    // displaying the wrong text.
1033
0
    mRedisplayTextEvent.Revoke();
1034
0
1035
0
    NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
1036
0
                 "If we happen to run our redisplay event now, we might kill "
1037
0
                 "ourselves!");
1038
0
1039
0
    mRedisplayTextEvent = new RedisplayTextEvent(this);
1040
0
    nsContentUtils::AddScriptRunner(mRedisplayTextEvent.get());
1041
0
  }
1042
0
  return rv;
1043
0
}
1044
1045
void
1046
nsComboboxControlFrame::HandleRedisplayTextEvent()
1047
0
{
1048
0
  // First, make sure that the content model is up to date and we've
1049
0
  // constructed the frames for all our content in the right places.
1050
0
  // Otherwise they'll end up under the wrong insertion frame when we
1051
0
  // ActuallyDisplayText, since that flushes out the content sink by
1052
0
  // calling SetText on a DOM node with aNotify set to true.  See bug
1053
0
  // 289730.
1054
0
  AutoWeakFrame weakThis(this);
1055
0
  PresContext()->Document()->
1056
0
    FlushPendingNotifications(FlushType::ContentAndNotify);
1057
0
  if (!weakThis.IsAlive())
1058
0
    return;
1059
0
1060
0
  // Redirect frame insertions during this method (see GetContentInsertionFrame())
1061
0
  // so that any reframing that the frame constructor forces upon us is inserted
1062
0
  // into the correct parent (mDisplayFrame). See bug 282607.
1063
0
  MOZ_ASSERT(!mInRedisplayText, "Nested RedisplayText");
1064
0
  mInRedisplayText = true;
1065
0
  mRedisplayTextEvent.Forget();
1066
0
1067
0
  ActuallyDisplayText(true);
1068
0
  // XXXbz This should perhaps be eResize.  Check.
1069
0
  PresShell()->FrameNeedsReflow(mDisplayFrame,
1070
0
                                               nsIPresShell::eStyleChange,
1071
0
                                               NS_FRAME_IS_DIRTY);
1072
0
1073
0
  mInRedisplayText = false;
1074
0
}
1075
1076
void
1077
nsComboboxControlFrame::ActuallyDisplayText(bool aNotify)
1078
0
{
1079
0
  if (mDisplayedOptionTextOrPreview.IsEmpty()) {
1080
0
    // Have to use a non-breaking space for line-block-size calculations
1081
0
    // to be right
1082
0
    static const char16_t space = 0xA0;
1083
0
    mDisplayContent->SetText(&space, 1, aNotify);
1084
0
  } else {
1085
0
    mDisplayContent->SetText(mDisplayedOptionTextOrPreview, aNotify);
1086
0
  }
1087
0
}
1088
1089
int32_t
1090
nsComboboxControlFrame::GetIndexOfDisplayArea()
1091
0
{
1092
0
  return mDisplayedIndex;
1093
0
}
1094
1095
//----------------------------------------------------------------------
1096
// nsISelectControlFrame
1097
//----------------------------------------------------------------------
1098
NS_IMETHODIMP
1099
nsComboboxControlFrame::DoneAddingChildren(bool aIsDone)
1100
0
{
1101
0
  nsISelectControlFrame* listFrame = do_QueryFrame(mDropdownFrame);
1102
0
  if (!listFrame)
1103
0
    return NS_ERROR_FAILURE;
1104
0
1105
0
  return listFrame->DoneAddingChildren(aIsDone);
1106
0
}
1107
1108
NS_IMETHODIMP
1109
nsComboboxControlFrame::AddOption(int32_t aIndex)
1110
0
{
1111
0
  if (aIndex <= mDisplayedIndex) {
1112
0
    ++mDisplayedIndex;
1113
0
  }
1114
0
1115
0
  nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame);
1116
0
  return lcf->AddOption(aIndex);
1117
0
}
1118
1119
1120
NS_IMETHODIMP
1121
nsComboboxControlFrame::RemoveOption(int32_t aIndex)
1122
0
{
1123
0
  AutoWeakFrame weakThis(this);
1124
0
  if (mListControlFrame->GetNumberOfOptions() > 0) {
1125
0
    if (aIndex < mDisplayedIndex) {
1126
0
      --mDisplayedIndex;
1127
0
    } else if (aIndex == mDisplayedIndex) {
1128
0
      mDisplayedIndex = 0; // IE6 compat
1129
0
      RedisplayText();
1130
0
    }
1131
0
  }
1132
0
  else {
1133
0
    // If we removed the last option, we need to blank things out
1134
0
    mDisplayedIndex = -1;
1135
0
    RedisplayText();
1136
0
  }
1137
0
1138
0
  if (!weakThis.IsAlive())
1139
0
    return NS_OK;
1140
0
1141
0
  nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame);
1142
0
  return lcf->RemoveOption(aIndex);
1143
0
}
1144
1145
NS_IMETHODIMP
1146
nsComboboxControlFrame::OnSetSelectedIndex(int32_t aOldIndex, int32_t aNewIndex)
1147
0
{
1148
0
  nsAutoScriptBlocker scriptBlocker;
1149
0
  mDisplayedIndex = aNewIndex;
1150
0
  RedisplayText();
1151
0
  NS_ASSERTION(mDropdownFrame, "No dropdown frame!");
1152
0
1153
0
  nsISelectControlFrame* listFrame = do_QueryFrame(mDropdownFrame);
1154
0
  NS_ASSERTION(listFrame, "No list frame!");
1155
0
1156
0
  return listFrame->OnSetSelectedIndex(aOldIndex, aNewIndex);
1157
0
}
1158
1159
// End nsISelectControlFrame
1160
//----------------------------------------------------------------------
1161
1162
nsresult
1163
nsComboboxControlFrame::HandleEvent(nsPresContext* aPresContext,
1164
                                    WidgetGUIEvent* aEvent,
1165
                                    nsEventStatus* aEventStatus)
1166
0
{
1167
0
  NS_ENSURE_ARG_POINTER(aEventStatus);
1168
0
1169
0
  if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
1170
0
    return NS_OK;
1171
0
  }
1172
0
1173
0
  EventStates eventStates = mContent->AsElement()->State();
1174
0
  if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
1175
0
    return NS_OK;
1176
0
  }
1177
0
1178
#if COMBOBOX_ROLLUP_CONSUME_EVENT == 0
1179
  if (aEvent->mMessage == eMouseDown) {
1180
    if (GetContent() == mozilla::widget::nsAutoRollup::GetLastRollup()) {
1181
      // This event did a Rollup on this control - prevent it from opening
1182
      // the dropdown again!
1183
      *aEventStatus = nsEventStatus_eConsumeNoDefault;
1184
      return NS_OK;
1185
    }
1186
  }
1187
#endif
1188
1189
0
  // If we have style that affects how we are selected, feed event down to
1190
0
  // nsFrame::HandleEvent so that selection takes place when appropriate.
1191
0
  if (IsContentDisabled()) {
1192
0
    return nsBlockFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
1193
0
  }
1194
0
  return NS_OK;
1195
0
}
1196
1197
1198
nsresult
1199
nsComboboxControlFrame::SetFormProperty(nsAtom* aName, const nsAString& aValue)
1200
0
{
1201
0
  nsIFormControlFrame* fcFrame = do_QueryFrame(mDropdownFrame);
1202
0
  if (!fcFrame) {
1203
0
    return NS_NOINTERFACE;
1204
0
  }
1205
0
1206
0
  return fcFrame->SetFormProperty(aName, aValue);
1207
0
}
1208
1209
nsContainerFrame*
1210
0
nsComboboxControlFrame::GetContentInsertionFrame() {
1211
0
  return mInRedisplayText ? mDisplayFrame : mDropdownFrame->GetContentInsertionFrame();
1212
0
}
1213
1214
void
1215
nsComboboxControlFrame::AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult)
1216
0
{
1217
0
  aResult.AppendElement(OwnedAnonBox(mDropdownFrame));
1218
0
  aResult.AppendElement(OwnedAnonBox(mDisplayFrame));
1219
0
}
1220
1221
nsresult
1222
nsComboboxControlFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
1223
0
{
1224
0
  // The frames used to display the combo box and the button used to popup the dropdown list
1225
0
  // are created through anonymous content. The dropdown list is not created through anonymous
1226
0
  // content because its frame is initialized specifically for the drop-down case and it is placed
1227
0
  // a special list referenced through NS_COMBO_FRAME_POPUP_LIST_INDEX to keep separate from the
1228
0
  // layout of the display and button.
1229
0
  //
1230
0
  // Note: The value attribute of the display content is set when an item is selected in the dropdown list.
1231
0
  // If the content specified below does not honor the value attribute than nothing will be displayed.
1232
0
1233
0
  // For now the content that is created corresponds to two input buttons. It would be better to create the
1234
0
  // tag as something other than input, but then there isn't any way to create a button frame since it
1235
0
  // isn't possible to set the display type in CSS2 to create a button frame.
1236
0
1237
0
    // create content used for display
1238
0
  //nsAtom* tag = NS_Atomize("mozcombodisplay");
1239
0
1240
0
  // Add a child text content node for the label
1241
0
1242
0
  nsNodeInfoManager *nimgr = mContent->NodeInfo()->NodeInfoManager();
1243
0
1244
0
  mDisplayContent = new nsTextNode(nimgr);
1245
0
1246
0
  // set the value of the text node
1247
0
  mDisplayedIndex = mListControlFrame->GetSelectedIndex();
1248
0
  if (mDisplayedIndex != -1) {
1249
0
    mListControlFrame->GetOptionText(mDisplayedIndex, mDisplayedOptionTextOrPreview);
1250
0
  }
1251
0
  ActuallyDisplayText(false);
1252
0
1253
0
  if (!aElements.AppendElement(mDisplayContent))
1254
0
    return NS_ERROR_OUT_OF_MEMORY;
1255
0
1256
0
  mButtonContent = mContent->OwnerDoc()->CreateHTMLElement(nsGkAtoms::button);
1257
0
  if (!mButtonContent)
1258
0
    return NS_ERROR_OUT_OF_MEMORY;
1259
0
1260
0
  // make someone to listen to the button. If its pressed by someone like Accessibility
1261
0
  // then open or close the combo box.
1262
0
  mButtonListener = new nsComboButtonListener(this);
1263
0
  mButtonContent->AddEventListener(NS_LITERAL_STRING("click"), mButtonListener,
1264
0
                                   false, false);
1265
0
1266
0
  mButtonContent->SetAttr(kNameSpaceID_None, nsGkAtoms::type,
1267
0
                          NS_LITERAL_STRING("button"), false);
1268
0
  // Set tabindex="-1" so that the button is not tabbable
1269
0
  mButtonContent->SetAttr(kNameSpaceID_None, nsGkAtoms::tabindex,
1270
0
                          NS_LITERAL_STRING("-1"), false);
1271
0
1272
0
  WritingMode wm = GetWritingMode();
1273
0
  if (wm.IsVertical()) {
1274
0
    mButtonContent->SetAttr(kNameSpaceID_None, nsGkAtoms::orientation,
1275
0
                            wm.IsVerticalRL() ? NS_LITERAL_STRING("left")
1276
0
                                              : NS_LITERAL_STRING("right"),
1277
0
                            false);
1278
0
  }
1279
0
1280
0
  if (!aElements.AppendElement(mButtonContent))
1281
0
    return NS_ERROR_OUT_OF_MEMORY;
1282
0
1283
0
  return NS_OK;
1284
0
}
1285
1286
void
1287
nsComboboxControlFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
1288
                                                 uint32_t aFilter)
1289
0
{
1290
0
  if (mDisplayContent) {
1291
0
    aElements.AppendElement(mDisplayContent);
1292
0
  }
1293
0
1294
0
  if (mButtonContent) {
1295
0
    aElements.AppendElement(mButtonContent);
1296
0
  }
1297
0
}
1298
1299
nsIContent*
1300
nsComboboxControlFrame::GetDisplayNode() const
1301
0
{
1302
0
  return mDisplayContent;
1303
0
}
1304
1305
// XXXbz this is a for-now hack.  Now that display:inline-block works,
1306
// need to revisit this.
1307
class nsComboboxDisplayFrame final : public nsBlockFrame
1308
{
1309
public:
1310
  NS_DECL_FRAMEARENA_HELPERS(nsComboboxDisplayFrame)
1311
1312
  nsComboboxDisplayFrame(ComputedStyle* aStyle,
1313
                         nsComboboxControlFrame* aComboBox)
1314
    : nsBlockFrame(aStyle, kClassID)
1315
    , mComboBox(aComboBox)
1316
0
  {}
1317
1318
#ifdef DEBUG_FRAME_DUMP
1319
  nsresult GetFrameName(nsAString& aResult) const override
1320
  {
1321
    return MakeFrameName(NS_LITERAL_STRING("ComboboxDisplay"), aResult);
1322
  }
1323
#endif
1324
1325
  virtual bool IsFrameOfType(uint32_t aFlags) const override
1326
0
  {
1327
0
    return nsBlockFrame::IsFrameOfType(aFlags &
1328
0
      ~(nsIFrame::eReplacedContainsBlock));
1329
0
  }
1330
1331
  virtual void Reflow(nsPresContext*           aPresContext,
1332
                          ReflowOutput&     aDesiredSize,
1333
                          const ReflowInput& aReflowInput,
1334
                          nsReflowStatus&          aStatus) override;
1335
1336
  virtual void BuildDisplayList(nsDisplayListBuilder*   aBuilder,
1337
                                const nsDisplayListSet& aLists) override;
1338
1339
protected:
1340
  nsComboboxControlFrame* mComboBox;
1341
};
1342
1343
NS_IMPL_FRAMEARENA_HELPERS(nsComboboxDisplayFrame)
1344
1345
void
1346
nsComboboxDisplayFrame::Reflow(nsPresContext*           aPresContext,
1347
                               ReflowOutput&     aDesiredSize,
1348
                               const ReflowInput& aReflowInput,
1349
                               nsReflowStatus&          aStatus)
1350
0
{
1351
0
  MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
1352
0
1353
0
  ReflowInput state(aReflowInput);
1354
0
  if (state.ComputedBSize() == NS_INTRINSICSIZE) {
1355
0
    // Note that the only way we can have a computed block size here is
1356
0
    // if the combobox had a specified block size.  If it didn't, size
1357
0
    // based on what our rows look like, for lack of anything better.
1358
0
    state.SetComputedBSize(mComboBox->mListControlFrame->GetBSizeOfARow());
1359
0
  }
1360
0
  WritingMode wm = aReflowInput.GetWritingMode();
1361
0
  nscoord computedISize = mComboBox->mDisplayISize -
1362
0
    state.ComputedLogicalBorderPadding().IStartEnd(wm);
1363
0
  if (computedISize < 0) {
1364
0
    computedISize = 0;
1365
0
  }
1366
0
  state.SetComputedISize(computedISize);
1367
0
  nsBlockFrame::Reflow(aPresContext, aDesiredSize, state, aStatus);
1368
0
  aStatus.Reset(); // this type of frame can't be split
1369
0
}
1370
1371
void
1372
nsComboboxDisplayFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
1373
                                         const nsDisplayListSet& aLists)
1374
0
{
1375
0
  nsDisplayListCollection set(aBuilder);
1376
0
  nsBlockFrame::BuildDisplayList(aBuilder, set);
1377
0
1378
0
  // remove background items if parent frame is themed
1379
0
  if (mComboBox->IsThemed()) {
1380
0
    set.BorderBackground()->DeleteAll(aBuilder);
1381
0
  }
1382
0
1383
0
  set.MoveTo(aLists);
1384
0
}
1385
1386
nsIFrame*
1387
nsComboboxControlFrame::CreateFrameForDisplayNode()
1388
0
{
1389
0
  MOZ_ASSERT(mDisplayContent);
1390
0
1391
0
  // Get PresShell
1392
0
  nsIPresShell *shell = PresShell();
1393
0
  ServoStyleSet* styleSet = shell->StyleSet();
1394
0
1395
0
  // create the ComputedStyle for the anonymous block frame and text frame
1396
0
  RefPtr<ComputedStyle> computedStyle;
1397
0
  computedStyle = styleSet->
1398
0
    ResolveInheritingAnonymousBoxStyle(nsCSSAnonBoxes::mozDisplayComboboxControlFrame(),
1399
0
                                       mComputedStyle);
1400
0
1401
0
  RefPtr<ComputedStyle> textComputedStyle;
1402
0
  textComputedStyle =
1403
0
    styleSet->ResolveStyleForText(mDisplayContent, mComputedStyle);
1404
0
1405
0
  // Start by creating our anonymous block frame
1406
0
  mDisplayFrame = new (shell) nsComboboxDisplayFrame(computedStyle, this);
1407
0
  mDisplayFrame->Init(mContent, this, nullptr);
1408
0
1409
0
  // Create a text frame and put it inside the block frame
1410
0
  nsIFrame* textFrame = NS_NewTextFrame(shell, textComputedStyle);
1411
0
1412
0
  // initialize the text frame
1413
0
  textFrame->Init(mDisplayContent, mDisplayFrame, nullptr);
1414
0
  mDisplayContent->SetPrimaryFrame(textFrame);
1415
0
1416
0
  nsFrameList textList(textFrame, textFrame);
1417
0
  mDisplayFrame->SetInitialChildList(kPrincipalList, textList);
1418
0
  return mDisplayFrame;
1419
0
}
1420
1421
void
1422
nsComboboxControlFrame::DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData)
1423
0
{
1424
0
  if (sFocused == this) {
1425
0
    sFocused = nullptr;
1426
0
  }
1427
0
1428
0
  // Revoke any pending RedisplayTextEvent
1429
0
  mRedisplayTextEvent.Revoke();
1430
0
1431
0
  nsCheckboxRadioFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
1432
0
1433
0
  if (!nsLayoutUtils::IsContentSelectEnabled() && mDroppedDown) {
1434
0
    MOZ_ASSERT(mDropdownFrame, "mDroppedDown without frame");
1435
0
    nsView* view = mDropdownFrame->GetView();
1436
0
    MOZ_ASSERT(view);
1437
0
    nsIWidget* widget = view->GetWidget();
1438
0
    if (widget) {
1439
0
      widget->CaptureRollupEvents(this, false);
1440
0
    }
1441
0
  }
1442
0
1443
0
  // Cleanup frames in popup child list
1444
0
  mPopupFrames.DestroyFramesFrom(aDestructRoot, aPostDestroyData);
1445
0
  aPostDestroyData.AddAnonymousContent(mDisplayContent.forget());
1446
0
  aPostDestroyData.AddAnonymousContent(mButtonContent.forget());
1447
0
  nsBlockFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
1448
0
}
1449
1450
const nsFrameList&
1451
nsComboboxControlFrame::GetChildList(ChildListID aListID) const
1452
0
{
1453
0
  if (kSelectPopupList == aListID) {
1454
0
    return mPopupFrames;
1455
0
  }
1456
0
  return nsBlockFrame::GetChildList(aListID);
1457
0
}
1458
1459
void
1460
nsComboboxControlFrame::GetChildLists(nsTArray<ChildList>* aLists) const
1461
0
{
1462
0
  nsBlockFrame::GetChildLists(aLists);
1463
0
  mPopupFrames.AppendIfNonempty(aLists, kSelectPopupList);
1464
0
}
1465
1466
void
1467
nsComboboxControlFrame::SetInitialChildList(ChildListID     aListID,
1468
                                            nsFrameList&    aChildList)
1469
0
{
1470
0
  if (kSelectPopupList == aListID) {
1471
0
    mPopupFrames.SetFrames(aChildList);
1472
0
  } else {
1473
0
    for (nsFrameList::Enumerator e(aChildList); !e.AtEnd(); e.Next()) {
1474
0
      nsCOMPtr<nsIFormControl> formControl =
1475
0
        do_QueryInterface(e.get()->GetContent());
1476
0
      if (formControl && formControl->ControlType() == NS_FORM_BUTTON_BUTTON) {
1477
0
        mButtonFrame = e.get();
1478
0
        break;
1479
0
      }
1480
0
    }
1481
0
    NS_ASSERTION(mButtonFrame, "missing button frame in initial child list");
1482
0
    nsBlockFrame::SetInitialChildList(aListID, aChildList);
1483
0
  }
1484
0
}
1485
1486
//----------------------------------------------------------------------
1487
  //nsIRollupListener
1488
//----------------------------------------------------------------------
1489
bool
1490
nsComboboxControlFrame::Rollup(uint32_t aCount, bool aFlush,
1491
                               const nsIntPoint* pos, nsIContent** aLastRolledUp)
1492
0
{
1493
0
  if (aLastRolledUp) {
1494
0
    *aLastRolledUp = nullptr;
1495
0
  }
1496
0
1497
0
  if (!mDroppedDown) {
1498
0
    return false;
1499
0
  }
1500
0
1501
0
  bool consume = !!COMBOBOX_ROLLUP_CONSUME_EVENT;
1502
0
  AutoWeakFrame weakFrame(this);
1503
0
  mListControlFrame->AboutToRollup(); // might destroy us
1504
0
  if (!weakFrame.IsAlive()) {
1505
0
    return consume;
1506
0
  }
1507
0
  ShowDropDown(false); // might destroy us
1508
0
  if (weakFrame.IsAlive()) {
1509
0
    mListControlFrame->CaptureMouseEvents(false);
1510
0
  }
1511
0
1512
0
  if (!nsLayoutUtils::IsContentSelectEnabled() &&
1513
0
      aFlush && weakFrame.IsAlive()) {
1514
0
    // The popup's visibility doesn't update until the minimize animation has
1515
0
    // finished, so call UpdateWidgetGeometry to update it right away.
1516
0
    nsViewManager* viewManager = mDropdownFrame->GetView()->GetViewManager();
1517
0
    viewManager->UpdateWidgetGeometry(); // might destroy us
1518
0
  }
1519
0
1520
0
  if (!weakFrame.IsAlive()) {
1521
0
    return consume;
1522
0
  }
1523
0
1524
0
  if (aLastRolledUp) {
1525
0
    *aLastRolledUp = GetContent();
1526
0
  }
1527
0
  return consume;
1528
0
}
1529
1530
nsIWidget*
1531
nsComboboxControlFrame::GetRollupWidget()
1532
0
{
1533
0
  if (nsLayoutUtils::IsContentSelectEnabled()) {
1534
0
    return nullptr;
1535
0
  }
1536
0
1537
0
  nsView* view = mDropdownFrame->GetView();
1538
0
  MOZ_ASSERT(view);
1539
0
  return view->GetWidget();
1540
0
}
1541
1542
void
1543
nsComboboxControlFrame::RollupFromList()
1544
0
{
1545
0
  if (ShowList(false))
1546
0
    mListControlFrame->CaptureMouseEvents(false);
1547
0
}
1548
1549
int32_t
1550
nsComboboxControlFrame::UpdateRecentIndex(int32_t aIndex)
1551
0
{
1552
0
  int32_t index = mRecentSelectedIndex;
1553
0
  if (mRecentSelectedIndex == NS_SKIP_NOTIFY_INDEX || aIndex == NS_SKIP_NOTIFY_INDEX)
1554
0
    mRecentSelectedIndex = aIndex;
1555
0
  return index;
1556
0
}
1557
1558
class nsDisplayComboboxFocus : public nsDisplayItem
1559
{
1560
public:
1561
  nsDisplayComboboxFocus(nsDisplayListBuilder* aBuilder,
1562
                         nsComboboxControlFrame* aFrame)
1563
0
    : nsDisplayItem(aBuilder, aFrame) {
1564
0
    MOZ_COUNT_CTOR(nsDisplayComboboxFocus);
1565
0
  }
1566
#ifdef NS_BUILD_REFCNT_LOGGING
1567
  virtual ~nsDisplayComboboxFocus() {
1568
    MOZ_COUNT_DTOR(nsDisplayComboboxFocus);
1569
  }
1570
#endif
1571
1572
  virtual void Paint(nsDisplayListBuilder* aBuilder,
1573
                     gfxContext* aCtx) override;
1574
  NS_DISPLAY_DECL_NAME("ComboboxFocus", TYPE_COMBOBOX_FOCUS)
1575
};
1576
1577
void nsDisplayComboboxFocus::Paint(nsDisplayListBuilder* aBuilder,
1578
                                   gfxContext* aCtx)
1579
0
{
1580
0
  static_cast<nsComboboxControlFrame*>(mFrame)
1581
0
    ->PaintFocus(*aCtx->GetDrawTarget(), ToReferenceFrame());
1582
0
}
1583
1584
void
1585
nsComboboxControlFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
1586
                                         const nsDisplayListSet& aLists)
1587
0
{
1588
0
  if (aBuilder->IsForEventDelivery()) {
1589
0
    // Don't allow children to receive events.
1590
0
    // REVIEW: following old GetFrameForPoint
1591
0
    DisplayBorderBackgroundOutline(aBuilder, aLists);
1592
0
  } else {
1593
0
    // REVIEW: Our in-flow child frames are inline-level so they will paint in our
1594
0
    // content list, so we don't need to mess with layers.
1595
0
    nsBlockFrame::BuildDisplayList(aBuilder, aLists);
1596
0
  }
1597
0
1598
0
  // draw a focus indicator only when focus rings should be drawn
1599
0
  nsIDocument* doc = mContent->GetComposedDoc();
1600
0
  if (doc) {
1601
0
    nsPIDOMWindowOuter* window = doc->GetWindow();
1602
0
    if (window && window->ShouldShowFocusRing()) {
1603
0
      nsPresContext *presContext = PresContext();
1604
0
      const nsStyleDisplay *disp = StyleDisplay();
1605
0
      if ((!IsThemed(disp) ||
1606
0
           !presContext->GetTheme()->ThemeDrawsFocusForWidget(disp->mAppearance)) &&
1607
0
          mDisplayFrame && IsVisibleForPainting(aBuilder)) {
1608
0
        aLists.Content()->AppendToTop(
1609
0
          MakeDisplayItem<nsDisplayComboboxFocus>(aBuilder, this));
1610
0
      }
1611
0
    }
1612
0
  }
1613
0
1614
0
  DisplaySelectionOverlay(aBuilder, aLists.Content());
1615
0
}
1616
1617
void nsComboboxControlFrame::PaintFocus(DrawTarget& aDrawTarget, nsPoint aPt)
1618
0
{
1619
0
  /* Do we need to do anything? */
1620
0
  EventStates eventStates = mContent->AsElement()->State();
1621
0
  if (eventStates.HasState(NS_EVENT_STATE_DISABLED) || sFocused != this)
1622
0
    return;
1623
0
1624
0
  int32_t appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel();
1625
0
1626
0
  nsRect clipRect = mDisplayFrame->GetRect() + aPt;
1627
0
  aDrawTarget.PushClipRect(NSRectToSnappedRect(clipRect,
1628
0
                                               appUnitsPerDevPixel,
1629
0
                                               aDrawTarget));
1630
0
1631
0
  // REVIEW: Why does the old code paint mDisplayFrame again? We've
1632
0
  // already painted it in the children above. So clipping it here won't do
1633
0
  // us much good.
1634
0
1635
0
  /////////////////////
1636
0
  // draw focus
1637
0
1638
0
  StrokeOptions strokeOptions;
1639
0
  nsLayoutUtils::InitDashPattern(strokeOptions, NS_STYLE_BORDER_STYLE_DOTTED);
1640
0
  ColorPattern color(ToDeviceColor(StyleColor()->mColor));
1641
0
  nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1);
1642
0
  clipRect.width -= onePixel;
1643
0
  clipRect.height -= onePixel;
1644
0
  Rect r = ToRect(nsLayoutUtils::RectToGfxRect(clipRect, appUnitsPerDevPixel));
1645
0
  StrokeSnappedEdgesOfRect(r, aDrawTarget, color, strokeOptions);
1646
0
1647
0
  aDrawTarget.PopClip();
1648
0
}
1649
1650
//---------------------------------------------------------
1651
// gets the content (an option) by index and then set it as
1652
// being selected or not selected
1653
//---------------------------------------------------------
1654
NS_IMETHODIMP
1655
nsComboboxControlFrame::OnOptionSelected(int32_t aIndex, bool aSelected)
1656
0
{
1657
0
  if (mDroppedDown) {
1658
0
    nsISelectControlFrame *selectFrame = do_QueryFrame(mListControlFrame);
1659
0
    if (selectFrame) {
1660
0
      selectFrame->OnOptionSelected(aIndex, aSelected);
1661
0
    }
1662
0
  } else {
1663
0
    if (aSelected) {
1664
0
      nsAutoScriptBlocker blocker;
1665
0
      mDisplayedIndex = aIndex;
1666
0
      RedisplayText();
1667
0
    } else {
1668
0
      AutoWeakFrame weakFrame(this);
1669
0
      RedisplaySelectedText();
1670
0
      if (weakFrame.IsAlive()) {
1671
0
        FireValueChangeEvent(); // Fire after old option is unselected
1672
0
      }
1673
0
    }
1674
0
  }
1675
0
1676
0
  return NS_OK;
1677
0
}
1678
1679
void nsComboboxControlFrame::FireValueChangeEvent()
1680
0
{
1681
0
  // Fire ValueChange event to indicate data value of combo box has changed
1682
0
  nsContentUtils::AddScriptRunner(
1683
0
    new AsyncEventDispatcher(mContent, NS_LITERAL_STRING("ValueChange"),
1684
0
                             CanBubble::eYes, ChromeOnlyDispatch::eNo));
1685
0
}
1686
1687
void
1688
nsComboboxControlFrame::OnContentReset()
1689
0
{
1690
0
  if (mListControlFrame) {
1691
0
    mListControlFrame->OnContentReset();
1692
0
  }
1693
0
}
1694
1695
1696
//--------------------------------------------------------
1697
// nsIStatefulFrame
1698
//--------------------------------------------------------
1699
UniquePtr<PresState>
1700
nsComboboxControlFrame::SaveState()
1701
0
{
1702
0
  UniquePtr<PresState> state = NewPresState();
1703
0
  state->droppedDown() = mDroppedDown;
1704
0
  return state;
1705
0
}
1706
1707
NS_IMETHODIMP
1708
nsComboboxControlFrame::RestoreState(PresState* aState)
1709
0
{
1710
0
  if (!aState) {
1711
0
    return NS_ERROR_FAILURE;
1712
0
  }
1713
0
  ShowList(aState->droppedDown()); // might destroy us
1714
0
  return NS_OK;
1715
0
}
1716
1717
// Append a suffix so that the state key for the combobox is different
1718
// from the state key the list control uses to sometimes save the scroll
1719
// position for the same Element
1720
NS_IMETHODIMP
1721
nsComboboxControlFrame::GenerateStateKey(nsIContent* aContent,
1722
                                        nsIDocument* aDocument,
1723
                                        nsACString& aKey)
1724
0
{
1725
0
  nsresult rv = nsContentUtils::GenerateStateKey(aContent, aDocument, aKey);
1726
0
  if (NS_FAILED(rv) || aKey.IsEmpty()) {
1727
0
    return rv;
1728
0
  }
1729
0
  aKey.AppendLiteral("CCF");
1730
0
  return NS_OK;
1731
0
}
1732
1733
// Fennec uses a custom combobox built-in widget.
1734
//
1735
1736
/* static */
1737
bool
1738
nsComboboxControlFrame::ToolkitHasNativePopup()
1739
0
{
1740
#ifdef MOZ_USE_NATIVE_POPUP_WINDOWS
1741
  return true;
1742
#else
1743
  return false;
1744
0
#endif /* MOZ_USE_NATIVE_POPUP_WINDOWS */
1745
0
}