1/*
2 * Copyright (C) 2010 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. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#import "config.h"
27#import "NetscapePlugin.h"
28
29#if ENABLE(NETSCAPE_PLUGIN_API)
30
31#import "NetscapeBrowserFuncs.h"
32#import "PluginController.h"
33#import "WKNPAPIPlugInContainerInternal.h"
34#import "WebEvent.h"
35#import <Carbon/Carbon.h>
36#import <WebCore/GraphicsContext.h>
37#import <WebCore/NotImplemented.h>
38#import <WebKitSystemInterface.h>
39
40using namespace WebCore;
41
42namespace WebKit {
43
44#ifndef NP_NO_CARBON
45static const double nullEventIntervalActive = 0.02;
46static const double nullEventIntervalNotActive = 0.25;
47
48static unsigned buttonStateFromLastMouseEvent;
49
50#endif
51
52NPError NetscapePlugin::setDrawingModel(NPDrawingModel drawingModel)
53{
54    // The drawing model can only be set from NPP_New.
55    if (!m_inNPPNew)
56        return NPERR_GENERIC_ERROR;
57
58    switch (drawingModel) {
59#ifndef NP_NO_QUICKDRAW
60        case NPDrawingModelQuickDraw:
61#endif
62        case NPDrawingModelCoreGraphics:
63        case NPDrawingModelCoreAnimation:
64            m_drawingModel = drawingModel;
65            break;
66
67        default:
68            return NPERR_GENERIC_ERROR;
69    }
70
71    return NPERR_NO_ERROR;
72}
73
74NPError NetscapePlugin::setEventModel(NPEventModel eventModel)
75{
76    // The event model can only be set from NPP_New.
77    if (!m_inNPPNew)
78        return NPERR_GENERIC_ERROR;
79
80    switch (eventModel) {
81#ifndef NP_NO_CARBON
82        case NPEventModelCarbon:
83#endif
84        case NPEventModelCocoa:
85            m_eventModel = eventModel;
86            break;
87
88        default:
89            return NPERR_GENERIC_ERROR;
90    }
91
92    return NPERR_NO_ERROR;
93}
94
95bool NetscapePlugin::getScreenTransform(NPCoordinateSpace sourceSpace, AffineTransform& transform)
96{
97    ASSERT(transform.isIdentity());
98
99    switch (sourceSpace) {
100        case NPCoordinateSpacePlugin: {
101            transform.translate(m_windowFrameInScreenCoordinates.x(), m_windowFrameInScreenCoordinates.y());
102            transform.translate(m_viewFrameInWindowCoordinates.x(), m_viewFrameInWindowCoordinates.height() + m_viewFrameInWindowCoordinates.y());
103            transform.flipY();
104            transform *= m_pluginToRootViewTransform;
105            return true;
106        }
107
108        case NPCoordinateSpaceWindow: {
109            transform.translate(m_windowFrameInScreenCoordinates.x(), m_windowFrameInScreenCoordinates.y());
110            return true;
111        }
112
113        case NPCoordinateSpaceFlippedWindow: {
114            transform.translate(m_windowFrameInScreenCoordinates.x(), m_windowFrameInScreenCoordinates.height() + m_windowFrameInScreenCoordinates.y());
115            transform.flipY();
116            return true;
117        }
118
119        case NPCoordinateSpaceScreen: {
120            // Nothing to do.
121            return true;
122        }
123
124        case NPCoordinateSpaceFlippedScreen: {
125            double screenHeight = [(NSScreen *)[[NSScreen screens] objectAtIndex:0] frame].size.height;
126            transform.translate(0, screenHeight);
127            transform.flipY();
128            return true;
129        }
130
131        default:
132            return false;
133    }
134}
135
136NPBool NetscapePlugin::convertPoint(double sourceX, double sourceY, NPCoordinateSpace sourceSpace, double& destX, double& destY, NPCoordinateSpace destSpace)
137{
138    AffineTransform sourceTransform;
139    if (!getScreenTransform(sourceSpace, sourceTransform))
140        return false;
141
142    AffineTransform destTransform;
143    if (!getScreenTransform(destSpace, destTransform))
144        return false;
145
146    if (!destTransform.isInvertible())
147        return false;
148
149    AffineTransform transform = destTransform.inverse() * sourceTransform;
150
151    FloatPoint destinationPoint = transform.mapPoint(FloatPoint(sourceX, sourceY));
152
153    destX = destinationPoint.x();
154    destY = destinationPoint.y();
155    return true;
156}
157
158
159NPError NetscapePlugin::popUpContextMenu(NPMenu* npMenu)
160{
161    if (!m_currentMouseEvent)
162        return NPERR_GENERIC_ERROR;
163
164    double screenX, screenY;
165    if (!convertPoint(m_currentMouseEvent->data.mouse.pluginX, m_currentMouseEvent->data.mouse.pluginY, NPCoordinateSpacePlugin, screenX, screenY, NPCoordinateSpaceScreen))
166        ASSERT_NOT_REACHED();
167
168    WKPopupContextMenu(reinterpret_cast<NSMenu *>(npMenu), NSMakePoint(screenX, screenY));
169    return NPERR_NO_ERROR;
170}
171
172mach_port_t NetscapePlugin::compositingRenderServerPort()
173{
174#if HAVE(LAYER_HOSTING_IN_WINDOW_SERVER)
175    if (m_layerHostingMode == LayerHostingModeInWindowServer)
176        return MACH_PORT_NULL;
177#endif
178
179    return controller()->compositingRenderServerPort();
180}
181
182void NetscapePlugin::openPluginPreferencePane()
183{
184    controller()->openPluginPreferencePane();
185}
186
187WKNPAPIPlugInContainer* NetscapePlugin::plugInContainer()
188{
189    if (!m_plugInContainer)
190        m_plugInContainer = adoptNS([[WKNPAPIPlugInContainer alloc] _initWithNetscapePlugin:this]);
191
192    return m_plugInContainer.get();
193}
194
195#ifndef NP_NO_CARBON
196typedef HashMap<WindowRef, NetscapePlugin*> WindowMap;
197
198static WindowMap& windowMap()
199{
200    DEFINE_STATIC_LOCAL(WindowMap, windowMap, ());
201
202    return windowMap;
203}
204#endif
205
206static void NSException_release(id, SEL)
207{
208    // Do nothing.
209}
210
211void NetscapePlugin::platformPreInitialize()
212{
213    if (m_pluginModule->pluginQuirks().contains(PluginQuirks::LeakAllThrownNSExceptions)) {
214        // Patch -[NSException release] to not release the object.
215        static dispatch_once_t once;
216        dispatch_once(&once, ^{
217            Class exceptionClass = [NSException class];
218            Method exceptionReleaseMethod = class_getInstanceMethod(exceptionClass, @selector(release));
219            class_replaceMethod(exceptionClass, @selector(release), reinterpret_cast<IMP>(NSException_release), method_getTypeEncoding(exceptionReleaseMethod));
220        });
221    }
222}
223
224bool NetscapePlugin::platformPostInitialize()
225{
226    if (m_drawingModel == static_cast<NPDrawingModel>(-1)) {
227#ifndef NP_NO_QUICKDRAW
228        // Default to QuickDraw if the plugin did not specify a drawing model.
229        m_drawingModel = NPDrawingModelQuickDraw;
230#else
231        // QuickDraw is not available, so we can't default to it. Instead, default to CoreGraphics.
232        m_drawingModel = NPDrawingModelCoreGraphics;
233#endif
234    }
235
236    if (m_eventModel == static_cast<NPEventModel>(-1)) {
237        // If the plug-in did not specify a drawing model we default to Carbon when it is available.
238#ifndef NP_NO_CARBON
239        m_eventModel = NPEventModelCarbon;
240#else
241        m_eventModel = NPEventModelCocoa;
242#endif // NP_NO_CARBON
243    }
244
245#if !defined(NP_NO_CARBON) && !defined(NP_NO_QUICKDRAW)
246    // The CA drawing model does not work with the Carbon event model.
247    if (m_drawingModel == NPDrawingModelCoreAnimation && m_eventModel == NPEventModelCarbon)
248        return false;
249
250    // The Cocoa event model does not work with the QuickDraw drawing model.
251    if (m_eventModel == NPEventModelCocoa && m_drawingModel == NPDrawingModelQuickDraw)
252        return false;
253#endif
254
255#ifndef NP_NO_QUICKDRAW
256    // Right now we don't support the QuickDraw drawing model at all
257    if (m_drawingModel == NPDrawingModelQuickDraw &&
258        !m_pluginModule->pluginQuirks().contains(PluginQuirks::AllowHalfBakedQuickDrawSupport))
259        return false;
260#endif
261
262    updatePluginLayer();
263
264#ifndef NP_NO_CARBON
265    if (m_eventModel == NPEventModelCarbon) {
266        // Initialize the fake Carbon window.
267        ::Rect bounds = { 0, 0, 0, 0 };
268        CreateNewWindow(kDocumentWindowClass, kWindowNoTitleBarAttribute, &bounds, reinterpret_cast<WindowRef*>(&m_npCGContext.window));
269        ASSERT(m_npCGContext.window);
270
271        // FIXME: Disable the backing store.
272
273        m_npWindow.window = &m_npCGContext;
274
275        ASSERT(!windowMap().contains(windowRef()));
276        windowMap().set(windowRef(), this);
277
278        // Start the null event timer.
279        // FIXME: Throttle null events when the plug-in isn't visible on screen.
280        m_nullEventTimer.startRepeating(nullEventIntervalActive);
281    }
282#endif
283
284    return true;
285}
286
287void NetscapePlugin::platformDestroy()
288{
289    [m_plugInContainer _invalidate];
290
291#ifndef NP_NO_CARBON
292    if (m_eventModel == NPEventModelCarbon) {
293        if (WindowRef window = windowRef()) {
294            // Destroy the fake Carbon window.
295            DisposeWindow(window);
296
297            ASSERT(windowMap().contains(window));
298            windowMap().remove(window);
299        }
300
301        // Stop the null event timer.
302        m_nullEventTimer.stop();
303    }
304#endif
305}
306
307bool NetscapePlugin::platformInvalidate(const IntRect&)
308{
309    // NPN_InvalidateRect is just a no-op in the Core Animation drawing model.
310    if (m_drawingModel == NPDrawingModelCoreAnimation)
311        return true;
312
313    return false;
314}
315
316void NetscapePlugin::platformGeometryDidChange()
317{
318    switch (m_eventModel) {
319    case NPEventModelCocoa:
320        // Nothing to do
321        break;
322#ifndef NP_NO_CARBON
323    case NPEventModelCarbon:
324        updateFakeWindowBounds();
325        break;
326#endif
327    default:
328        ASSERT_NOT_REACHED();
329    }
330}
331
332void NetscapePlugin::platformVisibilityDidChange()
333{
334    // FIXME: Implement this. <http://webkit.org/b/44368>.
335    notImplemented();
336}
337
338static inline NPCocoaEvent initializeEvent(NPCocoaEventType type)
339{
340    NPCocoaEvent event;
341
342    event.type = type;
343    event.version = 0;
344
345    return event;
346}
347
348#ifndef NP_NO_CARBON
349NetscapePlugin* NetscapePlugin::netscapePluginFromWindow(WindowRef windowRef)
350{
351    return windowMap().get(windowRef);
352}
353
354WindowRef NetscapePlugin::windowRef() const
355{
356    ASSERT(m_eventModel == NPEventModelCarbon);
357
358    return reinterpret_cast<WindowRef>(m_npCGContext.window);
359}
360
361void NetscapePlugin::updateFakeWindowBounds()
362{
363    double screenX, screenY;
364    bool didConvert = convertPoint(0, 0, NPCoordinateSpacePlugin, screenX, screenY, NPCoordinateSpaceFlippedScreen);
365    ASSERT_UNUSED(didConvert, didConvert);
366
367    Rect bounds;
368    bounds.top = screenY;
369    bounds.left = screenX;
370    bounds.bottom = screenY + m_pluginSize.height();
371    bounds.right = screenX + m_pluginSize.width();
372
373    ::SetWindowBounds(windowRef(), kWindowStructureRgn, &bounds);
374}
375
376unsigned NetscapePlugin::buttonState()
377{
378    return buttonStateFromLastMouseEvent;
379}
380
381static inline EventRecord initializeEventRecord(EventKind eventKind)
382{
383    EventRecord eventRecord;
384
385    eventRecord.what = eventKind;
386    eventRecord.message = 0;
387    eventRecord.when = TickCount();
388    eventRecord.where = Point();
389    eventRecord.modifiers = 0;
390
391    return eventRecord;
392}
393
394static bool anyMouseButtonIsDown(const WebEvent& event)
395{
396    if (event.type() == WebEvent::MouseDown)
397        return true;
398
399    if (event.type() == WebEvent::MouseMove && static_cast<const WebMouseEvent&>(event).button() != WebMouseEvent::NoButton)
400        return true;
401
402    return false;
403}
404
405static bool rightMouseButtonIsDown(const WebEvent& event)
406{
407    if (event.type() == WebEvent::MouseDown && static_cast<const WebMouseEvent&>(event).button() == WebMouseEvent::RightButton)
408        return true;
409
410    if (event.type() == WebEvent::MouseMove && static_cast<const WebMouseEvent&>(event).button() == WebMouseEvent::RightButton)
411        return true;
412
413    return false;
414}
415
416static EventModifiers modifiersForEvent(const WebEvent& event)
417{
418    EventModifiers modifiers = 0;
419
420    // We only want to set the btnState if a mouse button is _not_ down.
421    if (!anyMouseButtonIsDown(event))
422        modifiers |= btnState;
423
424    if (event.metaKey())
425        modifiers |= cmdKey;
426
427    if (event.shiftKey())
428        modifiers |= shiftKey;
429
430    if (event.altKey())
431        modifiers |= optionKey;
432
433    // Set controlKey if the control key is down or the right mouse button is down.
434    if (event.controlKey() || rightMouseButtonIsDown(event))
435        modifiers |= controlKey;
436
437    return modifiers;
438}
439
440#endif
441
442void NetscapePlugin::platformPaint(GraphicsContext* context, const IntRect& dirtyRect, bool isSnapshot)
443{
444    CGContextRef platformContext = context->platformContext();
445
446    switch (m_eventModel) {
447        case NPEventModelCocoa: {
448            // Don't send draw events when we're using the Core Animation drawing model.
449            if (!isSnapshot && m_drawingModel == NPDrawingModelCoreAnimation)
450                return;
451
452            NPCocoaEvent event = initializeEvent(NPCocoaEventDrawRect);
453
454            event.data.draw.context = platformContext;
455            event.data.draw.x = dirtyRect.x();
456            event.data.draw.y = dirtyRect.y();
457            event.data.draw.width = dirtyRect.width();
458            event.data.draw.height = dirtyRect.height();
459
460            NPP_HandleEvent(&event);
461            break;
462        }
463
464#ifndef NP_NO_CARBON
465        case NPEventModelCarbon: {
466            if (platformContext != m_npCGContext.context) {
467                m_npCGContext.context = platformContext;
468                callSetWindow();
469            }
470
471            EventRecord event = initializeEventRecord(updateEvt);
472            event.message = reinterpret_cast<unsigned long>(windowRef());
473
474            NPP_HandleEvent(&event);
475            break;
476        }
477#endif
478
479        default:
480            ASSERT_NOT_REACHED();
481    }
482}
483
484static uint32_t modifierFlags(const WebEvent& event)
485{
486    uint32_t modifiers = 0;
487
488    if (event.shiftKey())
489        modifiers |= NSShiftKeyMask;
490    if (event.controlKey())
491        modifiers |= NSControlKeyMask;
492    if (event.altKey())
493        modifiers |= NSAlternateKeyMask;
494    if (event.metaKey())
495        modifiers |= NSCommandKeyMask;
496
497    return modifiers;
498}
499
500static int32_t buttonNumber(WebMouseEvent::Button button)
501{
502    switch (button) {
503    case WebMouseEvent::NoButton:
504    case WebMouseEvent::LeftButton:
505        return 0;
506    case WebMouseEvent::RightButton:
507        return 1;
508    case WebMouseEvent::MiddleButton:
509        return 2;
510    }
511
512    ASSERT_NOT_REACHED();
513    return -1;
514}
515
516static void fillInCocoaEventFromMouseEvent(NPCocoaEvent& event, const WebMouseEvent& mouseEvent, const WebCore::IntPoint& eventPositionInPluginCoordinates)
517{
518    event.data.mouse.modifierFlags = modifierFlags(mouseEvent);
519    event.data.mouse.pluginX = eventPositionInPluginCoordinates.x();
520    event.data.mouse.pluginY = eventPositionInPluginCoordinates.y();
521    event.data.mouse.buttonNumber = buttonNumber(mouseEvent.button());
522    event.data.mouse.clickCount = mouseEvent.clickCount();
523    event.data.mouse.deltaX = mouseEvent.deltaX();
524    event.data.mouse.deltaY = mouseEvent.deltaY();
525    event.data.mouse.deltaZ = mouseEvent.deltaZ();
526}
527
528static NPCocoaEvent initializeMouseEvent(const WebMouseEvent& mouseEvent, const WebCore::IntPoint& eventPositionInPluginCoordinates)
529{
530    NPCocoaEventType eventType;
531
532    switch (mouseEvent.type()) {
533    case WebEvent::MouseDown:
534        eventType = NPCocoaEventMouseDown;
535        break;
536    case WebEvent::MouseUp:
537        eventType = NPCocoaEventMouseUp;
538        break;
539    case WebEvent::MouseMove:
540        if (mouseEvent.button() == WebMouseEvent::NoButton)
541            eventType = NPCocoaEventMouseMoved;
542        else
543            eventType = NPCocoaEventMouseDragged;
544        break;
545    default:
546        ASSERT_NOT_REACHED();
547        return NPCocoaEvent();
548    }
549
550    NPCocoaEvent event = initializeEvent(eventType);
551    fillInCocoaEventFromMouseEvent(event, mouseEvent, eventPositionInPluginCoordinates);
552    return event;
553}
554
555bool NetscapePlugin::platformHandleMouseEvent(const WebMouseEvent& mouseEvent)
556{
557    IntPoint eventPositionInPluginCoordinates;
558    if (!convertFromRootView(mouseEvent.position(), eventPositionInPluginCoordinates))
559        return true;
560
561    switch (m_eventModel) {
562        case NPEventModelCocoa: {
563            NPCocoaEvent event = initializeMouseEvent(mouseEvent, eventPositionInPluginCoordinates);
564
565            NPCocoaEvent* previousMouseEvent = m_currentMouseEvent;
566            m_currentMouseEvent = &event;
567
568            // Protect against NPP_HandleEvent causing the plug-in to be destroyed, since we
569            // access m_currentMouseEvent afterwards.
570            RefPtr<NetscapePlugin> protect(this);
571
572            NPP_HandleEvent(&event);
573
574            m_currentMouseEvent = previousMouseEvent;
575
576            // Some plug-ins return false even if the mouse event has been handled.
577            // This leads to bugs such as <rdar://problem/9167611>. Work around this
578            // by always returning true.
579            return true;
580        }
581
582#ifndef NP_NO_CARBON
583        case NPEventModelCarbon: {
584            EventKind eventKind = nullEvent;
585
586            switch (mouseEvent.type()) {
587            case WebEvent::MouseDown:
588                eventKind = mouseDown;
589                buttonStateFromLastMouseEvent |= (1 << buttonNumber(mouseEvent.button()));
590                break;
591            case WebEvent::MouseUp:
592                eventKind = mouseUp;
593                buttonStateFromLastMouseEvent &= ~(1 << buttonNumber(mouseEvent.button()));
594                break;
595            case WebEvent::MouseMove:
596                eventKind = nullEvent;
597                break;
598            default:
599                ASSERT_NOT_REACHED();
600            }
601
602            EventRecord event = initializeEventRecord(eventKind);
603            event.modifiers = modifiersForEvent(mouseEvent);
604
605            double globalX;
606            double globalY;
607            if (!convertPoint(eventPositionInPluginCoordinates.x(), eventPositionInPluginCoordinates.y(), NPCoordinateSpacePlugin, globalX, globalY, NPCoordinateSpaceFlippedScreen))
608                ASSERT_NOT_REACHED();
609
610            event.where.h = globalX;
611            event.where.v = globalY;
612
613            NPP_HandleEvent(&event);
614
615            // Some plug-ins return false even if the mouse event has been handled.
616            // This leads to bugs such as <rdar://problem/9167611>. Work around this
617            // by always returning true.
618            return true;
619        }
620#endif
621
622        default:
623            ASSERT_NOT_REACHED();
624    }
625
626    return false;
627}
628
629bool NetscapePlugin::platformHandleWheelEvent(const WebWheelEvent& wheelEvent)
630{
631    switch (m_eventModel) {
632        case NPEventModelCocoa: {
633            IntPoint eventPositionInPluginCoordinates;
634            if (!convertFromRootView(wheelEvent.position(), eventPositionInPluginCoordinates))
635                return true;
636
637            NPCocoaEvent event = initializeEvent(NPCocoaEventScrollWheel);
638
639            event.data.mouse.modifierFlags = modifierFlags(wheelEvent);
640            event.data.mouse.pluginX = eventPositionInPluginCoordinates.x();
641            event.data.mouse.pluginY = eventPositionInPluginCoordinates.y();
642            event.data.mouse.buttonNumber = 0;
643            event.data.mouse.clickCount = 0;
644            event.data.mouse.deltaX = wheelEvent.delta().width();
645            event.data.mouse.deltaY = wheelEvent.delta().height();
646            event.data.mouse.deltaZ = 0;
647            return NPP_HandleEvent(&event);
648        }
649
650#ifndef NP_NO_CARBON
651        case NPEventModelCarbon:
652            // Carbon doesn't have wheel events.
653            break;
654#endif
655
656        default:
657            ASSERT_NOT_REACHED();
658    }
659
660    return false;
661}
662
663bool NetscapePlugin::platformHandleMouseEnterEvent(const WebMouseEvent& mouseEvent)
664{
665    switch (m_eventModel) {
666        case NPEventModelCocoa: {
667            NPCocoaEvent event = initializeEvent(NPCocoaEventMouseEntered);
668
669            fillInCocoaEventFromMouseEvent(event, mouseEvent, IntPoint());
670            return NPP_HandleEvent(&event);
671        }
672
673#ifndef NP_NO_CARBON
674        case NPEventModelCarbon: {
675            EventRecord eventRecord = initializeEventRecord(NPEventType_AdjustCursorEvent);
676            eventRecord.modifiers = modifiersForEvent(mouseEvent);
677
678            return NPP_HandleEvent(&eventRecord);
679        }
680#endif
681
682        default:
683            ASSERT_NOT_REACHED();
684    }
685
686    return false;
687}
688
689bool NetscapePlugin::platformHandleMouseLeaveEvent(const WebMouseEvent& mouseEvent)
690{
691    switch (m_eventModel) {
692        case NPEventModelCocoa: {
693            NPCocoaEvent event = initializeEvent(NPCocoaEventMouseExited);
694
695            fillInCocoaEventFromMouseEvent(event, mouseEvent, IntPoint());
696            return NPP_HandleEvent(&event);
697        }
698
699#ifndef NP_NO_CARBON
700        case NPEventModelCarbon: {
701            EventRecord eventRecord = initializeEventRecord(NPEventType_AdjustCursorEvent);
702            eventRecord.modifiers = modifiersForEvent(mouseEvent);
703
704            return NPP_HandleEvent(&eventRecord);
705        }
706#endif
707
708        default:
709            ASSERT_NOT_REACHED();
710    }
711
712    return false;
713}
714
715static unsigned modifierFlags(const WebKeyboardEvent& keyboardEvent)
716{
717    unsigned modifierFlags = 0;
718
719    if (keyboardEvent.capsLockKey())
720        modifierFlags |= NSAlphaShiftKeyMask;
721    if (keyboardEvent.shiftKey())
722        modifierFlags |= NSShiftKeyMask;
723    if (keyboardEvent.controlKey())
724        modifierFlags |= NSControlKeyMask;
725    if (keyboardEvent.altKey())
726        modifierFlags |= NSAlternateKeyMask;
727    if (keyboardEvent.metaKey())
728        modifierFlags |= NSCommandKeyMask;
729
730    return modifierFlags;
731}
732
733static bool isFlagsChangedEvent(const WebKeyboardEvent& keyboardEvent)
734{
735    switch (keyboardEvent.nativeVirtualKeyCode()) {
736    case 54: // Right Command
737    case 55: // Left Command
738
739    case 57: // Capslock
740
741    case 56: // Left Shift
742    case 60: // Right Shift
743
744    case 58: // Left Alt
745    case 61: // Right Alt
746
747    case 59: // Left Ctrl
748    case 62: // Right Ctrl
749        return true;
750    }
751
752    return false;
753}
754
755static NPCocoaEvent initializeKeyboardEvent(const WebKeyboardEvent& keyboardEvent)
756{
757    NPCocoaEventType eventType;
758
759    if (isFlagsChangedEvent(keyboardEvent))
760        eventType = NPCocoaEventFlagsChanged;
761    else {
762        switch (keyboardEvent.type()) {
763            case WebEvent::KeyDown:
764                eventType = NPCocoaEventKeyDown;
765                break;
766            case WebEvent::KeyUp:
767                eventType = NPCocoaEventKeyUp;
768                break;
769            default:
770                ASSERT_NOT_REACHED();
771                return NPCocoaEvent();
772        }
773    }
774
775    NPCocoaEvent event = initializeEvent(eventType);
776    event.data.key.modifierFlags = modifierFlags(keyboardEvent);
777    event.data.key.characters = reinterpret_cast<NPNSString*>(static_cast<NSString*>(keyboardEvent.text()));
778    event.data.key.charactersIgnoringModifiers = reinterpret_cast<NPNSString*>(static_cast<NSString*>(keyboardEvent.unmodifiedText()));
779    event.data.key.isARepeat = keyboardEvent.isAutoRepeat();
780    event.data.key.keyCode = keyboardEvent.nativeVirtualKeyCode();
781
782    return event;
783}
784
785bool NetscapePlugin::platformHandleKeyboardEvent(const WebKeyboardEvent& keyboardEvent)
786{
787    bool handled = false;
788
789    switch (m_eventModel) {
790    case NPEventModelCocoa: {
791        if (keyboardEvent.type() == WebEvent::KeyDown) {
792            m_hasHandledAKeyDownEvent = true;
793
794            if (!m_pluginWantsLegacyCocoaTextInput && m_isComplexTextInputEnabled && !keyboardEvent.isAutoRepeat()) {
795                // When complex text is enabled in the new model, the plug-in should never
796                // receive any key down or key up events until the composition is complete.
797                m_ignoreNextKeyUpEventCounter++;
798                return true;
799            }
800        } else if (keyboardEvent.type() == WebEvent::KeyUp && m_ignoreNextKeyUpEventCounter) {
801            m_ignoreNextKeyUpEventCounter--;
802            return true;
803        }
804
805        NPCocoaEvent event = initializeKeyboardEvent(keyboardEvent);
806        int16_t returnValue = NPP_HandleEvent(&event);
807        handled = returnValue;
808
809        if (!m_pluginWantsLegacyCocoaTextInput) {
810            if (event.type == NPCocoaEventKeyDown && returnValue == kNPEventStartIME) {
811                if (!keyboardEvent.isAutoRepeat())
812                    m_ignoreNextKeyUpEventCounter++;
813                setComplexTextInputEnabled(true);
814            }
815        }
816
817        break;
818    }
819
820#ifndef NP_NO_CARBON
821    case NPEventModelCarbon: {
822        EventKind eventKind = nullEvent;
823
824        switch (keyboardEvent.type()) {
825        case WebEvent::KeyDown:
826            eventKind = keyboardEvent.isAutoRepeat() ? autoKey : keyDown;
827            break;
828        case WebEvent::KeyUp:
829            eventKind = keyUp;
830            break;
831        default:
832            ASSERT_NOT_REACHED();
833        }
834
835        EventRecord event = initializeEventRecord(eventKind);
836        event.modifiers = modifiersForEvent(keyboardEvent);
837        event.message = keyboardEvent.nativeVirtualKeyCode() << 8 | keyboardEvent.macCharCode();
838        handled = NPP_HandleEvent(&event);
839        break;
840    }
841#endif
842
843    default:
844        ASSERT_NOT_REACHED();
845    }
846
847    // Most plug-ins simply return true for all keyboard events, even those that aren't handled.
848    // This leads to bugs such as <rdar://problem/8740926>. We work around this by returning false
849    // if the keyboard event has the command modifier pressed.
850    // However, for command-A (the shortcurt for 'Select All' we will always return true, since we don't
851    // want the entire page to be selected if the focus is in a plug-in text field (see <rdar://problem/9309903>).
852    if (keyboardEvent.metaKey()) {
853        if (keyboardEvent.text() == "a")
854            return true;
855
856        return false;
857    }
858
859    return handled;
860}
861
862void NetscapePlugin::platformSetFocus(bool hasFocus)
863{
864    m_pluginHasFocus = hasFocus;
865    pluginFocusOrWindowFocusChanged();
866
867    switch (m_eventModel) {
868        case NPEventModelCocoa: {
869            NPCocoaEvent event = initializeEvent(NPCocoaEventFocusChanged);
870
871            event.data.focus.hasFocus = hasFocus;
872            NPP_HandleEvent(&event);
873            break;
874        }
875
876#ifndef NP_NO_CARBON
877        case NPEventModelCarbon: {
878            EventRecord event = initializeEventRecord(hasFocus ? NPEventType_GetFocusEvent : NPEventType_LoseFocusEvent);
879
880            NPP_HandleEvent(&event);
881            break;
882        }
883#endif
884
885        default:
886            ASSERT_NOT_REACHED();
887    }
888}
889
890bool NetscapePlugin::wantsPluginRelativeNPWindowCoordinates()
891{
892    return true;
893}
894
895void NetscapePlugin::windowFocusChanged(bool hasFocus)
896{
897    m_windowHasFocus = hasFocus;
898    pluginFocusOrWindowFocusChanged();
899
900    switch (m_eventModel) {
901        case NPEventModelCocoa: {
902            NPCocoaEvent event = initializeEvent(NPCocoaEventWindowFocusChanged);
903
904            event.data.focus.hasFocus = hasFocus;
905            NPP_HandleEvent(&event);
906            break;
907        }
908
909#ifndef NP_NO_CARBON
910        case NPEventModelCarbon: {
911            HiliteWindow(windowRef(), hasFocus);
912            if (hasFocus)
913                SetUserFocusWindow(windowRef());
914
915            EventRecord event = initializeEventRecord(activateEvt);
916            event.message = reinterpret_cast<unsigned long>(windowRef());
917            if (hasFocus)
918                event.modifiers |= activeFlag;
919
920            NPP_HandleEvent(&event);
921            break;
922        }
923#endif
924
925        default:
926            ASSERT_NOT_REACHED();
927    }
928}
929
930void NetscapePlugin::windowAndViewFramesChanged(const IntRect& windowFrameInScreenCoordinates, const IntRect& viewFrameInWindowCoordinates)
931{
932    m_windowFrameInScreenCoordinates = windowFrameInScreenCoordinates;
933    m_viewFrameInWindowCoordinates = viewFrameInWindowCoordinates;
934
935    switch (m_eventModel) {
936        case NPEventModelCocoa:
937            // Nothing to do.
938            break;
939
940#ifndef NP_NO_CARBON
941        case NPEventModelCarbon:
942            updateFakeWindowBounds();
943            break;
944#endif
945
946        default:
947            ASSERT_NOT_REACHED();
948    }
949}
950
951void NetscapePlugin::windowVisibilityChanged(bool visible)
952{
953    if (visible)
954        callSetWindow();
955    else
956        callSetWindowInvisible();
957}
958
959uint64_t NetscapePlugin::pluginComplexTextInputIdentifier() const
960{
961    // Just return a dummy value; this is only called for in-process plug-ins, which we don't support on Mac.
962    return static_cast<uint64_t>(reinterpret_cast<uintptr_t>(this));
963}
964
965
966#ifndef NP_NO_CARBON
967static bool convertStringToKeyCodes(const String& string, ScriptCode scriptCode, Vector<UInt8>& keyCodes)
968{
969    // Create the mapping.
970    UnicodeMapping mapping;
971
972    if (GetTextEncodingFromScriptInfo(scriptCode, kTextLanguageDontCare, kTextRegionDontCare, &mapping.otherEncoding) != noErr)
973        return false;
974
975    mapping.unicodeEncoding = CreateTextEncoding(kTextEncodingUnicodeDefault, kTextEncodingDefaultVariant, kTextEncodingDefaultFormat);
976    mapping.mappingVersion = kUnicodeUseLatestMapping;
977
978    // Create the converter
979    UnicodeToTextInfo textInfo;
980
981    if (CreateUnicodeToTextInfo(&mapping, &textInfo) != noErr)
982        return false;
983
984    ByteCount inputLength = string.length() * sizeof(UniChar);
985    ByteCount inputRead;
986    ByteCount outputLength;
987    ByteCount maxOutputLength = string.length() * sizeof(UniChar);
988
989    Vector<UInt8> outputData(maxOutputLength);
990    OSStatus status = ConvertFromUnicodeToText(textInfo, inputLength, string.characters(), kNilOptions, 0, 0, 0, 0, maxOutputLength, &inputRead, &outputLength, outputData.data());
991
992    DisposeUnicodeToTextInfo(&textInfo);
993
994    if (status != noErr)
995        return false;
996
997    outputData.swap(keyCodes);
998    return true;
999}
1000#endif
1001
1002void NetscapePlugin::sendComplexTextInput(const String& textInput)
1003{
1004    if (!m_pluginWantsLegacyCocoaTextInput) {
1005        // In the updated Cocoa text input spec, text input is disabled when the text input string has been sent
1006        // by the UI process. Since the UI process has also updated its state, we can just reset the variable here
1007        // instead of calling setComplexTextInputEnabled.
1008        m_isComplexTextInputEnabled = false;
1009
1010        // The UI process can also disable text input by sending an empty input string. In this case, we don't want
1011        // to send it to the plug-in.
1012        if (textInput.isNull())
1013            return;
1014    }
1015
1016    switch (m_eventModel) {
1017    case NPEventModelCocoa: {
1018        NPCocoaEvent event = initializeEvent(NPCocoaEventTextInput);
1019        event.data.text.text = reinterpret_cast<NPNSString*>(static_cast<NSString*>(textInput));
1020        NPP_HandleEvent(&event);
1021        break;
1022    }
1023#ifndef NP_NO_CARBON
1024    case NPEventModelCarbon: {
1025        ScriptCode scriptCode = WKGetScriptCodeFromCurrentKeyboardInputSource();
1026        Vector<UInt8> keyCodes;
1027
1028        if (!convertStringToKeyCodes(textInput, scriptCode, keyCodes))
1029            return;
1030
1031        // Set the script code as the keyboard script. Normally Carbon does this whenever the input source changes.
1032        // However, this is only done for the process that has the keyboard focus. We cheat and do it here instead.
1033        SetScriptManagerVariable(smKeyScript, scriptCode);
1034
1035        EventRecord event = initializeEventRecord(keyDown);
1036        event.modifiers = 0;
1037
1038        for (size_t i = 0; i < keyCodes.size(); i++) {
1039            event.message = keyCodes[i];
1040            NPP_HandleEvent(&event);
1041        }
1042        break;
1043    }
1044#endif
1045    default:
1046        ASSERT_NOT_REACHED();
1047    }
1048}
1049
1050void NetscapePlugin::setLayerHostingMode(LayerHostingMode layerHostingMode)
1051{
1052    m_layerHostingMode = layerHostingMode;
1053
1054    // Tell the plug-in about the new compositing render server port. If it returns OK we'll ask it again for a new layer.
1055    mach_port_t port = NetscapePlugin::compositingRenderServerPort();
1056    if (NPP_SetValue(static_cast<NPNVariable>(WKNVCALayerRenderServerPort), &port) != NPERR_NO_ERROR)
1057        return;
1058
1059    m_pluginLayer = nullptr;
1060    updatePluginLayer();
1061}
1062
1063void NetscapePlugin::pluginFocusOrWindowFocusChanged()
1064{
1065    bool pluginHasFocusAndWindowHasFocus = m_pluginHasFocus && m_windowHasFocus;
1066
1067    controller()->pluginFocusOrWindowFocusChanged(pluginHasFocusAndWindowHasFocus);
1068
1069    // In the updated Cocoa text input spec, the plug-in will enable complex text input
1070    // by returning kNPEventStartIME from it's NPCocoaEventKeyDown handler.
1071    if (!m_pluginWantsLegacyCocoaTextInput)
1072        return;
1073
1074    // In the old model, if the plug-in is focused, enable complex text input.
1075    setComplexTextInputEnabled(pluginHasFocusAndWindowHasFocus);
1076}
1077
1078void NetscapePlugin::setComplexTextInputEnabled(bool complexTextInputEnabled)
1079{
1080    if (m_isComplexTextInputEnabled == complexTextInputEnabled)
1081        return;
1082
1083    m_isComplexTextInputEnabled = complexTextInputEnabled;
1084
1085    PluginComplexTextInputState complexTextInputState = PluginComplexTextInputDisabled;
1086    if (m_isComplexTextInputEnabled)
1087        complexTextInputState = m_pluginWantsLegacyCocoaTextInput ? PluginComplexTextInputEnabledLegacy : PluginComplexTextInputEnabled;
1088
1089    controller()->setComplexTextInputState(complexTextInputState);
1090}
1091
1092PlatformLayer* NetscapePlugin::pluginLayer()
1093{
1094    return static_cast<PlatformLayer*>(m_pluginLayer.get());
1095}
1096
1097static void makeCGLPresentLayerOpaque(CALayer *pluginRootLayer)
1098{
1099    // We look for a layer that's the only sublayer of the root layer that is an instance
1100    // of the CGLPresentLayer class which in turn is a subclass of CAOpenGLLayer and make
1101    // it opaque if all these conditions hold.
1102
1103    NSArray *sublayers = [pluginRootLayer sublayers];
1104    if ([sublayers count] != 1)
1105        return;
1106
1107    Class cglPresentLayerClass = NSClassFromString(@"CGLPresentLayer");
1108    if (![cglPresentLayerClass isSubclassOfClass:[CAOpenGLLayer class]])
1109        return;
1110
1111    CALayer *layer = [sublayers objectAtIndex:0];
1112    if (![layer isKindOfClass:cglPresentLayerClass])
1113        return;
1114
1115    [layer setOpaque:YES];
1116}
1117
1118void NetscapePlugin::updatePluginLayer()
1119{
1120    if (m_drawingModel != NPDrawingModelCoreAnimation)
1121        return;
1122
1123    void* value = 0;
1124
1125    // Get the Core Animation layer.
1126    if (NPP_GetValue(NPPVpluginCoreAnimationLayer, &value) != NPERR_NO_ERROR)
1127        return;
1128
1129    if (!value)
1130        return;
1131
1132    ASSERT(!m_pluginLayer);
1133
1134    // The original Core Animation drawing model required that plug-ins pass a retained layer
1135    // to the browser, which the browser would then adopt. However, the final spec changed this
1136    // (See https://wiki.mozilla.org/NPAPI:CoreAnimationDrawingModel for more information)
1137    // after a version of WebKit1 with the original implementation had shipped, but that now means
1138    // that any plug-ins that expect the WebKit1 behavior would leak the CALayer.
1139    // For plug-ins that we know return retained layers, we have the ReturnsRetainedCoreAnimationLayer
1140    // plug-in quirk. Plug-ins can also check for whether the browser expects a non-retained layer to
1141    // be returned by using NPN_GetValue and pass the WKNVExpectsNonretainedLayer parameter.
1142    // https://bugs.webkit.org/show_bug.cgi?id=58282 describes the bug where WebKit expects retained layers.
1143    if (m_pluginReturnsNonretainedLayer)
1144        m_pluginLayer = reinterpret_cast<CALayer *>(value);
1145    else
1146        m_pluginLayer = adoptNS(reinterpret_cast<CALayer *>(value));
1147
1148    if (m_pluginModule->pluginQuirks().contains(PluginQuirks::MakeOpaqueUnlessTransparentSilverlightBackgroundAttributeExists) &&
1149        !m_isTransparent)
1150        makeCGLPresentLayerOpaque(m_pluginLayer.get());
1151}
1152
1153#ifndef NP_NO_CARBON
1154void NetscapePlugin::nullEventTimerFired()
1155{
1156    EventRecord event = initializeEventRecord(nullEvent);
1157
1158    event.message = 0;
1159    CGPoint mousePosition;
1160    HIGetMousePosition(kHICoordSpaceScreenPixel, 0, &mousePosition);
1161    event.where.h = mousePosition.x;
1162    event.where.v = mousePosition.y;
1163
1164    event.modifiers = GetCurrentKeyModifiers();
1165    if (!Button())
1166        event.modifiers |= btnState;
1167
1168    NPP_HandleEvent(&event);
1169}
1170#endif
1171
1172} // namespace WebKit
1173
1174#endif // ENABLE(NETSCAPE_PLUGIN_API)
1175