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
251 if ([m_frame->bridge() firstResponder] != view) {
252 // Normally [NSWindow sendEvent:] handles setting the first responder.
253 // But in our case, the event was sent to the view representing the entire web page.
254 if ([currentEvent().get() clickCount] <= 1 && [view acceptsFirstResponder] && [view needsPanelToBecomeKey])
255 [m_frame->bridge() makeFirstResponder:view];
258 // We need to "defer loading" while tracking the mouse, because tearing down the
259 // page while an AppKit control is tracking the mouse can cause a crash.
261 // FIXME: In theory, WebCore now tolerates tear-down while tracking the
262 // mouse. We should confirm that, and then remove the deferrsLoading
265 bool wasDeferringLoading = m_frame->page()->defersLoading();
266 if (!wasDeferringLoading)
267 m_frame->page()->setDefersLoading(true);
269 ASSERT(!m_sendingEventToSubview);
270 m_sendingEventToSubview = true;
271 [view mouseDown:currentEvent().get()];
272 m_sendingEventToSubview = false;
274 if (!wasDeferringLoading)
275 m_frame->page()->setDefersLoading(false);
277 // Remember which view we sent the event to, so we can direct the release event properly.
278 m_mouseDownView = view;
279 m_mouseDownWasInSubframe = false;
281 // Many AppKit widgets run their own event loops and consume events while the mouse is down.
282 // When they finish, currentEvent is the mouseUp that they exited on. We need to update
283 // the EventHandler state with this mouseUp, which we never saw.
284 // If this event isn't a mouseUp, we assume that the mouseUp will be coming later. There
285 // is a hole here if the widget consumes both the mouseUp and subsequent events.
286 if (lastEventIsMouseUp())
287 m_mousePressed = false;
289 END_BLOCK_OBJC_EXCEPTIONS;
294 // Note that this does the same kind of check as [target isDescendantOf:superview].
295 // There are two differences: This is a lot slower because it has to walk the whole
296 // tree, and this works in cases where the target has already been deallocated.
297 static bool findViewInSubviews(NSView *superview, NSView *target)
299 BEGIN_BLOCK_OBJC_EXCEPTIONS;
300 NSEnumerator *e = [[superview subviews] objectEnumerator];
302 while ((subview = [e nextObject])) {
303 if (subview == target || findViewInSubviews(subview, target)) {
307 END_BLOCK_OBJC_EXCEPTIONS;
312 NSView *EventHandler::mouseDownViewIfStillGood()
314 // Since we have no way of tracking the lifetime of m_mouseDownView, we have to assume that
315 // it could be deallocated already. We search for it in our subview tree; if we don't find
316 // it, we set it to nil.
317 NSView *mouseDownView = m_mouseDownView;
318 if (!mouseDownView) {
321 FrameView* topFrameView = m_frame->view();
322 NSView *topView = topFrameView ? topFrameView->getView() : nil;
323 if (!topView || !findViewInSubviews(topView, mouseDownView)) {
324 m_mouseDownView = nil;
327 return mouseDownView;
330 bool EventHandler::eventActivatedView(const PlatformMouseEvent& event) const
332 return m_activationEventNumber == event.eventNumber();
335 bool EventHandler::eventLoopHandleMouseDragged(const MouseEventWithHitTestResults&)
337 NSView *view = mouseDownViewIfStillGood();
342 if (!m_mouseDownWasInSubframe) {
343 m_sendingEventToSubview = true;
344 BEGIN_BLOCK_OBJC_EXCEPTIONS;
345 [view mouseDragged:currentEvent().get()];
346 END_BLOCK_OBJC_EXCEPTIONS;
347 m_sendingEventToSubview = false;
353 Clipboard* EventHandler::createDraggingClipboard() const
355 NSPasteboard *pasteboard = [NSPasteboard pasteboardWithName:NSDragPboard];
356 // Must be done before ondragstart adds types and data to the pboard,
357 // also done for security, as it erases data from the last drag
358 [pasteboard declareTypes:[NSArray array] owner:nil];
359 return new ClipboardMac(true, pasteboard, ClipboardWritable, m_frame);
362 bool EventHandler::eventLoopHandleMouseUp(const MouseEventWithHitTestResults&)
364 NSView *view = mouseDownViewIfStillGood();
368 if (!m_mouseDownWasInSubframe) {
369 m_sendingEventToSubview = true;
370 BEGIN_BLOCK_OBJC_EXCEPTIONS;
371 [view mouseUp:currentEvent().get()];
372 END_BLOCK_OBJC_EXCEPTIONS;
373 m_sendingEventToSubview = false;
379 bool EventHandler::passSubframeEventToSubframe(MouseEventWithHitTestResults& event, Frame* subframe, HitTestResult* hoveredNode)
381 BEGIN_BLOCK_OBJC_EXCEPTIONS;
383 switch ([currentEvent().get() type]) {
385 // Since we're passing in currentEvent() here, we can call
386 // handleMouseMoveEvent() directly, since the save/restore of
387 // currentEvent() that mouseMoved() does would have no effect.
388 subframe->eventHandler()->handleMouseMoveEvent(currentEvent().get(), hoveredNode);
391 case NSLeftMouseDown: {
392 Node* node = event.targetNode();
395 RenderObject* renderer = node->renderer();
396 if (!renderer || !renderer->isWidget())
398 Widget* widget = static_cast<RenderWidget*>(renderer)->widget();
399 if (!widget || !widget->isFrameView())
401 if (!passWidgetMouseDownEventToWidget(static_cast<RenderWidget*>(renderer)))
403 m_mouseDownWasInSubframe = true;
406 case NSLeftMouseUp: {
407 if (!m_mouseDownWasInSubframe)
409 NSView *view = mouseDownViewIfStillGood();
412 ASSERT(!m_sendingEventToSubview);
413 m_sendingEventToSubview = true;
414 [view mouseUp:currentEvent().get()];
415 m_sendingEventToSubview = false;
418 case NSLeftMouseDragged: {
419 if (!m_mouseDownWasInSubframe)
421 NSView *view = mouseDownViewIfStillGood();
424 ASSERT(!m_sendingEventToSubview);
425 m_sendingEventToSubview = true;
426 [view mouseDragged:currentEvent().get()];
427 m_sendingEventToSubview = false;
433 END_BLOCK_OBJC_EXCEPTIONS;
438 bool EventHandler::passWheelEventToWidget(PlatformWheelEvent&, Widget* widget)
440 BEGIN_BLOCK_OBJC_EXCEPTIONS;
442 if ([currentEvent().get() type] != NSScrollWheel || m_sendingEventToSubview || !widget)
445 NSView *nodeView = widget->getView();
447 ASSERT([nodeView superview]);
448 NSView *view = [nodeView hitTest:[[nodeView superview] convertPoint:[currentEvent().get() locationInWindow] fromView:nil]];
450 // We probably hit the border of a RenderWidget
453 m_sendingEventToSubview = true;
454 [view scrollWheel:currentEvent().get()];
455 m_sendingEventToSubview = false;
458 END_BLOCK_OBJC_EXCEPTIONS;
462 void EventHandler::mouseDown(NSEvent *event)
464 FrameView* v = m_frame->view();
465 if (!v || m_sendingEventToSubview)
468 BEGIN_BLOCK_OBJC_EXCEPTIONS;
470 m_frame->loader()->resetMultipleFormSubmissionProtection();
472 m_mouseDownView = nil;
473 dragState().m_dragSrc = 0;
475 RetainPtr<NSEvent> oldCurrentEvent = currentEvent();
476 currentEvent() = event;
477 m_mouseDown = PlatformMouseEvent(event);
479 handleMousePressEvent(event);
481 ASSERT(currentEvent() == event);
482 currentEvent() = oldCurrentEvent;
484 END_BLOCK_OBJC_EXCEPTIONS;
487 void EventHandler::mouseDragged(NSEvent *event)
489 FrameView* v = m_frame->view();
490 if (!v || m_sendingEventToSubview)
493 BEGIN_BLOCK_OBJC_EXCEPTIONS;
495 RetainPtr<NSEvent> oldCurrentEvent = currentEvent();
496 currentEvent() = event;
498 handleMouseMoveEvent(event);
500 ASSERT(currentEvent() == event);
501 currentEvent() = oldCurrentEvent;
503 END_BLOCK_OBJC_EXCEPTIONS;
506 void EventHandler::mouseUp(NSEvent *event)
508 FrameView* v = m_frame->view();
509 if (!v || m_sendingEventToSubview)
512 BEGIN_BLOCK_OBJC_EXCEPTIONS;
514 RetainPtr<NSEvent> oldCurrentEvent = currentEvent();
515 currentEvent() = event;
517 // Our behavior here is a little different that Qt. Qt always sends
518 // a mouse release event, even for a double click. To correct problems
519 // in khtml's DOM click event handling we do not send a release here
520 // for a double click. Instead we send that event from FrameView's
521 // handleMouseDoubleClickEvent. Note also that the third click of
522 // a triple click is treated as a single click, but the fourth is then
523 // treated as another double click. Hence the "% 2" below.
524 int clickCount = [event clickCount];
525 if (clickCount > 0 && clickCount % 2 == 0)
526 handleMouseDoubleClickEvent(event);
528 handleMouseReleaseEvent(event);
530 ASSERT(currentEvent() == event);
531 currentEvent() = oldCurrentEvent;
533 m_mouseDownView = nil;
535 END_BLOCK_OBJC_EXCEPTIONS;
539 A hack for the benefit of AK's PopUpButton, which uses the Carbon menu manager, which thus
540 eats all subsequent events after it is starts its modal tracking loop. After the interaction
541 is done, this routine is used to fix things up. When a mouse down started us tracking in
542 the widget, we post a fake mouse up to balance the mouse down we started with. When a
543 key down started us tracking in the widget, we post a fake key up to balance things out.
544 In addition, we post a fake mouseMoved to get the cursor in sync with whatever we happen to
545 be over after the tracking is done.
547 void EventHandler::sendFakeEventsAfterWidgetTracking(NSEvent *initiatingEvent)
549 BEGIN_BLOCK_OBJC_EXCEPTIONS;
551 m_sendingEventToSubview = false;
552 int eventType = [initiatingEvent type];
553 if (eventType == NSLeftMouseDown || eventType == NSKeyDown) {
554 NSEvent *fakeEvent = nil;
555 if (eventType == NSLeftMouseDown) {
556 fakeEvent = [NSEvent mouseEventWithType:NSLeftMouseUp
557 location:[initiatingEvent locationInWindow]
558 modifierFlags:[initiatingEvent modifierFlags]
559 timestamp:[initiatingEvent timestamp]
560 windowNumber:[initiatingEvent windowNumber]
561 context:[initiatingEvent context]
562 eventNumber:[initiatingEvent eventNumber]
563 clickCount:[initiatingEvent clickCount]
564 pressure:[initiatingEvent pressure]];
566 [NSApp postEvent:fakeEvent atStart:YES];
567 } else { // eventType == NSKeyDown
568 fakeEvent = [NSEvent keyEventWithType:NSKeyUp
569 location:[initiatingEvent locationInWindow]
570 modifierFlags:[initiatingEvent modifierFlags]
571 timestamp:[initiatingEvent timestamp]
572 windowNumber:[initiatingEvent windowNumber]
573 context:[initiatingEvent context]
574 characters:[initiatingEvent characters]
575 charactersIgnoringModifiers:[initiatingEvent charactersIgnoringModifiers]
576 isARepeat:[initiatingEvent isARepeat]
577 keyCode:[initiatingEvent keyCode]];
578 [NSApp postEvent:fakeEvent atStart:YES];
580 // FIXME: We should really get the current modifierFlags here, but there's no way to poll
581 // them in Cocoa, and because the event stream was stolen by the Carbon menu code we have
582 // no up-to-date cache of them anywhere.
583 fakeEvent = [NSEvent mouseEventWithType:NSMouseMoved
584 location:[[m_frame->bridge() window] convertScreenToBase:[NSEvent mouseLocation]]
585 modifierFlags:[initiatingEvent modifierFlags]
586 timestamp:[initiatingEvent timestamp]
587 windowNumber:[initiatingEvent windowNumber]
588 context:[initiatingEvent context]
592 [NSApp postEvent:fakeEvent atStart:YES];
595 END_BLOCK_OBJC_EXCEPTIONS;
598 void EventHandler::mouseMoved(NSEvent *event)
600 // Reject a mouse moved if the button is down - screws up tracking during autoscroll
601 // These happen because WebKit sometimes has to fake up moved events.
602 if (!m_frame->view() || m_mousePressed || m_sendingEventToSubview)
605 BEGIN_BLOCK_OBJC_EXCEPTIONS;
607 RetainPtr<NSEvent> oldCurrentEvent = currentEvent();
608 currentEvent() = event;
610 mouseMoved(PlatformMouseEvent(event));
612 ASSERT(currentEvent() == event);
613 currentEvent() = oldCurrentEvent;
615 END_BLOCK_OBJC_EXCEPTIONS;
618 bool EventHandler::passMousePressEventToSubframe(MouseEventWithHitTestResults& mev, Frame* subframe)
620 return passSubframeEventToSubframe(mev, subframe);
623 bool EventHandler::passMouseMoveEventToSubframe(MouseEventWithHitTestResults& mev, Frame* subframe, HitTestResult* hoveredNode)
625 return passSubframeEventToSubframe(mev, subframe, hoveredNode);
628 bool EventHandler::passMouseReleaseEventToSubframe(MouseEventWithHitTestResults& mev, Frame* subframe)
630 return passSubframeEventToSubframe(mev, subframe);
633 bool EventHandler::passMousePressEventToScrollbar(MouseEventWithHitTestResults&, PlatformScrollbar* scrollbar)
635 return passMouseDownEventToWidget(scrollbar);