2 * Copyright (C) 2006 Apple Computer, Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 #include "EventHandler.h"
29 #include "BlockExceptions.h"
30 #include "ClipboardMac.h"
33 #include "DragController.h"
34 #include "EventNames.h"
35 #include "FloatPoint.h"
36 #include "FocusController.h"
37 #include "FoundationExtras.h"
38 #include "FrameLoader.h"
40 #include "FrameTree.h"
41 #include "FrameView.h"
42 #include "HTMLFrameOwnerElement.h"
43 #include "HTMLFrameSetElement.h"
44 #include "HitTestRequest.h"
45 #include "HitTestResult.h"
46 #include "KeyboardEvent.h"
47 #include "MouseEventWithHitTestResults.h"
49 #include "PlatformKeyboardEvent.h"
50 #include "PlatformScrollBar.h"
51 #include "PlatformWheelEvent.h"
52 #include "RenderWidget.h"
54 #include "WebCoreFrameBridge.h"
58 using namespace EventNames;
60 static RetainPtr<NSEvent>& currentEvent()
62 static RetainPtr<NSEvent> event;
66 NSEvent *EventHandler::currentNSEvent()
68 return currentEvent().get();
71 bool EventHandler::wheelEvent(NSEvent *event)
73 RetainPtr<NSEvent> oldCurrentEvent = currentEvent();
74 currentEvent() = event;
76 PlatformWheelEvent wheelEvent(event);
77 handleWheelEvent(wheelEvent);
79 ASSERT(currentEvent() == event);
80 currentEvent() = oldCurrentEvent;
82 return wheelEvent.isAccepted();
85 PassRefPtr<KeyboardEvent> EventHandler::currentKeyboardEvent() const
87 NSEvent *event = [NSApp currentEvent];
90 switch ([event type]) {
92 PlatformKeyboardEvent platformEvent(event);
93 platformEvent.disambiguateKeyDownEvent(PlatformKeyboardEvent::RawKeyDown);
94 return new KeyboardEvent(platformEvent, m_frame->document() ? m_frame->document()->defaultView() : 0);
97 return new KeyboardEvent(event, m_frame->document() ? m_frame->document()->defaultView() : 0);
103 static inline bool isKeyboardOptionTab(KeyboardEvent* event)
106 && (event->type() == keydownEvent || event->type() == keypressEvent)
108 && event->keyIdentifier() == "U+0009";
111 bool EventHandler::invertSenseOfTabsToLinks(KeyboardEvent* event) const
113 return isKeyboardOptionTab(event);
116 bool EventHandler::tabsToAllControls(KeyboardEvent* event) const
118 KeyboardUIMode keyboardUIMode = [m_frame->bridge() keyboardUIMode];
119 bool handlingOptionTab = isKeyboardOptionTab(event);
121 // If tab-to-links is off, option-tab always highlights all controls
122 if ((keyboardUIMode & KeyboardAccessTabsToLinks) == 0 && handlingOptionTab)
125 // If system preferences say to include all controls, we always include all controls
126 if (keyboardUIMode & KeyboardAccessFull)
129 // Otherwise tab-to-links includes all controls, unless the sense is flipped via option-tab.
130 if (keyboardUIMode & KeyboardAccessTabsToLinks)
131 return !handlingOptionTab;
133 return handlingOptionTab;
136 bool EventHandler::needsKeyboardEventDisambiguationQuirks() const
138 static BOOL checkedSafari = NO;
139 static BOOL isSafari = NO;
141 if (!checkedSafari) {
142 isSafari = [[[NSBundle mainBundle] bundleIdentifier] isEqualToString:@"com.apple.Safari"];
146 Document* document = m_frame->document();
150 // RSS view needs arrow key keypress events.
151 if (isSafari && document->url().startsWith("feed:", false) || document->url().startsWith("feeds:", false))
153 Settings* settings = m_frame->settings();
156 return settings->usesDashboardBackwardCompatibilityMode() || settings->needsKeyboardEventDisambiguationQuirks();
159 bool EventHandler::keyEvent(NSEvent *event)
162 BEGIN_BLOCK_OBJC_EXCEPTIONS;
164 ASSERT([event type] == NSKeyDown || [event type] == NSKeyUp);
166 RetainPtr<NSEvent> oldCurrentEvent = currentEvent();
167 currentEvent() = event;
169 result = keyEvent(PlatformKeyboardEvent(event));
171 ASSERT(currentEvent() == event);
172 currentEvent() = oldCurrentEvent;
176 END_BLOCK_OBJC_EXCEPTIONS;
181 void EventHandler::focusDocumentView()
183 Page* page = m_frame->page();
187 if (FrameView* frameView = m_frame->view())
188 if (NSView *documentView = frameView->getDocumentView())
189 page->chrome()->focusNSView(documentView);
191 page->focusController()->setFocusedFrame(m_frame);
194 bool EventHandler::passWidgetMouseDownEventToWidget(const MouseEventWithHitTestResults& event)
196 // Figure out which view to send the event to.
197 RenderObject* target = event.targetNode() ? event.targetNode()->renderer() : 0;
198 if (!target || !target->isWidget())
201 // Double-click events don't exist in Cocoa. Since passWidgetMouseDownEventToWidget will
202 // just pass currentEvent down to the widget, we don't want to call it for events that
203 // don't correspond to Cocoa events. The mousedown/ups will have already been passed on as
204 // part of the pressed/released handling.
205 return passMouseDownEventToWidget(static_cast<RenderWidget*>(target)->widget());
208 bool EventHandler::passWidgetMouseDownEventToWidget(RenderWidget* renderWidget)
210 return passMouseDownEventToWidget(renderWidget->widget());
213 static bool lastEventIsMouseUp()
215 // Many AK widgets run their own event loops and consume events while the mouse is down.
216 // When they finish, currentEvent is the mouseUp that they exited on. We need to update
217 // the khtml state with this mouseUp, which khtml never saw. This method lets us detect
220 BEGIN_BLOCK_OBJC_EXCEPTIONS;
221 NSEvent *currentEventAfterHandlingMouseDown = [NSApp currentEvent];
222 if (currentEvent() != currentEventAfterHandlingMouseDown &&
223 [currentEventAfterHandlingMouseDown type] == NSLeftMouseUp &&
224 [currentEventAfterHandlingMouseDown timestamp] >= [currentEvent().get() timestamp])
226 END_BLOCK_OBJC_EXCEPTIONS;
231 bool EventHandler::passMouseDownEventToWidget(Widget* widget)
233 // FIXME: this method always returns true
236 LOG_ERROR("hit a RenderWidget without a corresponding Widget, means a frame is half-constructed");
240 BEGIN_BLOCK_OBJC_EXCEPTIONS;
242 NSView *nodeView = widget->getView();
244 ASSERT([nodeView superview]);
245 NSView *view = [nodeView hitTest:[[nodeView superview] convertPoint:[currentEvent().get() locationInWindow] fromView:nil]];
247 // We probably hit the border of a RenderWidget
250 if ([m_frame->bridge() firstResponder] == view) {
251 // In the case where we just became first responder, we should send the mouseDown:
252 // to the NSTextField, not the NSTextField's editor. This code makes sure that happens.
253 // If we don't do this, we see a flash of selected text when clicking in a text field.
254 // FIXME: This is the only caller of textViewWasFirstResponderAtMouseDownTime. When we
255 // eliminate all use of NSTextField/NSTextView in form fields we can eliminate this code,
256 // and textViewWasFirstResponderAtMouseDownTime:, and the instance variable WebHTMLView
257 // keeps solely to support textViewWasFirstResponderAtMouseDownTime:.
258 if ([view isKindOfClass:[NSTextView class]] && ![m_frame->bridge() textViewWasFirstResponderAtMouseDownTime:(NSTextView *)view]) {
259 NSView *superview = view;
260 while (superview != nodeView) {
261 superview = [superview superview];
263 if ([superview isKindOfClass:[NSControl class]]) {
264 NSControl *control = static_cast<NSControl*>(superview);
265 if ([control currentEditor] == view)
272 // Normally [NSWindow sendEvent:] handles setting the first responder.
273 // But in our case, the event was sent to the view representing the entire web page.
274 if ([currentEvent().get() clickCount] <= 1 && [view acceptsFirstResponder] && [view needsPanelToBecomeKey]) {
275 [m_frame->bridge() makeFirstResponder:view];
279 // We need to "defer loading" while tracking the mouse, because tearing down the
280 // page while an AppKit control is tracking the mouse can cause a crash.
282 // FIXME: In theory, WebCore now tolerates tear-down while tracking the
283 // mouse. We should confirm that, and then remove the deferrsLoading
286 bool wasDeferringLoading = m_frame->page()->defersLoading();
287 if (!wasDeferringLoading)
288 m_frame->page()->setDefersLoading(true);
290 ASSERT(!m_sendingEventToSubview);
291 m_sendingEventToSubview = true;
292 [view mouseDown:currentEvent().get()];
293 m_sendingEventToSubview = false;
295 if (!wasDeferringLoading)
296 m_frame->page()->setDefersLoading(false);
298 // Remember which view we sent the event to, so we can direct the release event properly.
299 m_mouseDownView = view;
300 m_mouseDownWasInSubframe = false;
302 // Many AppKit widgets run their own event loops and consume events while the mouse is down.
303 // When they finish, currentEvent is the mouseUp that they exited on. We need to update
304 // the EventHandler state with this mouseUp, which we never saw.
305 // If this event isn't a mouseUp, we assume that the mouseUp will be coming later. There
306 // is a hole here if the widget consumes both the mouseUp and subsequent events.
307 if (lastEventIsMouseUp())
308 m_mousePressed = false;
310 END_BLOCK_OBJC_EXCEPTIONS;
315 // Note that this does the same kind of check as [target isDescendantOf:superview].
316 // There are two differences: This is a lot slower because it has to walk the whole
317 // tree, and this works in cases where the target has already been deallocated.
318 static bool findViewInSubviews(NSView *superview, NSView *target)
320 BEGIN_BLOCK_OBJC_EXCEPTIONS;
321 NSEnumerator *e = [[superview subviews] objectEnumerator];
323 while ((subview = [e nextObject])) {
324 if (subview == target || findViewInSubviews(subview, target)) {
328 END_BLOCK_OBJC_EXCEPTIONS;
333 NSView *EventHandler::mouseDownViewIfStillGood()
335 // Since we have no way of tracking the lifetime of m_mouseDownView, we have to assume that
336 // it could be deallocated already. We search for it in our subview tree; if we don't find
337 // it, we set it to nil.
338 NSView *mouseDownView = m_mouseDownView;
339 if (!mouseDownView) {
342 FrameView* topFrameView = m_frame->view();
343 NSView *topView = topFrameView ? topFrameView->getView() : nil;
344 if (!topView || !findViewInSubviews(topView, mouseDownView)) {
345 m_mouseDownView = nil;
348 return mouseDownView;
351 bool EventHandler::eventActivatedView(const PlatformMouseEvent& event) const
353 return m_activationEventNumber == event.eventNumber();
356 bool EventHandler::eventLoopHandleMouseDragged(const MouseEventWithHitTestResults&)
358 NSView *view = mouseDownViewIfStillGood();
363 if (!m_mouseDownWasInSubframe) {
364 m_sendingEventToSubview = true;
365 BEGIN_BLOCK_OBJC_EXCEPTIONS;
366 [view mouseDragged:currentEvent().get()];
367 END_BLOCK_OBJC_EXCEPTIONS;
368 m_sendingEventToSubview = false;
374 Clipboard* EventHandler::createDraggingClipboard() const
376 NSPasteboard *pasteboard = [NSPasteboard pasteboardWithName:NSDragPboard];
377 // Must be done before ondragstart adds types and data to the pboard,
378 // also done for security, as it erases data from the last drag
379 [pasteboard declareTypes:[NSArray array] owner:nil];
380 return new ClipboardMac(true, pasteboard, ClipboardWritable, m_frame);
383 bool EventHandler::eventLoopHandleMouseUp(const MouseEventWithHitTestResults&)
385 NSView *view = mouseDownViewIfStillGood();
389 if (!m_mouseDownWasInSubframe) {
390 m_sendingEventToSubview = true;
391 BEGIN_BLOCK_OBJC_EXCEPTIONS;
392 [view mouseUp:currentEvent().get()];
393 END_BLOCK_OBJC_EXCEPTIONS;
394 m_sendingEventToSubview = false;
400 bool EventHandler::passSubframeEventToSubframe(MouseEventWithHitTestResults& event, Frame* subframe, HitTestResult* hoveredNode)
402 BEGIN_BLOCK_OBJC_EXCEPTIONS;
404 switch ([currentEvent().get() type]) {
406 // Since we're passing in currentEvent() here, we can call
407 // handleMouseMoveEvent() directly, since the save/restore of
408 // currentEvent() that mouseMoved() does would have no effect.
409 subframe->eventHandler()->handleMouseMoveEvent(currentEvent().get(), hoveredNode);
412 case NSLeftMouseDown: {
413 Node* node = event.targetNode();
416 RenderObject* renderer = node->renderer();
417 if (!renderer || !renderer->isWidget())
419 Widget* widget = static_cast<RenderWidget*>(renderer)->widget();
420 if (!widget || !widget->isFrameView())
422 if (!passWidgetMouseDownEventToWidget(static_cast<RenderWidget*>(renderer)))
424 m_mouseDownWasInSubframe = true;
427 case NSLeftMouseUp: {
428 if (!m_mouseDownWasInSubframe)
430 NSView *view = mouseDownViewIfStillGood();
433 ASSERT(!m_sendingEventToSubview);
434 m_sendingEventToSubview = true;
435 [view mouseUp:currentEvent().get()];
436 m_sendingEventToSubview = false;
439 case NSLeftMouseDragged: {
440 if (!m_mouseDownWasInSubframe)
442 NSView *view = mouseDownViewIfStillGood();
445 ASSERT(!m_sendingEventToSubview);
446 m_sendingEventToSubview = true;
447 [view mouseDragged:currentEvent().get()];
448 m_sendingEventToSubview = false;
454 END_BLOCK_OBJC_EXCEPTIONS;
459 bool EventHandler::passWheelEventToWidget(PlatformWheelEvent&, Widget* widget)
461 BEGIN_BLOCK_OBJC_EXCEPTIONS;
463 if ([currentEvent().get() type] != NSScrollWheel || m_sendingEventToSubview || !widget)
466 NSView *nodeView = widget->getView();
468 ASSERT([nodeView superview]);
469 NSView *view = [nodeView hitTest:[[nodeView superview] convertPoint:[currentEvent().get() locationInWindow] fromView:nil]];
471 // We probably hit the border of a RenderWidget
474 m_sendingEventToSubview = true;
475 [view scrollWheel:currentEvent().get()];
476 m_sendingEventToSubview = false;
479 END_BLOCK_OBJC_EXCEPTIONS;
483 void EventHandler::mouseDown(NSEvent *event)
485 FrameView* v = m_frame->view();
486 if (!v || m_sendingEventToSubview)
489 BEGIN_BLOCK_OBJC_EXCEPTIONS;
491 m_frame->loader()->resetMultipleFormSubmissionProtection();
493 m_mouseDownView = nil;
494 dragState().m_dragSrc = 0;
496 RetainPtr<NSEvent> oldCurrentEvent = currentEvent();
497 currentEvent() = event;
498 m_mouseDown = PlatformMouseEvent(event);
500 handleMousePressEvent(event);
502 ASSERT(currentEvent() == event);
503 currentEvent() = oldCurrentEvent;
505 END_BLOCK_OBJC_EXCEPTIONS;
508 void EventHandler::mouseDragged(NSEvent *event)
510 FrameView* v = m_frame->view();
511 if (!v || m_sendingEventToSubview)
514 BEGIN_BLOCK_OBJC_EXCEPTIONS;
516 RetainPtr<NSEvent> oldCurrentEvent = currentEvent();
517 currentEvent() = event;
519 handleMouseMoveEvent(event);
521 ASSERT(currentEvent() == event);
522 currentEvent() = oldCurrentEvent;
524 END_BLOCK_OBJC_EXCEPTIONS;
527 void EventHandler::mouseUp(NSEvent *event)
529 FrameView* v = m_frame->view();
530 if (!v || m_sendingEventToSubview)
533 BEGIN_BLOCK_OBJC_EXCEPTIONS;
535 RetainPtr<NSEvent> oldCurrentEvent = currentEvent();
536 currentEvent() = event;
538 // Our behavior here is a little different that Qt. Qt always sends
539 // a mouse release event, even for a double click. To correct problems
540 // in khtml's DOM click event handling we do not send a release here
541 // for a double click. Instead we send that event from FrameView's
542 // handleMouseDoubleClickEvent. Note also that the third click of
543 // a triple click is treated as a single click, but the fourth is then
544 // treated as another double click. Hence the "% 2" below.
545 int clickCount = [event clickCount];
546 if (clickCount > 0 && clickCount % 2 == 0)
547 handleMouseDoubleClickEvent(event);
549 handleMouseReleaseEvent(event);
551 ASSERT(currentEvent() == event);
552 currentEvent() = oldCurrentEvent;
554 m_mouseDownView = nil;
556 END_BLOCK_OBJC_EXCEPTIONS;
560 A hack for the benefit of AK's PopUpButton, which uses the Carbon menu manager, which thus
561 eats all subsequent events after it is starts its modal tracking loop. After the interaction
562 is done, this routine is used to fix things up. When a mouse down started us tracking in
563 the widget, we post a fake mouse up to balance the mouse down we started with. When a
564 key down started us tracking in the widget, we post a fake key up to balance things out.
565 In addition, we post a fake mouseMoved to get the cursor in sync with whatever we happen to
566 be over after the tracking is done.
568 void EventHandler::sendFakeEventsAfterWidgetTracking(NSEvent *initiatingEvent)
570 BEGIN_BLOCK_OBJC_EXCEPTIONS;
572 m_sendingEventToSubview = false;
573 int eventType = [initiatingEvent type];
574 if (eventType == NSLeftMouseDown || eventType == NSKeyDown) {
575 NSEvent *fakeEvent = nil;
576 if (eventType == NSLeftMouseDown) {
577 fakeEvent = [NSEvent mouseEventWithType:NSLeftMouseUp
578 location:[initiatingEvent locationInWindow]
579 modifierFlags:[initiatingEvent modifierFlags]
580 timestamp:[initiatingEvent timestamp]
581 windowNumber:[initiatingEvent windowNumber]
582 context:[initiatingEvent context]
583 eventNumber:[initiatingEvent eventNumber]
584 clickCount:[initiatingEvent clickCount]
585 pressure:[initiatingEvent pressure]];
587 [NSApp postEvent:fakeEvent atStart:YES];
588 } else { // eventType == NSKeyDown
589 fakeEvent = [NSEvent keyEventWithType:NSKeyUp
590 location:[initiatingEvent locationInWindow]
591 modifierFlags:[initiatingEvent modifierFlags]
592 timestamp:[initiatingEvent timestamp]
593 windowNumber:[initiatingEvent windowNumber]
594 context:[initiatingEvent context]
595 characters:[initiatingEvent characters]
596 charactersIgnoringModifiers:[initiatingEvent charactersIgnoringModifiers]
597 isARepeat:[initiatingEvent isARepeat]
598 keyCode:[initiatingEvent keyCode]];
599 [NSApp postEvent:fakeEvent atStart:YES];
601 // FIXME: We should really get the current modifierFlags here, but there's no way to poll
602 // them in Cocoa, and because the event stream was stolen by the Carbon menu code we have
603 // no up-to-date cache of them anywhere.
604 fakeEvent = [NSEvent mouseEventWithType:NSMouseMoved
605 location:[[m_frame->bridge() window] convertScreenToBase:[NSEvent mouseLocation]]
606 modifierFlags:[initiatingEvent modifierFlags]
607 timestamp:[initiatingEvent timestamp]
608 windowNumber:[initiatingEvent windowNumber]
609 context:[initiatingEvent context]
613 [NSApp postEvent:fakeEvent atStart:YES];
616 END_BLOCK_OBJC_EXCEPTIONS;
619 void EventHandler::mouseMoved(NSEvent *event)
621 // Reject a mouse moved if the button is down - screws up tracking during autoscroll
622 // These happen because WebKit sometimes has to fake up moved events.
623 if (!m_frame->view() || m_mousePressed || m_sendingEventToSubview)
626 BEGIN_BLOCK_OBJC_EXCEPTIONS;
628 RetainPtr<NSEvent> oldCurrentEvent = currentEvent();
629 currentEvent() = event;
631 mouseMoved(PlatformMouseEvent(event));
633 ASSERT(currentEvent() == event);
634 currentEvent() = oldCurrentEvent;
636 END_BLOCK_OBJC_EXCEPTIONS;
639 bool EventHandler::passMousePressEventToSubframe(MouseEventWithHitTestResults& mev, Frame* subframe)
641 return passSubframeEventToSubframe(mev, subframe);
644 bool EventHandler::passMouseMoveEventToSubframe(MouseEventWithHitTestResults& mev, Frame* subframe, HitTestResult* hoveredNode)
646 return passSubframeEventToSubframe(mev, subframe, hoveredNode);
649 bool EventHandler::passMouseReleaseEventToSubframe(MouseEventWithHitTestResults& mev, Frame* subframe)
651 return passSubframeEventToSubframe(mev, subframe);
654 bool EventHandler::passMousePressEventToScrollbar(MouseEventWithHitTestResults&, PlatformScrollbar* scrollbar)
656 return passMouseDownEventToWidget(scrollbar);