OLD | NEW |
| (Empty) |
1 /* | |
2 * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. | |
3 * Copyright (C) 2010 Google Inc. All rights reserved. | |
4 * | |
5 * Redistribution and use in source and binary forms, with or without | |
6 * modification, are permitted provided that the following conditions are | |
7 * met: | |
8 * | |
9 * * Redistributions of source code must retain the above copyright | |
10 * notice, this list of conditions and the following disclaimer. | |
11 * * Redistributions in binary form must reproduce the above | |
12 * copyright notice, this list of conditions and the following disclaimer | |
13 * in the documentation and/or other materials provided with the | |
14 * distribution. | |
15 * * Neither the name of Google Inc. nor the names of its | |
16 * contributors may be used to endorse or promote products derived from | |
17 * this software without specific prior written permission. | |
18 * | |
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
30 */ | |
31 | |
32 #include "core/html/shadow/SliderThumbElement.h" | |
33 | |
34 #include "core/dom/shadow/ShadowRoot.h" | |
35 #include "core/events/Event.h" | |
36 #include "core/events/MouseEvent.h" | |
37 #include "core/events/TouchEvent.h" | |
38 #include "core/frame/EventHandlerRegistry.h" | |
39 #include "core/frame/LocalFrame.h" | |
40 #include "core/html/HTMLInputElement.h" | |
41 #include "core/html/forms/StepRange.h" | |
42 #include "core/html/parser/HTMLParserIdioms.h" | |
43 #include "core/html/shadow/ShadowElementNames.h" | |
44 #include "core/input/EventHandler.h" | |
45 #include "core/layout/LayoutSliderContainer.h" | |
46 #include "core/layout/LayoutSliderThumb.h" | |
47 #include "core/layout/LayoutTheme.h" | |
48 | |
49 namespace blink { | |
50 | |
51 using namespace HTMLNames; | |
52 | |
53 inline static bool HasVerticalAppearance(HTMLInputElement* input) { | |
54 DCHECK(input->GetLayoutObject()); | |
55 const ComputedStyle& slider_style = input->GetLayoutObject()->StyleRef(); | |
56 | |
57 return slider_style.Appearance() == kSliderVerticalPart; | |
58 } | |
59 | |
60 inline SliderThumbElement::SliderThumbElement(Document& document) | |
61 : HTMLDivElement(document), in_drag_mode_(false) {} | |
62 | |
63 SliderThumbElement* SliderThumbElement::Create(Document& document) { | |
64 SliderThumbElement* element = new SliderThumbElement(document); | |
65 element->setAttribute(idAttr, ShadowElementNames::SliderThumb()); | |
66 return element; | |
67 } | |
68 | |
69 void SliderThumbElement::SetPositionFromValue() { | |
70 // Since the code to calculate position is in the LayoutSliderThumb layout | |
71 // path, we don't actually update the value here. Instead, we poke at the | |
72 // layoutObject directly to trigger layout. | |
73 if (GetLayoutObject()) | |
74 GetLayoutObject()->SetNeedsLayoutAndFullPaintInvalidation( | |
75 LayoutInvalidationReason::kSliderValueChanged); | |
76 } | |
77 | |
78 LayoutObject* SliderThumbElement::CreateLayoutObject(const ComputedStyle&) { | |
79 return new LayoutSliderThumb(this); | |
80 } | |
81 | |
82 bool SliderThumbElement::IsDisabledFormControl() const { | |
83 return HostInput() && HostInput()->IsDisabledFormControl(); | |
84 } | |
85 | |
86 bool SliderThumbElement::MatchesReadOnlyPseudoClass() const { | |
87 return HostInput() && HostInput()->MatchesReadOnlyPseudoClass(); | |
88 } | |
89 | |
90 bool SliderThumbElement::MatchesReadWritePseudoClass() const { | |
91 return HostInput() && HostInput()->MatchesReadWritePseudoClass(); | |
92 } | |
93 | |
94 Node* SliderThumbElement::FocusDelegate() { | |
95 return HostInput(); | |
96 } | |
97 | |
98 void SliderThumbElement::DragFrom(const LayoutPoint& point) { | |
99 StartDragging(); | |
100 SetPositionFromPoint(point); | |
101 } | |
102 | |
103 void SliderThumbElement::SetPositionFromPoint(const LayoutPoint& point) { | |
104 HTMLInputElement* input(HostInput()); | |
105 Element* track_element = input->UserAgentShadowRoot()->GetElementById( | |
106 ShadowElementNames::SliderTrack()); | |
107 | |
108 if (!input->GetLayoutObject() || !GetLayoutBox() || | |
109 !track_element->GetLayoutBox()) | |
110 return; | |
111 | |
112 LayoutPoint offset = LayoutPoint(input->GetLayoutObject()->AbsoluteToLocal( | |
113 FloatPoint(point), kUseTransforms)); | |
114 bool is_vertical = HasVerticalAppearance(input); | |
115 bool is_left_to_right_direction = | |
116 GetLayoutBox()->Style()->IsLeftToRightDirection(); | |
117 LayoutUnit track_size; | |
118 LayoutUnit position; | |
119 LayoutUnit current_position; | |
120 // We need to calculate currentPosition from absolute points becaue the | |
121 // layoutObject for this node is usually on a layer and layoutBox()->x() and | |
122 // y() are unusable. | |
123 // FIXME: This should probably respect transforms. | |
124 LayoutPoint absolute_thumb_origin = | |
125 GetLayoutBox()->AbsoluteBoundingBoxRectIgnoringTransforms().Location(); | |
126 LayoutPoint absolute_slider_content_origin = | |
127 LayoutPoint(input->GetLayoutObject()->LocalToAbsolute()); | |
128 IntRect track_bounding_box = | |
129 track_element->GetLayoutObject() | |
130 ->AbsoluteBoundingBoxRectIgnoringTransforms(); | |
131 IntRect input_bounding_box = | |
132 input->GetLayoutObject()->AbsoluteBoundingBoxRectIgnoringTransforms(); | |
133 if (is_vertical) { | |
134 track_size = track_element->GetLayoutBox()->ContentHeight() - | |
135 GetLayoutBox()->Size().Height(); | |
136 position = offset.Y() - GetLayoutBox()->Size().Height() / 2 - | |
137 track_bounding_box.Y() + input_bounding_box.Y() - | |
138 GetLayoutBox()->MarginBottom(); | |
139 current_position = | |
140 absolute_thumb_origin.Y() - absolute_slider_content_origin.Y(); | |
141 } else { | |
142 track_size = track_element->GetLayoutBox()->ContentWidth() - | |
143 GetLayoutBox()->Size().Width(); | |
144 position = offset.X() - GetLayoutBox()->Size().Width() / 2 - | |
145 track_bounding_box.X() + input_bounding_box.X(); | |
146 position -= is_left_to_right_direction ? GetLayoutBox()->MarginLeft() | |
147 : GetLayoutBox()->MarginRight(); | |
148 current_position = | |
149 absolute_thumb_origin.X() - absolute_slider_content_origin.X(); | |
150 } | |
151 position = std::min(position, track_size).ClampNegativeToZero(); | |
152 const Decimal ratio = | |
153 Decimal::FromDouble(static_cast<double>(position) / track_size); | |
154 const Decimal fraction = | |
155 is_vertical || !is_left_to_right_direction ? Decimal(1) - ratio : ratio; | |
156 StepRange step_range(input->CreateStepRange(kRejectAny)); | |
157 Decimal value = | |
158 step_range.ClampValue(step_range.ValueFromProportion(fraction)); | |
159 | |
160 Decimal closest = input->FindClosestTickMarkValue(value); | |
161 if (closest.IsFinite()) { | |
162 double closest_fraction = | |
163 step_range.ProportionFromValue(closest).ToDouble(); | |
164 double closest_ratio = is_vertical || !is_left_to_right_direction | |
165 ? 1.0 - closest_fraction | |
166 : closest_fraction; | |
167 LayoutUnit closest_position(track_size * closest_ratio); | |
168 const LayoutUnit snapping_threshold(5); | |
169 if ((closest_position - position).Abs() <= snapping_threshold) | |
170 value = closest; | |
171 } | |
172 | |
173 String value_string = SerializeForNumberType(value); | |
174 if (value_string == input->value()) | |
175 return; | |
176 | |
177 // FIXME: This is no longer being set from renderer. Consider updating the | |
178 // method name. | |
179 input->SetValueFromRenderer(value_string); | |
180 if (GetLayoutObject()) | |
181 GetLayoutObject()->SetNeedsLayoutAndFullPaintInvalidation( | |
182 LayoutInvalidationReason::kSliderValueChanged); | |
183 } | |
184 | |
185 void SliderThumbElement::StartDragging() { | |
186 if (LocalFrame* frame = GetDocument().GetFrame()) { | |
187 // Note that we get to here only we through mouse event path. The touch | |
188 // events are implicitly captured to the starting element and will be | |
189 // handled in handleTouchEvent function. | |
190 frame->GetEventHandler().SetPointerCapture(PointerEventFactory::kMouseId, | |
191 this); | |
192 in_drag_mode_ = true; | |
193 } | |
194 } | |
195 | |
196 void SliderThumbElement::StopDragging() { | |
197 if (!in_drag_mode_) | |
198 return; | |
199 | |
200 if (LocalFrame* frame = GetDocument().GetFrame()) { | |
201 frame->GetEventHandler().ReleasePointerCapture( | |
202 PointerEventFactory::kMouseId, this); | |
203 } | |
204 in_drag_mode_ = false; | |
205 if (GetLayoutObject()) | |
206 GetLayoutObject()->SetNeedsLayoutAndFullPaintInvalidation( | |
207 LayoutInvalidationReason::kSliderValueChanged); | |
208 if (HostInput()) | |
209 HostInput()->DispatchFormControlChangeEvent(); | |
210 } | |
211 | |
212 void SliderThumbElement::DefaultEventHandler(Event* event) { | |
213 if (event->IsPointerEvent() && | |
214 event->type() == EventTypeNames::lostpointercapture) { | |
215 StopDragging(); | |
216 return; | |
217 } | |
218 | |
219 if (!event->IsMouseEvent()) { | |
220 HTMLDivElement::DefaultEventHandler(event); | |
221 return; | |
222 } | |
223 | |
224 // FIXME: Should handle this readonly/disabled check in more general way. | |
225 // Missing this kind of check is likely to occur elsewhere if adding it in | |
226 // each shadow element. | |
227 HTMLInputElement* input = HostInput(); | |
228 if (!input || input->IsDisabledFormControl()) { | |
229 StopDragging(); | |
230 HTMLDivElement::DefaultEventHandler(event); | |
231 return; | |
232 } | |
233 | |
234 MouseEvent* mouse_event = ToMouseEvent(event); | |
235 bool is_left_button = mouse_event->button() == | |
236 static_cast<short>(WebPointerProperties::Button::kLeft); | |
237 const AtomicString& event_type = event->type(); | |
238 | |
239 // We intentionally do not call event->setDefaultHandled() here because | |
240 // MediaControlTimelineElement::defaultEventHandler() wants to handle these | |
241 // mouse events. | |
242 if (event_type == EventTypeNames::mousedown && is_left_button) { | |
243 StartDragging(); | |
244 return; | |
245 } | |
246 if (event_type == EventTypeNames::mouseup && is_left_button) { | |
247 StopDragging(); | |
248 return; | |
249 } | |
250 if (event_type == EventTypeNames::mousemove) { | |
251 if (in_drag_mode_) | |
252 SetPositionFromPoint(LayoutPoint(mouse_event->AbsoluteLocation())); | |
253 return; | |
254 } | |
255 | |
256 HTMLDivElement::DefaultEventHandler(event); | |
257 } | |
258 | |
259 bool SliderThumbElement::WillRespondToMouseMoveEvents() { | |
260 const HTMLInputElement* input = HostInput(); | |
261 if (input && !input->IsDisabledFormControl() && in_drag_mode_) | |
262 return true; | |
263 | |
264 return HTMLDivElement::WillRespondToMouseMoveEvents(); | |
265 } | |
266 | |
267 bool SliderThumbElement::WillRespondToMouseClickEvents() { | |
268 const HTMLInputElement* input = HostInput(); | |
269 if (input && !input->IsDisabledFormControl()) | |
270 return true; | |
271 | |
272 return HTMLDivElement::WillRespondToMouseClickEvents(); | |
273 } | |
274 | |
275 void SliderThumbElement::DetachLayoutTree(const AttachContext& context) { | |
276 if (in_drag_mode_) { | |
277 if (LocalFrame* frame = GetDocument().GetFrame()) | |
278 frame->GetEventHandler().SetCapturingMouseEventsNode(nullptr); | |
279 } | |
280 HTMLDivElement::DetachLayoutTree(context); | |
281 } | |
282 | |
283 HTMLInputElement* SliderThumbElement::HostInput() const { | |
284 // Only HTMLInputElement creates SliderThumbElement instances as its shadow | |
285 // nodes. So, ownerShadowHost() must be an HTMLInputElement. | |
286 return toHTMLInputElement(OwnerShadowHost()); | |
287 } | |
288 | |
289 static const AtomicString& SliderThumbShadowPartId() { | |
290 DEFINE_STATIC_LOCAL(const AtomicString, slider_thumb, | |
291 ("-webkit-slider-thumb")); | |
292 return slider_thumb; | |
293 } | |
294 | |
295 static const AtomicString& MediaSliderThumbShadowPartId() { | |
296 DEFINE_STATIC_LOCAL(const AtomicString, media_slider_thumb, | |
297 ("-webkit-media-slider-thumb")); | |
298 return media_slider_thumb; | |
299 } | |
300 | |
301 const AtomicString& SliderThumbElement::ShadowPseudoId() const { | |
302 HTMLInputElement* input = HostInput(); | |
303 if (!input || !input->GetLayoutObject()) | |
304 return SliderThumbShadowPartId(); | |
305 | |
306 const ComputedStyle& slider_style = input->GetLayoutObject()->StyleRef(); | |
307 switch (slider_style.Appearance()) { | |
308 case kMediaSliderPart: | |
309 case kMediaSliderThumbPart: | |
310 case kMediaVolumeSliderPart: | |
311 case kMediaVolumeSliderThumbPart: | |
312 return MediaSliderThumbShadowPartId(); | |
313 default: | |
314 return SliderThumbShadowPartId(); | |
315 } | |
316 } | |
317 | |
318 // -------------------------------- | |
319 | |
320 inline SliderContainerElement::SliderContainerElement(Document& document) | |
321 : HTMLDivElement(document), | |
322 has_touch_event_handler_(false), | |
323 touch_started_(false), | |
324 sliding_direction_(kNoMove) { | |
325 UpdateTouchEventHandlerRegistry(); | |
326 } | |
327 | |
328 DEFINE_NODE_FACTORY(SliderContainerElement) | |
329 | |
330 HTMLInputElement* SliderContainerElement::HostInput() const { | |
331 return toHTMLInputElement(OwnerShadowHost()); | |
332 } | |
333 | |
334 LayoutObject* SliderContainerElement::CreateLayoutObject(const ComputedStyle&) { | |
335 return new LayoutSliderContainer(this); | |
336 } | |
337 | |
338 void SliderContainerElement::DefaultEventHandler(Event* event) { | |
339 if (event->IsTouchEvent()) { | |
340 HandleTouchEvent(ToTouchEvent(event)); | |
341 return; | |
342 } | |
343 } | |
344 | |
345 void SliderContainerElement::HandleTouchEvent(TouchEvent* event) { | |
346 HTMLInputElement* input = HostInput(); | |
347 if (input->IsDisabledFormControl()) | |
348 return; | |
349 | |
350 if (event->type() == EventTypeNames::touchend) { | |
351 // TODO: Also do this for touchcancel? | |
352 input->DispatchFormControlChangeEvent(); | |
353 event->SetDefaultHandled(); | |
354 sliding_direction_ = kNoMove; | |
355 touch_started_ = false; | |
356 return; | |
357 } | |
358 | |
359 // The direction of this series of touch actions has been determined, which is | |
360 // perpendicular to the slider, so no need to adjust the value. | |
361 if (!CanSlide()) { | |
362 return; | |
363 } | |
364 | |
365 TouchList* touches = event->targetTouches(); | |
366 SliderThumbElement* thumb = ToSliderThumbElement( | |
367 GetTreeScope().GetElementById(ShadowElementNames::SliderThumb())); | |
368 if (touches->length() == 1) { | |
369 if (event->type() == EventTypeNames::touchstart) { | |
370 start_point_ = touches->item(0)->AbsoluteLocation(); | |
371 sliding_direction_ = kNoMove; | |
372 touch_started_ = true; | |
373 thumb->SetPositionFromPoint(touches->item(0)->AbsoluteLocation()); | |
374 } else if (touch_started_) { | |
375 LayoutPoint current_point = touches->item(0)->AbsoluteLocation(); | |
376 if (sliding_direction_ == | |
377 kNoMove) { // Still needs to update the direction. | |
378 sliding_direction_ = GetDirection(current_point, start_point_); | |
379 } | |
380 | |
381 // m_slidingDirection has been updated, so check whether it's okay to | |
382 // slide again. | |
383 if (CanSlide()) { | |
384 thumb->SetPositionFromPoint(touches->item(0)->AbsoluteLocation()); | |
385 event->SetDefaultHandled(); | |
386 } | |
387 } | |
388 } | |
389 } | |
390 | |
391 SliderContainerElement::Direction SliderContainerElement::GetDirection( | |
392 LayoutPoint& point1, | |
393 LayoutPoint& point2) { | |
394 if (point1 == point2) { | |
395 return kNoMove; | |
396 } | |
397 if ((point1.X() - point2.X()).Abs() >= (point1.Y() - point2.Y()).Abs()) { | |
398 return kHorizontal; | |
399 } | |
400 return kVertical; | |
401 } | |
402 | |
403 bool SliderContainerElement::CanSlide() { | |
404 if (!HostInput() || !HostInput()->GetLayoutObject() || | |
405 !HostInput()->GetLayoutObject()->Style()) { | |
406 return false; | |
407 } | |
408 const ComputedStyle* slider_style = HostInput()->GetLayoutObject()->Style(); | |
409 const TransformOperations& transforms = slider_style->Transform(); | |
410 int transform_size = transforms.size(); | |
411 if (transform_size > 0) { | |
412 for (int i = 0; i < transform_size; ++i) { | |
413 if (transforms.at(i)->GetType() == TransformOperation::kRotate) { | |
414 return true; | |
415 } | |
416 } | |
417 } | |
418 if ((sliding_direction_ == kVertical && | |
419 slider_style->Appearance() == kSliderHorizontalPart) || | |
420 (sliding_direction_ == kHorizontal && | |
421 slider_style->Appearance() == kSliderVerticalPart)) { | |
422 return false; | |
423 } | |
424 return true; | |
425 } | |
426 | |
427 const AtomicString& SliderContainerElement::ShadowPseudoId() const { | |
428 DEFINE_STATIC_LOCAL(const AtomicString, media_slider_container, | |
429 ("-webkit-media-slider-container")); | |
430 DEFINE_STATIC_LOCAL(const AtomicString, slider_container, | |
431 ("-webkit-slider-container")); | |
432 | |
433 if (!OwnerShadowHost() || !OwnerShadowHost()->GetLayoutObject()) | |
434 return slider_container; | |
435 | |
436 const ComputedStyle& slider_style = | |
437 OwnerShadowHost()->GetLayoutObject()->StyleRef(); | |
438 switch (slider_style.Appearance()) { | |
439 case kMediaSliderPart: | |
440 case kMediaSliderThumbPart: | |
441 case kMediaVolumeSliderPart: | |
442 case kMediaVolumeSliderThumbPart: | |
443 return media_slider_container; | |
444 default: | |
445 return slider_container; | |
446 } | |
447 } | |
448 | |
449 void SliderContainerElement::UpdateTouchEventHandlerRegistry() { | |
450 if (has_touch_event_handler_) { | |
451 return; | |
452 } | |
453 if (GetDocument().GetPage() && | |
454 GetDocument().Lifecycle().GetState() < DocumentLifecycle::kStopping) { | |
455 EventHandlerRegistry& registry = | |
456 GetDocument().GetPage()->GetEventHandlerRegistry(); | |
457 registry.DidAddEventHandler( | |
458 *this, EventHandlerRegistry::kTouchStartOrMoveEventPassive); | |
459 has_touch_event_handler_ = true; | |
460 } | |
461 } | |
462 | |
463 void SliderContainerElement::DidMoveToNewDocument(Document& old_document) { | |
464 UpdateTouchEventHandlerRegistry(); | |
465 HTMLElement::DidMoveToNewDocument(old_document); | |
466 } | |
467 | |
468 void SliderContainerElement::RemoveAllEventListeners() { | |
469 Node::RemoveAllEventListeners(); | |
470 has_touch_event_handler_ = false; | |
471 } | |
472 | |
473 } // namespace blink | |
OLD | NEW |