WebNetscapePluginEventHandlerCarbon.mm   [plain text]

/*
* Copyright (C) 2008-2017 Apple Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#if ENABLE(NETSCAPE_PLUGIN_API) && !defined(__LP64__)

#import "WebNetscapePluginEventHandlerCarbon.h"

#import "WebNetscapePluginView.h"
#import "WebKitLogging.h"
#import <Carbon/Carbon.h>
#import <pal/spi/mac/HIToolboxSPI.h>
#import <pal/spi/mac/NSEventSPI.h>

// Send null events 50 times a second when active, so plug-ins like Flash get high frame rates.
#define NullEventIntervalActive 0.02
#define NullEventIntervalNotActive 0.25

WebNetscapePluginEventHandlerCarbon::WebNetscapePluginEventHandlerCarbon(WebNetscapePluginView* pluginView)
: WebNetscapePluginEventHandler(pluginView)
, m_keyEventHandler(0)
, m_suspendKeyUpEvents(false)
{
}

static void getCarbonEvent(EventRecord* carbonEvent)
{
carbonEvent->what = nullEvent;
carbonEvent->message = 0;
carbonEvent->when = TickCount();

GetGlobalMouse(&carbonEvent->where);
carbonEvent->modifiers = GetCurrentKeyModifiers();
if (!Button())
carbonEvent->modifiers |= btnState;
}

static EventModifiers modifiersForEvent(NSEvent *event)
{
EventModifiers modifiers;
unsigned int modifierFlags = [event modifierFlags];
NSEventType eventType = [event type];

modifiers = 0;

if (eventType != NSEventTypeLeftMouseDown && eventType != NSEventTypeRightMouseDown)
modifiers |= btnState;

if (modifierFlags & NSEventModifierFlagCommand)
modifiers |= cmdKey;

if (modifierFlags & NSEventModifierFlagShift)
modifiers |= shiftKey;

if (modifierFlags & NSEventModifierFlagCapsLock)
modifiers |= alphaLock;

if (modifierFlags & NSEventModifierFlagOption)
modifiers |= optionKey;

if (modifierFlags & NSEventModifierFlagControl || eventType == NSEventTypeRightMouseDown)
modifiers |= controlKey;

return modifiers;
}

static void getCarbonEvent(EventRecord *carbonEvent, NSEvent *cocoaEvent)
{
if ([cocoaEvent _eventRef] && ConvertEventRefToEventRecord((EventRef)[cocoaEvent _eventRef], carbonEvent))
return;

NSPoint where = [[cocoaEvent window] convertBaseToScreen:[cocoaEvent locationInWindow]];

carbonEvent->what = nullEvent;
carbonEvent->message = 0;
carbonEvent->when = (UInt32)([cocoaEvent timestamp] * 60); // seconds to ticks
carbonEvent->where.h = (short)where.x;
carbonEvent->where.v = (short)(NSMaxY([(NSScreen *)[[NSScreen screens] objectAtIndex:0] frame]) - where.y);
carbonEvent->modifiers = modifiersForEvent(cocoaEvent);
}

void WebNetscapePluginEventHandlerCarbon::sendNullEvent()
{
EventRecord event;

getCarbonEvent(&event);

// Plug-in should not react to cursor position when not active or when a menu is down.
MenuTrackingData trackingData;
OSStatus error = GetMenuTrackingData(NULL, &trackingData);

// Plug-in should not react to cursor position when the actual window is not key.
if (![[m_pluginView window] isKeyWindow] || (error == noErr && trackingData.menu)) {
// FIXME: Does passing a v and h of -1 really prevent it from reacting to the cursor position?
event.where.v = -1;
event.where.h = -1;
}

sendEvent(&event);
}

void WebNetscapePluginEventHandlerCarbon::drawRect(CGContextRef, const NSRect&)
{
EventRecord event;

getCarbonEvent(&event);
event.what = updateEvt;
WindowRef windowRef = (WindowRef)[[m_pluginView window] windowRef];
event.message = (unsigned long)windowRef;

BOOL acceptedEvent;
acceptedEvent = sendEvent(&event);

LOG(PluginEvents, "NPP_HandleEvent(updateEvt): %d", acceptedEvent);
}

void WebNetscapePluginEventHandlerCarbon::mouseDown(NSEvent* theEvent)
{
EventRecord event;

getCarbonEvent(&event, theEvent);
event.what = ::mouseDown;

BOOL acceptedEvent;
acceptedEvent = sendEvent(&event);

LOG(PluginEvents, "NPP_HandleEvent(mouseDown): %d pt.v=%d, pt.h=%d", acceptedEvent, event.where.v, event.where.h);
}

void WebNetscapePluginEventHandlerCarbon::mouseUp(NSEvent* theEvent)
{
EventRecord event;

getCarbonEvent(&event, theEvent);
event.what = ::mouseUp;

BOOL acceptedEvent;
acceptedEvent = sendEvent(&event);

LOG(PluginEvents, "NPP_HandleEvent(mouseUp): %d pt.v=%d, pt.h=%d", acceptedEvent, event.where.v, event.where.h);
}

bool WebNetscapePluginEventHandlerCarbon::scrollWheel(NSEvent* theEvent)
{
return false;
}

void WebNetscapePluginEventHandlerCarbon::mouseEntered(NSEvent* theEvent)
{
EventRecord event;

getCarbonEvent(&event, theEvent);
event.what = NPEventType_AdjustCursorEvent;

BOOL acceptedEvent;
acceptedEvent = sendEvent(&event);

LOG(PluginEvents, "NPP_HandleEvent(mouseEntered): %d", acceptedEvent);
}

void WebNetscapePluginEventHandlerCarbon::mouseExited(NSEvent* theEvent)
{
EventRecord event;

getCarbonEvent(&event, theEvent);
event.what = NPEventType_AdjustCursorEvent;

BOOL acceptedEvent;
acceptedEvent = sendEvent(&event);

LOG(PluginEvents, "NPP_HandleEvent(mouseExited): %d", acceptedEvent);
}

void WebNetscapePluginEventHandlerCarbon::mouseDragged(NSEvent*)
{
}

void WebNetscapePluginEventHandlerCarbon::mouseMoved(NSEvent* theEvent)
{
EventRecord event;

getCarbonEvent(&event, theEvent);
event.what = NPEventType_AdjustCursorEvent;

BOOL acceptedEvent;
acceptedEvent = sendEvent(&event);

LOG(PluginEvents, "NPP_HandleEvent(mouseMoved): %d", acceptedEvent);
}

void WebNetscapePluginEventHandlerCarbon::keyDown(NSEvent *theEvent)
{
m_suspendKeyUpEvents = true;
TSMProcessRawKeyEvent((EventRef)[theEvent _eventRef]);
}

void WebNetscapePluginEventHandlerCarbon::syntheticKeyDownWithCommandModifier(int keyCode, char character)
{
EventRecord event;
getCarbonEvent(&event);

event.what = ::keyDown;
event.modifiers |= cmdKey;
event.message = keyCode << 8 | character;
sendEvent(&event);
}

static UInt32 keyMessageForEvent(NSEvent *event)
{
NSData *data = [[event characters] dataUsingEncoding:CFStringConvertEncodingToNSStringEncoding(CFStringGetSystemEncoding())];
if (!data)
return 0;

UInt8 characterCode;
[data getBytes:&characterCode length:1];
UInt16 keyCode = [event keyCode];
return keyCode << 8 | characterCode;
}

void WebNetscapePluginEventHandlerCarbon::keyUp(NSEvent* theEvent)
{
TSMProcessRawKeyEvent((EventRef)[theEvent _eventRef]);

// TSM won't send keyUp events so we have to send them ourselves.
// Only send keyUp events after we receive the TSM callback because this is what plug-in expect from OS 9.
if (!m_suspendKeyUpEvents) {
EventRecord event;

getCarbonEvent(&event, theEvent);
event.what = ::keyUp;

if (event.message == 0)
event.message = keyMessageForEvent(theEvent);

sendEvent(&event);
}
}

void WebNetscapePluginEventHandlerCarbon::flagsChanged(NSEvent*)
{
}

void WebNetscapePluginEventHandlerCarbon::focusChanged(bool hasFocus)
{
EventRecord event;

getCarbonEvent(&event);
bool acceptedEvent;
if (hasFocus) {
event.what = NPEventType_GetFocusEvent;
acceptedEvent = sendEvent(&event);
LOG(PluginEvents, "NPP_HandleEvent(NPEventType_GetFocusEvent): %d", acceptedEvent);
installKeyEventHandler();
} else {
event.what = NPEventType_LoseFocusEvent;
acceptedEvent = sendEvent(&event);
LOG(PluginEvents, "NPP_HandleEvent(NPEventType_LoseFocusEvent): %d", acceptedEvent);
removeKeyEventHandler();
}
}

void WebNetscapePluginEventHandlerCarbon::windowFocusChanged(bool hasFocus)
{
WindowRef windowRef = (WindowRef)[[m_pluginView window] windowRef];

SetUserFocusWindow(windowRef);

EventRecord event;

getCarbonEvent(&event);
event.what = activateEvt;
event.message = (unsigned long)windowRef;
if (hasFocus)
event.modifiers |= activeFlag;

BOOL acceptedEvent;
acceptedEvent = sendEvent(&event);

LOG(PluginEvents, "NPP_HandleEvent(activateEvent): %d isActive: %d", acceptedEvent, hasFocus);
}

OSStatus WebNetscapePluginEventHandlerCarbon::TSMEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEvent, void *eventHandler)
{
EventRef rawKeyEventRef;
OSStatus status = GetEventParameter(inEvent, kEventParamTextInputSendKeyboardEvent, typeEventRef, NULL, sizeof(EventRef), NULL, &rawKeyEventRef);
if (status != noErr) {
LOG_ERROR("GetEventParameter failed with error: %d", status);
return noErr;
}

// Two-pass read to allocate/extract Mac charCodes
ByteCount numBytes;
status = GetEventParameter(rawKeyEventRef, kEventParamKeyMacCharCodes, typeChar, NULL, 0, &numBytes, NULL);
if (status != noErr) {
LOG_ERROR("GetEventParameter failed with error: %d", status);
return noErr;
}
char *buffer = (char *)malloc(numBytes);
status = GetEventParameter(rawKeyEventRef, kEventParamKeyMacCharCodes, typeChar, NULL, numBytes, NULL, buffer);
if (status != noErr) {
LOG_ERROR("GetEventParameter failed with error: %d", status);
free(buffer);
return noErr;
}

EventRef cloneEvent = CopyEvent(rawKeyEventRef);
unsigned i;
for (i = 0; i < numBytes; i++) {
status = SetEventParameter(cloneEvent, kEventParamKeyMacCharCodes, typeChar, 1 /* one char code */, &buffer[i]);
if (status != noErr) {
LOG_ERROR("SetEventParameter failed with error: %d", status);
free(buffer);
return noErr;
}

