Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "ui/events/blink/input_handler_proxy.h" | 5 #include "ui/events/blink/input_handler_proxy.h" |
| 6 | 6 |
| 7 #include <stddef.h> | 7 #include <stddef.h> |
| 8 | 8 |
| 9 #include <algorithm> | 9 #include <algorithm> |
| 10 | 10 |
| (...skipping 190 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 201 "Event.Latency.RendererImpl.GestureFlingStart", | 201 "Event.Latency.RendererImpl.GestureFlingStart", |
| 202 delta.InMicroseconds(), 1, 1000000, 100); | 202 delta.InMicroseconds(), 1, 1000000, 100); |
| 203 break; | 203 break; |
| 204 default: | 204 default: |
| 205 NOTREACHED(); | 205 NOTREACHED(); |
| 206 break; | 206 break; |
| 207 } | 207 } |
| 208 } | 208 } |
| 209 } | 209 } |
| 210 | 210 |
| 211 cc::InputHandler::ScrollInputType GestureScrollInputType( | |
| 212 blink::WebGestureDevice device) { | |
| 213 return device == blink::WebGestureDeviceTouchpad | |
| 214 ? cc::InputHandler::WHEEL | |
| 215 : cc::InputHandler::TOUCHSCREEN; | |
| 216 } | |
| 217 | |
| 211 } // namespace | 218 } // namespace |
| 212 | 219 |
| 213 namespace ui { | 220 namespace ui { |
| 214 | 221 |
| 215 InputHandlerProxy::InputHandlerProxy(cc::InputHandler* input_handler, | 222 InputHandlerProxy::InputHandlerProxy(cc::InputHandler* input_handler, |
| 216 InputHandlerProxyClient* client) | 223 InputHandlerProxyClient* client) |
| 217 : client_(client), | 224 : client_(client), |
| 218 input_handler_(input_handler), | 225 input_handler_(input_handler), |
| 219 deferred_fling_cancel_time_seconds_(0), | 226 deferred_fling_cancel_time_seconds_(0), |
| 220 synchronous_input_handler_(nullptr), | 227 synchronous_input_handler_(nullptr), |
| (...skipping 196 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 417 cc::MainThreadScrollingReason::kMainThreadScrollingReasonCount); | 424 cc::MainThreadScrollingReason::kMainThreadScrollingReasonCount); |
| 418 } else { | 425 } else { |
| 419 UMA_HISTOGRAM_ENUMERATION( | 426 UMA_HISTOGRAM_ENUMERATION( |
| 420 kWheelHistogramName, i + 1, | 427 kWheelHistogramName, i + 1, |
| 421 cc::MainThreadScrollingReason::kMainThreadScrollingReasonCount); | 428 cc::MainThreadScrollingReason::kMainThreadScrollingReasonCount); |
| 422 } | 429 } |
| 423 } | 430 } |
| 424 } | 431 } |
| 425 } | 432 } |
| 426 | 433 |
| 427 bool InputHandlerProxy::ShouldAnimate( | 434 bool InputHandlerProxy::ShouldAnimate(bool has_coarse_scroll_deltas) const { |
| 428 const blink::WebMouseWheelEvent& event) const { | |
| 429 #if defined(OS_MACOSX) | 435 #if defined(OS_MACOSX) |
| 430 // Mac does not smooth scroll wheel events (crbug.com/574283). | 436 // Mac does not smooth scroll wheel events (crbug.com/574283). |
| 431 return false; | 437 return false; |
| 432 #else | 438 #else |
| 433 return smooth_scroll_enabled_ && !event.hasPreciseScrollingDeltas; | 439 return smooth_scroll_enabled_ && has_coarse_scroll_deltas; |
| 440 ; | |
|
tdresser
2016/03/08 14:28:53
Remove extra ;
dtapuska
2016/03/08 20:31:49
Done.
| |
| 434 #endif | 441 #endif |
| 435 } | 442 } |
| 436 | 443 |
| 437 InputHandlerProxy::EventDisposition InputHandlerProxy::HandleMouseWheel( | 444 InputHandlerProxy::EventDisposition InputHandlerProxy::HandleMouseWheel( |
| 438 const WebMouseWheelEvent& wheel_event) { | 445 const WebMouseWheelEvent& wheel_event) { |
| 439 // Only call |CancelCurrentFling()| if a fling was active, as it will | 446 // Only call |CancelCurrentFling()| if a fling was active, as it will |
| 440 // otherwise disrupt an in-progress touch scroll. | 447 // otherwise disrupt an in-progress touch scroll. |
| 441 if (!wheel_event.hasPreciseScrollingDeltas && fling_curve_) | 448 if (!wheel_event.hasPreciseScrollingDeltas && fling_curve_) |
| 442 CancelCurrentFling(); | 449 CancelCurrentFling(); |
| 443 | 450 |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 480 // TODO(jamesr): We don't properly handle scroll by page in the compositor | 487 // TODO(jamesr): We don't properly handle scroll by page in the compositor |
| 481 // thread, so punt it to the main thread. http://crbug.com/236639 | 488 // thread, so punt it to the main thread. http://crbug.com/236639 |
| 482 result = DID_NOT_HANDLE; | 489 result = DID_NOT_HANDLE; |
| 483 RecordMainThreadScrollingReasons( | 490 RecordMainThreadScrollingReasons( |
| 484 wheel_event.type, cc::MainThreadScrollingReason::kPageBasedScrolling); | 491 wheel_event.type, cc::MainThreadScrollingReason::kPageBasedScrolling); |
| 485 | 492 |
| 486 } else if (!wheel_event.canScroll) { | 493 } else if (!wheel_event.canScroll) { |
| 487 // Wheel events with |canScroll| == false will not trigger scrolling, | 494 // Wheel events with |canScroll| == false will not trigger scrolling, |
| 488 // only event handlers. Forward to the main thread. | 495 // only event handlers. Forward to the main thread. |
| 489 result = DID_NOT_HANDLE; | 496 result = DID_NOT_HANDLE; |
| 490 } else if (ShouldAnimate(wheel_event)) { | 497 } else if (ShouldAnimate(!wheel_event.hasPreciseScrollingDeltas)) { |
| 491 cc::InputHandler::ScrollStatus scroll_status = | 498 cc::InputHandler::ScrollStatus scroll_status = |
| 492 input_handler_->ScrollAnimated(gfx::Point(wheel_event.x, wheel_event.y), | 499 input_handler_->ScrollAnimated(gfx::Point(wheel_event.x, wheel_event.y), |
| 493 scroll_delta); | 500 scroll_delta); |
| 494 | 501 |
| 495 RecordMainThreadScrollingReasons( | 502 RecordMainThreadScrollingReasons( |
| 496 wheel_event.type, scroll_status.main_thread_scrolling_reasons); | 503 wheel_event.type, scroll_status.main_thread_scrolling_reasons); |
| 497 | 504 |
| 498 switch (scroll_status.thread) { | 505 switch (scroll_status.thread) { |
| 499 case cc::InputHandler::SCROLL_ON_IMPL_THREAD: | 506 case cc::InputHandler::SCROLL_ON_IMPL_THREAD: |
| 500 result = DID_HANDLE; | 507 result = DID_HANDLE; |
| (...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 585 expect_scroll_update_end_ = true; | 592 expect_scroll_update_end_ = true; |
| 586 #endif | 593 #endif |
| 587 cc::ScrollState scroll_state = CreateScrollStateForGesture(gesture_event); | 594 cc::ScrollState scroll_state = CreateScrollStateForGesture(gesture_event); |
| 588 cc::InputHandler::ScrollStatus scroll_status; | 595 cc::InputHandler::ScrollStatus scroll_status; |
| 589 if (gesture_event.data.scrollBegin.deltaHintUnits == | 596 if (gesture_event.data.scrollBegin.deltaHintUnits == |
| 590 blink::WebGestureEvent::ScrollUnits::Page) { | 597 blink::WebGestureEvent::ScrollUnits::Page) { |
| 591 scroll_status.thread = cc::InputHandler::SCROLL_ON_MAIN_THREAD; | 598 scroll_status.thread = cc::InputHandler::SCROLL_ON_MAIN_THREAD; |
| 592 scroll_status.main_thread_scrolling_reasons = | 599 scroll_status.main_thread_scrolling_reasons = |
| 593 cc::MainThreadScrollingReason::kContinuingMainThreadScroll; | 600 cc::MainThreadScrollingReason::kContinuingMainThreadScroll; |
| 594 } else if (gesture_event.data.scrollBegin.targetViewport) { | 601 } else if (gesture_event.data.scrollBegin.targetViewport) { |
| 595 scroll_status = input_handler_->RootScrollBegin(&scroll_state, | 602 scroll_status = input_handler_->RootScrollBegin( |
| 596 cc::InputHandler::GESTURE); | 603 &scroll_state, GestureScrollInputType(gesture_event.sourceDevice)); |
| 597 } else if (smooth_scroll_enabled_ && | 604 } else if (ShouldAnimate(gesture_event.data.scrollBegin.deltaHintUnits == |
| 598 gesture_event.data.scrollBegin.deltaHintUnits == | 605 blink::WebGestureEvent::ScrollUnits::Pixels)) { |
| 599 blink::WebGestureEvent::ScrollUnits::Pixels) { | 606 gfx::Point scroll_point(gesture_event.x, gesture_event.y); |
| 600 // Generate a scroll begin/end combination to determine if | 607 scroll_status = input_handler_->ScrollAnimatedBegin(scroll_point); |
| 601 // this can actually be handled by the impl thread or not. But | 608 } else { |
| 602 // don't generate any scroll yet; GestureScrollUpdate will generate | |
| 603 // the scroll animation. | |
| 604 scroll_status = input_handler_->ScrollBegin( | 609 scroll_status = input_handler_->ScrollBegin( |
| 605 &scroll_state, cc::InputHandler::ANIMATED_WHEEL); | 610 &scroll_state, GestureScrollInputType(gesture_event.sourceDevice)); |
| 606 if (scroll_status.thread == cc::InputHandler::SCROLL_ON_IMPL_THREAD) { | |
| 607 cc::ScrollStateData scroll_state_end_data; | |
| 608 scroll_state_end_data.is_ending = true; | |
| 609 cc::ScrollState scroll_state_end(scroll_state_end_data); | |
| 610 input_handler_->ScrollEnd(&scroll_state_end); | |
| 611 } | |
| 612 } else { | |
| 613 scroll_status = | |
| 614 input_handler_->ScrollBegin(&scroll_state, cc::InputHandler::GESTURE); | |
| 615 } | 611 } |
| 616 UMA_HISTOGRAM_ENUMERATION("Renderer4.CompositorScrollHitTestResult", | 612 UMA_HISTOGRAM_ENUMERATION("Renderer4.CompositorScrollHitTestResult", |
| 617 scroll_status.thread, | 613 scroll_status.thread, |
| 618 cc::InputHandler::LAST_SCROLL_STATUS + 1); | 614 cc::InputHandler::LAST_SCROLL_STATUS + 1); |
| 619 | 615 |
| 620 RecordMainThreadScrollingReasons(gesture_event.type, | 616 RecordMainThreadScrollingReasons(gesture_event.type, |
| 621 scroll_status.main_thread_scrolling_reasons); | 617 scroll_status.main_thread_scrolling_reasons); |
| 622 | 618 |
| 619 InputHandlerProxy::EventDisposition result = DID_NOT_HANDLE; | |
| 623 switch (scroll_status.thread) { | 620 switch (scroll_status.thread) { |
| 624 case cc::InputHandler::SCROLL_ON_IMPL_THREAD: | 621 case cc::InputHandler::SCROLL_ON_IMPL_THREAD: |
| 625 TRACE_EVENT_INSTANT0("input", | 622 TRACE_EVENT_INSTANT0("input", |
| 626 "InputHandlerProxy::handle_input gesture scroll", | 623 "InputHandlerProxy::handle_input gesture scroll", |
| 627 TRACE_EVENT_SCOPE_THREAD); | 624 TRACE_EVENT_SCOPE_THREAD); |
| 628 gesture_scroll_on_impl_thread_ = true; | 625 gesture_scroll_on_impl_thread_ = true; |
| 629 return DID_HANDLE; | 626 result = DID_HANDLE; |
| 627 break; | |
| 630 case cc::InputHandler::SCROLL_UNKNOWN: | 628 case cc::InputHandler::SCROLL_UNKNOWN: |
| 631 case cc::InputHandler::SCROLL_ON_MAIN_THREAD: | 629 case cc::InputHandler::SCROLL_ON_MAIN_THREAD: |
| 632 return DID_NOT_HANDLE; | 630 result = DID_NOT_HANDLE; |
| 631 break; | |
| 633 case cc::InputHandler::SCROLL_IGNORED: | 632 case cc::InputHandler::SCROLL_IGNORED: |
| 634 return DROP_EVENT; | 633 result = DROP_EVENT; |
| 634 break; | |
| 635 } | 635 } |
| 636 return DID_NOT_HANDLE; | 636 if (scroll_elasticity_controller_ && result != DID_NOT_HANDLE) |
| 637 HandleScrollElasticityOverscroll(gesture_event, | |
| 638 cc::InputHandlerScrollResult()); | |
| 639 | |
| 640 return result; | |
| 637 } | 641 } |
| 638 | 642 |
| 639 InputHandlerProxy::EventDisposition | 643 InputHandlerProxy::EventDisposition |
| 640 InputHandlerProxy::HandleGestureScrollUpdate( | 644 InputHandlerProxy::HandleGestureScrollUpdate( |
| 641 const WebGestureEvent& gesture_event) { | 645 const WebGestureEvent& gesture_event) { |
| 642 #ifndef NDEBUG | 646 #ifndef NDEBUG |
| 643 DCHECK(expect_scroll_update_end_); | 647 DCHECK(expect_scroll_update_end_); |
| 644 #endif | 648 #endif |
| 645 if (!gesture_scroll_on_impl_thread_ && !gesture_pinch_on_impl_thread_) | 649 if (!gesture_scroll_on_impl_thread_ && !gesture_pinch_on_impl_thread_) |
| 646 return DID_NOT_HANDLE; | 650 return DID_NOT_HANDLE; |
| 647 | 651 |
| 648 cc::ScrollState scroll_state = CreateScrollStateForGesture(gesture_event); | 652 cc::ScrollState scroll_state = CreateScrollStateForGesture(gesture_event); |
| 649 gfx::Point scroll_point(gesture_event.x, gesture_event.y); | 653 gfx::Point scroll_point(gesture_event.x, gesture_event.y); |
| 650 gfx::Vector2dF scroll_delta(-gesture_event.data.scrollUpdate.deltaX, | 654 gfx::Vector2dF scroll_delta(-gesture_event.data.scrollUpdate.deltaX, |
| 651 -gesture_event.data.scrollUpdate.deltaY); | 655 -gesture_event.data.scrollUpdate.deltaY); |
| 652 | 656 |
| 653 if (smooth_scroll_enabled_ && | 657 if (ShouldAnimate(gesture_event.data.scrollUpdate.deltaUnits == |
| 654 gesture_event.data.scrollUpdate.deltaUnits == | 658 blink::WebGestureEvent::ScrollUnits::Pixels)) { |
| 655 blink::WebGestureEvent::ScrollUnits::Pixels) { | |
| 656 switch (input_handler_->ScrollAnimated(scroll_point, scroll_delta).thread) { | 659 switch (input_handler_->ScrollAnimated(scroll_point, scroll_delta).thread) { |
| 657 case cc::InputHandler::SCROLL_ON_IMPL_THREAD: | 660 case cc::InputHandler::SCROLL_ON_IMPL_THREAD: |
| 658 return DID_HANDLE; | 661 return DID_HANDLE; |
| 659 case cc::InputHandler::SCROLL_IGNORED: | 662 case cc::InputHandler::SCROLL_IGNORED: |
| 660 return DROP_EVENT; | 663 return DROP_EVENT; |
| 661 default: | 664 default: |
| 662 return DID_NOT_HANDLE; | 665 return DID_NOT_HANDLE; |
| 663 } | 666 } |
| 664 } | 667 } |
| 665 cc::InputHandlerScrollResult scroll_result = | 668 cc::InputHandlerScrollResult scroll_result = |
| 666 input_handler_->ScrollBy(&scroll_state); | 669 input_handler_->ScrollBy(&scroll_state); |
| 667 HandleOverscroll(scroll_point, scroll_result); | 670 HandleOverscroll(scroll_point, scroll_result); |
| 671 | |
| 672 if (scroll_elasticity_controller_) | |
| 673 HandleScrollElasticityOverscroll(gesture_event, scroll_result); | |
| 674 | |
| 668 return scroll_result.did_scroll ? DID_HANDLE : DROP_EVENT; | 675 return scroll_result.did_scroll ? DID_HANDLE : DROP_EVENT; |
| 669 } | 676 } |
| 670 | 677 |
| 671 InputHandlerProxy::EventDisposition InputHandlerProxy::HandleGestureScrollEnd( | 678 InputHandlerProxy::EventDisposition InputHandlerProxy::HandleGestureScrollEnd( |
| 672 const WebGestureEvent& gesture_event) { | 679 const WebGestureEvent& gesture_event) { |
| 673 #ifndef NDEBUG | 680 #ifndef NDEBUG |
| 674 DCHECK(expect_scroll_update_end_); | 681 DCHECK(expect_scroll_update_end_); |
| 675 expect_scroll_update_end_ = false; | 682 expect_scroll_update_end_ = false; |
| 676 #endif | 683 #endif |
| 677 cc::ScrollState scroll_state = CreateScrollStateForGesture(gesture_event); | 684 if (ShouldAnimate(gesture_event.data.scrollEnd.deltaUnits == |
| 678 input_handler_->ScrollEnd(&scroll_state); | 685 blink::WebGestureEvent::ScrollUnits::Pixels)) { |
| 686 // Do nothing if we generated called ScrollAnimate; it will handle the end | |
|
tdresser
2016/03/08 14:28:53
"if we generated called"
grammar
dtapuska
2016/03/08 20:31:49
Done.
| |
| 687 // of the gesture. | |
| 688 } else { | |
| 689 cc::ScrollState scroll_state = CreateScrollStateForGesture(gesture_event); | |
| 690 input_handler_->ScrollEnd(&scroll_state); | |
| 691 } | |
| 679 if (!gesture_scroll_on_impl_thread_) | 692 if (!gesture_scroll_on_impl_thread_) |
| 680 return DID_NOT_HANDLE; | 693 return DID_NOT_HANDLE; |
| 694 | |
| 695 if (scroll_elasticity_controller_) | |
| 696 HandleScrollElasticityOverscroll(gesture_event, | |
| 697 cc::InputHandlerScrollResult()); | |
| 698 | |
| 681 gesture_scroll_on_impl_thread_ = false; | 699 gesture_scroll_on_impl_thread_ = false; |
| 682 return DID_HANDLE; | 700 return DID_HANDLE; |
| 683 } | 701 } |
| 684 | 702 |
| 685 InputHandlerProxy::EventDisposition InputHandlerProxy::HandleGestureFlingStart( | 703 InputHandlerProxy::EventDisposition InputHandlerProxy::HandleGestureFlingStart( |
| 686 const WebGestureEvent& gesture_event) { | 704 const WebGestureEvent& gesture_event) { |
| 687 cc::ScrollState scroll_state = CreateScrollStateForGesture(gesture_event); | 705 cc::ScrollState scroll_state = CreateScrollStateForGesture(gesture_event); |
| 688 cc::InputHandler::ScrollStatus scroll_status; | 706 cc::InputHandler::ScrollStatus scroll_status; |
| 689 scroll_status.main_thread_scrolling_reasons = | 707 scroll_status.main_thread_scrolling_reasons = |
| 690 cc::MainThreadScrollingReason::kNotScrollingOnMain; | 708 cc::MainThreadScrollingReason::kNotScrollingOnMain; |
| (...skipping 192 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 883 switch (gesture_event.type) { | 901 switch (gesture_event.type) { |
| 884 case WebInputEvent::GestureTapCancel: | 902 case WebInputEvent::GestureTapCancel: |
| 885 case WebInputEvent::GestureTapDown: | 903 case WebInputEvent::GestureTapDown: |
| 886 return false; | 904 return false; |
| 887 | 905 |
| 888 case WebInputEvent::GestureScrollBegin: | 906 case WebInputEvent::GestureScrollBegin: |
| 889 if (!input_handler_->IsCurrentlyScrollingLayerAt( | 907 if (!input_handler_->IsCurrentlyScrollingLayerAt( |
| 890 gfx::Point(gesture_event.x, gesture_event.y), | 908 gfx::Point(gesture_event.x, gesture_event.y), |
| 891 fling_parameters_.sourceDevice == blink::WebGestureDeviceTouchpad | 909 fling_parameters_.sourceDevice == blink::WebGestureDeviceTouchpad |
| 892 ? cc::InputHandler::NON_BUBBLING_GESTURE | 910 ? cc::InputHandler::NON_BUBBLING_GESTURE |
| 893 : cc::InputHandler::GESTURE)) { | 911 : cc::InputHandler::TOUCHSCREEN)) { |
| 894 CancelCurrentFling(); | 912 CancelCurrentFling(); |
| 895 return false; | 913 return false; |
| 896 } | 914 } |
| 897 | 915 |
| 898 // TODO(jdduke): Use |gesture_event.data.scrollBegin.delta{X,Y}Hint| to | 916 // TODO(jdduke): Use |gesture_event.data.scrollBegin.delta{X,Y}Hint| to |
| 899 // determine if the ScrollBegin should immediately cancel the fling. | 917 // determine if the ScrollBegin should immediately cancel the fling. |
| 900 ExtendBoostedFlingTimeout(gesture_event); | 918 ExtendBoostedFlingTimeout(gesture_event); |
| 901 return true; | 919 return true; |
| 902 | 920 |
| 903 case WebInputEvent::GestureScrollUpdate: { | 921 case WebInputEvent::GestureScrollUpdate: { |
| (...skipping 399 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1303 // It's possible the provided |increment| is sufficiently small as to not | 1321 // It's possible the provided |increment| is sufficiently small as to not |
| 1304 // trigger a scroll, e.g., with a trivial time delta between fling updates. | 1322 // trigger a scroll, e.g., with a trivial time delta between fling updates. |
| 1305 // Return true in this case to prevent early fling termination. | 1323 // Return true in this case to prevent early fling termination. |
| 1306 if (std::abs(clipped_increment.width) < kScrollEpsilon && | 1324 if (std::abs(clipped_increment.width) < kScrollEpsilon && |
| 1307 std::abs(clipped_increment.height) < kScrollEpsilon) | 1325 std::abs(clipped_increment.height) < kScrollEpsilon) |
| 1308 return true; | 1326 return true; |
| 1309 | 1327 |
| 1310 return did_scroll; | 1328 return did_scroll; |
| 1311 } | 1329 } |
| 1312 | 1330 |
| 1331 void InputHandlerProxy::HandleScrollElasticityOverscroll( | |
| 1332 const WebGestureEvent& gesture_event, | |
| 1333 const cc::InputHandlerScrollResult& scroll_result) { | |
| 1334 DCHECK(scroll_elasticity_controller_); | |
| 1335 // Send the event and its disposition to the elasticity controller to update | |
| 1336 // the over-scroll animation.Note that the call to the elasticity controller | |
|
tdresser
2016/03/08 14:28:53
animation.Note
missing space
dtapuska
2016/03/08 20:31:49
Done.
| |
| 1337 // is made asynchronously, to minimize divergence between main thread and | |
| 1338 // impl thread event handling paths. | |
| 1339 base::ThreadTaskRunnerHandle::Get()->PostTask( | |
| 1340 FROM_HERE, | |
| 1341 base::Bind(&InputScrollElasticityController::ObserveGestureEventAndResult, | |
| 1342 scroll_elasticity_controller_->GetWeakPtr(), gesture_event, | |
| 1343 scroll_result)); | |
| 1344 } | |
| 1345 | |
| 1313 } // namespace ui | 1346 } // namespace ui |
| OLD | NEW |