| /* |
| * Copyright (C) 2006-2016 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #import "config.h" |
| #import "EventHandler.h" |
| |
| #if PLATFORM(IOS_FAMILY) |
| |
| #import "AXObjectCache.h" |
| #import "AutoscrollController.h" |
| #import "Chrome.h" |
| #import "ChromeClient.h" |
| #import "DataTransfer.h" |
| #import "DragState.h" |
| #import "EventNames.h" |
| #import "FocusController.h" |
| #import "Frame.h" |
| #import "FrameView.h" |
| #import "KeyboardEvent.h" |
| #import "MouseEventWithHitTestResults.h" |
| #import "Page.h" |
| #import "Pasteboard.h" |
| #import "PlatformEventFactoryIOS.h" |
| #import "PlatformKeyboardEvent.h" |
| #import "RenderWidget.h" |
| #import "WAKView.h" |
| #import "WAKWindow.h" |
| #import "WebEvent.h" |
| #import <wtf/BlockObjCExceptions.h> |
| #import <wtf/NeverDestroyed.h> |
| #import <wtf/Noncopyable.h> |
| #import <wtf/SetForScope.h> |
| |
| #if ENABLE(CONTENT_CHANGE_OBSERVER) |
| #import "ContentChangeObserver.h" |
| #endif |
| |
| #if ENABLE(IOS_TOUCH_EVENTS) |
| #import <WebKitAdditions/EventHandlerIOSTouch.cpp> |
| #endif |
| |
| namespace WebCore { |
| |
| static RetainPtr<WebEvent>& currentEventSlot() |
| { |
| static NeverDestroyed<RetainPtr<WebEvent>> event; |
| return event; |
| } |
| |
| WebEvent *EventHandler::currentEvent() |
| { |
| return currentEventSlot().get(); |
| } |
| |
| class CurrentEventScope { |
| WTF_MAKE_NONCOPYABLE(CurrentEventScope); |
| public: |
| CurrentEventScope(WebEvent *); |
| ~CurrentEventScope(); |
| |
| private: |
| RetainPtr<WebEvent> m_savedCurrentEvent; |
| #ifndef NDEBUG |
| RetainPtr<WebEvent> m_event; |
| #endif |
| }; |
| |
| inline CurrentEventScope::CurrentEventScope(WebEvent *event) |
| : m_savedCurrentEvent(currentEventSlot()) |
| #ifndef NDEBUG |
| , m_event(event) |
| #endif |
| { |
| currentEventSlot() = event; |
| } |
| |
| inline CurrentEventScope::~CurrentEventScope() |
| { |
| ASSERT(currentEventSlot() == m_event); |
| currentEventSlot() = m_savedCurrentEvent; |
| } |
| |
| bool EventHandler::wheelEvent(WebEvent *event) |
| { |
| Page* page = m_frame.page(); |
| if (!page) |
| return false; |
| |
| CurrentEventScope scope(event); |
| |
| auto wheelEvent = PlatformEventFactory::createPlatformWheelEvent(event); |
| OptionSet<WheelEventProcessingSteps> processingSteps = { WheelEventProcessingSteps::MainThreadForScrolling, WheelEventProcessingSteps::MainThreadForBlockingDOMEventDispatch }; |
| |
| if (wheelEvent.isGestureStart()) |
| m_wheelScrollGestureState = std::nullopt; |
| else if (wheelEvent.phase() == PlatformWheelEventPhase::Changed || wheelEvent.momentumPhase() == PlatformWheelEventPhase::Changed) { |
| if (m_wheelScrollGestureState && *m_wheelScrollGestureState == WheelScrollGestureState::NonBlocking) |
| processingSteps = { WheelEventProcessingSteps::MainThreadForScrolling, WheelEventProcessingSteps::MainThreadForNonBlockingDOMEventDispatch }; |
| } |
| |
| bool eventWasHandled = handleWheelEvent(wheelEvent, processingSteps); |
| event.wasHandled = eventWasHandled; |
| return eventWasHandled; |
| } |
| |
| #if ENABLE(IOS_TOUCH_EVENTS) |
| |
| bool EventHandler::dispatchSimulatedTouchEvent(IntPoint location) |
| { |
| bool handled = handleTouchEvent(PlatformEventFactory::createPlatformSimulatedTouchEvent(PlatformEvent::TouchStart, location)); |
| handled |= handleTouchEvent(PlatformEventFactory::createPlatformSimulatedTouchEvent(PlatformEvent::TouchEnd, location)); |
| return handled; |
| } |
| |
| void EventHandler::touchEvent(WebEvent *event) |
| { |
| CurrentEventScope scope(event); |
| |
| event.wasHandled = handleTouchEvent(PlatformEventFactory::createPlatformTouchEvent(event)); |
| } |
| #endif |
| |
| bool EventHandler::tabsToAllFormControls(KeyboardEvent* event) const |
| { |
| Page* page = m_frame.page(); |
| if (!page) |
| return false; |
| |
| KeyboardUIMode keyboardUIMode = page->chrome().client().keyboardUIMode(); |
| bool handlingOptionTab = event && isKeyboardOptionTab(*event); |
| |
| // If tab-to-links is off, option-tab always highlights all controls. |
| if ((keyboardUIMode & KeyboardAccessTabsToLinks) == 0 && handlingOptionTab) |
| return true; |
| |
| // If system preferences say to include all controls, we always include all controls. |
| if (keyboardUIMode & KeyboardAccessFull) |
| return true; |
| |
| // Otherwise tab-to-links includes all controls, unless the sense is flipped via option-tab. |
| if (keyboardUIMode & KeyboardAccessTabsToLinks) |
| return !handlingOptionTab; |
| |
| return handlingOptionTab; |
| } |
| |
| bool EventHandler::keyEvent(WebEvent *event) |
| { |
| BEGIN_BLOCK_OBJC_EXCEPTIONS |
| |
| ASSERT(event.type == WebEventKeyDown || event.type == WebEventKeyUp); |
| |
| CurrentEventScope scope(event); |
| bool eventWasHandled = keyEvent(PlatformEventFactory::createPlatformKeyboardEvent(event)); |
| event.wasHandled = eventWasHandled; |
| return eventWasHandled; |
| |
| END_BLOCK_OBJC_EXCEPTIONS |
| |
| return false; |
| } |
| |
| void EventHandler::focusDocumentView() |
| { |
| Page* page = m_frame.page(); |
| if (!page) |
| return; |
| |
| if (RefPtr frameView = m_frame.view()) { |
| if (NSView *documentView = frameView->documentView()) { |
| page->chrome().focusNSView(documentView); |
| // Check page() again because focusNSView can cause reentrancy. |
| if (!m_frame.page()) |
| return; |
| } |
| } |
| |
| RELEASE_ASSERT(page == m_frame.page()); |
| CheckedRef(page->focusController())->setFocusedFrame(&m_frame); |
| } |
| |
| bool EventHandler::passWidgetMouseDownEventToWidget(const MouseEventWithHitTestResults& event) |
| { |
| // Figure out which view to send the event to. |
| auto* target = event.targetNode() ? event.targetNode()->renderer() : nullptr; |
| if (!is<RenderWidget>(target)) |
| return false; |
| |
| // Double-click events don't exist in Cocoa. Since passWidgetMouseDownEventToWidget() will |
| // just pass currentEvent down to the widget, we don't want to call it for events that |
| // don't correspond to Cocoa events. The mousedown/ups will have already been passed on as |
| // part of the pressed/released handling. |
| return passMouseDownEventToWidget(downcast<RenderWidget>(*target).widget()); |
| } |
| |
| bool EventHandler::passWidgetMouseDownEventToWidget(RenderWidget* renderWidget) |
| { |
| return passMouseDownEventToWidget(renderWidget->widget()); |
| } |
| |
| static bool lastEventIsMouseUp() |
| { |
| // Many AppKit widgets run their own event loops and consume events while the mouse is down. |
| // When they finish, currentEvent is the mouseUp that they exited on. We need to update |
| // the WebCore state with this mouseUp, which we never saw. This method lets us detect |
| // that state. Handling this was critical when we used AppKit widgets for form elements. |
| // It's not clear in what cases this is helpful now -- it's possible it can be removed. |
| |
| BEGIN_BLOCK_OBJC_EXCEPTIONS |
| WebEvent *currentEventAfterHandlingMouseDown = [WAKWindow currentEvent]; |
| return currentEventAfterHandlingMouseDown |
| && EventHandler::currentEvent() != currentEventAfterHandlingMouseDown |
| && currentEventAfterHandlingMouseDown.type == WebEventMouseUp |
| && currentEventAfterHandlingMouseDown.timestamp >= EventHandler::currentEvent().timestamp; |
| END_BLOCK_OBJC_EXCEPTIONS |
| |
| return false; |
| } |
| |
| bool EventHandler::passMouseDownEventToWidget(Widget* pWidget) |
| { |
| // FIXME: This function always returns true. It should be changed either to return |
| // false in some cases or the return value should be removed. |
| |
| RefPtr<Widget> widget = pWidget; |
| |
| if (!widget) { |
| LOG_ERROR("hit a RenderWidget without a corresponding Widget, means a frame is half-constructed"); |
| return true; |
| } |
| |
| // In WebKit2 we will never have a native widget. Just return early and let the regular event handler machinery take care of |
| // dispatching the event. |
| if (!widget->platformWidget()) |
| return false; |
| |
| BEGIN_BLOCK_OBJC_EXCEPTIONS |
| |
| NSView *nodeView = widget->platformWidget(); |
| ASSERT(nodeView); |
| ASSERT([nodeView superview]); |
| NSView *view = [nodeView hitTest:[[nodeView superview] convertPoint:currentEvent().locationInWindow fromView:nil]]; |
| if (!view) { |
| // We probably hit the border of a RenderWidget |
| return true; |
| } |
| |
| Page* page = m_frame.page(); |
| if (!page) |
| return true; |
| |
| if (page->chrome().client().firstResponder() != view) { |
| // Normally [NSWindow sendEvent:] handles setting the first responder. |
| // But in our case, the event was sent to the view representing the entire web page. |
| if ([view acceptsFirstResponder] && [view needsPanelToBecomeKey]) |
| page->chrome().client().makeFirstResponder(view); |
| } |
| |
| // We need to "defer loading" while tracking the mouse, because tearing down the |
| // page while an AppKit control is tracking the mouse can cause a crash. |
| |
| // FIXME: In theory, WebCore now tolerates tear-down while tracking the |
| // mouse. We should confirm that, and then remove the deferrsLoading |
| // hack entirely. |
| |
| bool wasDeferringLoading = page->defersLoading(); |
| if (!wasDeferringLoading) |
| page->setDefersLoading(true); |
| |
| ASSERT(!m_sendingEventToSubview); |
| m_sendingEventToSubview = true; |
| |
| { |
| WidgetHierarchyUpdatesSuspensionScope suspendWidgetHierarchyUpdates; |
| [view mouseDown:currentEvent()]; |
| } |
| |
| m_sendingEventToSubview = false; |
| |
| if (!wasDeferringLoading) |
| page->setDefersLoading(false); |
| |
| // Remember which view we sent the event to, so we can direct the release event properly. |
| m_mouseDownView = view; |
| m_mouseDownWasInSubframe = false; |
| |
| // Many AppKit widgets run their own event loops and consume events while the mouse is down. |
| // When they finish, currentEvent is the mouseUp that they exited on. We need to update |
| // the EventHandler state with this mouseUp, which we never saw. |
| // If this event isn't a mouseUp, we assume that the mouseUp will be coming later. There |
| // is a hole here if the widget consumes both the mouseUp and subsequent events. |
| if (lastEventIsMouseUp()) |
| m_mousePressed = false; |
| |
| END_BLOCK_OBJC_EXCEPTIONS |
| |
| return true; |
| } |
| |
| // Note that this does the same kind of check as [target isDescendantOf:superview]. |
| // There are two differences: This is a lot slower because it has to walk the whole |
| // tree, and this works in cases where the target has already been deallocated. |
| static bool findViewInSubviews(NSView *superview, NSView *target) |
| { |
| BEGIN_BLOCK_OBJC_EXCEPTIONS |
| NSEnumerator *e = [[superview subviews] objectEnumerator]; |
| NSView *subview; |
| while ((subview = [e nextObject])) { |
| if (subview == target || findViewInSubviews(subview, target)) { |
| return true; |
| } |
| } |
| END_BLOCK_OBJC_EXCEPTIONS |
| |
| return false; |
| } |
| |
| NSView *EventHandler::mouseDownViewIfStillGood() |
| { |
| // Since we have no way of tracking the lifetime of m_mouseDownView, we have to assume that |
| // it could be deallocated already. We search for it in our subview tree; if we don't find |
| // it, we set it to nil. |
| NSView *mouseDownView = m_mouseDownView; |
| if (!mouseDownView) { |
| return nil; |
| } |
| FrameView* topFrameView = m_frame.view(); |
| NSView *topView = topFrameView ? topFrameView->platformWidget() : nil; |
| if (!topView || !findViewInSubviews(topView, mouseDownView)) { |
| m_mouseDownView = nil; |
| return nil; |
| } |
| return mouseDownView; |
| } |
| |
| bool EventHandler::eventActivatedView(const PlatformMouseEvent&) const |
| { |
| return false; |
| } |
| |
| bool EventHandler::eventLoopHandleMouseUp(const MouseEventWithHitTestResults&) |
| { |
| NSView *view = mouseDownViewIfStillGood(); |
| if (!view) |
| return false; |
| |
| if (!m_mouseDownWasInSubframe) { |
| ASSERT(!m_sendingEventToSubview); |
| m_sendingEventToSubview = true; |
| BEGIN_BLOCK_OBJC_EXCEPTIONS |
| [view mouseUp:currentEvent()]; |
| END_BLOCK_OBJC_EXCEPTIONS |
| m_sendingEventToSubview = false; |
| } |
| |
| return true; |
| } |
| |
| bool EventHandler::passSubframeEventToSubframe(MouseEventWithHitTestResults& event, Frame& subframe, HitTestResult* hitTestResult) |
| { |
| BEGIN_BLOCK_OBJC_EXCEPTIONS |
| |
| WebEventType currentEventType = currentEvent().type; |
| switch (currentEventType) { |
| case WebEventMouseMoved: { |
| // Since we're passing in currentNSEvent() here, we can call |
| // handleMouseMoveEvent() directly, since the save/restore of |
| // currentNSEvent() that mouseMoved() does would have no effect. |
| ASSERT(!m_sendingEventToSubview); |
| m_sendingEventToSubview = true; |
| subframe.eventHandler().handleMouseMoveEvent(currentPlatformMouseEvent(), hitTestResult); |
| m_sendingEventToSubview = false; |
| return true; |
| } |
| case WebEventMouseDown: { |
| auto* node = event.targetNode(); |
| if (!node) |
| return false; |
| auto* renderer = node->renderer(); |
| if (!is<RenderWidget>(renderer)) |
| return false; |
| auto* widget = downcast<RenderWidget>(*renderer).widget(); |
| if (!widget || !widget->isFrameView()) |
| return false; |
| if (!passWidgetMouseDownEventToWidget(downcast<RenderWidget>(renderer))) |
| return false; |
| m_mouseDownWasInSubframe = true; |
| return true; |
| } |
| case WebEventMouseUp: { |
| if (!m_mouseDownWasInSubframe) |
| return false; |
| ASSERT(!m_sendingEventToSubview); |
| m_sendingEventToSubview = true; |
| subframe.eventHandler().handleMouseReleaseEvent(currentPlatformMouseEvent()); |
| m_sendingEventToSubview = false; |
| return true; |
| } |
| case WebEventKeyDown: |
| case WebEventKeyUp: |
| case WebEventScrollWheel: |
| case WebEventTouchBegin: |
| case WebEventTouchCancel: |
| case WebEventTouchChange: |
| case WebEventTouchEnd: |
| return false; |
| } |
| END_BLOCK_OBJC_EXCEPTIONS |
| |
| return false; |
| } |
| |
| bool EventHandler::passWheelEventToWidget(const PlatformWheelEvent&, Widget& widget, OptionSet<WheelEventProcessingSteps>) |
| { |
| BEGIN_BLOCK_OBJC_EXCEPTIONS |
| |
| NSView* nodeView = widget.platformWidget(); |
| if (!nodeView) { |
| // WK2 code path. No wheel events on iOS anyway. |
| return false; |
| } |
| |
| if (currentEvent().type != WebEventScrollWheel || m_sendingEventToSubview) |
| return false; |
| |
| ASSERT(nodeView); |
| ASSERT([nodeView superview]); |
| NSView *view = [nodeView hitTest:[[nodeView superview] convertPoint:currentEvent().locationInWindow fromView:nil]]; |
| if (!view) { |
| // We probably hit the border of a RenderWidget |
| return false; |
| } |
| |
| ASSERT(!m_sendingEventToSubview); |
| m_sendingEventToSubview = true; |
| [view scrollWheel:currentEvent()]; |
| m_sendingEventToSubview = false; |
| return true; |
| |
| END_BLOCK_OBJC_EXCEPTIONS |
| return false; |
| } |
| |
| void EventHandler::mouseDown(WebEvent *event) |
| { |
| FrameView* v = m_frame.view(); |
| if (!v || m_sendingEventToSubview) |
| return; |
| |
| BEGIN_BLOCK_OBJC_EXCEPTIONS |
| |
| // FIXME: Why is this here? EventHandler::handleMousePressEvent() calls it. |
| m_frame.loader().resetMultipleFormSubmissionProtection(); |
| |
| m_mouseDownView = nil; |
| |
| CurrentEventScope scope(event); |
| |
| event.wasHandled = handleMousePressEvent(currentPlatformMouseEvent()); |
| |
| END_BLOCK_OBJC_EXCEPTIONS |
| } |
| |
| void EventHandler::mouseUp(WebEvent *event) |
| { |
| FrameView* v = m_frame.view(); |
| if (!v || m_sendingEventToSubview) |
| return; |
| |
| BEGIN_BLOCK_OBJC_EXCEPTIONS |
| |
| CurrentEventScope scope(event); |
| |
| event.wasHandled = handleMouseReleaseEvent(currentPlatformMouseEvent()); |
| |
| m_mouseDownView = nil; |
| |
| END_BLOCK_OBJC_EXCEPTIONS |
| } |
| |
| void EventHandler::mouseMoved(WebEvent *event) |
| { |
| // Reject a mouse moved if the button is down - screws up tracking during autoscroll |
| // These happen because WebKit sometimes has to fake up moved events. |
| if (!m_frame.document() || !m_frame.view() || m_mousePressed || m_sendingEventToSubview) |
| return; |
| |
| BEGIN_BLOCK_OBJC_EXCEPTIONS |
| auto& document = *m_frame.document(); |
| // Ensure we start mouse move event dispatching on a clear tree. |
| document.updateStyleIfNeeded(); |
| CurrentEventScope scope(event); |
| { |
| #if ENABLE(CONTENT_CHANGE_OBSERVER) |
| ContentChangeObserver::MouseMovedScope observingScope(document); |
| #endif |
| event.wasHandled = mouseMoved(currentPlatformMouseEvent()); |
| // Run style recalc to be able to capture content changes as the result of the mouse move event. |
| document.updateStyleIfNeeded(); |
| #if ENABLE(CONTENT_CHANGE_OBSERVER) |
| callOnMainThread([protectedFrame = Ref { m_frame }] { |
| // This is called by WebKitLegacy only. |
| if (auto* document = protectedFrame->document()) |
| document->contentChangeObserver().willNotProceedWithFixedObservationTimeWindow(); |
| }); |
| #endif |
| } |
| |
| END_BLOCK_OBJC_EXCEPTIONS |
| } |
| |
| static bool frameHasPlatformWidget(const Frame& frame) |
| { |
| if (FrameView* frameView = frame.view()) { |
| if (frameView->platformWidget()) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void EventHandler::dispatchSyntheticMouseOut(const PlatformMouseEvent& platformMouseEvent) |
| { |
| updateMouseEventTargetNode(eventNames().mouseoutEvent, nullptr, platformMouseEvent, FireMouseOverOut::Yes); |
| } |
| |
| void EventHandler::dispatchSyntheticMouseMove(const PlatformMouseEvent& platformMouseEvent) |
| { |
| mouseMoved(platformMouseEvent); |
| } |
| |
| bool EventHandler::passMousePressEventToSubframe(MouseEventWithHitTestResults& mouseEventAndResult, Frame& subframe) |
| { |
| // WebKit1 code path. |
| if (frameHasPlatformWidget(m_frame)) |
| return passSubframeEventToSubframe(mouseEventAndResult, subframe); |
| |
| // WebKit2 code path. |
| subframe.eventHandler().handleMousePressEvent(mouseEventAndResult.event()); |
| return true; |
| } |
| |
| bool EventHandler::passMouseMoveEventToSubframe(MouseEventWithHitTestResults& mouseEventAndResult, Frame& subframe, HitTestResult* hitTestResult) |
| { |
| // WebKit1 code path. |
| if (frameHasPlatformWidget(m_frame)) |
| return passSubframeEventToSubframe(mouseEventAndResult, subframe, hitTestResult); |
| |
| subframe.eventHandler().handleMouseMoveEvent(mouseEventAndResult.event(), hitTestResult); |
| return true; |
| } |
| |
| bool EventHandler::passMouseReleaseEventToSubframe(MouseEventWithHitTestResults& mouseEventAndResult, Frame& subframe) |
| { |
| // WebKit1 code path. |
| if (frameHasPlatformWidget(m_frame)) |
| return passSubframeEventToSubframe(mouseEventAndResult, subframe); |
| |
| // WebKit2 code path. |
| subframe.eventHandler().handleMouseReleaseEvent(mouseEventAndResult.event()); |
| return true; |
| } |
| |
| OptionSet<PlatformEvent::Modifier> EventHandler::accessKeyModifiers() |
| { |
| // Control+Option key combinations are usually unused on Mac OS X, but not when VoiceOver is enabled. |
| // So, we use Control in this case, even though it conflicts with Emacs-style key bindings. |
| // See <https://bugs.webkit.org/show_bug.cgi?id=21107> for more detail. |
| if (AXObjectCache::accessibilityEnhancedUserInterfaceEnabled()) |
| return PlatformEvent::Modifier::ControlKey; |
| |
| return { PlatformEvent::Modifier::ControlKey, PlatformEvent::Modifier::AltKey }; |
| } |
| |
| PlatformMouseEvent EventHandler::currentPlatformMouseEvent() const |
| { |
| return PlatformEventFactory::createPlatformMouseEvent(currentEvent()); |
| } |
| |
| void EventHandler::startSelectionAutoscroll(RenderObject* renderer, const FloatPoint& positionInWindow) |
| { |
| Ref<Frame> protectedFrame(m_frame); |
| |
| m_targetAutoscrollPositionInUnscrolledRootViewCoordinates = protectedFrame->view()->contentsToRootView(roundedIntPoint(positionInWindow)) - toIntSize(protectedFrame->view()->documentScrollPositionRelativeToViewOrigin()); |
| |
| if (!m_isAutoscrolling) |
| m_initialTargetAutoscrollPositionInUnscrolledRootViewCoordinates = m_targetAutoscrollPositionInUnscrolledRootViewCoordinates; |
| |
| m_isAutoscrolling = true; |
| m_autoscrollController->startAutoscrollForSelection(renderer); |
| } |
| |
| void EventHandler::cancelSelectionAutoscroll() |
| { |
| m_isAutoscrolling = false; |
| m_initialTargetAutoscrollPositionInUnscrolledRootViewCoordinates = std::nullopt; |
| m_autoscrollController->stopAutoscrollTimer(); |
| } |
| |
| static IntPoint adjustAutoscrollDestinationForInsetEdges(IntPoint autoscrollPoint, std::optional<IntPoint> initialAutoscrollPoint, FloatRect unobscuredRootViewRect) |
| { |
| IntPoint resultPoint = autoscrollPoint; |
| |
| const float edgeInset = 75; |
| const float maximumScrollingSpeed = 20; |
| const float insetDistanceThreshold = edgeInset / 2; |
| |
| // FIXME: Ideally we would only inset on edges that touch the edge of the screen, |
| // like macOS, but we don't have enough information in WebCore to do that currently. |
| FloatRect insetUnobscuredRootViewRect = unobscuredRootViewRect; |
| |
| if (initialAutoscrollPoint) { |
| IntSize autoscrollDelta = autoscrollPoint - *initialAutoscrollPoint; |
| |
| // Inset edges in the direction of the autoscroll. |
| // Do not apply insets until you drag in the direction of the edge at least `insetDistanceThreshold`, |
| // to make it possible to select text that abuts the edge of `unobscuredRootViewRect` without causing |
| // unwanted autoscrolling. |
| if (autoscrollDelta.width() < insetDistanceThreshold) |
| insetUnobscuredRootViewRect.shiftXEdgeTo(insetUnobscuredRootViewRect.x() + std::min<float>(edgeInset, -autoscrollDelta.width() - insetDistanceThreshold)); |
| else if (autoscrollDelta.width() > insetDistanceThreshold) |
| insetUnobscuredRootViewRect.shiftMaxXEdgeTo(insetUnobscuredRootViewRect.maxX() - std::min<float>(edgeInset, autoscrollDelta.width() - insetDistanceThreshold)); |
| |
| if (autoscrollDelta.height() < insetDistanceThreshold) |
| insetUnobscuredRootViewRect.shiftYEdgeTo(insetUnobscuredRootViewRect.y() + std::min<float>(edgeInset, -autoscrollDelta.height() - insetDistanceThreshold)); |
| else if (autoscrollDelta.height() > insetDistanceThreshold) |
| insetUnobscuredRootViewRect.shiftMaxYEdgeTo(insetUnobscuredRootViewRect.maxY() - std::min<float>(edgeInset, autoscrollDelta.height() - insetDistanceThreshold)); |
| } |
| |
| // If the current autoscroll point is beyond the edge of the view (respecting insets), shift it outside |
| // of the view, so that autoscrolling will occur. The distance we move outside of the view is scaled from |
| // `edgeInset` to `maximumScrollingSpeed` so that the inset's contribution to the speed is independent of its size. |
| if (autoscrollPoint.x() < insetUnobscuredRootViewRect.x()) { |
| float distanceFromEdge = autoscrollPoint.x() - insetUnobscuredRootViewRect.x(); |
| if (distanceFromEdge < 0) |
| resultPoint.setX(unobscuredRootViewRect.x() + ((distanceFromEdge / edgeInset) * maximumScrollingSpeed)); |
| } else if (autoscrollPoint.x() >= insetUnobscuredRootViewRect.maxX()) { |
| float distanceFromEdge = autoscrollPoint.x() - insetUnobscuredRootViewRect.maxX(); |
| if (distanceFromEdge > 0) |
| resultPoint.setX(unobscuredRootViewRect.maxX() + ((distanceFromEdge / edgeInset) * maximumScrollingSpeed)); |
| } |
| |
| if (autoscrollPoint.y() < insetUnobscuredRootViewRect.y()) { |
| float distanceFromEdge = autoscrollPoint.y() - insetUnobscuredRootViewRect.y(); |
| if (distanceFromEdge < 0) |
| resultPoint.setY(unobscuredRootViewRect.y() + ((distanceFromEdge / edgeInset) * maximumScrollingSpeed)); |
| } else if (autoscrollPoint.y() >= insetUnobscuredRootViewRect.maxY()) { |
| float distanceFromEdge = autoscrollPoint.y() - insetUnobscuredRootViewRect.maxY(); |
| if (distanceFromEdge > 0) |
| resultPoint.setY(unobscuredRootViewRect.maxY() + ((distanceFromEdge / edgeInset) * maximumScrollingSpeed)); |
| } |
| |
| return resultPoint; |
| } |
| |
| IntPoint EventHandler::targetPositionInWindowForSelectionAutoscroll() const |
| { |
| Ref<Frame> protectedFrame(m_frame); |
| |
| if (!m_frame.view()) |
| return { }; |
| auto& frameView = *m_frame.view(); |
| |
| // All work is done in "unscrolled" root view coordinates (as if delegatesScrolling were off), |
| // so that when the autoscrolling timer fires, it uses the new scroll position, so that it |
| // can keep scrolling without the client pushing a new contents-space target position via startSelectionAutoscroll. |
| auto scrollPosition = toIntSize(frameView.documentScrollPositionRelativeToViewOrigin()); |
| |
| FloatRect unobscuredContentRectInUnscrolledRootViewCoordinates = frameView.contentsToRootView(frameView.unobscuredContentRect()); |
| unobscuredContentRectInUnscrolledRootViewCoordinates.move(-scrollPosition); |
| |
| return adjustAutoscrollDestinationForInsetEdges(m_targetAutoscrollPositionInUnscrolledRootViewCoordinates, m_initialTargetAutoscrollPositionInUnscrolledRootViewCoordinates, unobscuredContentRectInUnscrolledRootViewCoordinates) + scrollPosition; |
| } |
| |
| bool EventHandler::shouldUpdateAutoscroll() |
| { |
| return m_isAutoscrolling; |
| } |
| |
| #if ENABLE(DRAG_SUPPORT) |
| |
| bool EventHandler::eventLoopHandleMouseDragged(const MouseEventWithHitTestResults&) |
| { |
| return false; |
| } |
| |
| bool EventHandler::tryToBeginDragAtPoint(const IntPoint& clientPosition, const IntPoint&) |
| { |
| Ref<Frame> protectedFrame(m_frame); |
| |
| auto* document = m_frame.document(); |
| if (!document) |
| return false; |
| |
| SetForScope shouldAllowMouseDownToStartDrag { m_shouldAllowMouseDownToStartDrag, true }; |
| |
| document->updateLayoutIgnorePendingStylesheets(); |
| |
| FloatPoint adjustedClientPositionAsFloatPoint(clientPosition); |
| protectedFrame->nodeRespondingToClickEvents(clientPosition, adjustedClientPositionAsFloatPoint); |
| IntPoint adjustedClientPosition = roundedIntPoint(adjustedClientPositionAsFloatPoint); |
| IntPoint adjustedGlobalPosition = protectedFrame->view()->windowToContents(adjustedClientPosition); |
| |
| PlatformMouseEvent syntheticMousePressEvent(adjustedClientPosition, adjustedGlobalPosition, LeftButton, PlatformEvent::MousePressed, 1, false, false, false, false, WallTime::now(), 0, NoTap); |
| PlatformMouseEvent syntheticMouseMoveEvent(adjustedClientPosition, adjustedGlobalPosition, LeftButton, PlatformEvent::MouseMoved, 0, false, false, false, false, WallTime::now(), 0, NoTap); |
| |
| constexpr OptionSet<HitTestRequest::Type> hitType { HitTestRequest::Type::Active, HitTestRequest::Type::DisallowUserAgentShadowContent }; |
| auto documentPoint = protectedFrame->view() ? protectedFrame->view()->windowToContents(syntheticMouseMoveEvent.position()) : syntheticMouseMoveEvent.position(); |
| auto hitTestedMouseEvent = document->prepareMouseEvent(hitType, documentPoint, syntheticMouseMoveEvent); |
| |
| auto subframe = subframeForHitTestResult(hitTestedMouseEvent); |
| if (subframe && subframe->eventHandler().tryToBeginDragAtPoint(adjustedClientPosition, adjustedGlobalPosition)) |
| return true; |
| |
| if (!eventMayStartDrag(syntheticMousePressEvent)) |
| return false; |
| |
| handleMousePressEvent(syntheticMousePressEvent); |
| bool handledDrag = m_mouseDownMayStartDrag && handleMouseDraggedEvent(hitTestedMouseEvent, DontCheckDragHysteresis); |
| // Reset this bit to prevent autoscrolling from updating the selection with the last mouse location. |
| m_mouseDownMayStartSelect = false; |
| // Reset this bit to ensure that WebChromeClientIOS::observedContentChange() is called by EventHandler::mousePressed() |
| // when we would process the next tap after a drag interaction. |
| m_mousePressed = false; |
| return handledDrag; |
| } |
| |
| bool EventHandler::supportsSelectionUpdatesOnMouseDrag() const |
| { |
| return false; |
| } |
| |
| bool EventHandler::shouldAllowMouseDownToStartDrag() const |
| { |
| return m_shouldAllowMouseDownToStartDrag; |
| } |
| |
| #endif // ENABLE(DRAG_SUPPORT) |
| |
| } |
| |
| #endif // PLATFORM(IOS_FAMILY) |