nsListControlFrame.cpp 81 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523
  1. /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this
  4. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. #include "nscore.h"
  6. #include "nsCOMPtr.h"
  7. #include "nsUnicharUtils.h"
  8. #include "nsListControlFrame.h"
  9. #include "nsFormControlFrame.h" // for COMPARE macro
  10. #include "nsGkAtoms.h"
  11. #include "nsIDOMHTMLSelectElement.h"
  12. #include "nsIDOMHTMLOptionElement.h"
  13. #include "nsComboboxControlFrame.h"
  14. #include "nsIDOMHTMLOptGroupElement.h"
  15. #include "nsIPresShell.h"
  16. #include "nsIDOMMouseEvent.h"
  17. #include "nsIXULRuntime.h"
  18. #include "nsFontMetrics.h"
  19. #include "nsIScrollableFrame.h"
  20. #include "nsCSSRendering.h"
  21. #include "nsIDOMEventListener.h"
  22. #include "nsLayoutUtils.h"
  23. #include "nsDisplayList.h"
  24. #include "nsContentUtils.h"
  25. #include "mozilla/Attributes.h"
  26. #include "mozilla/dom/HTMLOptionsCollection.h"
  27. #include "mozilla/dom/HTMLSelectElement.h"
  28. #include "mozilla/EventStateManager.h"
  29. #include "mozilla/EventStates.h"
  30. #include "mozilla/LookAndFeel.h"
  31. #include "mozilla/MouseEvents.h"
  32. #include "mozilla/Preferences.h"
  33. #include "mozilla/TextEvents.h"
  34. #include <algorithm>
  35. using namespace mozilla;
  36. // Constants
  37. const uint32_t kMaxDropDownRows = 20; // This matches the setting for 4.x browsers
  38. const int32_t kNothingSelected = -1;
  39. // Static members
  40. nsListControlFrame * nsListControlFrame::mFocused = nullptr;
  41. nsString * nsListControlFrame::sIncrementalString = nullptr;
  42. // Using for incremental typing navigation
  43. #define INCREMENTAL_SEARCH_KEYPRESS_TIME 1000
  44. // XXX, kyle.yuan@sun.com, there are 4 definitions for the same purpose:
  45. // nsMenuPopupFrame.h, nsListControlFrame.cpp, listbox.xml, tree.xml
  46. // need to find a good place to put them together.
  47. // if someone changes one, please also change the other.
  48. DOMTimeStamp nsListControlFrame::gLastKeyTime = 0;
  49. /******************************************************************************
  50. * nsListEventListener
  51. * This class is responsible for propagating events to the nsListControlFrame.
  52. * Frames are not refcounted so they can't be used as event listeners.
  53. *****************************************************************************/
  54. class nsListEventListener final : public nsIDOMEventListener
  55. {
  56. public:
  57. explicit nsListEventListener(nsListControlFrame *aFrame)
  58. : mFrame(aFrame) { }
  59. void SetFrame(nsListControlFrame *aFrame) { mFrame = aFrame; }
  60. NS_DECL_ISUPPORTS
  61. NS_DECL_NSIDOMEVENTLISTENER
  62. private:
  63. ~nsListEventListener() {}
  64. nsListControlFrame *mFrame;
  65. };
  66. //---------------------------------------------------------
  67. nsContainerFrame*
  68. NS_NewListControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
  69. {
  70. nsListControlFrame* it =
  71. new (aPresShell) nsListControlFrame(aContext);
  72. it->AddStateBits(NS_FRAME_INDEPENDENT_SELECTION);
  73. return it;
  74. }
  75. NS_IMPL_FRAMEARENA_HELPERS(nsListControlFrame)
  76. //---------------------------------------------------------
  77. nsListControlFrame::nsListControlFrame(nsStyleContext* aContext)
  78. : nsHTMLScrollFrame(aContext, false),
  79. mMightNeedSecondPass(false),
  80. mHasPendingInterruptAtStartOfReflow(false),
  81. mDropdownCanGrow(false),
  82. mForceSelection(false),
  83. mLastDropdownComputedBSize(NS_UNCONSTRAINEDSIZE)
  84. {
  85. mComboboxFrame = nullptr;
  86. mChangesSinceDragStart = false;
  87. mButtonDown = false;
  88. mIsAllContentHere = false;
  89. mIsAllFramesHere = false;
  90. mHasBeenInitialized = false;
  91. mNeedToReset = true;
  92. mPostChildrenLoadedReset = false;
  93. mControlSelectMode = false;
  94. }
  95. //---------------------------------------------------------
  96. nsListControlFrame::~nsListControlFrame()
  97. {
  98. mComboboxFrame = nullptr;
  99. }
  100. // for Bug 47302 (remove this comment later)
  101. void
  102. nsListControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
  103. {
  104. // get the receiver interface from the browser button's content node
  105. ENSURE_TRUE(mContent);
  106. // Clear the frame pointer on our event listener, just in case the
  107. // event listener can outlive the frame.
  108. mEventListener->SetFrame(nullptr);
  109. mContent->RemoveSystemEventListener(NS_LITERAL_STRING("keydown"),
  110. mEventListener, false);
  111. mContent->RemoveSystemEventListener(NS_LITERAL_STRING("keypress"),
  112. mEventListener, false);
  113. mContent->RemoveSystemEventListener(NS_LITERAL_STRING("mousedown"),
  114. mEventListener, false);
  115. mContent->RemoveSystemEventListener(NS_LITERAL_STRING("mouseup"),
  116. mEventListener, false);
  117. mContent->RemoveSystemEventListener(NS_LITERAL_STRING("mousemove"),
  118. mEventListener, false);
  119. if (XRE_IsContentProcess() &&
  120. Preferences::GetBool("browser.tabs.remote.desktopbehavior", false)) {
  121. nsContentUtils::AddScriptRunner(
  122. new AsyncEventDispatcher(mContent,
  123. NS_LITERAL_STRING("mozhidedropdown"), true,
  124. true));
  125. }
  126. nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
  127. nsHTMLScrollFrame::DestroyFrom(aDestructRoot);
  128. }
  129. void
  130. nsListControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
  131. const nsDisplayListSet& aLists)
  132. {
  133. // We allow visibility:hidden <select>s to contain visible options.
  134. // Don't allow painting of list controls when painting is suppressed.
  135. // XXX why do we need this here? we should never reach this. Maybe
  136. // because these can have widgets? Hmm
  137. if (aBuilder->IsBackgroundOnly())
  138. return;
  139. DO_GLOBAL_REFLOW_COUNT_DSP("nsListControlFrame");
  140. if (IsInDropDownMode()) {
  141. NS_ASSERTION(NS_GET_A(mLastDropdownBackstopColor) == 255,
  142. "need an opaque backstop color");
  143. // XXX Because we have an opaque widget and we get called to paint with
  144. // this frame as the root of a stacking context we need make sure to draw
  145. // some opaque color over the whole widget. (Bug 511323)
  146. aLists.BorderBackground()->AppendNewToBottom(
  147. new (aBuilder) nsDisplaySolidColor(aBuilder,
  148. this, nsRect(aBuilder->ToReferenceFrame(this), GetSize()),
  149. mLastDropdownBackstopColor));
  150. }
  151. nsHTMLScrollFrame::BuildDisplayList(aBuilder, aLists);
  152. }
  153. /**
  154. * This is called by the SelectsAreaFrame, which is the same
  155. * as the frame returned by GetOptionsContainer. It's the frame which is
  156. * scrolled by us.
  157. * @param aPt the offset of this frame, relative to the rendering reference
  158. * frame
  159. */
  160. void nsListControlFrame::PaintFocus(DrawTarget* aDrawTarget, nsPoint aPt)
  161. {
  162. if (mFocused != this) return;
  163. nsPresContext* presContext = PresContext();
  164. nsIFrame* containerFrame = GetOptionsContainer();
  165. if (!containerFrame) return;
  166. nsIFrame* childframe = nullptr;
  167. nsCOMPtr<nsIContent> focusedContent = GetCurrentOption();
  168. if (focusedContent) {
  169. childframe = focusedContent->GetPrimaryFrame();
  170. }
  171. nsRect fRect;
  172. if (childframe) {
  173. // get the child rect
  174. fRect = childframe->GetRect();
  175. // get it into our coordinates
  176. fRect.MoveBy(childframe->GetParent()->GetOffsetTo(this));
  177. } else {
  178. float inflation = nsLayoutUtils::FontSizeInflationFor(this);
  179. fRect.x = fRect.y = 0;
  180. if (GetWritingMode().IsVertical()) {
  181. fRect.width = GetScrollPortRect().width;
  182. fRect.height = CalcFallbackRowBSize(inflation);
  183. } else {
  184. fRect.width = CalcFallbackRowBSize(inflation);
  185. fRect.height = GetScrollPortRect().height;
  186. }
  187. fRect.MoveBy(containerFrame->GetOffsetTo(this));
  188. }
  189. fRect += aPt;
  190. bool lastItemIsSelected = false;
  191. if (focusedContent) {
  192. nsCOMPtr<nsIDOMHTMLOptionElement> domOpt =
  193. do_QueryInterface(focusedContent);
  194. if (domOpt) {
  195. domOpt->GetSelected(&lastItemIsSelected);
  196. }
  197. }
  198. // set up back stop colors and then ask L&F service for the real colors
  199. nscolor color =
  200. LookAndFeel::GetColor(lastItemIsSelected ?
  201. LookAndFeel::eColorID_WidgetSelectForeground :
  202. LookAndFeel::eColorID_WidgetSelectBackground);
  203. nsCSSRendering::PaintFocus(presContext, aDrawTarget, fRect, color);
  204. }
  205. void
  206. nsListControlFrame::InvalidateFocus()
  207. {
  208. if (mFocused != this)
  209. return;
  210. nsIFrame* containerFrame = GetOptionsContainer();
  211. if (containerFrame) {
  212. containerFrame->InvalidateFrame();
  213. }
  214. }
  215. NS_QUERYFRAME_HEAD(nsListControlFrame)
  216. NS_QUERYFRAME_ENTRY(nsIFormControlFrame)
  217. NS_QUERYFRAME_ENTRY(nsIListControlFrame)
  218. NS_QUERYFRAME_ENTRY(nsISelectControlFrame)
  219. NS_QUERYFRAME_TAIL_INHERITING(nsHTMLScrollFrame)
  220. #ifdef ACCESSIBILITY
  221. a11y::AccType
  222. nsListControlFrame::AccessibleType()
  223. {
  224. return a11y::eHTMLSelectListType;
  225. }
  226. #endif
  227. static nscoord
  228. GetMaxOptionBSize(nsIFrame* aContainer, WritingMode aWM)
  229. {
  230. nscoord result = 0;
  231. for (nsIFrame* option : aContainer->PrincipalChildList()) {
  232. nscoord optionBSize;
  233. if (nsCOMPtr<nsIDOMHTMLOptGroupElement>
  234. (do_QueryInterface(option->GetContent()))) {
  235. // An optgroup; drill through any scroll frame and recurse. |frame| might
  236. // be null here though if |option| is an anonymous leaf frame of some sort.
  237. auto frame = option->GetContentInsertionFrame();
  238. optionBSize = frame ? GetMaxOptionBSize(frame, aWM) : 0;
  239. } else {
  240. // an option
  241. optionBSize = option->BSize(aWM);
  242. }
  243. if (result < optionBSize)
  244. result = optionBSize;
  245. }
  246. return result;
  247. }
  248. //-----------------------------------------------------------------
  249. // Main Reflow for ListBox/Dropdown
  250. //-----------------------------------------------------------------
  251. nscoord
  252. nsListControlFrame::CalcBSizeOfARow()
  253. {
  254. // Calculate the block size in our writing mode of a single row in the
  255. // listbox or dropdown list by using the tallest thing in the subtree,
  256. // since there may be option groups in addition to option elements,
  257. // either of which may be visible or invisible, may use different
  258. // fonts, etc.
  259. int32_t blockSizeOfARow = GetMaxOptionBSize(GetOptionsContainer(),
  260. GetWritingMode());
  261. // Check to see if we have zero items (and optimize by checking
  262. // blockSizeOfARow first)
  263. if (blockSizeOfARow == 0 && GetNumberOfOptions() == 0) {
  264. float inflation = nsLayoutUtils::FontSizeInflationFor(this);
  265. blockSizeOfARow = CalcFallbackRowBSize(inflation);
  266. }
  267. return blockSizeOfARow;
  268. }
  269. nscoord
  270. nsListControlFrame::GetPrefISize(nsRenderingContext *aRenderingContext)
  271. {
  272. nscoord result;
  273. DISPLAY_PREF_WIDTH(this, result);
  274. // Always add scrollbar inline sizes to the pref-inline-size of the
  275. // scrolled content. Combobox frames depend on this happening in the
  276. // dropdown, and standalone listboxes are overflow:scroll so they need
  277. // it too.
  278. WritingMode wm = GetWritingMode();
  279. result = GetScrolledFrame()->GetPrefISize(aRenderingContext);
  280. LogicalMargin scrollbarSize(wm, GetDesiredScrollbarSizes(PresContext(),
  281. aRenderingContext));
  282. result = NSCoordSaturatingAdd(result, scrollbarSize.IStartEnd(wm));
  283. return result;
  284. }
  285. nscoord
  286. nsListControlFrame::GetMinISize(nsRenderingContext *aRenderingContext)
  287. {
  288. nscoord result;
  289. DISPLAY_MIN_WIDTH(this, result);
  290. // Always add scrollbar inline sizes to the min-inline-size of the
  291. // scrolled content. Combobox frames depend on this happening in the
  292. // dropdown, and standalone listboxes are overflow:scroll so they need
  293. // it too.
  294. WritingMode wm = GetWritingMode();
  295. result = GetScrolledFrame()->GetMinISize(aRenderingContext);
  296. LogicalMargin scrollbarSize(wm, GetDesiredScrollbarSizes(PresContext(),
  297. aRenderingContext));
  298. result += scrollbarSize.IStartEnd(wm);
  299. return result;
  300. }
  301. void
  302. nsListControlFrame::Reflow(nsPresContext* aPresContext,
  303. ReflowOutput& aDesiredSize,
  304. const ReflowInput& aReflowInput,
  305. nsReflowStatus& aStatus)
  306. {
  307. NS_PRECONDITION(aReflowInput.ComputedISize() != NS_UNCONSTRAINEDSIZE,
  308. "Must have a computed inline size");
  309. SchedulePaint();
  310. mHasPendingInterruptAtStartOfReflow = aPresContext->HasPendingInterrupt();
  311. // If all the content and frames are here
  312. // then initialize it before reflow
  313. if (mIsAllContentHere && !mHasBeenInitialized) {
  314. if (false == mIsAllFramesHere) {
  315. CheckIfAllFramesHere();
  316. }
  317. if (mIsAllFramesHere && !mHasBeenInitialized) {
  318. mHasBeenInitialized = true;
  319. }
  320. }
  321. if (GetStateBits() & NS_FRAME_FIRST_REFLOW) {
  322. nsFormControlFrame::RegUnRegAccessKey(this, true);
  323. }
  324. if (IsInDropDownMode()) {
  325. ReflowAsDropdown(aPresContext, aDesiredSize, aReflowInput, aStatus);
  326. return;
  327. }
  328. MarkInReflow();
  329. /*
  330. * Due to the fact that our intrinsic block size depends on the block
  331. * sizes of our kids, we end up having to do two-pass reflow, in
  332. * general -- the first pass to find the intrinsic block size and a
  333. * second pass to reflow the scrollframe at that block size (which
  334. * will size the scrollbars correctly, etc).
  335. *
  336. * Naturally, we want to avoid doing the second reflow as much as
  337. * possible.
  338. * We can skip it in the following cases (in all of which the first
  339. * reflow is already happening at the right block size):
  340. *
  341. * - We're reflowing with a constrained computed block size -- just
  342. * use that block size.
  343. * - We're not dirty and have no dirty kids and shouldn't be reflowing
  344. * all kids. In this case, our cached max block size of a child is
  345. * not going to change.
  346. * - We do our first reflow using our cached max block size of a
  347. * child, then compute the new max block size and it's the same as
  348. * the old one.
  349. */
  350. bool autoBSize = (aReflowInput.ComputedBSize() == NS_UNCONSTRAINEDSIZE);
  351. mMightNeedSecondPass = autoBSize &&
  352. (NS_SUBTREE_DIRTY(this) || aReflowInput.ShouldReflowAllKids());
  353. ReflowInput state(aReflowInput);
  354. int32_t length = GetNumberOfRows();
  355. nscoord oldBSizeOfARow = BSizeOfARow();
  356. if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW) && autoBSize) {
  357. // When not doing an initial reflow, and when the block size is
  358. // auto, start off with our computed block size set to what we'd
  359. // expect our block size to be.
  360. nscoord computedBSize = CalcIntrinsicBSize(oldBSizeOfARow, length);
  361. computedBSize = state.ApplyMinMaxBSize(computedBSize);
  362. state.SetComputedBSize(computedBSize);
  363. }
  364. nsHTMLScrollFrame::Reflow(aPresContext, aDesiredSize, state, aStatus);
  365. if (!mMightNeedSecondPass) {
  366. NS_ASSERTION(!autoBSize || BSizeOfARow() == oldBSizeOfARow,
  367. "How did our BSize of a row change if nothing was dirty?");
  368. NS_ASSERTION(!autoBSize ||
  369. !(GetStateBits() & NS_FRAME_FIRST_REFLOW),
  370. "How do we not need a second pass during initial reflow at "
  371. "auto BSize?");
  372. NS_ASSERTION(!IsScrollbarUpdateSuppressed(),
  373. "Shouldn't be suppressing if we don't need a second pass!");
  374. if (!autoBSize) {
  375. // Update our mNumDisplayRows based on our new row block size now
  376. // that we know it. Note that if autoBSize and we landed in this
  377. // code then we already set mNumDisplayRows in CalcIntrinsicBSize.
  378. // Also note that we can't use BSizeOfARow() here because that
  379. // just uses a cached value that we didn't compute.
  380. nscoord rowBSize = CalcBSizeOfARow();
  381. if (rowBSize == 0) {
  382. // Just pick something
  383. mNumDisplayRows = 1;
  384. } else {
  385. mNumDisplayRows = std::max(1, state.ComputedBSize() / rowBSize);
  386. }
  387. }
  388. return;
  389. }
  390. mMightNeedSecondPass = false;
  391. // Now see whether we need a second pass. If we do, our
  392. // nsSelectsAreaFrame will have suppressed the scrollbar update.
  393. if (!IsScrollbarUpdateSuppressed()) {
  394. // All done. No need to do more reflow.
  395. NS_ASSERTION(!IsScrollbarUpdateSuppressed(),
  396. "Shouldn't be suppressing if the block size of a row has not "
  397. "changed!");
  398. return;
  399. }
  400. SetSuppressScrollbarUpdate(false);
  401. // Gotta reflow again.
  402. // XXXbz We're just changing the block size here; do we need to dirty
  403. // ourselves or anything like that? We might need to, per the letter
  404. // of the reflow protocol, but things seem to work fine without it...
  405. // Is that just an implementation detail of nsHTMLScrollFrame that
  406. // we're depending on?
  407. nsHTMLScrollFrame::DidReflow(aPresContext, &state,
  408. nsDidReflowStatus::FINISHED);
  409. // Now compute the block size we want to have
  410. nscoord computedBSize = CalcIntrinsicBSize(BSizeOfARow(), length);
  411. computedBSize = state.ApplyMinMaxBSize(computedBSize);
  412. state.SetComputedBSize(computedBSize);
  413. // XXXbz to make the ascent really correct, we should add our
  414. // mComputedPadding.top to it (and subtract it from descent). Need that
  415. // because nsGfxScrollFrame just adds in the border....
  416. nsHTMLScrollFrame::Reflow(aPresContext, aDesiredSize, state, aStatus);
  417. }
  418. void
  419. nsListControlFrame::ReflowAsDropdown(nsPresContext* aPresContext,
  420. ReflowOutput& aDesiredSize,
  421. const ReflowInput& aReflowInput,
  422. nsReflowStatus& aStatus)
  423. {
  424. NS_PRECONDITION(aReflowInput.ComputedBSize() == NS_UNCONSTRAINEDSIZE,
  425. "We should not have a computed block size here!");
  426. mMightNeedSecondPass = NS_SUBTREE_DIRTY(this) ||
  427. aReflowInput.ShouldReflowAllKids();
  428. WritingMode wm = aReflowInput.GetWritingMode();
  429. #ifdef DEBUG
  430. nscoord oldBSizeOfARow = BSizeOfARow();
  431. nscoord oldVisibleBSize = (GetStateBits() & NS_FRAME_FIRST_REFLOW) ?
  432. NS_UNCONSTRAINEDSIZE : GetScrolledFrame()->BSize(wm);
  433. #endif
  434. ReflowInput state(aReflowInput);
  435. if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
  436. // When not doing an initial reflow, and when the block size is
  437. // auto, start off with our computed block size set to what we'd
  438. // expect our block size to be.
  439. // Note: At this point, mLastDropdownComputedBSize can be
  440. // NS_UNCONSTRAINEDSIZE in cases when last time we didn't have to
  441. // constrain the block size. That's fine; just do the same thing as
  442. // last time.
  443. state.SetComputedBSize(mLastDropdownComputedBSize);
  444. }
  445. nsHTMLScrollFrame::Reflow(aPresContext, aDesiredSize, state, aStatus);
  446. if (!mMightNeedSecondPass) {
  447. NS_ASSERTION(oldVisibleBSize == GetScrolledFrame()->BSize(wm),
  448. "How did our kid's BSize change if nothing was dirty?");
  449. NS_ASSERTION(BSizeOfARow() == oldBSizeOfARow,
  450. "How did our BSize of a row change if nothing was dirty?");
  451. NS_ASSERTION(!IsScrollbarUpdateSuppressed(),
  452. "Shouldn't be suppressing if we don't need a second pass!");
  453. NS_ASSERTION(!(GetStateBits() & NS_FRAME_FIRST_REFLOW),
  454. "How can we avoid a second pass during first reflow?");
  455. return;
  456. }
  457. mMightNeedSecondPass = false;
  458. // Now see whether we need a second pass. If we do, our nsSelectsAreaFrame
  459. // will have suppressed the scrollbar update.
  460. if (!IsScrollbarUpdateSuppressed()) {
  461. // All done. No need to do more reflow.
  462. NS_ASSERTION(!(GetStateBits() & NS_FRAME_FIRST_REFLOW),
  463. "How can we avoid a second pass during first reflow?");
  464. return;
  465. }
  466. SetSuppressScrollbarUpdate(false);
  467. nscoord visibleBSize = GetScrolledFrame()->BSize(wm);
  468. nscoord blockSizeOfARow = BSizeOfARow();
  469. // Gotta reflow again.
  470. // XXXbz We're just changing the block size here; do we need to dirty
  471. // ourselves or anything like that? We might need to, per the letter
  472. // of the reflow protocol, but things seem to work fine without it...
  473. // Is that just an implementation detail of nsHTMLScrollFrame that
  474. // we're depending on?
  475. nsHTMLScrollFrame::DidReflow(aPresContext, &state,
  476. nsDidReflowStatus::FINISHED);
  477. // Now compute the block size we want to have.
  478. // Note: no need to apply min/max constraints, since we have no such
  479. // rules applied to the combobox dropdown.
  480. mDropdownCanGrow = false;
  481. if (visibleBSize <= 0 || blockSizeOfARow <= 0 || XRE_IsContentProcess()) {
  482. // Looks like we have no options. Just size us to a single row
  483. // block size.
  484. state.SetComputedBSize(blockSizeOfARow);
  485. mNumDisplayRows = 1;
  486. } else {
  487. nsComboboxControlFrame* combobox =
  488. static_cast<nsComboboxControlFrame*>(mComboboxFrame);
  489. LogicalPoint translation(wm);
  490. nscoord before, after;
  491. combobox->GetAvailableDropdownSpace(wm, &before, &after, &translation);
  492. if (before <= 0 && after <= 0) {
  493. state.SetComputedBSize(blockSizeOfARow);
  494. mNumDisplayRows = 1;
  495. mDropdownCanGrow = GetNumberOfRows() > 1;
  496. } else {
  497. nscoord bp = aReflowInput.ComputedLogicalBorderPadding().BStartEnd(wm);
  498. nscoord availableBSize = std::max(before, after) - bp;
  499. nscoord newBSize;
  500. uint32_t rows;
  501. if (visibleBSize <= availableBSize) {
  502. // The dropdown fits in the available block size.
  503. rows = GetNumberOfRows();
  504. mNumDisplayRows = clamped<uint32_t>(rows, 1, kMaxDropDownRows);
  505. if (mNumDisplayRows == rows) {
  506. newBSize = visibleBSize; // use the exact block size
  507. } else {
  508. newBSize = mNumDisplayRows * blockSizeOfARow; // approximate
  509. // The approximation here might actually be too big (bug 1208978);
  510. // don't let it exceed the actual block-size of the list.
  511. newBSize = std::min(newBSize, visibleBSize);
  512. }
  513. } else {
  514. rows = availableBSize / blockSizeOfARow;
  515. mNumDisplayRows = clamped<uint32_t>(rows, 1, kMaxDropDownRows);
  516. newBSize = mNumDisplayRows * blockSizeOfARow; // approximate
  517. }
  518. state.SetComputedBSize(newBSize);
  519. mDropdownCanGrow = visibleBSize - newBSize >= blockSizeOfARow &&
  520. mNumDisplayRows != kMaxDropDownRows;
  521. }
  522. }
  523. mLastDropdownComputedBSize = state.ComputedBSize();
  524. nsHTMLScrollFrame::Reflow(aPresContext, aDesiredSize, state, aStatus);
  525. }
  526. ScrollStyles
  527. nsListControlFrame::GetScrollStyles() const
  528. {
  529. // We can't express this in the style system yet; when we can, this can go away
  530. // and GetScrollStyles can be devirtualized
  531. int32_t style = IsInDropDownMode() ? NS_STYLE_OVERFLOW_AUTO
  532. : NS_STYLE_OVERFLOW_SCROLL;
  533. if (GetWritingMode().IsVertical()) {
  534. return ScrollStyles(style, NS_STYLE_OVERFLOW_HIDDEN);
  535. } else {
  536. return ScrollStyles(NS_STYLE_OVERFLOW_HIDDEN, style);
  537. }
  538. }
  539. bool
  540. nsListControlFrame::ShouldPropagateComputedBSizeToScrolledContent() const
  541. {
  542. return !IsInDropDownMode();
  543. }
  544. //---------------------------------------------------------
  545. nsContainerFrame*
  546. nsListControlFrame::GetContentInsertionFrame() {
  547. return GetOptionsContainer()->GetContentInsertionFrame();
  548. }
  549. //---------------------------------------------------------
  550. bool
  551. nsListControlFrame::ExtendedSelection(int32_t aStartIndex,
  552. int32_t aEndIndex,
  553. bool aClearAll)
  554. {
  555. return SetOptionsSelectedFromFrame(aStartIndex, aEndIndex,
  556. true, aClearAll);
  557. }
  558. //---------------------------------------------------------
  559. bool
  560. nsListControlFrame::SingleSelection(int32_t aClickedIndex, bool aDoToggle)
  561. {
  562. if (mComboboxFrame) {
  563. mComboboxFrame->UpdateRecentIndex(GetSelectedIndex());
  564. }
  565. bool wasChanged = false;
  566. // Get Current selection
  567. if (aDoToggle) {
  568. wasChanged = ToggleOptionSelectedFromFrame(aClickedIndex);
  569. } else {
  570. wasChanged = SetOptionsSelectedFromFrame(aClickedIndex, aClickedIndex,
  571. true, true);
  572. }
  573. nsWeakFrame weakFrame(this);
  574. ScrollToIndex(aClickedIndex);
  575. if (!weakFrame.IsAlive()) {
  576. return wasChanged;
  577. }
  578. #ifdef ACCESSIBILITY
  579. bool isCurrentOptionChanged = mEndSelectionIndex != aClickedIndex;
  580. #endif
  581. mStartSelectionIndex = aClickedIndex;
  582. mEndSelectionIndex = aClickedIndex;
  583. InvalidateFocus();
  584. #ifdef ACCESSIBILITY
  585. if (isCurrentOptionChanged) {
  586. FireMenuItemActiveEvent();
  587. }
  588. #endif
  589. return wasChanged;
  590. }
  591. void
  592. nsListControlFrame::InitSelectionRange(int32_t aClickedIndex)
  593. {
  594. //
  595. // If nothing is selected, set the start selection depending on where
  596. // the user clicked and what the initial selection is:
  597. // - if the user clicked *before* selectedIndex, set the start index to
  598. // the end of the first contiguous selection.
  599. // - if the user clicked *after* the end of the first contiguous
  600. // selection, set the start index to selectedIndex.
  601. // - if the user clicked *within* the first contiguous selection, set the
  602. // start index to selectedIndex.
  603. // The last two rules, of course, boil down to the same thing: if the user
  604. // clicked >= selectedIndex, return selectedIndex.
  605. //
  606. // This makes it so that shift click works properly when you first click
  607. // in a multiple select.
  608. //
  609. int32_t selectedIndex = GetSelectedIndex();
  610. if (selectedIndex >= 0) {
  611. // Get the end of the contiguous selection
  612. RefPtr<dom::HTMLOptionsCollection> options = GetOptions();
  613. NS_ASSERTION(options, "Collection of options is null!");
  614. uint32_t numOptions = options->Length();
  615. // Push i to one past the last selected index in the group.
  616. uint32_t i;
  617. for (i = selectedIndex + 1; i < numOptions; i++) {
  618. if (!options->ItemAsOption(i)->Selected()) {
  619. break;
  620. }
  621. }
  622. if (aClickedIndex < selectedIndex) {
  623. // User clicked before selection, so start selection at end of
  624. // contiguous selection
  625. mStartSelectionIndex = i-1;
  626. mEndSelectionIndex = selectedIndex;
  627. } else {
  628. // User clicked after selection, so start selection at start of
  629. // contiguous selection
  630. mStartSelectionIndex = selectedIndex;
  631. mEndSelectionIndex = i-1;
  632. }
  633. }
  634. }
  635. static uint32_t
  636. CountOptionsAndOptgroups(nsIFrame* aFrame)
  637. {
  638. uint32_t count = 0;
  639. nsFrameList::Enumerator e(aFrame->PrincipalChildList());
  640. for (; !e.AtEnd(); e.Next()) {
  641. nsIFrame* child = e.get();
  642. nsIContent* content = child->GetContent();
  643. if (content) {
  644. if (content->IsHTMLElement(nsGkAtoms::option)) {
  645. ++count;
  646. } else {
  647. nsCOMPtr<nsIDOMHTMLOptGroupElement> optgroup = do_QueryInterface(content);
  648. if (optgroup) {
  649. nsAutoString label;
  650. optgroup->GetLabel(label);
  651. if (label.Length() > 0) {
  652. ++count;
  653. }
  654. count += CountOptionsAndOptgroups(child);
  655. }
  656. }
  657. }
  658. }
  659. return count;
  660. }
  661. uint32_t
  662. nsListControlFrame::GetNumberOfRows()
  663. {
  664. return ::CountOptionsAndOptgroups(GetContentInsertionFrame());
  665. }
  666. //---------------------------------------------------------
  667. bool
  668. nsListControlFrame::PerformSelection(int32_t aClickedIndex,
  669. bool aIsShift,
  670. bool aIsControl)
  671. {
  672. bool wasChanged = false;
  673. if (aClickedIndex == kNothingSelected && !mForceSelection) {
  674. // Ignore kNothingSelected unless the selection is forced
  675. } else if (GetMultiple()) {
  676. if (aIsShift) {
  677. // Make sure shift+click actually does something expected when
  678. // the user has never clicked on the select
  679. if (mStartSelectionIndex == kNothingSelected) {
  680. InitSelectionRange(aClickedIndex);
  681. }
  682. // Get the range from beginning (low) to end (high)
  683. // Shift *always* works, even if the current option is disabled
  684. int32_t startIndex;
  685. int32_t endIndex;
  686. if (mStartSelectionIndex == kNothingSelected) {
  687. startIndex = aClickedIndex;
  688. endIndex = aClickedIndex;
  689. } else if (mStartSelectionIndex <= aClickedIndex) {
  690. startIndex = mStartSelectionIndex;
  691. endIndex = aClickedIndex;
  692. } else {
  693. startIndex = aClickedIndex;
  694. endIndex = mStartSelectionIndex;
  695. }
  696. // Clear only if control was not pressed
  697. wasChanged = ExtendedSelection(startIndex, endIndex, !aIsControl);
  698. nsWeakFrame weakFrame(this);
  699. ScrollToIndex(aClickedIndex);
  700. if (!weakFrame.IsAlive()) {
  701. return wasChanged;
  702. }
  703. if (mStartSelectionIndex == kNothingSelected) {
  704. mStartSelectionIndex = aClickedIndex;
  705. }
  706. #ifdef ACCESSIBILITY
  707. bool isCurrentOptionChanged = mEndSelectionIndex != aClickedIndex;
  708. #endif
  709. mEndSelectionIndex = aClickedIndex;
  710. InvalidateFocus();
  711. #ifdef ACCESSIBILITY
  712. if (isCurrentOptionChanged) {
  713. FireMenuItemActiveEvent();
  714. }
  715. #endif
  716. } else if (aIsControl) {
  717. wasChanged = SingleSelection(aClickedIndex, true); // might destroy us
  718. } else {
  719. wasChanged = SingleSelection(aClickedIndex, false); // might destroy us
  720. }
  721. } else {
  722. wasChanged = SingleSelection(aClickedIndex, false); // might destroy us
  723. }
  724. return wasChanged;
  725. }
  726. //---------------------------------------------------------
  727. bool
  728. nsListControlFrame::HandleListSelection(nsIDOMEvent* aEvent,
  729. int32_t aClickedIndex)
  730. {
  731. nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aEvent);
  732. bool isShift;
  733. bool isControl;
  734. mouseEvent->GetCtrlKey(&isControl);
  735. mouseEvent->GetShiftKey(&isShift);
  736. return PerformSelection(aClickedIndex, isShift, isControl); // might destroy us
  737. }
  738. //---------------------------------------------------------
  739. void
  740. nsListControlFrame::CaptureMouseEvents(bool aGrabMouseEvents)
  741. {
  742. // Currently cocoa widgets use a native popup widget which tracks clicks synchronously,
  743. // so we never want to do mouse capturing. Note that we only bail if the list
  744. // is in drop-down mode, and the caller is requesting capture (we let release capture
  745. // requests go through to ensure that we can release capture requested via other
  746. // code paths, if any exist).
  747. if (aGrabMouseEvents && IsInDropDownMode() && nsComboboxControlFrame::ToolkitHasNativePopup())
  748. return;
  749. if (aGrabMouseEvents) {
  750. nsIPresShell::SetCapturingContent(mContent, CAPTURE_IGNOREALLOWED);
  751. } else {
  752. nsIContent* capturingContent = nsIPresShell::GetCapturingContent();
  753. bool dropDownIsHidden = false;
  754. if (IsInDropDownMode()) {
  755. dropDownIsHidden = !mComboboxFrame->IsDroppedDown();
  756. }
  757. if (capturingContent == mContent || dropDownIsHidden) {
  758. // only clear the capturing content if *we* are the ones doing the
  759. // capturing (or if the dropdown is hidden, in which case NO-ONE should
  760. // be capturing anything - it could be a scrollbar inside this listbox
  761. // which is actually grabbing
  762. // This shouldn't be necessary. We should simply ensure that events targeting
  763. // scrollbars are never visible to DOM consumers.
  764. nsIPresShell::SetCapturingContent(nullptr, 0);
  765. }
  766. }
  767. }
  768. //---------------------------------------------------------
  769. nsresult
  770. nsListControlFrame::HandleEvent(nsPresContext* aPresContext,
  771. WidgetGUIEvent* aEvent,
  772. nsEventStatus* aEventStatus)
  773. {
  774. NS_ENSURE_ARG_POINTER(aEventStatus);
  775. /*const char * desc[] = {"eMouseMove",
  776. "NS_MOUSE_LEFT_BUTTON_UP",
  777. "NS_MOUSE_LEFT_BUTTON_DOWN",
  778. "<NA>","<NA>","<NA>","<NA>","<NA>","<NA>","<NA>",
  779. "NS_MOUSE_MIDDLE_BUTTON_UP",
  780. "NS_MOUSE_MIDDLE_BUTTON_DOWN",
  781. "<NA>","<NA>","<NA>","<NA>","<NA>","<NA>","<NA>","<NA>",
  782. "NS_MOUSE_RIGHT_BUTTON_UP",
  783. "NS_MOUSE_RIGHT_BUTTON_DOWN",
  784. "eMouseOver",
  785. "eMouseOut",
  786. "NS_MOUSE_LEFT_DOUBLECLICK",
  787. "NS_MOUSE_MIDDLE_DOUBLECLICK",
  788. "NS_MOUSE_RIGHT_DOUBLECLICK",
  789. "NS_MOUSE_LEFT_CLICK",
  790. "NS_MOUSE_MIDDLE_CLICK",
  791. "NS_MOUSE_RIGHT_CLICK"};
  792. int inx = aEvent->mMessage - eMouseEventFirst;
  793. if (inx >= 0 && inx <= (NS_MOUSE_RIGHT_CLICK - eMouseEventFirst)) {
  794. printf("Mouse in ListFrame %s [%d]\n", desc[inx], aEvent->mMessage);
  795. } else {
  796. printf("Mouse in ListFrame <UNKNOWN> [%d]\n", aEvent->mMessage);
  797. }*/
  798. if (nsEventStatus_eConsumeNoDefault == *aEventStatus)
  799. return NS_OK;
  800. // do we have style that affects how we are selected?
  801. // do we have user-input style?
  802. const nsStyleUserInterface* uiStyle = StyleUserInterface();
  803. if (uiStyle->mUserInput == StyleUserInput::None ||
  804. uiStyle->mUserInput == StyleUserInput::Disabled) {
  805. return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
  806. }
  807. EventStates eventStates = mContent->AsElement()->State();
  808. if (eventStates.HasState(NS_EVENT_STATE_DISABLED))
  809. return NS_OK;
  810. return nsHTMLScrollFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
  811. }
  812. //---------------------------------------------------------
  813. void
  814. nsListControlFrame::SetInitialChildList(ChildListID aListID,
  815. nsFrameList& aChildList)
  816. {
  817. if (aListID == kPrincipalList) {
  818. // First check to see if all the content has been added
  819. mIsAllContentHere = mContent->IsDoneAddingChildren();
  820. if (!mIsAllContentHere) {
  821. mIsAllFramesHere = false;
  822. mHasBeenInitialized = false;
  823. }
  824. }
  825. nsHTMLScrollFrame::SetInitialChildList(aListID, aChildList);
  826. // If all the content is here now check
  827. // to see if all the frames have been created
  828. /*if (mIsAllContentHere) {
  829. // If all content and frames are here
  830. // the reset/initialize
  831. if (CheckIfAllFramesHere()) {
  832. ResetList(aPresContext);
  833. mHasBeenInitialized = true;
  834. }
  835. }*/
  836. }
  837. //---------------------------------------------------------
  838. void
  839. nsListControlFrame::Init(nsIContent* aContent,
  840. nsContainerFrame* aParent,
  841. nsIFrame* aPrevInFlow)
  842. {
  843. nsHTMLScrollFrame::Init(aContent, aParent, aPrevInFlow);
  844. // we shouldn't have to unregister this listener because when
  845. // our frame goes away all these content node go away as well
  846. // because our frame is the only one who references them.
  847. // we need to hook up our listeners before the editor is initialized
  848. mEventListener = new nsListEventListener(this);
  849. mContent->AddSystemEventListener(NS_LITERAL_STRING("keydown"),
  850. mEventListener, false, false);
  851. mContent->AddSystemEventListener(NS_LITERAL_STRING("keypress"),
  852. mEventListener, false, false);
  853. mContent->AddSystemEventListener(NS_LITERAL_STRING("mousedown"),
  854. mEventListener, false, false);
  855. mContent->AddSystemEventListener(NS_LITERAL_STRING("mouseup"),
  856. mEventListener, false, false);
  857. mContent->AddSystemEventListener(NS_LITERAL_STRING("mousemove"),
  858. mEventListener, false, false);
  859. mStartSelectionIndex = kNothingSelected;
  860. mEndSelectionIndex = kNothingSelected;
  861. mLastDropdownBackstopColor = PresContext()->DefaultBackgroundColor();
  862. if (IsInDropDownMode()) {
  863. AddStateBits(NS_FRAME_IN_POPUP);
  864. }
  865. }
  866. dom::HTMLOptionsCollection*
  867. nsListControlFrame::GetOptions() const
  868. {
  869. dom::HTMLSelectElement* select =
  870. dom::HTMLSelectElement::FromContentOrNull(mContent);
  871. NS_ENSURE_TRUE(select, nullptr);
  872. return select->Options();
  873. }
  874. dom::HTMLOptionElement*
  875. nsListControlFrame::GetOption(uint32_t aIndex) const
  876. {
  877. dom::HTMLSelectElement* select =
  878. dom::HTMLSelectElement::FromContentOrNull(mContent);
  879. NS_ENSURE_TRUE(select, nullptr);
  880. return select->Item(aIndex);
  881. }
  882. NS_IMETHODIMP
  883. nsListControlFrame::OnOptionSelected(int32_t aIndex, bool aSelected)
  884. {
  885. if (aSelected) {
  886. ScrollToIndex(aIndex);
  887. }
  888. return NS_OK;
  889. }
  890. void
  891. nsListControlFrame::OnContentReset()
  892. {
  893. ResetList(true);
  894. }
  895. void
  896. nsListControlFrame::ResetList(bool aAllowScrolling)
  897. {
  898. // if all the frames aren't here
  899. // don't bother reseting
  900. if (!mIsAllFramesHere) {
  901. return;
  902. }
  903. if (aAllowScrolling) {
  904. mPostChildrenLoadedReset = true;
  905. // Scroll to the selected index
  906. int32_t indexToSelect = kNothingSelected;
  907. nsCOMPtr<nsIDOMHTMLSelectElement> selectElement(do_QueryInterface(mContent));
  908. NS_ASSERTION(selectElement, "No select element!");
  909. if (selectElement) {
  910. selectElement->GetSelectedIndex(&indexToSelect);
  911. nsWeakFrame weakFrame(this);
  912. ScrollToIndex(indexToSelect);
  913. if (!weakFrame.IsAlive()) {
  914. return;
  915. }
  916. }
  917. }
  918. mStartSelectionIndex = kNothingSelected;
  919. mEndSelectionIndex = kNothingSelected;
  920. InvalidateFocus();
  921. // Combobox will redisplay itself with the OnOptionSelected event
  922. }
  923. void
  924. nsListControlFrame::SetFocus(bool aOn, bool aRepaint)
  925. {
  926. InvalidateFocus();
  927. if (aOn) {
  928. ComboboxFocusSet();
  929. mFocused = this;
  930. } else {
  931. mFocused = nullptr;
  932. }
  933. InvalidateFocus();
  934. }
  935. void nsListControlFrame::ComboboxFocusSet()
  936. {
  937. gLastKeyTime = 0;
  938. }
  939. void
  940. nsListControlFrame::SetComboboxFrame(nsIFrame* aComboboxFrame)
  941. {
  942. if (nullptr != aComboboxFrame) {
  943. mComboboxFrame = do_QueryFrame(aComboboxFrame);
  944. }
  945. }
  946. void
  947. nsListControlFrame::GetOptionText(uint32_t aIndex, nsAString& aStr)
  948. {
  949. aStr.Truncate();
  950. if (dom::HTMLOptionElement* optionElement = GetOption(aIndex)) {
  951. optionElement->GetText(aStr);
  952. }
  953. }
  954. int32_t
  955. nsListControlFrame::GetSelectedIndex()
  956. {
  957. dom::HTMLSelectElement* select =
  958. dom::HTMLSelectElement::FromContentOrNull(mContent);
  959. return select->SelectedIndex();
  960. }
  961. dom::HTMLOptionElement*
  962. nsListControlFrame::GetCurrentOption()
  963. {
  964. // The mEndSelectionIndex is what is currently being selected. Use
  965. // the selected index if this is kNothingSelected.
  966. int32_t focusedIndex = (mEndSelectionIndex == kNothingSelected) ?
  967. GetSelectedIndex() : mEndSelectionIndex;
  968. if (focusedIndex != kNothingSelected) {
  969. return GetOption(AssertedCast<uint32_t>(focusedIndex));
  970. }
  971. // There is no selected item. Return the first non-disabled item.
  972. RefPtr<dom::HTMLSelectElement> selectElement =
  973. dom::HTMLSelectElement::FromContent(mContent);
  974. for (uint32_t i = 0, length = selectElement->Length(); i < length; ++i) {
  975. dom::HTMLOptionElement* node = selectElement->Item(i);
  976. if (!node) {
  977. return nullptr;
  978. }
  979. if (!selectElement->IsOptionDisabled(node)) {
  980. return node;
  981. }
  982. }
  983. return nullptr;
  984. }
  985. bool
  986. nsListControlFrame::IsInDropDownMode() const
  987. {
  988. return (mComboboxFrame != nullptr);
  989. }
  990. uint32_t
  991. nsListControlFrame::GetNumberOfOptions()
  992. {
  993. dom::HTMLOptionsCollection* options = GetOptions();
  994. if (!options) {
  995. return 0;
  996. }
  997. return options->Length();
  998. }
  999. //----------------------------------------------------------------------
  1000. // nsISelectControlFrame
  1001. //----------------------------------------------------------------------
  1002. bool nsListControlFrame::CheckIfAllFramesHere()
  1003. {
  1004. // Get the number of optgroups and options
  1005. //int32_t numContentItems = 0;
  1006. nsCOMPtr<nsIDOMNode> node(do_QueryInterface(mContent));
  1007. if (node) {
  1008. // XXX Need to find a fail proff way to determine that
  1009. // all the frames are there
  1010. mIsAllFramesHere = true;//NS_OK == CountAllChild(node, numContentItems);
  1011. }
  1012. // now make sure we have a frame each piece of content
  1013. return mIsAllFramesHere;
  1014. }
  1015. NS_IMETHODIMP
  1016. nsListControlFrame::DoneAddingChildren(bool aIsDone)
  1017. {
  1018. mIsAllContentHere = aIsDone;
  1019. if (mIsAllContentHere) {
  1020. // Here we check to see if all the frames have been created
  1021. // for all the content.
  1022. // If so, then we can initialize;
  1023. if (!mIsAllFramesHere) {
  1024. // if all the frames are now present we can initialize
  1025. if (CheckIfAllFramesHere()) {
  1026. mHasBeenInitialized = true;
  1027. ResetList(true);
  1028. }
  1029. }
  1030. }
  1031. return NS_OK;
  1032. }
  1033. NS_IMETHODIMP
  1034. nsListControlFrame::AddOption(int32_t aIndex)
  1035. {
  1036. #ifdef DO_REFLOW_DEBUG
  1037. printf("---- Id: %d nsLCF %p Added Option %d\n", mReflowId, this, aIndex);
  1038. #endif
  1039. if (!mIsAllContentHere) {
  1040. mIsAllContentHere = mContent->IsDoneAddingChildren();
  1041. if (!mIsAllContentHere) {
  1042. mIsAllFramesHere = false;
  1043. mHasBeenInitialized = false;
  1044. } else {
  1045. mIsAllFramesHere = (aIndex == static_cast<int32_t>(GetNumberOfOptions()-1));
  1046. }
  1047. }
  1048. // Make sure we scroll to the selected option as needed
  1049. mNeedToReset = true;
  1050. if (!mHasBeenInitialized) {
  1051. return NS_OK;
  1052. }
  1053. mPostChildrenLoadedReset = mIsAllContentHere;
  1054. return NS_OK;
  1055. }
  1056. static int32_t
  1057. DecrementAndClamp(int32_t aSelectionIndex, int32_t aLength)
  1058. {
  1059. return aLength == 0 ? kNothingSelected : std::max(0, aSelectionIndex - 1);
  1060. }
  1061. NS_IMETHODIMP
  1062. nsListControlFrame::RemoveOption(int32_t aIndex)
  1063. {
  1064. NS_PRECONDITION(aIndex >= 0, "negative <option> index");
  1065. // Need to reset if we're a dropdown
  1066. if (IsInDropDownMode()) {
  1067. mNeedToReset = true;
  1068. mPostChildrenLoadedReset = mIsAllContentHere;
  1069. }
  1070. if (mStartSelectionIndex != kNothingSelected) {
  1071. NS_ASSERTION(mEndSelectionIndex != kNothingSelected, "");
  1072. int32_t numOptions = GetNumberOfOptions();
  1073. // NOTE: numOptions is the new number of options whereas aIndex is the
  1074. // unadjusted index of the removed option (hence the <= below).
  1075. NS_ASSERTION(aIndex <= numOptions, "out-of-bounds <option> index");
  1076. int32_t forward = mEndSelectionIndex - mStartSelectionIndex;
  1077. int32_t* low = forward >= 0 ? &mStartSelectionIndex : &mEndSelectionIndex;
  1078. int32_t* high = forward >= 0 ? &mEndSelectionIndex : &mStartSelectionIndex;
  1079. if (aIndex < *low)
  1080. *low = ::DecrementAndClamp(*low, numOptions);
  1081. if (aIndex <= *high)
  1082. *high = ::DecrementAndClamp(*high, numOptions);
  1083. if (forward == 0)
  1084. *low = *high;
  1085. }
  1086. else
  1087. NS_ASSERTION(mEndSelectionIndex == kNothingSelected, "");
  1088. InvalidateFocus();
  1089. return NS_OK;
  1090. }
  1091. //---------------------------------------------------------
  1092. // Set the option selected in the DOM. This method is named
  1093. // as it is because it indicates that the frame is the source
  1094. // of this event rather than the receiver.
  1095. bool
  1096. nsListControlFrame::SetOptionsSelectedFromFrame(int32_t aStartIndex,
  1097. int32_t aEndIndex,
  1098. bool aValue,
  1099. bool aClearAll)
  1100. {
  1101. RefPtr<dom::HTMLSelectElement> selectElement =
  1102. dom::HTMLSelectElement::FromContent(mContent);
  1103. uint32_t mask = dom::HTMLSelectElement::NOTIFY;
  1104. if (mForceSelection) {
  1105. mask |= dom::HTMLSelectElement::SET_DISABLED;
  1106. }
  1107. if (aValue) {
  1108. mask |= dom::HTMLSelectElement::IS_SELECTED;
  1109. }
  1110. if (aClearAll) {
  1111. mask |= dom::HTMLSelectElement::CLEAR_ALL;
  1112. }
  1113. return selectElement->SetOptionsSelectedByIndex(aStartIndex, aEndIndex, mask);
  1114. }
  1115. bool
  1116. nsListControlFrame::ToggleOptionSelectedFromFrame(int32_t aIndex)
  1117. {
  1118. RefPtr<dom::HTMLOptionElement> option =
  1119. GetOption(static_cast<uint32_t>(aIndex));
  1120. NS_ENSURE_TRUE(option, false);
  1121. RefPtr<dom::HTMLSelectElement> selectElement =
  1122. dom::HTMLSelectElement::FromContent(mContent);
  1123. uint32_t mask = dom::HTMLSelectElement::NOTIFY;
  1124. if (!option->Selected()) {
  1125. mask |= dom::HTMLSelectElement::IS_SELECTED;
  1126. }
  1127. return selectElement->SetOptionsSelectedByIndex(aIndex, aIndex, mask);
  1128. }
  1129. // Dispatch event and such
  1130. bool
  1131. nsListControlFrame::UpdateSelection()
  1132. {
  1133. if (mIsAllFramesHere) {
  1134. // if it's a combobox, display the new text
  1135. nsWeakFrame weakFrame(this);
  1136. if (mComboboxFrame) {
  1137. mComboboxFrame->RedisplaySelectedText();
  1138. // When dropdown list is open, onchange event will be fired when Enter key
  1139. // is hit or when dropdown list is dismissed.
  1140. if (mComboboxFrame->IsDroppedDown()) {
  1141. return weakFrame.IsAlive();
  1142. }
  1143. }
  1144. if (mIsAllContentHere) {
  1145. FireOnInputAndOnChange();
  1146. }
  1147. return weakFrame.IsAlive();
  1148. }
  1149. return true;
  1150. }
  1151. void
  1152. nsListControlFrame::ComboboxFinish(int32_t aIndex)
  1153. {
  1154. gLastKeyTime = 0;
  1155. if (mComboboxFrame) {
  1156. int32_t displayIndex = mComboboxFrame->GetIndexOfDisplayArea();
  1157. // Make sure we can always reset to the displayed index
  1158. mForceSelection = displayIndex == aIndex;
  1159. nsWeakFrame weakFrame(this);
  1160. PerformSelection(aIndex, false, false); // might destroy us
  1161. if (!weakFrame.IsAlive() || !mComboboxFrame) {
  1162. return;
  1163. }
  1164. if (displayIndex != aIndex) {
  1165. mComboboxFrame->RedisplaySelectedText(); // might destroy us
  1166. }
  1167. if (weakFrame.IsAlive() && mComboboxFrame) {
  1168. mComboboxFrame->RollupFromList(); // might destroy us
  1169. }
  1170. }
  1171. }
  1172. // Send out an onInput and onChange notification.
  1173. void
  1174. nsListControlFrame::FireOnInputAndOnChange()
  1175. {
  1176. if (mComboboxFrame) {
  1177. // Return hit without changing anything
  1178. int32_t index = mComboboxFrame->UpdateRecentIndex(NS_SKIP_NOTIFY_INDEX);
  1179. if (index == NS_SKIP_NOTIFY_INDEX) {
  1180. return;
  1181. }
  1182. // See if the selection actually changed
  1183. if (index == GetSelectedIndex()) {
  1184. return;
  1185. }
  1186. }
  1187. nsCOMPtr<nsIContent> content = mContent;
  1188. // Dispatch the input event.
  1189. nsContentUtils::DispatchTrustedEvent(content->OwnerDoc(), content,
  1190. NS_LITERAL_STRING("input"), true,
  1191. false);
  1192. // Dispatch the change event.
  1193. nsContentUtils::DispatchTrustedEvent(content->OwnerDoc(), content,
  1194. NS_LITERAL_STRING("change"), true,
  1195. false);
  1196. }
  1197. NS_IMETHODIMP
  1198. nsListControlFrame::OnSetSelectedIndex(int32_t aOldIndex, int32_t aNewIndex)
  1199. {
  1200. if (mComboboxFrame) {
  1201. // UpdateRecentIndex with NS_SKIP_NOTIFY_INDEX, so that we won't fire an onchange
  1202. // event for this setting of selectedIndex.
  1203. mComboboxFrame->UpdateRecentIndex(NS_SKIP_NOTIFY_INDEX);
  1204. }
  1205. nsWeakFrame weakFrame(this);
  1206. ScrollToIndex(aNewIndex);
  1207. if (!weakFrame.IsAlive()) {
  1208. return NS_OK;
  1209. }
  1210. mStartSelectionIndex = aNewIndex;
  1211. mEndSelectionIndex = aNewIndex;
  1212. InvalidateFocus();
  1213. #ifdef ACCESSIBILITY
  1214. FireMenuItemActiveEvent();
  1215. #endif
  1216. return NS_OK;
  1217. }
  1218. //----------------------------------------------------------------------
  1219. // End nsISelectControlFrame
  1220. //----------------------------------------------------------------------
  1221. nsresult
  1222. nsListControlFrame::SetFormProperty(nsIAtom* aName,
  1223. const nsAString& aValue)
  1224. {
  1225. if (nsGkAtoms::selected == aName) {
  1226. return NS_ERROR_INVALID_ARG; // Selected is readonly according to spec.
  1227. } else if (nsGkAtoms::selectedindex == aName) {
  1228. // You shouldn't be calling me for this!!!
  1229. return NS_ERROR_INVALID_ARG;
  1230. }
  1231. // We should be told about selectedIndex by the DOM element through
  1232. // OnOptionSelected
  1233. return NS_OK;
  1234. }
  1235. void
  1236. nsListControlFrame::AboutToDropDown()
  1237. {
  1238. NS_ASSERTION(IsInDropDownMode(),
  1239. "AboutToDropDown called without being in dropdown mode");
  1240. // Our widget doesn't get invalidated on changes to the rest of the document,
  1241. // so compute and store this color at the start of a dropdown so we don't
  1242. // get weird painting behaviour.
  1243. // We start looking for backgrounds above the combobox frame to avoid
  1244. // duplicating the combobox frame's background and compose each background
  1245. // color we find underneath until we have an opaque color, or run out of
  1246. // backgrounds. We compose with the PresContext default background color,
  1247. // which is always opaque, in case we don't end up with an opaque color.
  1248. // This gives us a very poor approximation of translucency.
  1249. nsIFrame* comboboxFrame = do_QueryFrame(mComboboxFrame);
  1250. nsStyleContext* context = comboboxFrame->StyleContext()->GetParent();
  1251. mLastDropdownBackstopColor = NS_RGBA(0,0,0,0);
  1252. while (NS_GET_A(mLastDropdownBackstopColor) < 255 && context) {
  1253. mLastDropdownBackstopColor =
  1254. NS_ComposeColors(context->StyleBackground()->mBackgroundColor,
  1255. mLastDropdownBackstopColor);
  1256. context = context->GetParent();
  1257. }
  1258. mLastDropdownBackstopColor =
  1259. NS_ComposeColors(PresContext()->DefaultBackgroundColor(),
  1260. mLastDropdownBackstopColor);
  1261. if (mIsAllContentHere && mIsAllFramesHere && mHasBeenInitialized) {
  1262. nsWeakFrame weakFrame(this);
  1263. ScrollToIndex(GetSelectedIndex());
  1264. if (!weakFrame.IsAlive()) {
  1265. return;
  1266. }
  1267. #ifdef ACCESSIBILITY
  1268. FireMenuItemActiveEvent(); // Inform assistive tech what got focus
  1269. #endif
  1270. }
  1271. mItemSelectionStarted = false;
  1272. mForceSelection = false;
  1273. }
  1274. // We are about to be rolledup from the outside (ComboboxFrame)
  1275. void
  1276. nsListControlFrame::AboutToRollup()
  1277. {
  1278. // We've been updating the combobox with the keyboard up until now, but not
  1279. // with the mouse. The problem is, even with mouse selection, we are
  1280. // updating the <select>. So if the mouse goes over an option just before
  1281. // he leaves the box and clicks, that's what the <select> will show.
  1282. //
  1283. // To deal with this we say "whatever is in the combobox is canonical."
  1284. // - IF the combobox is different from the current selected index, we
  1285. // reset the index.
  1286. if (IsInDropDownMode()) {
  1287. ComboboxFinish(mComboboxFrame->GetIndexOfDisplayArea()); // might destroy us
  1288. }
  1289. }
  1290. void
  1291. nsListControlFrame::DidReflow(nsPresContext* aPresContext,
  1292. const ReflowInput* aReflowInput,
  1293. nsDidReflowStatus aStatus)
  1294. {
  1295. bool wasInterrupted = !mHasPendingInterruptAtStartOfReflow &&
  1296. aPresContext->HasPendingInterrupt();
  1297. nsHTMLScrollFrame::DidReflow(aPresContext, aReflowInput, aStatus);
  1298. if (mNeedToReset && !wasInterrupted) {
  1299. mNeedToReset = false;
  1300. // Suppress scrolling to the selected element if we restored
  1301. // scroll history state AND the list contents have not changed
  1302. // since we loaded all the children AND nothing else forced us
  1303. // to scroll by calling ResetList(true). The latter two conditions
  1304. // are folded into mPostChildrenLoadedReset.
  1305. //
  1306. // The idea is that we want scroll history restoration to trump ResetList
  1307. // scrolling to the selected element, when the ResetList was probably only
  1308. // caused by content loading normally.
  1309. ResetList(!DidHistoryRestore() || mPostChildrenLoadedReset);
  1310. }
  1311. mHasPendingInterruptAtStartOfReflow = false;
  1312. }
  1313. nsIAtom*
  1314. nsListControlFrame::GetType() const
  1315. {
  1316. return nsGkAtoms::listControlFrame;
  1317. }
  1318. #ifdef DEBUG_FRAME_DUMP
  1319. nsresult
  1320. nsListControlFrame::GetFrameName(nsAString& aResult) const
  1321. {
  1322. return MakeFrameName(NS_LITERAL_STRING("ListControl"), aResult);
  1323. }
  1324. #endif
  1325. nscoord
  1326. nsListControlFrame::GetBSizeOfARow()
  1327. {
  1328. return BSizeOfARow();
  1329. }
  1330. nsresult
  1331. nsListControlFrame::IsOptionDisabled(int32_t anIndex, bool &aIsDisabled)
  1332. {
  1333. RefPtr<dom::HTMLSelectElement> sel =
  1334. dom::HTMLSelectElement::FromContent(mContent);
  1335. if (sel) {
  1336. sel->IsOptionDisabled(anIndex, &aIsDisabled);
  1337. return NS_OK;
  1338. }
  1339. return NS_ERROR_FAILURE;
  1340. }
  1341. //----------------------------------------------------------------------
  1342. // helper
  1343. //----------------------------------------------------------------------
  1344. bool
  1345. nsListControlFrame::IsLeftButton(nsIDOMEvent* aMouseEvent)
  1346. {
  1347. // only allow selection with the left button
  1348. nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent);
  1349. if (mouseEvent) {
  1350. int16_t whichButton;
  1351. if (NS_SUCCEEDED(mouseEvent->GetButton(&whichButton))) {
  1352. return whichButton != 0?false:true;
  1353. }
  1354. }
  1355. return false;
  1356. }
  1357. nscoord
  1358. nsListControlFrame::CalcFallbackRowBSize(float aFontSizeInflation)
  1359. {
  1360. RefPtr<nsFontMetrics> fontMet =
  1361. nsLayoutUtils::GetFontMetricsForFrame(this, aFontSizeInflation);
  1362. return fontMet->MaxHeight();
  1363. }
  1364. nscoord
  1365. nsListControlFrame::CalcIntrinsicBSize(nscoord aBSizeOfARow,
  1366. int32_t aNumberOfOptions)
  1367. {
  1368. NS_PRECONDITION(!IsInDropDownMode(),
  1369. "Shouldn't be in dropdown mode when we call this");
  1370. dom::HTMLSelectElement* select =
  1371. dom::HTMLSelectElement::FromContentOrNull(mContent);
  1372. if (select) {
  1373. mNumDisplayRows = select->Size();
  1374. } else {
  1375. mNumDisplayRows = 1;
  1376. }
  1377. if (mNumDisplayRows < 1) {
  1378. mNumDisplayRows = 4;
  1379. }
  1380. return mNumDisplayRows * aBSizeOfARow;
  1381. }
  1382. //----------------------------------------------------------------------
  1383. // nsIDOMMouseListener
  1384. //----------------------------------------------------------------------
  1385. nsresult
  1386. nsListControlFrame::MouseUp(nsIDOMEvent* aMouseEvent)
  1387. {
  1388. NS_ASSERTION(aMouseEvent != nullptr, "aMouseEvent is null.");
  1389. nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent);
  1390. NS_ENSURE_TRUE(mouseEvent, NS_ERROR_FAILURE);
  1391. UpdateInListState(aMouseEvent);
  1392. mButtonDown = false;
  1393. EventStates eventStates = mContent->AsElement()->State();
  1394. if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
  1395. return NS_OK;
  1396. }
  1397. // only allow selection with the left button
  1398. // if a right button click is on the combobox itself
  1399. // or on the select when in listbox mode, then let the click through
  1400. if (!IsLeftButton(aMouseEvent)) {
  1401. if (IsInDropDownMode()) {
  1402. if (!IgnoreMouseEventForSelection(aMouseEvent)) {
  1403. aMouseEvent->PreventDefault();
  1404. aMouseEvent->StopPropagation();
  1405. } else {
  1406. CaptureMouseEvents(false);
  1407. return NS_OK;
  1408. }
  1409. CaptureMouseEvents(false);
  1410. return NS_ERROR_FAILURE; // means consume event
  1411. } else {
  1412. CaptureMouseEvents(false);
  1413. return NS_OK;
  1414. }
  1415. }
  1416. const nsStyleVisibility* vis = StyleVisibility();
  1417. if (!vis->IsVisible()) {
  1418. return NS_OK;
  1419. }
  1420. if (IsInDropDownMode()) {
  1421. // XXX This is a bit of a hack, but.....
  1422. // But the idea here is to make sure you get an "onclick" event when you mouse
  1423. // down on the select and the drag over an option and let go
  1424. // And then NOT get an "onclick" event when when you click down on the select
  1425. // and then up outside of the select
  1426. // the EventStateManager tracks the content of the mouse down and the mouse up
  1427. // to make sure they are the same, and the onclick is sent in the PostHandleEvent
  1428. // depeneding on whether the clickCount is non-zero.
  1429. // So we cheat here by either setting or unsetting the clcikCount in the native event
  1430. // so the right thing happens for the onclick event
  1431. WidgetMouseEvent* mouseEvent =
  1432. aMouseEvent->WidgetEventPtr()->AsMouseEvent();
  1433. int32_t selectedIndex;
  1434. if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) {
  1435. // If it's disabled, disallow the click and leave.
  1436. bool isDisabled = false;
  1437. IsOptionDisabled(selectedIndex, isDisabled);
  1438. if (isDisabled) {
  1439. aMouseEvent->PreventDefault();
  1440. aMouseEvent->StopPropagation();
  1441. CaptureMouseEvents(false);
  1442. return NS_ERROR_FAILURE;
  1443. }
  1444. if (kNothingSelected != selectedIndex) {
  1445. nsWeakFrame weakFrame(this);
  1446. ComboboxFinish(selectedIndex);
  1447. if (!weakFrame.IsAlive()) {
  1448. return NS_OK;
  1449. }
  1450. FireOnInputAndOnChange();
  1451. }
  1452. mouseEvent->mClickCount = 1;
  1453. } else {
  1454. // the click was out side of the select or its dropdown
  1455. mouseEvent->mClickCount =
  1456. IgnoreMouseEventForSelection(aMouseEvent) ? 1 : 0;
  1457. }
  1458. } else {
  1459. CaptureMouseEvents(false);
  1460. // Notify
  1461. if (mChangesSinceDragStart) {
  1462. // reset this so that future MouseUps without a prior MouseDown
  1463. // won't fire onchange
  1464. mChangesSinceDragStart = false;
  1465. FireOnInputAndOnChange();
  1466. }
  1467. }
  1468. return NS_OK;
  1469. }
  1470. void
  1471. nsListControlFrame::UpdateInListState(nsIDOMEvent* aEvent)
  1472. {
  1473. if (!mComboboxFrame || !mComboboxFrame->IsDroppedDown())
  1474. return;
  1475. nsPoint pt = nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(aEvent, this);
  1476. nsRect borderInnerEdge = GetScrollPortRect();
  1477. if (pt.y >= borderInnerEdge.y && pt.y < borderInnerEdge.YMost()) {
  1478. mItemSelectionStarted = true;
  1479. }
  1480. }
  1481. bool nsListControlFrame::IgnoreMouseEventForSelection(nsIDOMEvent* aEvent)
  1482. {
  1483. if (!mComboboxFrame)
  1484. return false;
  1485. // Our DOM listener does get called when the dropdown is not
  1486. // showing, because it listens to events on the SELECT element
  1487. if (!mComboboxFrame->IsDroppedDown())
  1488. return true;
  1489. return !mItemSelectionStarted;
  1490. }
  1491. #ifdef ACCESSIBILITY
  1492. void
  1493. nsListControlFrame::FireMenuItemActiveEvent()
  1494. {
  1495. if (mFocused != this && !IsInDropDownMode()) {
  1496. return;
  1497. }
  1498. nsCOMPtr<nsIContent> optionContent = GetCurrentOption();
  1499. if (!optionContent) {
  1500. return;
  1501. }
  1502. FireDOMEvent(NS_LITERAL_STRING("DOMMenuItemActive"), optionContent);
  1503. }
  1504. #endif
  1505. nsresult
  1506. nsListControlFrame::GetIndexFromDOMEvent(nsIDOMEvent* aMouseEvent,
  1507. int32_t& aCurIndex)
  1508. {
  1509. if (IgnoreMouseEventForSelection(aMouseEvent))
  1510. return NS_ERROR_FAILURE;
  1511. if (nsIPresShell::GetCapturingContent() != mContent) {
  1512. // If we're not capturing, then ignore movement in the border
  1513. nsPoint pt = nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(aMouseEvent, this);
  1514. nsRect borderInnerEdge = GetScrollPortRect();
  1515. if (!borderInnerEdge.Contains(pt)) {
  1516. return NS_ERROR_FAILURE;
  1517. }
  1518. }
  1519. RefPtr<dom::HTMLOptionElement> option;
  1520. for (nsCOMPtr<nsIContent> content =
  1521. PresContext()->EventStateManager()->GetEventTargetContent(nullptr);
  1522. content && !option;
  1523. content = content->GetParent()) {
  1524. option = dom::HTMLOptionElement::FromContent(content);
  1525. }
  1526. if (option) {
  1527. aCurIndex = option->Index();
  1528. MOZ_ASSERT(aCurIndex >= 0);
  1529. return NS_OK;
  1530. }
  1531. return NS_ERROR_FAILURE;
  1532. }
  1533. static bool
  1534. FireShowDropDownEvent(nsIContent* aContent, bool aShow, bool aIsSourceTouchEvent)
  1535. {
  1536. if (XRE_IsContentProcess() &&
  1537. Preferences::GetBool("browser.tabs.remote.desktopbehavior", false)) {
  1538. nsString eventName;
  1539. if (aShow) {
  1540. eventName = aIsSourceTouchEvent ? NS_LITERAL_STRING("mozshowdropdown-sourcetouch") :
  1541. NS_LITERAL_STRING("mozshowdropdown");
  1542. } else {
  1543. eventName = NS_LITERAL_STRING("mozhidedropdown");
  1544. }
  1545. nsContentUtils::DispatchChromeEvent(aContent->OwnerDoc(), aContent,
  1546. eventName, true, false);
  1547. return true;
  1548. }
  1549. return false;
  1550. }
  1551. nsresult
  1552. nsListControlFrame::MouseDown(nsIDOMEvent* aMouseEvent)
  1553. {
  1554. NS_ASSERTION(aMouseEvent != nullptr, "aMouseEvent is null.");
  1555. nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent);
  1556. NS_ENSURE_TRUE(mouseEvent, NS_ERROR_FAILURE);
  1557. UpdateInListState(aMouseEvent);
  1558. EventStates eventStates = mContent->AsElement()->State();
  1559. if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
  1560. return NS_OK;
  1561. }
  1562. // only allow selection with the left button
  1563. // if a right button click is on the combobox itself
  1564. // or on the select when in listbox mode, then let the click through
  1565. if (!IsLeftButton(aMouseEvent)) {
  1566. if (IsInDropDownMode()) {
  1567. if (!IgnoreMouseEventForSelection(aMouseEvent)) {
  1568. aMouseEvent->PreventDefault();
  1569. aMouseEvent->StopPropagation();
  1570. } else {
  1571. return NS_OK;
  1572. }
  1573. return NS_ERROR_FAILURE; // means consume event
  1574. } else {
  1575. return NS_OK;
  1576. }
  1577. }
  1578. int32_t selectedIndex;
  1579. if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) {
  1580. // Handle Like List
  1581. mButtonDown = true;
  1582. CaptureMouseEvents(true);
  1583. nsWeakFrame weakFrame(this);
  1584. bool change =
  1585. HandleListSelection(aMouseEvent, selectedIndex); // might destroy us
  1586. if (!weakFrame.IsAlive()) {
  1587. return NS_OK;
  1588. }
  1589. mChangesSinceDragStart = change;
  1590. } else {
  1591. // NOTE: the combo box is responsible for dropping it down
  1592. if (mComboboxFrame) {
  1593. // Ignore the click that occurs on the option element when one is
  1594. // selected from the parent process popup.
  1595. if (mComboboxFrame->IsOpenInParentProcess()) {
  1596. nsCOMPtr<nsIDOMEventTarget> etarget;
  1597. aMouseEvent->GetTarget(getter_AddRefs(etarget));
  1598. nsCOMPtr<nsIDOMHTMLOptionElement> option = do_QueryInterface(etarget);
  1599. if (option) {
  1600. return NS_OK;
  1601. }
  1602. }
  1603. uint16_t inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
  1604. if (NS_FAILED(mouseEvent->GetMozInputSource(&inputSource))) {
  1605. return NS_ERROR_FAILURE;
  1606. }
  1607. bool isSourceTouchEvent = inputSource == nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
  1608. if (FireShowDropDownEvent(mContent, !mComboboxFrame->IsDroppedDownOrHasParentPopup(),
  1609. isSourceTouchEvent)) {
  1610. return NS_OK;
  1611. }
  1612. if (!IgnoreMouseEventForSelection(aMouseEvent)) {
  1613. return NS_OK;
  1614. }
  1615. if (!nsComboboxControlFrame::ToolkitHasNativePopup())
  1616. {
  1617. bool isDroppedDown = mComboboxFrame->IsDroppedDown();
  1618. nsIFrame* comboFrame = do_QueryFrame(mComboboxFrame);
  1619. nsWeakFrame weakFrame(comboFrame);
  1620. mComboboxFrame->ShowDropDown(!isDroppedDown);
  1621. if (!weakFrame.IsAlive())
  1622. return NS_OK;
  1623. if (isDroppedDown) {
  1624. CaptureMouseEvents(false);
  1625. }
  1626. }
  1627. }
  1628. }
  1629. return NS_OK;
  1630. }
  1631. //----------------------------------------------------------------------
  1632. // nsIDOMMouseMotionListener
  1633. //----------------------------------------------------------------------
  1634. nsresult
  1635. nsListControlFrame::MouseMove(nsIDOMEvent* aMouseEvent)
  1636. {
  1637. NS_ASSERTION(aMouseEvent, "aMouseEvent is null.");
  1638. nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent);
  1639. NS_ENSURE_TRUE(mouseEvent, NS_ERROR_FAILURE);
  1640. UpdateInListState(aMouseEvent);
  1641. if (IsInDropDownMode()) {
  1642. if (mComboboxFrame->IsDroppedDown()) {
  1643. int32_t selectedIndex;
  1644. if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) {
  1645. PerformSelection(selectedIndex, false, false); // might destroy us
  1646. }
  1647. }
  1648. } else {// XXX - temporary until we get drag events
  1649. if (mButtonDown) {
  1650. return DragMove(aMouseEvent); // might destroy us
  1651. }
  1652. }
  1653. return NS_OK;
  1654. }
  1655. nsresult
  1656. nsListControlFrame::DragMove(nsIDOMEvent* aMouseEvent)
  1657. {
  1658. NS_ASSERTION(aMouseEvent, "aMouseEvent is null.");
  1659. UpdateInListState(aMouseEvent);
  1660. if (!IsInDropDownMode()) {
  1661. int32_t selectedIndex;
  1662. if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) {
  1663. // Don't waste cycles if we already dragged over this item
  1664. if (selectedIndex == mEndSelectionIndex) {
  1665. return NS_OK;
  1666. }
  1667. nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent);
  1668. NS_ASSERTION(mouseEvent, "aMouseEvent is not an nsIDOMMouseEvent!");
  1669. bool isControl;
  1670. mouseEvent->GetCtrlKey(&isControl);
  1671. nsWeakFrame weakFrame(this);
  1672. // Turn SHIFT on when you are dragging, unless control is on.
  1673. bool wasChanged = PerformSelection(selectedIndex,
  1674. !isControl, isControl);
  1675. if (!weakFrame.IsAlive()) {
  1676. return NS_OK;
  1677. }
  1678. mChangesSinceDragStart = mChangesSinceDragStart || wasChanged;
  1679. }
  1680. }
  1681. return NS_OK;
  1682. }
  1683. //----------------------------------------------------------------------
  1684. // Scroll helpers.
  1685. //----------------------------------------------------------------------
  1686. void
  1687. nsListControlFrame::ScrollToIndex(int32_t aIndex)
  1688. {
  1689. if (aIndex < 0) {
  1690. // XXX shouldn't we just do nothing if we're asked to scroll to
  1691. // kNothingSelected?
  1692. ScrollTo(nsPoint(0, 0), nsIScrollableFrame::INSTANT);
  1693. } else {
  1694. RefPtr<dom::HTMLOptionElement> option =
  1695. GetOption(AssertedCast<uint32_t>(aIndex));
  1696. if (option) {
  1697. ScrollToFrame(*option);
  1698. }
  1699. }
  1700. }
  1701. void
  1702. nsListControlFrame::ScrollToFrame(dom::HTMLOptionElement& aOptElement)
  1703. {
  1704. // otherwise we find the content's frame and scroll to it
  1705. nsIFrame* childFrame = aOptElement.GetPrimaryFrame();
  1706. if (childFrame) {
  1707. PresContext()->PresShell()->
  1708. ScrollFrameRectIntoView(childFrame,
  1709. nsRect(nsPoint(0, 0), childFrame->GetSize()),
  1710. nsIPresShell::ScrollAxis(), nsIPresShell::ScrollAxis(),
  1711. nsIPresShell::SCROLL_OVERFLOW_HIDDEN |
  1712. nsIPresShell::SCROLL_FIRST_ANCESTOR_ONLY);
  1713. }
  1714. }
  1715. //---------------------------------------------------------------------
  1716. // Ok, the entire idea of this routine is to move to the next item that
  1717. // is suppose to be selected. If the item is disabled then we search in
  1718. // the same direction looking for the next item to select. If we run off
  1719. // the end of the list then we start at the end of the list and search
  1720. // backwards until we get back to the original item or an enabled option
  1721. //
  1722. // aStartIndex - the index to start searching from
  1723. // aNewIndex - will get set to the new index if it finds one
  1724. // aNumOptions - the total number of options in the list
  1725. // aDoAdjustInc - the initial increment 1-n
  1726. // aDoAdjustIncNext - the increment used to search for the next enabled option
  1727. //
  1728. // the aDoAdjustInc could be a "1" for a single item or
  1729. // any number greater representing a page of items
  1730. //
  1731. void
  1732. nsListControlFrame::AdjustIndexForDisabledOpt(int32_t aStartIndex,
  1733. int32_t &aNewIndex,
  1734. int32_t aNumOptions,
  1735. int32_t aDoAdjustInc,
  1736. int32_t aDoAdjustIncNext)
  1737. {
  1738. // Cannot select anything if there is nothing to select
  1739. if (aNumOptions == 0) {
  1740. aNewIndex = kNothingSelected;
  1741. return;
  1742. }
  1743. // means we reached the end of the list and now we are searching backwards
  1744. bool doingReverse = false;
  1745. // lowest index in the search range
  1746. int32_t bottom = 0;
  1747. // highest index in the search range
  1748. int32_t top = aNumOptions;
  1749. // Start off keyboard options at selectedIndex if nothing else is defaulted to
  1750. //
  1751. // XXX Perhaps this should happen for mouse too, to start off shift click
  1752. // automatically in multiple ... to do this, we'd need to override
  1753. // OnOptionSelected and set mStartSelectedIndex if nothing is selected. Not
  1754. // sure of the effects, though, so I'm not doing it just yet.
  1755. int32_t startIndex = aStartIndex;
  1756. if (startIndex < bottom) {
  1757. startIndex = GetSelectedIndex();
  1758. }
  1759. int32_t newIndex = startIndex + aDoAdjustInc;
  1760. // make sure we start off in the range
  1761. if (newIndex < bottom) {
  1762. newIndex = 0;
  1763. } else if (newIndex >= top) {
  1764. newIndex = aNumOptions-1;
  1765. }
  1766. while (1) {
  1767. // if the newIndex isn't disabled, we are golden, bail out
  1768. bool isDisabled = true;
  1769. if (NS_SUCCEEDED(IsOptionDisabled(newIndex, isDisabled)) && !isDisabled) {
  1770. break;
  1771. }
  1772. // it WAS disabled, so sart looking ahead for the next enabled option
  1773. newIndex += aDoAdjustIncNext;
  1774. // well, if we reach end reverse the search
  1775. if (newIndex < bottom) {
  1776. if (doingReverse) {
  1777. return; // if we are in reverse mode and reach the end bail out
  1778. } else {
  1779. // reset the newIndex to the end of the list we hit
  1780. // reverse the incrementer
  1781. // set the other end of the list to our original starting index
  1782. newIndex = bottom;
  1783. aDoAdjustIncNext = 1;
  1784. doingReverse = true;
  1785. top = startIndex;
  1786. }
  1787. } else if (newIndex >= top) {
  1788. if (doingReverse) {
  1789. return; // if we are in reverse mode and reach the end bail out
  1790. } else {
  1791. // reset the newIndex to the end of the list we hit
  1792. // reverse the incrementer
  1793. // set the other end of the list to our original starting index
  1794. newIndex = top - 1;
  1795. aDoAdjustIncNext = -1;
  1796. doingReverse = true;
  1797. bottom = startIndex;
  1798. }
  1799. }
  1800. }
  1801. // Looks like we found one
  1802. aNewIndex = newIndex;
  1803. }
  1804. nsAString&
  1805. nsListControlFrame::GetIncrementalString()
  1806. {
  1807. if (sIncrementalString == nullptr)
  1808. sIncrementalString = new nsString();
  1809. return *sIncrementalString;
  1810. }
  1811. void
  1812. nsListControlFrame::Shutdown()
  1813. {
  1814. delete sIncrementalString;
  1815. sIncrementalString = nullptr;
  1816. }
  1817. void
  1818. nsListControlFrame::DropDownToggleKey(nsIDOMEvent* aKeyEvent)
  1819. {
  1820. // Cocoa widgets do native popups, so don't try to show
  1821. // dropdowns there.
  1822. if (IsInDropDownMode() && !nsComboboxControlFrame::ToolkitHasNativePopup()) {
  1823. aKeyEvent->PreventDefault();
  1824. if (!mComboboxFrame->IsDroppedDown()) {
  1825. if (!FireShowDropDownEvent(mContent, true, false)) {
  1826. mComboboxFrame->ShowDropDown(true);
  1827. }
  1828. } else {
  1829. nsWeakFrame weakFrame(this);
  1830. // mEndSelectionIndex is the last item that got selected.
  1831. ComboboxFinish(mEndSelectionIndex);
  1832. if (weakFrame.IsAlive()) {
  1833. FireOnInputAndOnChange();
  1834. }
  1835. }
  1836. }
  1837. }
  1838. nsresult
  1839. nsListControlFrame::KeyDown(nsIDOMEvent* aKeyEvent)
  1840. {
  1841. MOZ_ASSERT(aKeyEvent, "aKeyEvent is null.");
  1842. EventStates eventStates = mContent->AsElement()->State();
  1843. if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
  1844. return NS_OK;
  1845. }
  1846. AutoIncrementalSearchResetter incrementalSearchResetter;
  1847. // Don't check defaultPrevented value because other browsers don't prevent
  1848. // the key navigation of list control even if preventDefault() is called.
  1849. // XXXmats 2015-04-16: the above is not true anymore, Chrome prevents all
  1850. // XXXmats keyboard events, even tabbing, when preventDefault() is called
  1851. // XXXmats in onkeydown. That seems sub-optimal though.
  1852. const WidgetKeyboardEvent* keyEvent =
  1853. aKeyEvent->WidgetEventPtr()->AsKeyboardEvent();
  1854. MOZ_ASSERT(keyEvent,
  1855. "DOM event must have WidgetKeyboardEvent for its internal event");
  1856. bool dropDownMenuOnUpDown;
  1857. bool dropDownMenuOnSpace;
  1858. dropDownMenuOnUpDown = keyEvent->IsAlt();
  1859. dropDownMenuOnSpace = IsInDropDownMode() && !mComboboxFrame->IsDroppedDown();
  1860. bool withinIncrementalSearchTime =
  1861. keyEvent->mTime - gLastKeyTime <= INCREMENTAL_SEARCH_KEYPRESS_TIME;
  1862. if ((dropDownMenuOnUpDown &&
  1863. (keyEvent->mKeyCode == NS_VK_UP || keyEvent->mKeyCode == NS_VK_DOWN)) ||
  1864. (dropDownMenuOnSpace && keyEvent->mKeyCode == NS_VK_SPACE &&
  1865. !withinIncrementalSearchTime)) {
  1866. DropDownToggleKey(aKeyEvent);
  1867. if (keyEvent->DefaultPrevented()) {
  1868. return NS_OK;
  1869. }
  1870. }
  1871. if (keyEvent->IsAlt()) {
  1872. return NS_OK;
  1873. }
  1874. // now make sure there are options or we are wasting our time
  1875. RefPtr<dom::HTMLOptionsCollection> options = GetOptions();
  1876. NS_ENSURE_TRUE(options, NS_ERROR_FAILURE);
  1877. uint32_t numOptions = options->Length();
  1878. // this is the new index to set
  1879. int32_t newIndex = kNothingSelected;
  1880. bool isControlOrMeta = (keyEvent->IsControl() || keyEvent->IsMeta());
  1881. // Don't try to handle multiple-select pgUp/pgDown in single-select lists.
  1882. if (isControlOrMeta && !GetMultiple() &&
  1883. (keyEvent->mKeyCode == NS_VK_PAGE_UP ||
  1884. keyEvent->mKeyCode == NS_VK_PAGE_DOWN)) {
  1885. return NS_OK;
  1886. }
  1887. if (isControlOrMeta && (keyEvent->mKeyCode == NS_VK_UP ||
  1888. keyEvent->mKeyCode == NS_VK_LEFT ||
  1889. keyEvent->mKeyCode == NS_VK_DOWN ||
  1890. keyEvent->mKeyCode == NS_VK_RIGHT ||
  1891. keyEvent->mKeyCode == NS_VK_HOME ||
  1892. keyEvent->mKeyCode == NS_VK_END)) {
  1893. // Don't go into multiple-select mode unless this list can handle it.
  1894. isControlOrMeta = mControlSelectMode = GetMultiple();
  1895. } else if (keyEvent->mKeyCode != NS_VK_SPACE) {
  1896. mControlSelectMode = false;
  1897. }
  1898. switch (keyEvent->mKeyCode) {
  1899. case NS_VK_UP:
  1900. case NS_VK_LEFT:
  1901. AdjustIndexForDisabledOpt(mEndSelectionIndex, newIndex,
  1902. static_cast<int32_t>(numOptions),
  1903. -1, -1);
  1904. break;
  1905. case NS_VK_DOWN:
  1906. case NS_VK_RIGHT:
  1907. AdjustIndexForDisabledOpt(mEndSelectionIndex, newIndex,
  1908. static_cast<int32_t>(numOptions),
  1909. 1, 1);
  1910. break;
  1911. case NS_VK_RETURN:
  1912. if (IsInDropDownMode()) {
  1913. if (mComboboxFrame->IsDroppedDown()) {
  1914. // If the select element is a dropdown style, Enter key should be
  1915. // consumed while the dropdown is open for security.
  1916. aKeyEvent->PreventDefault();
  1917. nsWeakFrame weakFrame(this);
  1918. ComboboxFinish(mEndSelectionIndex);
  1919. if (!weakFrame.IsAlive()) {
  1920. return NS_OK;
  1921. }
  1922. }
  1923. FireOnInputAndOnChange();
  1924. return NS_OK;
  1925. }
  1926. // If this is single select listbox, Enter key doesn't cause anything.
  1927. if (!GetMultiple()) {
  1928. return NS_OK;
  1929. }
  1930. newIndex = mEndSelectionIndex;
  1931. break;
  1932. case NS_VK_ESCAPE: {
  1933. // If the select element is a listbox style, Escape key causes nothing.
  1934. if (!IsInDropDownMode()) {
  1935. return NS_OK;
  1936. }
  1937. AboutToRollup();
  1938. // If the select element is a dropdown style, Enter key should be
  1939. // consumed everytime since Escape key may be pressed accidentally after
  1940. // the dropdown is closed by Escepe key.
  1941. aKeyEvent->PreventDefault();
  1942. return NS_OK;
  1943. }
  1944. case NS_VK_PAGE_UP: {
  1945. int32_t itemsPerPage =
  1946. std::max(1, static_cast<int32_t>(mNumDisplayRows - 1));
  1947. AdjustIndexForDisabledOpt(mEndSelectionIndex, newIndex,
  1948. static_cast<int32_t>(numOptions),
  1949. -itemsPerPage, -1);
  1950. break;
  1951. }
  1952. case NS_VK_PAGE_DOWN: {
  1953. int32_t itemsPerPage =
  1954. std::max(1, static_cast<int32_t>(mNumDisplayRows - 1));
  1955. AdjustIndexForDisabledOpt(mEndSelectionIndex, newIndex,
  1956. static_cast<int32_t>(numOptions),
  1957. itemsPerPage, 1);
  1958. break;
  1959. }
  1960. case NS_VK_HOME:
  1961. AdjustIndexForDisabledOpt(0, newIndex,
  1962. static_cast<int32_t>(numOptions),
  1963. 0, 1);
  1964. break;
  1965. case NS_VK_END:
  1966. AdjustIndexForDisabledOpt(static_cast<int32_t>(numOptions) - 1, newIndex,
  1967. static_cast<int32_t>(numOptions),
  1968. 0, -1);
  1969. break;
  1970. #if defined(XP_WIN)
  1971. case NS_VK_F4:
  1972. if (!isControlOrMeta) {
  1973. DropDownToggleKey(aKeyEvent);
  1974. }
  1975. return NS_OK;
  1976. #endif
  1977. default: // printable key will be handled by keypress event.
  1978. incrementalSearchResetter.Cancel();
  1979. return NS_OK;
  1980. }
  1981. aKeyEvent->PreventDefault();
  1982. // Actually process the new index and let the selection code
  1983. // do the scrolling for us
  1984. PostHandleKeyEvent(newIndex, 0, keyEvent->IsShift(), isControlOrMeta);
  1985. return NS_OK;
  1986. }
  1987. nsresult
  1988. nsListControlFrame::KeyPress(nsIDOMEvent* aKeyEvent)
  1989. {
  1990. MOZ_ASSERT(aKeyEvent, "aKeyEvent is null.");
  1991. EventStates eventStates = mContent->AsElement()->State();
  1992. if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
  1993. return NS_OK;
  1994. }
  1995. AutoIncrementalSearchResetter incrementalSearchResetter;
  1996. const WidgetKeyboardEvent* keyEvent =
  1997. aKeyEvent->WidgetEventPtr()->AsKeyboardEvent();
  1998. MOZ_ASSERT(keyEvent,
  1999. "DOM event must have WidgetKeyboardEvent for its internal event");
  2000. // Select option with this as the first character
  2001. // XXX Not I18N compliant
  2002. // Don't do incremental search if the key event has already consumed.
  2003. if (keyEvent->DefaultPrevented()) {
  2004. return NS_OK;
  2005. }
  2006. if (keyEvent->IsAlt()) {
  2007. return NS_OK;
  2008. }
  2009. // With some keyboard layout, space key causes non-ASCII space.
  2010. // So, the check in keydown event handler isn't enough, we need to check it
  2011. // again with keypress event.
  2012. if (keyEvent->mCharCode != ' ') {
  2013. mControlSelectMode = false;
  2014. }
  2015. bool isControlOrMeta = (keyEvent->IsControl() || keyEvent->IsMeta());
  2016. if (isControlOrMeta && keyEvent->mCharCode != ' ') {
  2017. return NS_OK;
  2018. }
  2019. // NOTE: If mKeyCode of keypress event is not 0, mCharCode is always 0.
  2020. // Therefore, all non-printable keys are not handled after this block.
  2021. if (!keyEvent->mCharCode) {
  2022. // Backspace key will delete the last char in the string. Otherwise,
  2023. // non-printable keypress should reset incremental search.
  2024. if (keyEvent->mKeyCode == NS_VK_BACK) {
  2025. incrementalSearchResetter.Cancel();
  2026. if (!GetIncrementalString().IsEmpty()) {
  2027. GetIncrementalString().Truncate(GetIncrementalString().Length() - 1);
  2028. }
  2029. aKeyEvent->PreventDefault();
  2030. } else {
  2031. // XXX When a select element has focus, even if the key causes nothing,
  2032. // it might be better to call preventDefault() here because nobody
  2033. // should expect one of other elements including chrome handles the
  2034. // key event.
  2035. }
  2036. return NS_OK;
  2037. }
  2038. incrementalSearchResetter.Cancel();
  2039. // We ate the key if we got this far.
  2040. aKeyEvent->PreventDefault();
  2041. // XXX Why don't we check/modify timestamp first?
  2042. // Incremental Search: if time elapsed is below
  2043. // INCREMENTAL_SEARCH_KEYPRESS_TIME, append this keystroke to the search
  2044. // string we will use to find options and start searching at the current
  2045. // keystroke. Otherwise, Truncate the string if it's been a long time
  2046. // since our last keypress.
  2047. if (keyEvent->mTime - gLastKeyTime > INCREMENTAL_SEARCH_KEYPRESS_TIME) {
  2048. // If this is ' ' and we are at the beginning of the string, treat it as
  2049. // "select this option" (bug 191543)
  2050. if (keyEvent->mCharCode == ' ') {
  2051. // Actually process the new index and let the selection code
  2052. // do the scrolling for us
  2053. PostHandleKeyEvent(mEndSelectionIndex, keyEvent->mCharCode,
  2054. keyEvent->IsShift(), isControlOrMeta);
  2055. return NS_OK;
  2056. }
  2057. GetIncrementalString().Truncate();
  2058. }
  2059. gLastKeyTime = keyEvent->mTime;
  2060. // Append this keystroke to the search string.
  2061. char16_t uniChar = ToLowerCase(static_cast<char16_t>(keyEvent->mCharCode));
  2062. GetIncrementalString().Append(uniChar);
  2063. // See bug 188199, if all letters in incremental string are same, just try to
  2064. // match the first one
  2065. nsAutoString incrementalString(GetIncrementalString());
  2066. uint32_t charIndex = 1, stringLength = incrementalString.Length();
  2067. while (charIndex < stringLength &&
  2068. incrementalString[charIndex] == incrementalString[charIndex - 1]) {
  2069. charIndex++;
  2070. }
  2071. if (charIndex == stringLength) {
  2072. incrementalString.Truncate(1);
  2073. stringLength = 1;
  2074. }
  2075. // Determine where we're going to start reading the string
  2076. // If we have multiple characters to look for, we start looking *at* the
  2077. // current option. If we have only one character to look for, we start
  2078. // looking *after* the current option.
  2079. // Exception: if there is no option selected to start at, we always start
  2080. // *at* 0.
  2081. int32_t startIndex = GetSelectedIndex();
  2082. if (startIndex == kNothingSelected) {
  2083. startIndex = 0;
  2084. } else if (stringLength == 1) {
  2085. startIndex++;
  2086. }
  2087. // now make sure there are options or we are wasting our time
  2088. RefPtr<dom::HTMLOptionsCollection> options = GetOptions();
  2089. NS_ENSURE_TRUE(options, NS_ERROR_FAILURE);
  2090. uint32_t numOptions = options->Length();
  2091. nsWeakFrame weakFrame(this);
  2092. for (uint32_t i = 0; i < numOptions; ++i) {
  2093. uint32_t index = (i + startIndex) % numOptions;
  2094. RefPtr<dom::HTMLOptionElement> optionElement =
  2095. options->ItemAsOption(index);
  2096. if (!optionElement || !optionElement->GetPrimaryFrame()) {
  2097. continue;
  2098. }
  2099. nsAutoString text;
  2100. if (NS_FAILED(optionElement->GetText(text)) ||
  2101. !StringBeginsWith(
  2102. nsContentUtils::TrimWhitespace<
  2103. nsContentUtils::IsHTMLWhitespaceOrNBSP>(text, false),
  2104. incrementalString, nsCaseInsensitiveStringComparator())) {
  2105. continue;
  2106. }
  2107. bool wasChanged = PerformSelection(index, keyEvent->IsShift(), isControlOrMeta);
  2108. if (!weakFrame.IsAlive()) {
  2109. return NS_OK;
  2110. }
  2111. if (!wasChanged) {
  2112. break;
  2113. }
  2114. // If UpdateSelection() returns false, that means the frame is no longer
  2115. // alive. We should stop doing anything.
  2116. if (!UpdateSelection()) {
  2117. return NS_OK;
  2118. }
  2119. break;
  2120. }
  2121. return NS_OK;
  2122. }
  2123. void
  2124. nsListControlFrame::PostHandleKeyEvent(int32_t aNewIndex,
  2125. uint32_t aCharCode,
  2126. bool aIsShift,
  2127. bool aIsControlOrMeta)
  2128. {
  2129. if (aNewIndex == kNothingSelected) {
  2130. return;
  2131. }
  2132. // If you hold control, but not shift, no key will actually do anything
  2133. // except space.
  2134. nsWeakFrame weakFrame(this);
  2135. bool wasChanged = false;
  2136. if (aIsControlOrMeta && !aIsShift && aCharCode != ' ') {
  2137. mStartSelectionIndex = aNewIndex;
  2138. mEndSelectionIndex = aNewIndex;
  2139. InvalidateFocus();
  2140. ScrollToIndex(aNewIndex);
  2141. if (!weakFrame.IsAlive()) {
  2142. return;
  2143. }
  2144. #ifdef ACCESSIBILITY
  2145. FireMenuItemActiveEvent();
  2146. #endif
  2147. } else if (mControlSelectMode && aCharCode == ' ') {
  2148. wasChanged = SingleSelection(aNewIndex, true);
  2149. } else {
  2150. wasChanged = PerformSelection(aNewIndex, aIsShift, aIsControlOrMeta);
  2151. }
  2152. if (wasChanged && weakFrame.IsAlive()) {
  2153. // dispatch event, update combobox, etc.
  2154. UpdateSelection();
  2155. }
  2156. }
  2157. /******************************************************************************
  2158. * nsListEventListener
  2159. *****************************************************************************/
  2160. NS_IMPL_ISUPPORTS(nsListEventListener, nsIDOMEventListener)
  2161. NS_IMETHODIMP
  2162. nsListEventListener::HandleEvent(nsIDOMEvent* aEvent)
  2163. {
  2164. if (!mFrame)
  2165. return NS_OK;
  2166. nsAutoString eventType;
  2167. aEvent->GetType(eventType);
  2168. if (eventType.EqualsLiteral("keydown")) {
  2169. return mFrame->nsListControlFrame::KeyDown(aEvent);
  2170. }
  2171. if (eventType.EqualsLiteral("keypress")) {
  2172. return mFrame->nsListControlFrame::KeyPress(aEvent);
  2173. }
  2174. if (eventType.EqualsLiteral("mousedown")) {
  2175. bool defaultPrevented = false;
  2176. aEvent->GetDefaultPrevented(&defaultPrevented);
  2177. if (defaultPrevented) {
  2178. return NS_OK;
  2179. }
  2180. return mFrame->nsListControlFrame::MouseDown(aEvent);
  2181. }
  2182. if (eventType.EqualsLiteral("mouseup")) {
  2183. // Don't try to honor defaultPrevented here - it's not web compatible.
  2184. // (bug 1194733)
  2185. return mFrame->nsListControlFrame::MouseUp(aEvent);
  2186. }
  2187. if (eventType.EqualsLiteral("mousemove")) {
  2188. // I don't think we want to honor defaultPrevented on mousemove
  2189. // in general, and it would only prevent highlighting here.
  2190. return mFrame->nsListControlFrame::MouseMove(aEvent);
  2191. }
  2192. NS_ABORT();
  2193. return NS_OK;
  2194. }