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"
53
#include "WebCoreFrameBridge.h"
57
using namespace EventNames;
59
static RetainPtr<NSEvent>& currentEvent()
61
static RetainPtr<NSEvent> event;
65
NSEvent *EventHandler::currentNSEvent()
67
return currentEvent().get();
70
bool EventHandler::wheelEvent(NSEvent *event)
72
RetainPtr<NSEvent> oldCurrentEvent = currentEvent();
73
currentEvent() = event;
75
PlatformWheelEvent wheelEvent(event);
76
handleWheelEvent(wheelEvent);
78
ASSERT(currentEvent() == event);
79
currentEvent() = oldCurrentEvent;
81
return wheelEvent.isAccepted();
84
PassRefPtr<KeyboardEvent> EventHandler::currentKeyboardEvent() const
86
NSEvent *event = [NSApp currentEvent];
89
switch ([event type]) {
92
return new KeyboardEvent(event, m_frame->document() ? m_frame->document()->defaultView() : 0);
98
static inline bool isKeyboardOptionTab(KeyboardEvent* event)
101
&& (event->type() == keydownEvent || event->type() == keypressEvent)
103
&& event->keyIdentifier() == "U+0009";
106
bool EventHandler::invertSenseOfTabsToLinks(KeyboardEvent* event) const
108
return isKeyboardOptionTab(event);
111
bool EventHandler::tabsToAllControls(KeyboardEvent* event) const
113
KeyboardUIMode keyboardUIMode = [m_frame->bridge() keyboardUIMode];
114
bool handlingOptionTab = isKeyboardOptionTab(event);
116
// If tab-to-links is off, option-tab always highlights all controls
117
if ((keyboardUIMode & KeyboardAccessTabsToLinks) == 0 && handlingOptionTab)
120
// If system preferences say to include all controls, we always include all controls
121
if (keyboardUIMode & KeyboardAccessFull)
124
// Otherwise tab-to-links includes all controls, unless the sense is flipped via option-tab.
125
if (keyboardUIMode & KeyboardAccessTabsToLinks)
126
return !handlingOptionTab;
128
return handlingOptionTab;
131
bool EventHandler::keyEvent(NSEvent *event)
134
BEGIN_BLOCK_OBJC_EXCEPTIONS;
136
ASSERT([event type] == NSKeyDown || [event type] == NSKeyUp);
138
RetainPtr<NSEvent> oldCurrentEvent = currentEvent();
139
currentEvent() = event;
141
result = keyEvent(PlatformKeyboardEvent(event));
143
ASSERT(currentEvent() == event);
144
currentEvent() = oldCurrentEvent;
148
END_BLOCK_OBJC_EXCEPTIONS;
153
void EventHandler::focusDocumentView()
155
Page* page = m_frame->page();
159
if (FrameView* frameView = m_frame->view())
160
if (NSView *documentView = frameView->getDocumentView())
161
page->chrome()->focusNSView(documentView);
163
page->focusController()->setFocusedFrame(m_frame);
166
bool EventHandler::passWidgetMouseDownEventToWidget(const MouseEventWithHitTestResults& event)
168
// Figure out which view to send the event to.
169
RenderObject* target = event.targetNode() ? event.targetNode()->renderer() : 0;
170
if (!target || !target->isWidget())
173
// Double-click events don't exist in Cocoa. Since passWidgetMouseDownEventToWidget will
174
// just pass currentEvent down to the widget, we don't want to call it for events that
175
// don't correspond to Cocoa events. The mousedown/ups will have already been passed on as
176
// part of the pressed/released handling.
177
return passMouseDownEventToWidget(static_cast<RenderWidget*>(target)->widget());
180
bool EventHandler::passWidgetMouseDownEventToWidget(RenderWidget* renderWidget)
182
return passMouseDownEventToWidget(renderWidget->widget());
185
static bool lastEventIsMouseUp()
187
// Many AK widgets run their own event loops and consume events while the mouse is down.
188
// When they finish, currentEvent is the mouseUp that they exited on. We need to update
189
// the khtml state with this mouseUp, which khtml never saw. This method lets us detect
192
BEGIN_BLOCK_OBJC_EXCEPTIONS;
193
NSEvent *currentEventAfterHandlingMouseDown = [NSApp currentEvent];
194
if (currentEvent() != currentEventAfterHandlingMouseDown &&
195
[currentEventAfterHandlingMouseDown type] == NSLeftMouseUp &&
196
[currentEventAfterHandlingMouseDown timestamp] >= [currentEvent().get() timestamp])
198
END_BLOCK_OBJC_EXCEPTIONS;
203
bool EventHandler::passMouseDownEventToWidget(Widget* widget)
205
// FIXME: this method always returns true
208
LOG_ERROR("hit a RenderWidget without a corresponding Widget, means a frame is half-constructed");
212
BEGIN_BLOCK_OBJC_EXCEPTIONS;
214
NSView *nodeView = widget->getView();
216
ASSERT([nodeView superview]);
217
NSView *view = [nodeView hitTest:[[nodeView superview] convertPoint:[currentEvent().get() locationInWindow] fromView:nil]];
219
// We probably hit the border of a RenderWidget
222
if ([m_frame->bridge() firstResponder] == view) {
223
// In the case where we just became first responder, we should send the mouseDown:
224
// to the NSTextField, not the NSTextField's editor. This code makes sure that happens.
225
// If we don't do this, we see a flash of selected text when clicking in a text field.
226
// FIXME: This is the only caller of textViewWasFirstResponderAtMouseDownTime. When we
227
// eliminate all use of NSTextField/NSTextView in form fields we can eliminate this code,
228
// and textViewWasFirstResponderAtMouseDownTime:, and the instance variable WebHTMLView
229
// keeps solely to support textViewWasFirstResponderAtMouseDownTime:.
230
if ([view isKindOfClass:[NSTextView class]] && ![m_frame->bridge() textViewWasFirstResponderAtMouseDownTime:(NSTextView *)view]) {
231
NSView *superview = view;
232
while (superview != nodeView) {
233
superview = [superview superview];
235
if ([superview isKindOfClass:[NSControl class]]) {
236
NSControl *control = static_cast<NSControl*>(superview);
237
if ([control currentEditor] == view)
244
// Normally [NSWindow sendEvent:] handles setting the first responder.
245
// But in our case, the event was sent to the view representing the entire web page.
246
if ([currentEvent().get() clickCount] <= 1 && [view acceptsFirstResponder] && [view needsPanelToBecomeKey]) {
247
[m_frame->bridge() makeFirstResponder:view];
251
// We need to "defer loading" while tracking the mouse, because tearing down the
252
// page while an AppKit control is tracking the mouse can cause a crash.
254
// FIXME: In theory, WebCore now tolerates tear-down while tracking the
255
// mouse. We should confirm that, and then remove the deferrsLoading
258
bool wasDeferringLoading = m_frame->page()->defersLoading();
259
if (!wasDeferringLoading)
260
m_frame->page()->setDefersLoading(true);
262
ASSERT(!m_sendingEventToSubview);
263
m_sendingEventToSubview = true;
264
[view mouseDown:currentEvent().get()];
265
m_sendingEventToSubview = false;
267
if (!wasDeferringLoading)
268
m_frame->page()->setDefersLoading(false);
270
// Remember which view we sent the event to, so we can direct the release event properly.
271
m_mouseDownView = view;
272
m_mouseDownWasInSubframe = false;
274
// Many AppKit widgets run their own event loops and consume events while the mouse is down.
275
// When they finish, currentEvent is the mouseUp that they exited on. We need to update
276
// the EventHandler state with this mouseUp, which we never saw.
277
// If this event isn't a mouseUp, we assume that the mouseUp will be coming later. There
278
// is a hole here if the widget consumes both the mouseUp and subsequent events.
279
if (lastEventIsMouseUp())
280
m_mousePressed = false;
282
END_BLOCK_OBJC_EXCEPTIONS;
287
// Note that this does the same kind of check as [target isDescendantOf:superview].
288
// There are two differences: This is a lot slower because it has to walk the whole
289
// tree, and this works in cases where the target has already been deallocated.
290
static bool findViewInSubviews(NSView *superview, NSView *target)
292
BEGIN_BLOCK_OBJC_EXCEPTIONS;
293
NSEnumerator *e = [[superview subviews] objectEnumerator];
295
while ((subview = [e nextObject])) {
296
if (subview == target || findViewInSubviews(subview, target)) {
300
END_BLOCK_OBJC_EXCEPTIONS;
305
NSView *EventHandler::mouseDownViewIfStillGood()
307
// Since we have no way of tracking the lifetime of m_mouseDownView, we have to assume that
308
// it could be deallocated already. We search for it in our subview tree; if we don't find
309
// it, we set it to nil.
310
NSView *mouseDownView = m_mouseDownView;
311
if (!mouseDownView) {
314
FrameView* topFrameView = m_frame->view();
315
NSView *topView = topFrameView ? topFrameView->getView() : nil;
316
if (!topView || !findViewInSubviews(topView, mouseDownView)) {
317
m_mouseDownView = nil;
320
return mouseDownView;
323
bool EventHandler::eventActivatedView(const PlatformMouseEvent& event) const
325
return m_activationEventNumber == event.eventNumber();
328
bool EventHandler::eventLoopHandleMouseDragged(const MouseEventWithHitTestResults&)
330
NSView *view = mouseDownViewIfStillGood();
335
if (!m_mouseDownWasInSubframe) {
336
m_sendingEventToSubview = true;
337
BEGIN_BLOCK_OBJC_EXCEPTIONS;
338
[view mouseDragged:currentEvent().get()];
339
END_BLOCK_OBJC_EXCEPTIONS;
340
m_sendingEventToSubview = false;
346
Clipboard* EventHandler::createDraggingClipboard() const
348
NSPasteboard *pasteboard = [NSPasteboard pasteboardWithName:NSDragPboard];
349
// Must be done before ondragstart adds types and data to the pboard,
350
// also done for security, as it erases data from the last drag
351
[pasteboard declareTypes:[NSArray array] owner:nil];
352
return new ClipboardMac(true, pasteboard, ClipboardWritable, m_frame);
355
bool EventHandler::eventLoopHandleMouseUp(const MouseEventWithHitTestResults&)
357
NSView *view = mouseDownViewIfStillGood();
361
if (!m_mouseDownWasInSubframe) {
362
m_sendingEventToSubview = true;
363
BEGIN_BLOCK_OBJC_EXCEPTIONS;
364
[view mouseUp:currentEvent().get()];
365
END_BLOCK_OBJC_EXCEPTIONS;
366
m_sendingEventToSubview = false;
372
bool EventHandler::passSubframeEventToSubframe(MouseEventWithHitTestResults& event, Frame* subframe, HitTestResult* hoveredNode)
374
BEGIN_BLOCK_OBJC_EXCEPTIONS;
376
switch ([currentEvent().get() type]) {
378
// Since we're passing in currentEvent() here, we can call
379
// handleMouseMoveEvent() directly, since the save/restore of
380
// currentEvent() that mouseMoved() does would have no effect.
381
subframe->eventHandler()->handleMouseMoveEvent(currentEvent().get(), hoveredNode);
384
case NSLeftMouseDown: {
385
Node* node = event.targetNode();
388
RenderObject* renderer = node->renderer();
389
if (!renderer || !renderer->isWidget())
391
Widget* widget = static_cast<RenderWidget*>(renderer)->widget();
392
if (!widget || !widget->isFrameView())
394
if (!passWidgetMouseDownEventToWidget(static_cast<RenderWidget*>(renderer)))
396
m_mouseDownWasInSubframe = true;
399
case NSLeftMouseUp: {
400
if (!m_mouseDownWasInSubframe)
402
NSView *view = mouseDownViewIfStillGood();
405
ASSERT(!m_sendingEventToSubview);
406
m_sendingEventToSubview = true;
407
[view mouseUp:currentEvent().get()];
408
m_sendingEventToSubview = false;
411
case NSLeftMouseDragged: {
412
if (!m_mouseDownWasInSubframe)
414
NSView *view = mouseDownViewIfStillGood();
417
ASSERT(!m_sendingEventToSubview);
418
m_sendingEventToSubview = true;
419
[view mouseDragged:currentEvent().get()];
420
m_sendingEventToSubview = false;
426
END_BLOCK_OBJC_EXCEPTIONS;
431
bool EventHandler::passWheelEventToWidget(PlatformWheelEvent&, Widget* widget)
433
BEGIN_BLOCK_OBJC_EXCEPTIONS;
435
if ([currentEvent().get() type] != NSScrollWheel || m_sendingEventToSubview || !widget)
438
NSView *nodeView = widget->getView();
440
ASSERT([nodeView superview]);
441
NSView *view = [nodeView hitTest:[[nodeView superview] convertPoint:[currentEvent().get() locationInWindow] fromView:nil]];
443
// We probably hit the border of a RenderWidget
446
m_sendingEventToSubview = true;
447
[view scrollWheel:currentEvent().get()];
448
m_sendingEventToSubview = false;
451
END_BLOCK_OBJC_EXCEPTIONS;
455
void EventHandler::mouseDown(NSEvent *event)
457
FrameView* v = m_frame->view();
458
if (!v || m_sendingEventToSubview)
461
BEGIN_BLOCK_OBJC_EXCEPTIONS;
463
m_frame->loader()->resetMultipleFormSubmissionProtection();
465
m_mouseDownView = nil;
466
dragState().m_dragSrc = 0;
468
RetainPtr<NSEvent> oldCurrentEvent = currentEvent();
469
currentEvent() = event;
470
m_mouseDown = PlatformMouseEvent(event);
472
handleMousePressEvent(event);
474
ASSERT(currentEvent() == event);
475
currentEvent() = oldCurrentEvent;
477
END_BLOCK_OBJC_EXCEPTIONS;
480
void EventHandler::mouseDragged(NSEvent *event)
482
FrameView* v = m_frame->view();
483
if (!v || m_sendingEventToSubview)
486
BEGIN_BLOCK_OBJC_EXCEPTIONS;
488
RetainPtr<NSEvent> oldCurrentEvent = currentEvent();
489
currentEvent() = event;
491
handleMouseMoveEvent(event);
493
ASSERT(currentEvent() == event);
494
currentEvent() = oldCurrentEvent;
496
END_BLOCK_OBJC_EXCEPTIONS;
499
void EventHandler::mouseUp(NSEvent *event)
501
FrameView* v = m_frame->view();
502
if (!v || m_sendingEventToSubview)
505
BEGIN_BLOCK_OBJC_EXCEPTIONS;
507
RetainPtr<NSEvent> oldCurrentEvent = currentEvent();
508
currentEvent() = event;
510
// Our behavior here is a little different that Qt. Qt always sends
511
// a mouse release event, even for a double click. To correct problems
512
// in khtml's DOM click event handling we do not send a release here
513
// for a double click. Instead we send that event from FrameView's
514
// handleMouseDoubleClickEvent. Note also that the third click of
515
// a triple click is treated as a single click, but the fourth is then
516
// treated as another double click. Hence the "% 2" below.
517
int clickCount = [event clickCount];
518
if (clickCount > 0 && clickCount % 2 == 0)
519
handleMouseDoubleClickEvent(event);
521
handleMouseReleaseEvent(event);
523
ASSERT(currentEvent() == event);
524
currentEvent() = oldCurrentEvent;
526
m_mouseDownView = nil;
528
END_BLOCK_OBJC_EXCEPTIONS;
532
A hack for the benefit of AK's PopUpButton, which uses the Carbon menu manager, which thus
533
eats all subsequent events after it is starts its modal tracking loop. After the interaction
534
is done, this routine is used to fix things up. When a mouse down started us tracking in
535
the widget, we post a fake mouse up to balance the mouse down we started with. When a
536
key down started us tracking in the widget, we post a fake key up to balance things out.
537
In addition, we post a fake mouseMoved to get the cursor in sync with whatever we happen to
538
be over after the tracking is done.
540
void EventHandler::sendFakeEventsAfterWidgetTracking(NSEvent *initiatingEvent)
542
BEGIN_BLOCK_OBJC_EXCEPTIONS;
544
m_sendingEventToSubview = false;
545
int eventType = [initiatingEvent type];
546
if (eventType == NSLeftMouseDown || eventType == NSKeyDown) {
547
NSEvent *fakeEvent = nil;
548
if (eventType == NSLeftMouseDown) {
549
fakeEvent = [NSEvent mouseEventWithType:NSLeftMouseUp
550
location:[initiatingEvent locationInWindow]
551
modifierFlags:[initiatingEvent modifierFlags]
552
timestamp:[initiatingEvent timestamp]
553
windowNumber:[initiatingEvent windowNumber]
554
context:[initiatingEvent context]
555
eventNumber:[initiatingEvent eventNumber]
556
clickCount:[initiatingEvent clickCount]
557
pressure:[initiatingEvent pressure]];
559
[NSApp postEvent:fakeEvent atStart:YES];
560
} else { // eventType == NSKeyDown
561
fakeEvent = [NSEvent keyEventWithType:NSKeyUp
562
location:[initiatingEvent locationInWindow]
563
modifierFlags:[initiatingEvent modifierFlags]
564
timestamp:[initiatingEvent timestamp]
565
windowNumber:[initiatingEvent windowNumber]
566
context:[initiatingEvent context]
567
characters:[initiatingEvent characters]
568
charactersIgnoringModifiers:[initiatingEvent charactersIgnoringModifiers]
569
isARepeat:[initiatingEvent isARepeat]
570
keyCode:[initiatingEvent keyCode]];
571
[NSApp postEvent:fakeEvent atStart:YES];
573
// FIXME: We should really get the current modifierFlags here, but there's no way to poll
574
// them in Cocoa, and because the event stream was stolen by the Carbon menu code we have
575
// no up-to-date cache of them anywhere.
576
fakeEvent = [NSEvent mouseEventWithType:NSMouseMoved
577
location:[[m_frame->bridge() window] convertScreenToBase:[NSEvent mouseLocation]]
578
modifierFlags:[initiatingEvent modifierFlags]
579
timestamp:[initiatingEvent timestamp]
580
windowNumber:[initiatingEvent windowNumber]
581
context:[initiatingEvent context]
585
[NSApp postEvent:fakeEvent atStart:YES];
588
END_BLOCK_OBJC_EXCEPTIONS;
591
void EventHandler::mouseMoved(NSEvent *event)
593
// Reject a mouse moved if the button is down - screws up tracking during autoscroll
594
// These happen because WebKit sometimes has to fake up moved events.
595
if (!m_frame->view() || m_mousePressed || m_sendingEventToSubview)
598
BEGIN_BLOCK_OBJC_EXCEPTIONS;
600
RetainPtr<NSEvent> oldCurrentEvent = currentEvent();
601
currentEvent() = event;
603
mouseMoved(PlatformMouseEvent(event));
605
ASSERT(currentEvent() == event);
606
currentEvent() = oldCurrentEvent;
608
END_BLOCK_OBJC_EXCEPTIONS;
611
bool EventHandler::passMousePressEventToSubframe(MouseEventWithHitTestResults& mev, Frame* subframe)
613
return passSubframeEventToSubframe(mev, subframe);
616
bool EventHandler::passMouseMoveEventToSubframe(MouseEventWithHitTestResults& mev, Frame* subframe, HitTestResult* hoveredNode)
618
return passSubframeEventToSubframe(mev, subframe, hoveredNode);
621
bool EventHandler::passMouseReleaseEventToSubframe(MouseEventWithHitTestResults& mev, Frame* subframe)
623
return passSubframeEventToSubframe(mev, subframe);
626
bool EventHandler::passMousePressEventToScrollbar(MouseEventWithHitTestResults&, PlatformScrollbar* scrollbar)
628
return passMouseDownEventToWidget(scrollbar);