EventRecord eventRec;
if (ConvertEventRefToEventRecord(cloneEvent, &eventRec)) {
BOOL acceptedEvent;
acceptedEvent = static_cast<WebNetscapePluginEventHandlerCarbon*>(eventHandler)->sendEvent(&eventRec);

LOG(PluginEvents, "NPP_HandleEvent(keyDown): %d charCode:%c keyCode:%lu",
acceptedEvent, (char) (eventRec.message & charCodeMask), (eventRec.message & keyCodeMask));

// We originally thought that if the plug-in didn't accept this event,
// we should pass it along so that keyboard scrolling, for example, will work.
// In practice, this is not a good idea, because plug-ins tend to eat the event but return false.
// MacIE handles each key event twice because of this, but we will emulate the other browsers instead.
}
}
ReleaseEvent(cloneEvent);

free(buffer);

return noErr;
}

void WebNetscapePluginEventHandlerCarbon::installKeyEventHandler()
{
static const EventTypeSpec sTSMEvents[] =
{
{ kEventClassTextInput, kEventTextInputUnicodeForKeyEvent }
};

if (!m_keyEventHandler) {
InstallEventHandler(GetWindowEventTarget((WindowRef)[[m_pluginView window] windowRef]),
NewEventHandlerUPP(TSMEventHandler),
GetEventTypeCount(sTSMEvents),
sTSMEvents,
this,
&m_keyEventHandler);
}
}

