nsRangeFrame.cpp 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945
  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 "nsRangeFrame.h"
  6. #include "mozilla/EventStates.h"
  7. #include "mozilla/TouchEvents.h"
  8. #include "nsContentCreatorFunctions.h"
  9. #include "nsContentList.h"
  10. #include "nsContentUtils.h"
  11. #include "nsCSSPseudoElements.h"
  12. #include "nsCSSRendering.h"
  13. #include "nsFormControlFrame.h"
  14. #include "nsIContent.h"
  15. #include "nsIDocument.h"
  16. #include "nsNameSpaceManager.h"
  17. #include "nsIPresShell.h"
  18. #include "nsGkAtoms.h"
  19. #include "mozilla/dom/HTMLInputElement.h"
  20. #include "nsPresContext.h"
  21. #include "nsNodeInfoManager.h"
  22. #include "nsRenderingContext.h"
  23. #include "mozilla/dom/Element.h"
  24. #include "mozilla/StyleSetHandle.h"
  25. #include "mozilla/StyleSetHandleInlines.h"
  26. #include "nsThemeConstants.h"
  27. #ifdef ACCESSIBILITY
  28. #include "nsAccessibilityService.h"
  29. #endif
  30. #define LONG_SIDE_TO_SHORT_SIDE_RATIO 10
  31. using namespace mozilla;
  32. using namespace mozilla::dom;
  33. using namespace mozilla::image;
  34. NS_IMPL_ISUPPORTS(nsRangeFrame::DummyTouchListener, nsIDOMEventListener)
  35. nsIFrame*
  36. NS_NewRangeFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
  37. {
  38. return new (aPresShell) nsRangeFrame(aContext);
  39. }
  40. nsRangeFrame::nsRangeFrame(nsStyleContext* aContext)
  41. : nsContainerFrame(aContext)
  42. {
  43. }
  44. nsRangeFrame::~nsRangeFrame()
  45. {
  46. #ifdef DEBUG
  47. if (mOuterFocusStyle) {
  48. mOuterFocusStyle->FrameRelease();
  49. }
  50. #endif
  51. }
  52. NS_IMPL_FRAMEARENA_HELPERS(nsRangeFrame)
  53. NS_QUERYFRAME_HEAD(nsRangeFrame)
  54. NS_QUERYFRAME_ENTRY(nsRangeFrame)
  55. NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
  56. NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
  57. void
  58. nsRangeFrame::Init(nsIContent* aContent,
  59. nsContainerFrame* aParent,
  60. nsIFrame* aPrevInFlow)
  61. {
  62. // With APZ enabled, touch events may be handled directly by the APZC code
  63. // if the APZ knows that there is no content interested in the touch event.
  64. // The range input element *is* interested in touch events, but doesn't use
  65. // the usual mechanism (i.e. registering an event listener) to handle touch
  66. // input. Instead, we do it here so that the APZ finds out about it, and
  67. // makes sure to wait for content to run handlers before handling the touch
  68. // input itself.
  69. if (!mDummyTouchListener) {
  70. mDummyTouchListener = new DummyTouchListener();
  71. }
  72. aContent->AddEventListener(NS_LITERAL_STRING("touchstart"), mDummyTouchListener, false);
  73. StyleSetHandle styleSet = PresContext()->StyleSet();
  74. mOuterFocusStyle =
  75. styleSet->ProbePseudoElementStyle(aContent->AsElement(),
  76. CSSPseudoElementType::mozFocusOuter,
  77. StyleContext());
  78. return nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
  79. }
  80. void
  81. nsRangeFrame::DestroyFrom(nsIFrame* aDestructRoot)
  82. {
  83. NS_ASSERTION(!GetPrevContinuation() && !GetNextContinuation(),
  84. "nsRangeFrame should not have continuations; if it does we "
  85. "need to call RegUnregAccessKey only for the first.");
  86. mContent->RemoveEventListener(NS_LITERAL_STRING("touchstart"), mDummyTouchListener, false);
  87. nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
  88. nsContentUtils::DestroyAnonymousContent(&mTrackDiv);
  89. nsContentUtils::DestroyAnonymousContent(&mProgressDiv);
  90. nsContentUtils::DestroyAnonymousContent(&mThumbDiv);
  91. nsContainerFrame::DestroyFrom(aDestructRoot);
  92. }
  93. nsresult
  94. nsRangeFrame::MakeAnonymousDiv(Element** aResult,
  95. CSSPseudoElementType aPseudoType,
  96. nsTArray<ContentInfo>& aElements)
  97. {
  98. nsCOMPtr<nsIDocument> doc = mContent->GetComposedDoc();
  99. RefPtr<Element> resultElement = doc->CreateHTMLElement(nsGkAtoms::div);
  100. // Associate the pseudo-element with the anonymous child.
  101. resultElement->SetPseudoElementType(aPseudoType);
  102. if (!aElements.AppendElement(resultElement)) {
  103. return NS_ERROR_OUT_OF_MEMORY;
  104. }
  105. resultElement.forget(aResult);
  106. return NS_OK;
  107. }
  108. nsresult
  109. nsRangeFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
  110. {
  111. nsresult rv;
  112. // Create the ::-moz-range-track pseuto-element (a div):
  113. rv = MakeAnonymousDiv(getter_AddRefs(mTrackDiv),
  114. CSSPseudoElementType::mozRangeTrack,
  115. aElements);
  116. NS_ENSURE_SUCCESS(rv, rv);
  117. // Create the ::-moz-range-progress pseudo-element (a div):
  118. rv = MakeAnonymousDiv(getter_AddRefs(mProgressDiv),
  119. CSSPseudoElementType::mozRangeProgress,
  120. aElements);
  121. NS_ENSURE_SUCCESS(rv, rv);
  122. // Create the ::-moz-range-thumb pseudo-element (a div):
  123. rv = MakeAnonymousDiv(getter_AddRefs(mThumbDiv),
  124. CSSPseudoElementType::mozRangeThumb,
  125. aElements);
  126. return rv;
  127. }
  128. void
  129. nsRangeFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
  130. uint32_t aFilter)
  131. {
  132. if (mTrackDiv) {
  133. aElements.AppendElement(mTrackDiv);
  134. }
  135. if (mProgressDiv) {
  136. aElements.AppendElement(mProgressDiv);
  137. }
  138. if (mThumbDiv) {
  139. aElements.AppendElement(mThumbDiv);
  140. }
  141. }
  142. class nsDisplayRangeFocusRing : public nsDisplayItem
  143. {
  144. public:
  145. nsDisplayRangeFocusRing(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
  146. : nsDisplayItem(aBuilder, aFrame) {
  147. MOZ_COUNT_CTOR(nsDisplayRangeFocusRing);
  148. }
  149. #ifdef NS_BUILD_REFCNT_LOGGING
  150. virtual ~nsDisplayRangeFocusRing() {
  151. MOZ_COUNT_DTOR(nsDisplayRangeFocusRing);
  152. }
  153. #endif
  154. nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override;
  155. void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
  156. const nsDisplayItemGeometry* aGeometry,
  157. nsRegion *aInvalidRegion) override;
  158. virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
  159. virtual void Paint(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx) override;
  160. NS_DISPLAY_DECL_NAME("RangeFocusRing", TYPE_RANGE_FOCUS_RING)
  161. };
  162. nsDisplayItemGeometry*
  163. nsDisplayRangeFocusRing::AllocateGeometry(nsDisplayListBuilder* aBuilder)
  164. {
  165. return new nsDisplayItemGenericImageGeometry(this, aBuilder);
  166. }
  167. void
  168. nsDisplayRangeFocusRing::ComputeInvalidationRegion(
  169. nsDisplayListBuilder* aBuilder,
  170. const nsDisplayItemGeometry* aGeometry,
  171. nsRegion* aInvalidRegion)
  172. {
  173. auto geometry =
  174. static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
  175. if (aBuilder->ShouldSyncDecodeImages() &&
  176. geometry->ShouldInvalidateToSyncDecodeImages()) {
  177. bool snap;
  178. aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
  179. }
  180. nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion);
  181. }
  182. nsRect
  183. nsDisplayRangeFocusRing::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap)
  184. {
  185. *aSnap = false;
  186. nsRect rect(ToReferenceFrame(), Frame()->GetSize());
  187. // We want to paint as if specifying a border for ::-moz-focus-outer
  188. // specifies an outline for our frame, so inflate by the border widths:
  189. nsStyleContext* styleContext =
  190. static_cast<nsRangeFrame*>(mFrame)->mOuterFocusStyle;
  191. MOZ_ASSERT(styleContext, "We only exist if mOuterFocusStyle is non-null");
  192. rect.Inflate(styleContext->StyleBorder()->GetComputedBorder());
  193. return rect;
  194. }
  195. void
  196. nsDisplayRangeFocusRing::Paint(nsDisplayListBuilder* aBuilder,
  197. nsRenderingContext* aCtx)
  198. {
  199. bool unused;
  200. nsStyleContext* styleContext =
  201. static_cast<nsRangeFrame*>(mFrame)->mOuterFocusStyle;
  202. MOZ_ASSERT(styleContext, "We only exist if mOuterFocusStyle is non-null");
  203. PaintBorderFlags flags = aBuilder->ShouldSyncDecodeImages()
  204. ? PaintBorderFlags::SYNC_DECODE_IMAGES
  205. : PaintBorderFlags();
  206. DrawResult result =
  207. nsCSSRendering::PaintBorder(mFrame->PresContext(), *aCtx, mFrame,
  208. mVisibleRect, GetBounds(aBuilder, &unused),
  209. styleContext, flags);
  210. nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
  211. }
  212. void
  213. nsRangeFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
  214. const nsDisplayListSet& aLists)
  215. {
  216. if (IsThemed()) {
  217. DisplayBorderBackgroundOutline(aBuilder, aLists);
  218. // Only create items for the thumb. Specifically, we do not want
  219. // the track to paint, since *our* background is used to paint
  220. // the track, and we don't want the unthemed track painting over
  221. // the top of the themed track.
  222. // This logic is copied from
  223. // nsContainerFrame::BuildDisplayListForNonBlockChildren as
  224. // called by BuildDisplayListForInline.
  225. nsIFrame* thumb = mThumbDiv->GetPrimaryFrame();
  226. if (thumb) {
  227. nsDisplayListSet set(aLists, aLists.Content());
  228. BuildDisplayListForChild(aBuilder, thumb, set, DISPLAY_CHILD_INLINE);
  229. }
  230. } else {
  231. BuildDisplayListForInline(aBuilder, aLists);
  232. }
  233. // Draw a focus outline if appropriate:
  234. if (!aBuilder->IsForPainting() ||
  235. !IsVisibleForPainting(aBuilder)) {
  236. // we don't want the focus ring item for hit-testing or if the item isn't
  237. // in the area being [re]painted
  238. return;
  239. }
  240. EventStates eventStates = mContent->AsElement()->State();
  241. if (eventStates.HasState(NS_EVENT_STATE_DISABLED) ||
  242. !eventStates.HasState(NS_EVENT_STATE_FOCUSRING)) {
  243. return; // can't have focus or doesn't match :-moz-focusring
  244. }
  245. if (!mOuterFocusStyle ||
  246. !mOuterFocusStyle->StyleBorder()->HasBorder()) {
  247. // no ::-moz-focus-outer specified border (how style specifies a focus ring
  248. // for range)
  249. return;
  250. }
  251. const nsStyleDisplay *disp = StyleDisplay();
  252. if (IsThemed(disp) &&
  253. PresContext()->GetTheme()->ThemeDrawsFocusForWidget(disp->mAppearance)) {
  254. return; // the native theme displays its own visual indication of focus
  255. }
  256. aLists.Content()->AppendNewToTop(
  257. new (aBuilder) nsDisplayRangeFocusRing(aBuilder, this));
  258. }
  259. void
  260. nsRangeFrame::Reflow(nsPresContext* aPresContext,
  261. ReflowOutput& aDesiredSize,
  262. const ReflowInput& aReflowInput,
  263. nsReflowStatus& aStatus)
  264. {
  265. MarkInReflow();
  266. DO_GLOBAL_REFLOW_COUNT("nsRangeFrame");
  267. DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
  268. NS_ASSERTION(mTrackDiv, "::-moz-range-track div must exist!");
  269. NS_ASSERTION(mProgressDiv, "::-moz-range-progress div must exist!");
  270. NS_ASSERTION(mThumbDiv, "::-moz-range-thumb div must exist!");
  271. NS_ASSERTION(!GetPrevContinuation() && !GetNextContinuation(),
  272. "nsRangeFrame should not have continuations; if it does we "
  273. "need to call RegUnregAccessKey only for the first.");
  274. if (mState & NS_FRAME_FIRST_REFLOW) {
  275. nsFormControlFrame::RegUnRegAccessKey(this, true);
  276. }
  277. WritingMode wm = aReflowInput.GetWritingMode();
  278. nscoord computedBSize = aReflowInput.ComputedBSize();
  279. if (computedBSize == NS_AUTOHEIGHT) {
  280. computedBSize = 0;
  281. }
  282. LogicalSize
  283. finalSize(wm,
  284. aReflowInput.ComputedISize() +
  285. aReflowInput.ComputedLogicalBorderPadding().IStartEnd(wm),
  286. computedBSize +
  287. aReflowInput.ComputedLogicalBorderPadding().BStartEnd(wm));
  288. aDesiredSize.SetSize(wm, finalSize);
  289. ReflowAnonymousContent(aPresContext, aDesiredSize, aReflowInput);
  290. aDesiredSize.SetOverflowAreasToDesiredBounds();
  291. nsIFrame* trackFrame = mTrackDiv->GetPrimaryFrame();
  292. if (trackFrame) {
  293. ConsiderChildOverflow(aDesiredSize.mOverflowAreas, trackFrame);
  294. }
  295. nsIFrame* rangeProgressFrame = mProgressDiv->GetPrimaryFrame();
  296. if (rangeProgressFrame) {
  297. ConsiderChildOverflow(aDesiredSize.mOverflowAreas, rangeProgressFrame);
  298. }
  299. nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame();
  300. if (thumbFrame) {
  301. ConsiderChildOverflow(aDesiredSize.mOverflowAreas, thumbFrame);
  302. }
  303. FinishAndStoreOverflow(&aDesiredSize);
  304. aStatus = NS_FRAME_COMPLETE;
  305. NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
  306. }
  307. void
  308. nsRangeFrame::ReflowAnonymousContent(nsPresContext* aPresContext,
  309. ReflowOutput& aDesiredSize,
  310. const ReflowInput& aReflowInput)
  311. {
  312. // The width/height of our content box, which is the available width/height
  313. // for our anonymous content:
  314. nscoord rangeFrameContentBoxWidth = aReflowInput.ComputedWidth();
  315. nscoord rangeFrameContentBoxHeight = aReflowInput.ComputedHeight();
  316. if (rangeFrameContentBoxHeight == NS_AUTOHEIGHT) {
  317. rangeFrameContentBoxHeight = 0;
  318. }
  319. nsIFrame* trackFrame = mTrackDiv->GetPrimaryFrame();
  320. if (trackFrame) { // display:none?
  321. // Position the track:
  322. // The idea here is that we allow content authors to style the width,
  323. // height, border and padding of the track, but we ignore margin and
  324. // positioning properties and do the positioning ourself to keep the center
  325. // of the track's border box on the center of the nsRangeFrame's content
  326. // box.
  327. WritingMode wm = trackFrame->GetWritingMode();
  328. LogicalSize availSize = aReflowInput.ComputedSize(wm);
  329. availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
  330. ReflowInput trackReflowInput(aPresContext, aReflowInput,
  331. trackFrame, availSize);
  332. // Find the x/y position of the track frame such that it will be positioned
  333. // as described above. These coordinates are with respect to the
  334. // nsRangeFrame's border-box.
  335. nscoord trackX = rangeFrameContentBoxWidth / 2;
  336. nscoord trackY = rangeFrameContentBoxHeight / 2;
  337. // Account for the track's border and padding (we ignore its margin):
  338. trackX -= trackReflowInput.ComputedPhysicalBorderPadding().left +
  339. trackReflowInput.ComputedWidth() / 2;
  340. trackY -= trackReflowInput.ComputedPhysicalBorderPadding().top +
  341. trackReflowInput.ComputedHeight() / 2;
  342. // Make relative to our border box instead of our content box:
  343. trackX += aReflowInput.ComputedPhysicalBorderPadding().left;
  344. trackY += aReflowInput.ComputedPhysicalBorderPadding().top;
  345. nsReflowStatus frameStatus;
  346. ReflowOutput trackDesiredSize(aReflowInput);
  347. ReflowChild(trackFrame, aPresContext, trackDesiredSize,
  348. trackReflowInput, trackX, trackY, 0, frameStatus);
  349. MOZ_ASSERT(NS_FRAME_IS_FULLY_COMPLETE(frameStatus),
  350. "We gave our child unconstrained height, so it should be complete");
  351. FinishReflowChild(trackFrame, aPresContext, trackDesiredSize,
  352. &trackReflowInput, trackX, trackY, 0);
  353. }
  354. nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame();
  355. if (thumbFrame) { // display:none?
  356. WritingMode wm = thumbFrame->GetWritingMode();
  357. LogicalSize availSize = aReflowInput.ComputedSize(wm);
  358. availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
  359. ReflowInput thumbReflowInput(aPresContext, aReflowInput,
  360. thumbFrame, availSize);
  361. // Where we position the thumb depends on its size, so we first reflow
  362. // the thumb at {0,0} to obtain its size, then position it afterwards.
  363. nsReflowStatus frameStatus;
  364. ReflowOutput thumbDesiredSize(aReflowInput);
  365. ReflowChild(thumbFrame, aPresContext, thumbDesiredSize,
  366. thumbReflowInput, 0, 0, 0, frameStatus);
  367. MOZ_ASSERT(NS_FRAME_IS_FULLY_COMPLETE(frameStatus),
  368. "We gave our child unconstrained height, so it should be complete");
  369. FinishReflowChild(thumbFrame, aPresContext, thumbDesiredSize,
  370. &thumbReflowInput, 0, 0, 0);
  371. DoUpdateThumbPosition(thumbFrame, nsSize(aDesiredSize.Width(),
  372. aDesiredSize.Height()));
  373. }
  374. nsIFrame* rangeProgressFrame = mProgressDiv->GetPrimaryFrame();
  375. if (rangeProgressFrame) { // display:none?
  376. WritingMode wm = rangeProgressFrame->GetWritingMode();
  377. LogicalSize availSize = aReflowInput.ComputedSize(wm);
  378. availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
  379. ReflowInput progressReflowInput(aPresContext, aReflowInput,
  380. rangeProgressFrame, availSize);
  381. // We first reflow the range-progress frame at {0,0} to obtain its
  382. // unadjusted dimensions, then we adjust it to so that the appropriate edge
  383. // ends at the thumb.
  384. nsReflowStatus frameStatus;
  385. ReflowOutput progressDesiredSize(aReflowInput);
  386. ReflowChild(rangeProgressFrame, aPresContext,
  387. progressDesiredSize, progressReflowInput, 0, 0,
  388. 0, frameStatus);
  389. MOZ_ASSERT(NS_FRAME_IS_FULLY_COMPLETE(frameStatus),
  390. "We gave our child unconstrained height, so it should be complete");
  391. FinishReflowChild(rangeProgressFrame, aPresContext,
  392. progressDesiredSize, &progressReflowInput, 0, 0, 0);
  393. DoUpdateRangeProgressFrame(rangeProgressFrame, nsSize(aDesiredSize.Width(),
  394. aDesiredSize.Height()));
  395. }
  396. }
  397. #ifdef ACCESSIBILITY
  398. a11y::AccType
  399. nsRangeFrame::AccessibleType()
  400. {
  401. return a11y::eHTMLRangeType;
  402. }
  403. #endif
  404. double
  405. nsRangeFrame::GetValueAsFractionOfRange()
  406. {
  407. MOZ_ASSERT(mContent->IsHTMLElement(nsGkAtoms::input), "bad cast");
  408. dom::HTMLInputElement* input = static_cast<dom::HTMLInputElement*>(mContent);
  409. MOZ_ASSERT(input->GetType() == NS_FORM_INPUT_RANGE);
  410. Decimal value = input->GetValueAsDecimal();
  411. Decimal minimum = input->GetMinimum();
  412. Decimal maximum = input->GetMaximum();
  413. MOZ_ASSERT(value.isFinite() && minimum.isFinite() && maximum.isFinite(),
  414. "type=range should have a default maximum/minimum");
  415. if (maximum <= minimum) {
  416. MOZ_ASSERT(value == minimum, "Unsanitized value");
  417. return 0.0;
  418. }
  419. MOZ_ASSERT(value >= minimum && value <= maximum, "Unsanitized value");
  420. return ((value - minimum) / (maximum - minimum)).toDouble();
  421. }
  422. Decimal
  423. nsRangeFrame::GetValueAtEventPoint(WidgetGUIEvent* aEvent)
  424. {
  425. MOZ_ASSERT(aEvent->mClass == eMouseEventClass ||
  426. aEvent->mClass == eTouchEventClass,
  427. "Unexpected event type - aEvent->mRefPoint may be meaningless");
  428. MOZ_ASSERT(mContent->IsHTMLElement(nsGkAtoms::input), "bad cast");
  429. dom::HTMLInputElement* input = static_cast<dom::HTMLInputElement*>(mContent);
  430. MOZ_ASSERT(input->GetType() == NS_FORM_INPUT_RANGE);
  431. Decimal minimum = input->GetMinimum();
  432. Decimal maximum = input->GetMaximum();
  433. MOZ_ASSERT(minimum.isFinite() && maximum.isFinite(),
  434. "type=range should have a default maximum/minimum");
  435. if (maximum <= minimum) {
  436. return minimum;
  437. }
  438. Decimal range = maximum - minimum;
  439. LayoutDeviceIntPoint absPoint;
  440. if (aEvent->mClass == eTouchEventClass) {
  441. MOZ_ASSERT(aEvent->AsTouchEvent()->mTouches.Length() == 1,
  442. "Unexpected number of mTouches");
  443. absPoint = aEvent->AsTouchEvent()->mTouches[0]->mRefPoint;
  444. } else {
  445. absPoint = aEvent->mRefPoint;
  446. }
  447. nsPoint point =
  448. nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, absPoint, this);
  449. if (point == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE)) {
  450. // We don't want to change the current value for this error state.
  451. return static_cast<dom::HTMLInputElement*>(mContent)->GetValueAsDecimal();
  452. }
  453. nsRect rangeContentRect = GetContentRectRelativeToSelf();
  454. nsSize thumbSize;
  455. if (IsThemed()) {
  456. // We need to get the size of the thumb from the theme.
  457. nsPresContext *presContext = PresContext();
  458. bool notUsedCanOverride;
  459. LayoutDeviceIntSize size;
  460. presContext->GetTheme()->
  461. GetMinimumWidgetSize(presContext, this, NS_THEME_RANGE_THUMB, &size,
  462. &notUsedCanOverride);
  463. thumbSize.width = presContext->DevPixelsToAppUnits(size.width);
  464. thumbSize.height = presContext->DevPixelsToAppUnits(size.height);
  465. MOZ_ASSERT(thumbSize.width > 0 && thumbSize.height > 0);
  466. } else {
  467. nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame();
  468. if (thumbFrame) { // diplay:none?
  469. thumbSize = thumbFrame->GetSize();
  470. }
  471. }
  472. Decimal fraction;
  473. if (IsHorizontal()) {
  474. nscoord traversableDistance = rangeContentRect.width - thumbSize.width;
  475. if (traversableDistance <= 0) {
  476. return minimum;
  477. }
  478. nscoord posAtStart = rangeContentRect.x + thumbSize.width/2;
  479. nscoord posAtEnd = posAtStart + traversableDistance;
  480. nscoord posOfPoint = mozilla::clamped(point.x, posAtStart, posAtEnd);
  481. fraction = Decimal(posOfPoint - posAtStart) / Decimal(traversableDistance);
  482. if (IsRightToLeft()) {
  483. fraction = Decimal(1) - fraction;
  484. }
  485. } else {
  486. nscoord traversableDistance = rangeContentRect.height - thumbSize.height;
  487. if (traversableDistance <= 0) {
  488. return minimum;
  489. }
  490. nscoord posAtStart = rangeContentRect.y + thumbSize.height/2;
  491. nscoord posAtEnd = posAtStart + traversableDistance;
  492. nscoord posOfPoint = mozilla::clamped(point.y, posAtStart, posAtEnd);
  493. // For a vertical range, the top (posAtStart) is the highest value, so we
  494. // subtract the fraction from 1.0 to get that polarity correct.
  495. fraction = Decimal(1) - Decimal(posOfPoint - posAtStart) / Decimal(traversableDistance);
  496. }
  497. MOZ_ASSERT(fraction >= Decimal(0) && fraction <= Decimal(1));
  498. return minimum + fraction * range;
  499. }
  500. void
  501. nsRangeFrame::UpdateForValueChange()
  502. {
  503. if (NS_SUBTREE_DIRTY(this)) {
  504. return; // we're going to be updated when we reflow
  505. }
  506. nsIFrame* rangeProgressFrame = mProgressDiv->GetPrimaryFrame();
  507. nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame();
  508. if (!rangeProgressFrame && !thumbFrame) {
  509. return; // diplay:none?
  510. }
  511. if (rangeProgressFrame) {
  512. DoUpdateRangeProgressFrame(rangeProgressFrame, GetSize());
  513. }
  514. if (thumbFrame) {
  515. DoUpdateThumbPosition(thumbFrame, GetSize());
  516. }
  517. if (IsThemed()) {
  518. // We don't know the exact dimensions or location of the thumb when native
  519. // theming is applied, so we just repaint the entire range.
  520. InvalidateFrame();
  521. }
  522. #ifdef ACCESSIBILITY
  523. nsAccessibilityService* accService = nsIPresShell::AccService();
  524. if (accService) {
  525. accService->RangeValueChanged(PresContext()->PresShell(), mContent);
  526. }
  527. #endif
  528. SchedulePaint();
  529. }
  530. void
  531. nsRangeFrame::DoUpdateThumbPosition(nsIFrame* aThumbFrame,
  532. const nsSize& aRangeSize)
  533. {
  534. MOZ_ASSERT(aThumbFrame);
  535. // The idea here is that we want to position the thumb so that the center
  536. // of the thumb is on an imaginary line drawn from the middle of one edge
  537. // of the range frame's content box to the middle of the opposite edge of
  538. // its content box (the opposite edges being the left/right edge if the
  539. // range is horizontal, or else the top/bottom edges if the range is
  540. // vertical). How far along this line the center of the thumb is placed
  541. // depends on the value of the range.
  542. nsMargin borderAndPadding = GetUsedBorderAndPadding();
  543. nsPoint newPosition(borderAndPadding.left, borderAndPadding.top);
  544. nsSize rangeContentBoxSize(aRangeSize);
  545. rangeContentBoxSize.width -= borderAndPadding.LeftRight();
  546. rangeContentBoxSize.height -= borderAndPadding.TopBottom();
  547. nsSize thumbSize = aThumbFrame->GetSize();
  548. double fraction = GetValueAsFractionOfRange();
  549. MOZ_ASSERT(fraction >= 0.0 && fraction <= 1.0);
  550. if (IsHorizontal()) {
  551. if (thumbSize.width < rangeContentBoxSize.width) {
  552. nscoord traversableDistance =
  553. rangeContentBoxSize.width - thumbSize.width;
  554. if (IsRightToLeft()) {
  555. newPosition.x += NSToCoordRound((1.0 - fraction) * traversableDistance);
  556. } else {
  557. newPosition.x += NSToCoordRound(fraction * traversableDistance);
  558. }
  559. newPosition.y += (rangeContentBoxSize.height - thumbSize.height)/2;
  560. }
  561. } else {
  562. if (thumbSize.height < rangeContentBoxSize.height) {
  563. nscoord traversableDistance =
  564. rangeContentBoxSize.height - thumbSize.height;
  565. newPosition.x += (rangeContentBoxSize.width - thumbSize.width)/2;
  566. newPosition.y += NSToCoordRound((1.0 - fraction) * traversableDistance);
  567. }
  568. }
  569. aThumbFrame->SetPosition(newPosition);
  570. }
  571. void
  572. nsRangeFrame::DoUpdateRangeProgressFrame(nsIFrame* aRangeProgressFrame,
  573. const nsSize& aRangeSize)
  574. {
  575. MOZ_ASSERT(aRangeProgressFrame);
  576. // The idea here is that we want to position the ::-moz-range-progress
  577. // pseudo-element so that the center line running along its length is on the
  578. // corresponding center line of the nsRangeFrame's content box. In the other
  579. // dimension, we align the "start" edge of the ::-moz-range-progress
  580. // pseudo-element's border-box with the corresponding edge of the
  581. // nsRangeFrame's content box, and we size the progress element's border-box
  582. // to have a length of GetValueAsFractionOfRange() times the nsRangeFrame's
  583. // content-box size.
  584. nsMargin borderAndPadding = GetUsedBorderAndPadding();
  585. nsSize progSize = aRangeProgressFrame->GetSize();
  586. nsRect progRect(borderAndPadding.left, borderAndPadding.top,
  587. progSize.width, progSize.height);
  588. nsSize rangeContentBoxSize(aRangeSize);
  589. rangeContentBoxSize.width -= borderAndPadding.LeftRight();
  590. rangeContentBoxSize.height -= borderAndPadding.TopBottom();
  591. double fraction = GetValueAsFractionOfRange();
  592. MOZ_ASSERT(fraction >= 0.0 && fraction <= 1.0);
  593. if (IsHorizontal()) {
  594. nscoord progLength = NSToCoordRound(fraction * rangeContentBoxSize.width);
  595. if (IsRightToLeft()) {
  596. progRect.x += rangeContentBoxSize.width - progLength;
  597. }
  598. progRect.y += (rangeContentBoxSize.height - progSize.height)/2;
  599. progRect.width = progLength;
  600. } else {
  601. nscoord progLength = NSToCoordRound(fraction * rangeContentBoxSize.height);
  602. progRect.x += (rangeContentBoxSize.width - progSize.width)/2;
  603. progRect.y += rangeContentBoxSize.height - progLength;
  604. progRect.height = progLength;
  605. }
  606. aRangeProgressFrame->SetRect(progRect);
  607. }
  608. nsresult
  609. nsRangeFrame::AttributeChanged(int32_t aNameSpaceID,
  610. nsIAtom* aAttribute,
  611. int32_t aModType)
  612. {
  613. NS_ASSERTION(mTrackDiv, "The track div must exist!");
  614. NS_ASSERTION(mThumbDiv, "The thumb div must exist!");
  615. if (aNameSpaceID == kNameSpaceID_None) {
  616. if (aAttribute == nsGkAtoms::value ||
  617. aAttribute == nsGkAtoms::min ||
  618. aAttribute == nsGkAtoms::max ||
  619. aAttribute == nsGkAtoms::step) {
  620. // We want to update the position of the thumb, except in one special
  621. // case: If the value attribute is being set, it is possible that we are
  622. // in the middle of a type change away from type=range, under the
  623. // SetAttr(..., nsGkAtoms::value, ...) call in HTMLInputElement::
  624. // HandleTypeChange. In that case the HTMLInputElement's type will
  625. // already have changed, and if we call UpdateForValueChange()
  626. // we'll fail the asserts under that call that check the type of our
  627. // HTMLInputElement. Given that we're changing away from being a range
  628. // and this frame will shortly be destroyed, there's no point in calling
  629. // UpdateForValueChange() anyway.
  630. MOZ_ASSERT(mContent->IsHTMLElement(nsGkAtoms::input), "bad cast");
  631. bool typeIsRange = static_cast<dom::HTMLInputElement*>(mContent)->GetType() ==
  632. NS_FORM_INPUT_RANGE;
  633. // If script changed the <input>'s type before setting these attributes
  634. // then we don't need to do anything since we are going to be reframed.
  635. if (typeIsRange) {
  636. UpdateForValueChange();
  637. }
  638. } else if (aAttribute == nsGkAtoms::orient) {
  639. PresContext()->PresShell()->FrameNeedsReflow(this, nsIPresShell::eResize,
  640. NS_FRAME_IS_DIRTY);
  641. }
  642. }
  643. return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
  644. }
  645. LogicalSize
  646. nsRangeFrame::ComputeAutoSize(nsRenderingContext* aRenderingContext,
  647. WritingMode aWM,
  648. const LogicalSize& aCBSize,
  649. nscoord aAvailableISize,
  650. const LogicalSize& aMargin,
  651. const LogicalSize& aBorder,
  652. const LogicalSize& aPadding,
  653. ComputeSizeFlags aFlags)
  654. {
  655. nscoord oneEm = NSToCoordRound(StyleFont()->mFont.size *
  656. nsLayoutUtils::FontSizeInflationFor(this)); // 1em
  657. bool isInlineOriented = IsInlineOriented();
  658. const WritingMode wm = GetWritingMode();
  659. LogicalSize autoSize(wm);
  660. // nsFrame::ComputeSize calls GetMinimumWidgetSize to prevent us from being
  661. // given too small a size when we're natively themed. If we're themed, we set
  662. // our "thickness" dimension to zero below and rely on that
  663. // GetMinimumWidgetSize check to correct that dimension to the natural
  664. // thickness of a slider in the current theme.
  665. if (isInlineOriented) {
  666. autoSize.ISize(wm) = LONG_SIDE_TO_SHORT_SIDE_RATIO * oneEm;
  667. autoSize.BSize(wm) = IsThemed() ? 0 : oneEm;
  668. } else {
  669. autoSize.ISize(wm) = IsThemed() ? 0 : oneEm;
  670. autoSize.BSize(wm) = LONG_SIDE_TO_SHORT_SIDE_RATIO * oneEm;
  671. }
  672. return autoSize.ConvertTo(aWM, wm);
  673. }
  674. nscoord
  675. nsRangeFrame::GetMinISize(nsRenderingContext *aRenderingContext)
  676. {
  677. // nsFrame::ComputeSize calls GetMinimumWidgetSize to prevent us from being
  678. // given too small a size when we're natively themed. If we aren't native
  679. // themed, we don't mind how small we're sized.
  680. return nscoord(0);
  681. }
  682. nscoord
  683. nsRangeFrame::GetPrefISize(nsRenderingContext *aRenderingContext)
  684. {
  685. bool isInline = IsInlineOriented();
  686. if (!isInline && IsThemed()) {
  687. // nsFrame::ComputeSize calls GetMinimumWidgetSize to prevent us from being
  688. // given too small a size when we're natively themed. We return zero and
  689. // depend on that correction to get our "natural" width when we're a
  690. // vertical slider.
  691. return 0;
  692. }
  693. nscoord prefISize = NSToCoordRound(StyleFont()->mFont.size *
  694. nsLayoutUtils::FontSizeInflationFor(this)); // 1em
  695. if (isInline) {
  696. prefISize *= LONG_SIDE_TO_SHORT_SIDE_RATIO;
  697. }
  698. return prefISize;
  699. }
  700. bool
  701. nsRangeFrame::IsHorizontal() const
  702. {
  703. dom::HTMLInputElement* element =
  704. static_cast<dom::HTMLInputElement*>(mContent);
  705. return element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::orient,
  706. nsGkAtoms::horizontal, eCaseMatters) ||
  707. (!element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::orient,
  708. nsGkAtoms::vertical, eCaseMatters) &&
  709. GetWritingMode().IsVertical() ==
  710. element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::orient,
  711. nsGkAtoms::block, eCaseMatters));
  712. }
  713. double
  714. nsRangeFrame::GetMin() const
  715. {
  716. return static_cast<dom::HTMLInputElement*>(mContent)->GetMinimum().toDouble();
  717. }
  718. double
  719. nsRangeFrame::GetMax() const
  720. {
  721. return static_cast<dom::HTMLInputElement*>(mContent)->GetMaximum().toDouble();
  722. }
  723. double
  724. nsRangeFrame::GetValue() const
  725. {
  726. return static_cast<dom::HTMLInputElement*>(mContent)->GetValueAsDecimal().toDouble();
  727. }
  728. nsIAtom*
  729. nsRangeFrame::GetType() const
  730. {
  731. return nsGkAtoms::rangeFrame;
  732. }
  733. #define STYLES_DISABLING_NATIVE_THEMING \
  734. NS_AUTHOR_SPECIFIED_BACKGROUND | \
  735. NS_AUTHOR_SPECIFIED_PADDING | \
  736. NS_AUTHOR_SPECIFIED_BORDER
  737. bool
  738. nsRangeFrame::ShouldUseNativeStyle() const
  739. {
  740. nsIFrame* trackFrame = mTrackDiv->GetPrimaryFrame();
  741. nsIFrame* progressFrame = mProgressDiv->GetPrimaryFrame();
  742. nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame();
  743. return (StyleDisplay()->mAppearance == NS_THEME_RANGE) &&
  744. !PresContext()->HasAuthorSpecifiedRules(this,
  745. (NS_AUTHOR_SPECIFIED_BORDER |
  746. NS_AUTHOR_SPECIFIED_BACKGROUND)) &&
  747. trackFrame &&
  748. !PresContext()->HasAuthorSpecifiedRules(trackFrame,
  749. STYLES_DISABLING_NATIVE_THEMING) &&
  750. progressFrame &&
  751. !PresContext()->HasAuthorSpecifiedRules(progressFrame,
  752. STYLES_DISABLING_NATIVE_THEMING) &&
  753. thumbFrame &&
  754. !PresContext()->HasAuthorSpecifiedRules(thumbFrame,
  755. STYLES_DISABLING_NATIVE_THEMING);
  756. }
  757. Element*
  758. nsRangeFrame::GetPseudoElement(CSSPseudoElementType aType)
  759. {
  760. if (aType == CSSPseudoElementType::mozRangeTrack) {
  761. return mTrackDiv;
  762. }
  763. if (aType == CSSPseudoElementType::mozRangeThumb) {
  764. return mThumbDiv;
  765. }
  766. if (aType == CSSPseudoElementType::mozRangeProgress) {
  767. return mProgressDiv;
  768. }
  769. return nsContainerFrame::GetPseudoElement(aType);
  770. }
  771. nsStyleContext*
  772. nsRangeFrame::GetAdditionalStyleContext(int32_t aIndex) const
  773. {
  774. // We only implement this so that SetAdditionalStyleContext will be
  775. // called if style changes that would change the -moz-focus-outer
  776. // pseudo-element have occurred.
  777. if (aIndex != 0) {
  778. return nullptr;
  779. }
  780. return mOuterFocusStyle;
  781. }
  782. void
  783. nsRangeFrame::SetAdditionalStyleContext(int32_t aIndex,
  784. nsStyleContext* aStyleContext)
  785. {
  786. MOZ_ASSERT(aIndex == 0,
  787. "GetAdditionalStyleContext is handling other indexes?");
  788. #ifdef DEBUG
  789. if (mOuterFocusStyle) {
  790. mOuterFocusStyle->FrameRelease();
  791. }
  792. #endif
  793. // The -moz-focus-outer pseudo-element's style has changed.
  794. mOuterFocusStyle = aStyleContext;
  795. #ifdef DEBUG
  796. if (mOuterFocusStyle) {
  797. mOuterFocusStyle->FrameAddRef();
  798. }
  799. #endif
  800. }