| 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 |