WebNetscapePluginEventHandlerCarbon.mm revision dcc8cf2e65d1aa555cce12431a16547e66b469ee
1/*
2 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
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.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE 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 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.
24 */
25
26#if ENABLE(NETSCAPE_PLUGIN_API) && !defined(__LP64__)
27
28#import "WebNetscapePluginEventHandlerCarbon.h"
29
30#import "WebNetscapePluginView.h"
31#import "WebKitLogging.h"
32#import "WebKitSystemInterface.h"
33
34// Send null events 50 times a second when active, so plug-ins like Flash get high frame rates.
35#define NullEventIntervalActive         0.02
36#define NullEventIntervalNotActive      0.25
37
38WebNetscapePluginEventHandlerCarbon::WebNetscapePluginEventHandlerCarbon(WebNetscapePluginView* pluginView)
39    : WebNetscapePluginEventHandler(pluginView)
40    , m_keyEventHandler(0)
41    , m_suspendKeyUpEvents(false)
42{
43}
44
45static void getCarbonEvent(EventRecord* carbonEvent)
46{
47    carbonEvent->what = nullEvent;
48    carbonEvent->message = 0;
49    carbonEvent->when = TickCount();
50
51    GetGlobalMouse(&carbonEvent->where);
52    carbonEvent->where.h = static_cast<short>(carbonEvent->where.h * HIGetScaleFactor());
53    carbonEvent->where.v = static_cast<short>(carbonEvent->where.v * HIGetScaleFactor());
54    carbonEvent->modifiers = GetCurrentKeyModifiers();
55    if (!Button())
56        carbonEvent->modifiers |= btnState;
57}
58
59static EventModifiers modifiersForEvent(NSEvent *event)
60{
61    EventModifiers modifiers;
62    unsigned int modifierFlags = [event modifierFlags];
63    NSEventType eventType = [event type];
64
65    modifiers = 0;
66
67    if (eventType != NSLeftMouseDown && eventType != NSRightMouseDown)
68        modifiers |= btnState;
69
70    if (modifierFlags & NSCommandKeyMask)
71        modifiers |= cmdKey;
72
73    if (modifierFlags & NSShiftKeyMask)
74        modifiers |= shiftKey;
75
76    if (modifierFlags & NSAlphaShiftKeyMask)
77        modifiers |= alphaLock;
78
79    if (modifierFlags & NSAlternateKeyMask)
80        modifiers |= optionKey;
81
82    if (modifierFlags & NSControlKeyMask || eventType == NSRightMouseDown)
83        modifiers |= controlKey;
84
85    return modifiers;
86}
87
88static void getCarbonEvent(EventRecord *carbonEvent, NSEvent *cocoaEvent)
89{
90    if (WKConvertNSEventToCarbonEvent(carbonEvent, cocoaEvent)) {
91        carbonEvent->where.h = static_cast<short>(carbonEvent->where.h * HIGetScaleFactor());
92        carbonEvent->where.v = static_cast<short>(carbonEvent->where.v * HIGetScaleFactor());
93        return;
94    }
95
96    NSPoint where = [[cocoaEvent window] convertBaseToScreen:[cocoaEvent locationInWindow]];
97
98    carbonEvent->what = nullEvent;
99    carbonEvent->message = 0;
100    carbonEvent->when = (UInt32)([cocoaEvent timestamp] * 60); // seconds to ticks
101    carbonEvent->where.h = (short)where.x;
102    carbonEvent->where.v = (short)(NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - where.y);
103    carbonEvent->modifiers = modifiersForEvent(cocoaEvent);
104}
105
106void WebNetscapePluginEventHandlerCarbon::sendNullEvent()
107{
108    EventRecord event;
109
110    getCarbonEvent(&event);
111
112    // Plug-in should not react to cursor position when not active or when a menu is down.
113    MenuTrackingData trackingData;
114    OSStatus error = GetMenuTrackingData(NULL, &trackingData);
115
116    // Plug-in should not react to cursor position when the actual window is not key.
117    if (![[m_pluginView window] isKeyWindow] || (error == noErr && trackingData.menu)) {
118        // FIXME: Does passing a v and h of -1 really prevent it from reacting to the cursor position?
119        event.where.v = -1;
120        event.where.h = -1;
121    }
122
123    sendEvent(&event);
124}
125
126void WebNetscapePluginEventHandlerCarbon::drawRect(CGContextRef, const NSRect&)
127{
128    EventRecord event;
129
130    getCarbonEvent(&event);
131    event.what = updateEvt;
132    WindowRef windowRef = (WindowRef)[[m_pluginView window] windowRef];
133    event.message = (unsigned long)windowRef;
134
135    BOOL acceptedEvent;
136    acceptedEvent = sendEvent(&event);
137
138    LOG(PluginEvents, "NPP_HandleEvent(updateEvt): %d", acceptedEvent);
139}
140
141void WebNetscapePluginEventHandlerCarbon::mouseDown(NSEvent* theEvent)
142{
143    EventRecord event;
144
145    getCarbonEvent(&event, theEvent);
146    event.what = ::mouseDown;
147
148    BOOL acceptedEvent;
149    acceptedEvent = sendEvent(&event);
150
151    LOG(PluginEvents, "NPP_HandleEvent(mouseDown): %d pt.v=%d, pt.h=%d", acceptedEvent, event.where.v, event.where.h);
152}
153
154void WebNetscapePluginEventHandlerCarbon::mouseUp(NSEvent* theEvent)
155{
156    EventRecord event;
157
158    getCarbonEvent(&event, theEvent);
159    event.what = ::mouseUp;
160
161    BOOL acceptedEvent;
162    acceptedEvent = sendEvent(&event);
163
164    LOG(PluginEvents, "NPP_HandleEvent(mouseUp): %d pt.v=%d, pt.h=%d", acceptedEvent, event.where.v, event.where.h);
165}
166
167bool WebNetscapePluginEventHandlerCarbon::scrollWheel(NSEvent* theEvent)
168{
169    return false;
170}
171
172void WebNetscapePluginEventHandlerCarbon::mouseEntered(NSEvent* theEvent)
173{
174    EventRecord event;
175
176    getCarbonEvent(&event, theEvent);
177    event.what = adjustCursorEvent;
178
179    BOOL acceptedEvent;
180    acceptedEvent = sendEvent(&event);
181
182    LOG(PluginEvents, "NPP_HandleEvent(mouseEntered): %d", acceptedEvent);
183}
184
185void WebNetscapePluginEventHandlerCarbon::mouseExited(NSEvent* theEvent)
186{
187    EventRecord event;
188
189    getCarbonEvent(&event, theEvent);
190    event.what = adjustCursorEvent;
191
192    BOOL acceptedEvent;
193    acceptedEvent = sendEvent(&event);
194
195    LOG(PluginEvents, "NPP_HandleEvent(mouseExited): %d", acceptedEvent);
196}
197
198void WebNetscapePluginEventHandlerCarbon::mouseDragged(NSEvent*)
199{
200}
201
202void WebNetscapePluginEventHandlerCarbon::mouseMoved(NSEvent* theEvent)
203{
204    EventRecord event;
205
206    getCarbonEvent(&event, theEvent);
207    event.what = adjustCursorEvent;
208
209    BOOL acceptedEvent;
210    acceptedEvent = sendEvent(&event);
211
212    LOG(PluginEvents, "NPP_HandleEvent(mouseMoved): %d", acceptedEvent);
213}
214
215void WebNetscapePluginEventHandlerCarbon::keyDown(NSEvent *theEvent)
216{
217    m_suspendKeyUpEvents = true;
218    WKSendKeyEventToTSM(theEvent);
219}
220
221void WebNetscapePluginEventHandlerCarbon::syntheticKeyDownWithCommandModifier(int keyCode, char character)
222{
223    EventRecord event;
224    getCarbonEvent(&event);
225
226    event.what = ::keyDown;
227    event.modifiers |= cmdKey;
228    event.message = keyCode << 8 | character;
229    sendEvent(&event);
230}
231
232static UInt32 keyMessageForEvent(NSEvent *event)
233{
234    NSData *data = [[event characters] dataUsingEncoding:CFStringConvertEncodingToNSStringEncoding(CFStringGetSystemEncoding())];
235    if (!data)
236        return 0;
237
238    UInt8 characterCode;
239    [data getBytes:&characterCode length:1];
240    UInt16 keyCode = [event keyCode];
241    return keyCode << 8 | characterCode;
242}
243
244void WebNetscapePluginEventHandlerCarbon::keyUp(NSEvent* theEvent)
245{
246    WKSendKeyEventToTSM(theEvent);
247
248    // TSM won't send keyUp events so we have to send them ourselves.
249    // Only send keyUp events after we receive the TSM callback because this is what plug-in expect from OS 9.
250    if (!m_suspendKeyUpEvents) {
251        EventRecord event;
252
253        getCarbonEvent(&event, theEvent);
254        event.what = ::keyUp;
255
256        if (event.message == 0)
257            event.message = keyMessageForEvent(theEvent);
258
259        sendEvent(&event);
260    }
261}
262
263void WebNetscapePluginEventHandlerCarbon::flagsChanged(NSEvent*)
264{
265}
266
267void WebNetscapePluginEventHandlerCarbon::focusChanged(bool hasFocus)
268{
269    EventRecord event;
270
271    getCarbonEvent(&event);
272    bool acceptedEvent;
273    if (hasFocus) {
274        event.what = getFocusEvent;
275        acceptedEvent = sendEvent(&event);
276        LOG(PluginEvents, "NPP_HandleEvent(getFocusEvent): %d", acceptedEvent);
277        installKeyEventHandler();
278    } else {
279        event.what = loseFocusEvent;
280        acceptedEvent = sendEvent(&event);
281        LOG(PluginEvents, "NPP_HandleEvent(loseFocusEvent): %d", acceptedEvent);
282        removeKeyEventHandler();
283    }
284}
285
286void WebNetscapePluginEventHandlerCarbon::windowFocusChanged(bool hasFocus)
287{
288    WindowRef windowRef = (WindowRef)[[m_pluginView window] windowRef];
289
290    SetUserFocusWindow(windowRef);
291
292    EventRecord event;
293
294    getCarbonEvent(&event);
295    event.what = activateEvt;
296    event.message = (unsigned long)windowRef;
297    if (hasFocus)
298        event.modifiers |= activeFlag;
299
300    BOOL acceptedEvent;
301    acceptedEvent = sendEvent(&event);
302
303    LOG(PluginEvents, "NPP_HandleEvent(activateEvent): %d  isActive: %d", acceptedEvent, hasFocus);
304}
305
306OSStatus WebNetscapePluginEventHandlerCarbon::TSMEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEvent, void *eventHandler)
307{
308    EventRef rawKeyEventRef;
309    OSStatus status = GetEventParameter(inEvent, kEventParamTextInputSendKeyboardEvent, typeEventRef, NULL, sizeof(EventRef), NULL, &rawKeyEventRef);
310    if (status != noErr) {
311        LOG_ERROR("GetEventParameter failed with error: %d", status);
312        return noErr;
313    }
314
315    // Two-pass read to allocate/extract Mac charCodes
316    ByteCount numBytes;
317    status = GetEventParameter(rawKeyEventRef, kEventParamKeyMacCharCodes, typeChar, NULL, 0, &numBytes, NULL);
318    if (status != noErr) {
319        LOG_ERROR("GetEventParameter failed with error: %d", status);
320        return noErr;
321    }
322    char *buffer = (char *)malloc(numBytes);
323    status = GetEventParameter(rawKeyEventRef, kEventParamKeyMacCharCodes, typeChar, NULL, numBytes, NULL, buffer);
324    if (status != noErr) {
325        LOG_ERROR("GetEventParameter failed with error: %d", status);
326        free(buffer);
327        return noErr;
328    }
329
330    EventRef cloneEvent = CopyEvent(rawKeyEventRef);
331    unsigned i;
332    for (i = 0; i < numBytes; i++) {
333        status = SetEventParameter(cloneEvent, kEventParamKeyMacCharCodes, typeChar, 1 /* one char code */, &buffer[i]);
334        if (status != noErr) {
335            LOG_ERROR("SetEventParameter failed with error: %d", status);
336            free(buffer);
337            return noErr;
338        }
339
340        EventRecord eventRec;
341        if (ConvertEventRefToEventRecord(cloneEvent, &eventRec)) {
342            BOOL acceptedEvent;
343            acceptedEvent = static_cast<WebNetscapePluginEventHandlerCarbon*>(eventHandler)->sendEvent(&eventRec);
344
345            LOG(PluginEvents, "NPP_HandleEvent(keyDown): %d charCode:%c keyCode:%lu",
346                acceptedEvent, (char) (eventRec.message & charCodeMask), (eventRec.message & keyCodeMask));
347
348            // We originally thought that if the plug-in didn't accept this event,
349            // we should pass it along so that keyboard scrolling, for example, will work.
350            // In practice, this is not a good idea, because plug-ins tend to eat the event but return false.
351            // MacIE handles each key event twice because of this, but we will emulate the other browsers instead.
352        }
353    }
354    ReleaseEvent(cloneEvent);
355
356    free(buffer);
357
358    return noErr;
359}
360
361void WebNetscapePluginEventHandlerCarbon::installKeyEventHandler()
362{
363    static const EventTypeSpec sTSMEvents[] =
364    {
365        { kEventClassTextInput, kEventTextInputUnicodeForKeyEvent }
366    };
367
368    if (!m_keyEventHandler) {
369        InstallEventHandler(GetWindowEventTarget((WindowRef)[[m_pluginView window] windowRef]),
370                            NewEventHandlerUPP(TSMEventHandler),
371                            GetEventTypeCount(sTSMEvents),
372                            sTSMEvents,
373                            this,
374                            &m_keyEventHandler);
375    }
376}
377
378void WebNetscapePluginEventHandlerCarbon::removeKeyEventHandler()
379{
380    if (m_keyEventHandler) {
381        RemoveEventHandler(m_keyEventHandler);
382        m_keyEventHandler = 0;
383    }
384}
385
386void WebNetscapePluginEventHandlerCarbon::nullEventTimerFired(CFRunLoopTimerRef timerRef, void *context)
387{
388    static_cast<WebNetscapePluginEventHandlerCarbon*>(context)->sendNullEvent();
389}
390
391void WebNetscapePluginEventHandlerCarbon::startTimers(bool throttleTimers)
392{
393    ASSERT(!m_nullEventTimer);
394
395    CFTimeInterval interval = !throttleTimers ? NullEventIntervalActive : NullEventIntervalNotActive;
396
397    CFRunLoopTimerContext context = { 0, this, NULL, NULL, NULL };
398    m_nullEventTimer.adoptCF(CFRunLoopTimerCreate(0, CFAbsoluteTimeGetCurrent() + interval, interval,
399                                                   0, 0, nullEventTimerFired, &context));
400    CFRunLoopAddTimer(CFRunLoopGetCurrent(), m_nullEventTimer.get(), kCFRunLoopDefaultMode);
401}
402
403void WebNetscapePluginEventHandlerCarbon::stopTimers()
404{
405    if (!m_nullEventTimer)
406        return;
407
408    CFRunLoopTimerInvalidate(m_nullEventTimer.get());
409    m_nullEventTimer = 0;
410}
411
412void* WebNetscapePluginEventHandlerCarbon::platformWindow(NSWindow* window)
413{
414    return [window windowRef];
415}
416
417bool WebNetscapePluginEventHandlerCarbon::sendEvent(EventRecord* event)
418{
419    // If at any point the user clicks or presses a key from within a plugin, set the
420    // currentEventIsUserGesture flag to true. This is important to differentiate legitimate
421    // window.open() calls;  we still want to allow those.  See rdar://problem/4010765
422    if (event->what == ::mouseDown || event->what == ::keyDown || event->what == ::mouseUp || event->what == ::autoKey)
423        m_currentEventIsUserGesture = true;
424
425    m_suspendKeyUpEvents = false;
426
427    bool result = [m_pluginView sendEvent:event isDrawRect:event->what == updateEvt];
428
429    m_currentEventIsUserGesture = false;
430
431    return result;
432}
433
434#endif // ENABLE(NETSCAPE_PLUGIN_API) && !defined(__LP64__)
435