#include "config.h"
#include "EventHandler.h"
#include "AXObjectCache.h"
#include "BlockExceptions.h"
#include "Chrome.h"
#include "ChromeClient.h"
#include "ClipboardMac.h"
#include "DragController.h"
#include "EventNames.h"
#include "FocusController.h"
#include "Frame.h"
#include "FrameLoader.h"
#include "FrameView.h"
#include "KeyboardEvent.h"
#include "MouseEventWithHitTestResults.h"
#include "NotImplemented.h"
#include "Page.h"
#include "PlatformKeyboardEvent.h"
#include "PlatformWheelEvent.h"
#include "RenderWidget.h"
#include "RuntimeApplicationChecks.h"
#include "Scrollbar.h"
#include "Settings.h"
#include "WebCoreSystemInterface.h"
#include <objc/objc-runtime.h>
#include <wtf/StdLibExtras.h>
#if !(defined(OBJC_API_VERSION) && OBJC_API_VERSION > 0)
static inline IMP method_setImplementation(Method m, IMP i)
{
IMP oi = m->method_imp;
m->method_imp = i;
return oi;
}
#endif
namespace WebCore {
#if ENABLE(DRAG_SUPPORT)
const double EventHandler::TextDragDelay = 0.15;
#endif
static RetainPtr<NSEvent>& currentNSEventSlot()
{
DEFINE_STATIC_LOCAL(RetainPtr<NSEvent>, event, ());
return event;
}
NSEvent *EventHandler::currentNSEvent()
{
return currentNSEventSlot().get();
}
class CurrentEventScope {
WTF_MAKE_NONCOPYABLE(CurrentEventScope);
public:
CurrentEventScope(NSEvent *);
~CurrentEventScope();
private:
RetainPtr<NSEvent> m_savedCurrentEvent;
#ifndef NDEBUG
RetainPtr<NSEvent> m_event;
#endif
};
inline CurrentEventScope::CurrentEventScope(NSEvent *event)
: m_savedCurrentEvent(currentNSEventSlot())
#ifndef NDEBUG
, m_event(event)
#endif
{
currentNSEventSlot() = event;
}
inline CurrentEventScope::~CurrentEventScope()
{
ASSERT(currentNSEventSlot() == m_event);
currentNSEventSlot() = m_savedCurrentEvent;
}
bool EventHandler::wheelEvent(NSEvent *event)
{
Page* page = m_frame->page();
if (!page)
return false;
CurrentEventScope scope(event);
PlatformWheelEvent wheelEvent(event, page->chrome()->platformPageClient());
handleWheelEvent(wheelEvent);
return wheelEvent.isAccepted();
}
PassRefPtr<KeyboardEvent> EventHandler::currentKeyboardEvent() const
{
NSEvent *event = [NSApp currentEvent];
if (!event)
return 0;
switch ([event type]) {
case NSKeyDown: {
PlatformKeyboardEvent platformEvent(event);
platformEvent.disambiguateKeyDownEvent(PlatformKeyboardEvent::RawKeyDown);
return KeyboardEvent::create(platformEvent, m_frame->document()->defaultView());
}
case NSKeyUp:
return KeyboardEvent::create(event, m_frame->document()->defaultView());
default:
return 0;
}
}
bool EventHandler::keyEvent(NSEvent *event)
{
BEGIN_BLOCK_OBJC_EXCEPTIONS;
ASSERT([event type] == NSKeyDown || [event type] == NSKeyUp);
CurrentEventScope scope(event);
return keyEvent(PlatformKeyboardEvent(event));
END_BLOCK_OBJC_EXCEPTIONS;
return false;
}
void EventHandler::focusDocumentView()
{
Page* page = m_frame->page();
if (!page)
return;
if (FrameView* frameView = m_frame->view()) {
if (NSView *documentView = frameView->documentView())
page->chrome()->focusNSView(documentView);
}
page->focusController()->setFocusedFrame(m_frame);
}
bool EventHandler::passWidgetMouseDownEventToWidget(const MouseEventWithHitTestResults& event)
{
RenderObject* target = targetNode(event) ? targetNode(event)->renderer() : 0;
if (!target || !target->isWidget())
return false;
return passMouseDownEventToWidget(toRenderWidget(target)->widget());
}
bool EventHandler::passWidgetMouseDownEventToWidget(RenderWidget* renderWidget)
{
return passMouseDownEventToWidget(renderWidget->widget());
}
static bool lastEventIsMouseUp()
{
BEGIN_BLOCK_OBJC_EXCEPTIONS;
NSEvent *currentEventAfterHandlingMouseDown = [NSApp currentEvent];
return EventHandler::currentNSEvent() != currentEventAfterHandlingMouseDown
&& [currentEventAfterHandlingMouseDown type] == NSLeftMouseUp
&& [currentEventAfterHandlingMouseDown timestamp] >= [EventHandler::currentNSEvent() timestamp];
END_BLOCK_OBJC_EXCEPTIONS;
return false;
}
bool EventHandler::passMouseDownEventToWidget(Widget* pWidget)
{
RefPtr<Widget> widget = pWidget;
if (!widget) {
LOG_ERROR("hit a RenderWidget without a corresponding Widget, means a frame is half-constructed");
return true;
}
if (!widget->platformWidget())
return false;
BEGIN_BLOCK_OBJC_EXCEPTIONS;
NSView *nodeView = widget->platformWidget();
ASSERT([nodeView superview]);
NSView *view = [nodeView hitTest:[[nodeView superview] convertPoint:[currentNSEvent() locationInWindow] fromView:nil]];
if (!view) {
return true;
}
Page* page = m_frame->page();
if (!page)
return true;
if (page->chrome()->client()->firstResponder() != view) {
if ([currentNSEvent() clickCount] <= 1 && [view acceptsFirstResponder] && [view needsPanelToBecomeKey])
page->chrome()->client()->makeFirstResponder(view);
}
bool wasDeferringLoading = page->defersLoading();
if (!wasDeferringLoading)
page->setDefersLoading(true);
ASSERT(!m_sendingEventToSubview);
m_sendingEventToSubview = true;
NSView *outerView = widget->getOuterView();
widget->beforeMouseDown(outerView, widget.get());
[view mouseDown:currentNSEvent()];
widget->afterMouseDown(outerView, widget.get());
m_sendingEventToSubview = false;
if (!wasDeferringLoading)
page->setDefersLoading(false);
m_mouseDownView = view;
m_mouseDownWasInSubframe = false;
if (lastEventIsMouseUp())
m_mousePressed = false;
END_BLOCK_OBJC_EXCEPTIONS;
return true;
}
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()
{
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;
}
#if ENABLE(DRAG_SUPPORT)
bool EventHandler::eventLoopHandleMouseDragged(const MouseEventWithHitTestResults&)
{
NSView *view = mouseDownViewIfStillGood();
if (!view)
return false;
if (!m_mouseDownWasInSubframe) {
ASSERT(!m_sendingEventToSubview);
m_sendingEventToSubview = true;
BEGIN_BLOCK_OBJC_EXCEPTIONS;
[view mouseDragged:currentNSEvent()];
END_BLOCK_OBJC_EXCEPTIONS;
m_sendingEventToSubview = false;
}
return true;
}
#endif
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:currentNSEvent()];
END_BLOCK_OBJC_EXCEPTIONS;
m_sendingEventToSubview = false;
}
return true;
}
bool EventHandler::passSubframeEventToSubframe(MouseEventWithHitTestResults& event, Frame* subframe, HitTestResult* hoveredNode)
{
BEGIN_BLOCK_OBJC_EXCEPTIONS;
switch ([currentNSEvent() type]) {
case NSLeftMouseDragged:
case NSOtherMouseDragged:
case NSRightMouseDragged:
if (!m_mouseDownWasInSubframe)
return false;
#if ENABLE(DRAG_SUPPORT)
if (subframe->page()->dragController()->didInitiateDrag())
return false;
#endif
case NSMouseMoved:
ASSERT(!m_sendingEventToSubview);
m_sendingEventToSubview = true;
subframe->eventHandler()->handleMouseMoveEvent(currentPlatformMouseEvent(), hoveredNode);
m_sendingEventToSubview = false;
return true;
case NSLeftMouseDown: {
Node* node = targetNode(event);
if (!node)
return false;
RenderObject* renderer = node->renderer();
if (!renderer || !renderer->isWidget())
return false;
Widget* widget = toRenderWidget(renderer)->widget();
if (!widget || !widget->isFrameView())
return false;
if (!passWidgetMouseDownEventToWidget(toRenderWidget(renderer)))
return false;
m_mouseDownWasInSubframe = true;
return true;
}
case NSLeftMouseUp: {
if (!m_mouseDownWasInSubframe)
return false;
ASSERT(!m_sendingEventToSubview);
m_sendingEventToSubview = true;
subframe->eventHandler()->handleMouseReleaseEvent(currentPlatformMouseEvent());
m_sendingEventToSubview = false;
return true;
}
default:
return false;
}
END_BLOCK_OBJC_EXCEPTIONS;
return false;
}
static IMP originalNSScrollViewScrollWheel;
static bool _nsScrollViewScrollWheelShouldRetainSelf;
static void selfRetainingNSScrollViewScrollWheel(NSScrollView *, SEL, NSEvent *);
static bool nsScrollViewScrollWheelShouldRetainSelf()
{
ASSERT(isMainThread());
return _nsScrollViewScrollWheelShouldRetainSelf;
}
static void setNSScrollViewScrollWheelShouldRetainSelf(bool shouldRetain)
{
ASSERT(isMainThread());
if (!originalNSScrollViewScrollWheel) {
Method method = class_getInstanceMethod(objc_getRequiredClass("NSScrollView"), @selector(scrollWheel:));
originalNSScrollViewScrollWheel = method_setImplementation(method, reinterpret_cast<IMP>(selfRetainingNSScrollViewScrollWheel));
}
_nsScrollViewScrollWheelShouldRetainSelf = shouldRetain;
}
static void selfRetainingNSScrollViewScrollWheel(NSScrollView *self, SEL selector, NSEvent *event)
{
bool shouldRetainSelf = isMainThread() && nsScrollViewScrollWheelShouldRetainSelf();
if (shouldRetainSelf)
[self retain];
originalNSScrollViewScrollWheel(self, selector, event);
if (shouldRetainSelf)
[self release];
}
bool EventHandler::passWheelEventToWidget(PlatformWheelEvent& wheelEvent, Widget* widget)
{
BEGIN_BLOCK_OBJC_EXCEPTIONS;
if (!widget)
return false;
NSView* nodeView = widget->platformWidget();
if (!nodeView) {
if (!widget->isFrameView())
return false;
return static_cast<FrameView*>(widget)->frame()->eventHandler()->handleWheelEvent(wheelEvent);
}
if ([currentNSEvent() type] != NSScrollWheel || m_sendingEventToSubview)
return false;
ASSERT(nodeView);
ASSERT([nodeView superview]);
NSView *view = [nodeView hitTest:[[nodeView superview] convertPoint:[currentNSEvent() locationInWindow] fromView:nil]];
if (!view)
return false;
ASSERT(!m_sendingEventToSubview);
m_sendingEventToSubview = true;
setNSScrollViewScrollWheelShouldRetainSelf(true);
[view scrollWheel:currentNSEvent()];
setNSScrollViewScrollWheelShouldRetainSelf(false);
m_sendingEventToSubview = false;
return true;
END_BLOCK_OBJC_EXCEPTIONS;
return false;
}
void EventHandler::mouseDown(NSEvent *event)
{
FrameView* v = m_frame->view();
if (!v || m_sendingEventToSubview)
return;
BEGIN_BLOCK_OBJC_EXCEPTIONS;
m_frame->loader()->resetMultipleFormSubmissionProtection();
m_mouseDownView = nil;
CurrentEventScope scope(event);
handleMousePressEvent(currentPlatformMouseEvent());
END_BLOCK_OBJC_EXCEPTIONS;
}
void EventHandler::mouseDragged(NSEvent *event)
{
FrameView* v = m_frame->view();
if (!v || m_sendingEventToSubview)
return;
BEGIN_BLOCK_OBJC_EXCEPTIONS;
CurrentEventScope scope(event);
handleMouseMoveEvent(currentPlatformMouseEvent());
END_BLOCK_OBJC_EXCEPTIONS;
}
void EventHandler::mouseUp(NSEvent *event)
{
FrameView* v = m_frame->view();
if (!v || m_sendingEventToSubview)
return;
BEGIN_BLOCK_OBJC_EXCEPTIONS;
CurrentEventScope scope(event);
int clickCount = [event clickCount];
if (clickCount > 0 && clickCount % 2 == 0)
handleMouseDoubleClickEvent(currentPlatformMouseEvent());
else
handleMouseReleaseEvent(currentPlatformMouseEvent());
m_mouseDownView = nil;
END_BLOCK_OBJC_EXCEPTIONS;
}
void EventHandler::sendFakeEventsAfterWidgetTracking(NSEvent *initiatingEvent)
{
FrameView* view = m_frame->view();
if (!view)
return;
BEGIN_BLOCK_OBJC_EXCEPTIONS;
m_sendingEventToSubview = false;
int eventType = [initiatingEvent type];
if (eventType == NSLeftMouseDown || eventType == NSKeyDown) {
NSEvent *fakeEvent = nil;
if (eventType == NSLeftMouseDown) {
fakeEvent = [NSEvent mouseEventWithType:NSLeftMouseUp
location:[initiatingEvent locationInWindow]
modifierFlags:[initiatingEvent modifierFlags]
timestamp:[initiatingEvent timestamp]
windowNumber:[initiatingEvent windowNumber]
context:[initiatingEvent context]
eventNumber:[initiatingEvent eventNumber]
clickCount:[initiatingEvent clickCount]
pressure:[initiatingEvent pressure]];
[NSApp postEvent:fakeEvent atStart:YES];
} else { fakeEvent = [NSEvent keyEventWithType:NSKeyUp
location:[initiatingEvent locationInWindow]
modifierFlags:[initiatingEvent modifierFlags]
timestamp:[initiatingEvent timestamp]
windowNumber:[initiatingEvent windowNumber]
context:[initiatingEvent context]
characters:[initiatingEvent characters]
charactersIgnoringModifiers:[initiatingEvent charactersIgnoringModifiers]
isARepeat:[initiatingEvent isARepeat]
keyCode:[initiatingEvent keyCode]];
[NSApp postEvent:fakeEvent atStart:YES];
}
fakeEvent = [NSEvent mouseEventWithType:NSMouseMoved
location:[[view->platformWidget() window] convertScreenToBase:[NSEvent mouseLocation]]
modifierFlags:[initiatingEvent modifierFlags]
timestamp:[initiatingEvent timestamp]
windowNumber:[initiatingEvent windowNumber]
context:[initiatingEvent context]
eventNumber:0
clickCount:0
pressure:0];
[NSApp postEvent:fakeEvent atStart:YES];
}
END_BLOCK_OBJC_EXCEPTIONS;
}
void EventHandler::mouseMoved(NSEvent *event)
{
if (!m_frame->view() || m_mousePressed || m_sendingEventToSubview)
return;
BEGIN_BLOCK_OBJC_EXCEPTIONS;
CurrentEventScope scope(event);
mouseMoved(currentPlatformMouseEvent());
END_BLOCK_OBJC_EXCEPTIONS;
}
static bool frameHasPlatformWidget(Frame* frame)
{
if (FrameView* frameView = frame->view()) {
if (frameView->platformWidget())
return true;
}
return false;
}
bool EventHandler::passMousePressEventToSubframe(MouseEventWithHitTestResults& mev, Frame* subframe)
{
if (frameHasPlatformWidget(m_frame))
return passSubframeEventToSubframe(mev, subframe);
subframe->eventHandler()->handleMousePressEvent(mev.event());
return true;
}
bool EventHandler::passMouseMoveEventToSubframe(MouseEventWithHitTestResults& mev, Frame* subframe, HitTestResult* hoveredNode)
{
if (frameHasPlatformWidget(m_frame))
return passSubframeEventToSubframe(mev, subframe, hoveredNode);
if (m_mouseDownMayStartDrag && !m_mouseDownWasInSubframe)
return false;
subframe->eventHandler()->handleMouseMoveEvent(mev.event(), hoveredNode);
return true;
}
bool EventHandler::passMouseReleaseEventToSubframe(MouseEventWithHitTestResults& mev, Frame* subframe)
{
if (frameHasPlatformWidget(m_frame))
return passSubframeEventToSubframe(mev, subframe);
subframe->eventHandler()->handleMouseReleaseEvent(mev.event());
return true;
}
PlatformMouseEvent EventHandler::currentPlatformMouseEvent() const
{
NSView *windowView = nil;
if (Page* page = m_frame->page())
windowView = page->chrome()->platformPageClient();
return PlatformMouseEvent(currentNSEvent(), windowView);
}
#if ENABLE(CONTEXT_MENUS)
bool EventHandler::sendContextMenuEvent(NSEvent *event)
{
Page* page = m_frame->page();
if (!page)
return false;
return sendContextMenuEvent(PlatformMouseEvent(event, page->chrome()->platformPageClient()));
}
#endif
#if ENABLE(DRAG_SUPPORT)
bool EventHandler::eventMayStartDrag(NSEvent *event)
{
Page* page = m_frame->page();
if (!page)
return false;
return eventMayStartDrag(PlatformMouseEvent(event, page->chrome()->platformPageClient()));
}
#endif
bool EventHandler::eventActivatedView(const PlatformMouseEvent& event) const
{
return m_activationEventNumber == event.eventNumber();
}
#if ENABLE(DRAG_SUPPORT)
PassRefPtr<Clipboard> EventHandler::createDraggingClipboard() const
{
NSPasteboard *pasteboard = [NSPasteboard pasteboardWithName:NSDragPboard];
[pasteboard declareTypes:[NSArray array] owner:nil];
return ClipboardMac::create(Clipboard::DragAndDrop, pasteboard, ClipboardWritable, m_frame);
}
#endif
bool EventHandler::tabsToAllFormControls(KeyboardEvent* event) const
{
Page* page = m_frame->page();
if (!page)
return false;
KeyboardUIMode keyboardUIMode = page->chrome()->client()->keyboardUIMode();
bool handlingOptionTab = isKeyboardOptionTab(event);
if ((keyboardUIMode & KeyboardAccessTabsToLinks) == 0 && handlingOptionTab)
return true;
if (keyboardUIMode & KeyboardAccessFull)
return true;
if (keyboardUIMode & KeyboardAccessTabsToLinks)
return !handlingOptionTab;
return handlingOptionTab;
}
bool EventHandler::needsKeyboardEventDisambiguationQuirks() const
{
Document* document = m_frame->document();
if (applicationIsSafari() && (document->url().protocolIs("feed") || document->url().protocolIs("feeds")))
return true;
Settings* settings = m_frame->settings();
if (!settings)
return false;
#if ENABLE(DASHBOARD_SUPPORT)
if (settings->usesDashboardBackwardCompatibilityMode())
return true;
#endif
if (settings->needsKeyboardEventDisambiguationQuirks())
return true;
return false;
}
unsigned EventHandler::accessKeyModifiers()
{
if (AXObjectCache::accessibilityEnhancedUserInterfaceEnabled())
return PlatformKeyboardEvent::CtrlKey;
return PlatformKeyboardEvent::CtrlKey | PlatformKeyboardEvent::AltKey;
}
}