void WebNetscapePluginEventHandlerCarbon::removeKeyEventHandler()
{
if (m_keyEventHandler) {
RemoveEventHandler(m_keyEventHandler);
m_keyEventHandler = 0;
}
}

void WebNetscapePluginEventHandlerCarbon::nullEventTimerFired(CFRunLoopTimerRef timerRef, void *context)
{
static_cast<WebNetscapePluginEventHandlerCarbon*>(context)->sendNullEvent();
}

void WebNetscapePluginEventHandlerCarbon::startTimers(bool throttleTimers)
{
ASSERT(!m_nullEventTimer);

CFTimeInterval interval = !throttleTimers ? NullEventIntervalActive : NullEventIntervalNotActive;

CFRunLoopTimerContext context = { 0, this, NULL, NULL, NULL };
m_nullEventTimer = adoptCF(CFRunLoopTimerCreate(0, CFAbsoluteTimeGetCurrent() + interval, interval,
0, 0, nullEventTimerFired, &context));
CFRunLoopAddTimer(CFRunLoopGetCurrent(), m_nullEventTimer.get(), kCFRunLoopDefaultMode);
}

void WebNetscapePluginEventHandlerCarbon::stopTimers()
{
if (!m_nullEventTimer)
return;

CFRunLoopTimerInvalidate(m_nullEventTimer.get());
m_nullEventTimer = 0;
}

void* WebNetscapePluginEventHandlerCarbon::platformWindow(NSWindow* window)
{
return [window windowRef];
}

bool WebNetscapePluginEventHandlerCarbon::sendEvent(EventRecord* event)
{
// If at any point the user clicks or presses a key from within a plugin, set the
// currentEventIsUserGesture flag to true. This is important to differentiate legitimate
// window.open() calls; we still want to allow those. See rdar://problem/4010765
if (event->what == ::mouseDown || event->what == ::keyDown || event->what == ::mouseUp || event->what == ::autoKey)
m_currentEventIsUserGesture = true;

m_suspendKeyUpEvents = false;

bool result = [m_pluginView sendEvent:event isDrawRect:event->what == updateEvt];

m_currentEventIsUserGesture = false;

return result;
}

#endif // ENABLE(NETSCAPE_PLUGIN_API) && !defined(__LP64__)