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#import "PluginController.h"
30#import "WebEvent.h"
31#import <WebCore/GraphicsContext.h>
32#import <Carbon/Carbon.h>
33#import <WebKitSystemInterface.h>
34
35using namespace WebCore;
36
37namespace WebKit {
38
39#ifndef NP_NO_CARBON
40static const double nullEventIntervalActive = 0.02;
41static const double nullEventIntervalNotActive = 0.25;
42
43static unsigned buttonStateFromLastMouseEvent;
44
45#endif
46
47NPError NetscapePlugin::setDrawingModel(NPDrawingModel drawingModel)
48{
49    // The drawing model can only be set from NPP_New.
50    if (!m_inNPPNew)
51        return NPERR_GENERIC_ERROR;
52
53    switch (drawingModel) {
54#ifndef NP_NO_QUICKDRAW
55        case NPDrawingModelQuickDraw:
56#endif
57        case NPDrawingModelCoreGraphics:
58        case NPDrawingModelCoreAnimation:
59            m_drawingModel = drawingModel;
60            break;
61
62        default:
63            return NPERR_GENERIC_ERROR;
64    }
65
66    return NPERR_NO_ERROR;
67}
68
69NPError NetscapePlugin::setEventModel(NPEventModel eventModel)
70{
71    // The event model can only be set from NPP_New.
72    if (!m_inNPPNew)
73        return NPERR_GENERIC_ERROR;
74
75    switch (eventModel) {
76#ifndef NP_NO_CARBON
77        case NPEventModelCarbon:
78#endif
79        case NPEventModelCocoa:
80            m_eventModel = eventModel;
81            break;
82
83        default:
84            return NPERR_GENERIC_ERROR;
85    }
86
87    return NPERR_NO_ERROR;
88}
89
90static double flipScreenYCoordinate(double y)
91{
92    return [[[NSScreen screens] objectAtIndex:0] frame].size.height - y;
93}
94
95NPBool NetscapePlugin::convertPoint(double sourceX, double sourceY, NPCoordinateSpace sourceSpace, double& destX, double& destY, NPCoordinateSpace destSpace)
96{
97    if (sourceSpace == destSpace) {
98        destX = sourceX;
99        destY = sourceY;
100        return true;
101    }
102
103    double sourceXInScreenSpace;
104    double sourceYInScreenSpace;
105
106    FloatPoint sourceInScreenSpace;
107    switch (sourceSpace) {
108    case NPCoordinateSpacePlugin:
109        sourceXInScreenSpace = sourceX + m_windowFrameInScreenCoordinates.x() + m_viewFrameInWindowCoordinates.x() + m_npWindow.x;
110        sourceYInScreenSpace = m_windowFrameInScreenCoordinates.y() + m_viewFrameInWindowCoordinates.y() + m_viewFrameInWindowCoordinates.height() - (sourceY + m_npWindow.y);
111        break;
112    case NPCoordinateSpaceWindow:
113        sourceXInScreenSpace = sourceX + m_windowFrameInScreenCoordinates.x();
114        sourceYInScreenSpace = sourceY + m_windowFrameInScreenCoordinates.y();
115        break;
116    case NPCoordinateSpaceFlippedWindow:
117        sourceXInScreenSpace = sourceX + m_windowFrameInScreenCoordinates.x();
118        sourceYInScreenSpace = m_windowFrameInScreenCoordinates.y() + m_windowFrameInScreenCoordinates.height() - sourceY;
119        break;
120    case NPCoordinateSpaceScreen:
121        sourceXInScreenSpace = sourceX;
122        sourceYInScreenSpace = sourceY;
123        break;
124    case NPCoordinateSpaceFlippedScreen:
125        sourceXInScreenSpace = sourceX;
126        sourceYInScreenSpace = flipScreenYCoordinate(sourceY);
127        break;
128    default:
129        return false;
130    }
131
132    // Now convert back.
133    switch (destSpace) {
134    case NPCoordinateSpacePlugin:
135        destX = sourceXInScreenSpace - (m_windowFrameInScreenCoordinates.x() + m_viewFrameInWindowCoordinates.x() + m_npWindow.x);
136        destY = m_windowFrameInScreenCoordinates.y() + m_viewFrameInWindowCoordinates.y() + m_viewFrameInWindowCoordinates.height() - (sourceYInScreenSpace + m_npWindow.y);
137        break;
138    case NPCoordinateSpaceWindow:
139        destX = sourceXInScreenSpace - m_windowFrameInScreenCoordinates.x();
140        destY = sourceYInScreenSpace - m_windowFrameInScreenCoordinates.y();
141        break;
142    case NPCoordinateSpaceFlippedWindow:
143        destX = sourceXInScreenSpace - m_windowFrameInScreenCoordinates.x();
144        destY = sourceYInScreenSpace - m_windowFrameInScreenCoordinates.y();
145        destY = m_windowFrameInScreenCoordinates.height() - destY;
146        break;
147    case NPCoordinateSpaceScreen:
148        destX = sourceXInScreenSpace;
149        destY = sourceYInScreenSpace;
150        break;
151    case NPCoordinateSpaceFlippedScreen:
152        destX = sourceXInScreenSpace;
153        destY = flipScreenYCoordinate(sourceYInScreenSpace);
154        break;
155    default:
156        return false;
157    }
158
159    return true;
160}
161
162
163NPError NetscapePlugin::popUpContextMenu(NPMenu* npMenu)
164{
165    if (!m_currentMouseEvent)
166        return NPERR_GENERIC_ERROR;
167
168    double screenX, screenY;
169    if (!convertPoint(m_currentMouseEvent->data.mouse.pluginX, m_currentMouseEvent->data.mouse.pluginY, NPCoordinateSpacePlugin, screenX, screenY, NPCoordinateSpaceScreen))
170        ASSERT_NOT_REACHED();
171
172    WKPopupContextMenu(reinterpret_cast<NSMenu *>(npMenu), NSMakePoint(screenX, screenY));
173    return NPERR_NO_ERROR;
174}
175
176mach_port_t NetscapePlugin::compositingRenderServerPort()
177{
178    return m_pluginController->compositingRenderServerPort();
179}
180
181#ifndef NP_NO_CARBON
182typedef HashMap<WindowRef, NetscapePlugin*> WindowMap;
183
184static WindowMap& windowMap()
185{
186    DEFINE_STATIC_LOCAL(WindowMap, windowMap, ());
187
188    return windowMap;
189}
190#endif
191
192bool NetscapePlugin::platformPostInitialize()
193{
194    if (m_drawingModel == static_cast<NPDrawingModel>(-1)) {
195#ifndef NP_NO_QUICKDRAW
196        // Default to QuickDraw if the plugin did not specify a drawing model.
197        m_drawingModel = NPDrawingModelQuickDraw;
198#else
199        // QuickDraw is not available, so we can't default to it. Instead, default to CoreGraphics.
200        m_drawingModel = NPDrawingModelCoreGraphics;
201#endif
202    }
203
204    if (m_eventModel == static_cast<NPEventModel>(-1)) {
205        // If the plug-in did not specify a drawing model we default to Carbon when it is available.
206#ifndef NP_NO_CARBON
207        m_eventModel = NPEventModelCarbon;
208#else
209        m_eventModel = NPEventModelCocoa;
210#endif // NP_NO_CARBON
211    }
212
213#if !defined(NP_NO_CARBON) && !defined(NP_NO_QUICKDRAW)
214    // The CA drawing model does not work with the Carbon event model.
215    if (m_drawingModel == NPDrawingModelCoreAnimation && m_eventModel == NPEventModelCarbon)
216        return false;
217
218    // The Cocoa event model does not work with the QuickDraw drawing model.
219    if (m_eventModel == NPEventModelCocoa && m_drawingModel == NPDrawingModelQuickDraw)
220        return false;
221#endif
222
223#ifndef NP_NO_QUICKDRAW
224    // Right now we don't support the QuickDraw drawing model at all
225    if (m_drawingModel == NPDrawingModelQuickDraw &&
226        !m_pluginModule->pluginQuirks().contains(PluginQuirks::AllowHalfBakedQuickDrawSupport))
227        return false;
228#endif
229
230    if (m_drawingModel == NPDrawingModelCoreAnimation) {
231        void* value = 0;
232        // Get the Core Animation layer.
233        if (NPP_GetValue(NPPVpluginCoreAnimationLayer, &value) == NPERR_NO_ERROR && value) {
234            ASSERT(!m_pluginLayer);
235            m_pluginLayer = reinterpret_cast<CALayer *>(value);
236        }
237    }
238
239#ifndef NP_NO_CARBON
240    if (m_eventModel == NPEventModelCarbon) {
241        // Initialize the fake Carbon window.
242        ::Rect bounds = { 0, 0, 0, 0 };
243        CreateNewWindow(kDocumentWindowClass, kWindowNoTitleBarAttribute, &bounds, reinterpret_cast<WindowRef*>(&m_npCGContext.window));
244        ASSERT(m_npCGContext.window);
245
246        // FIXME: Disable the backing store.
247
248        m_npWindow.window = &m_npCGContext;
249
250        ASSERT(!windowMap().contains(windowRef()));
251        windowMap().set(windowRef(), this);
252
253        // Start the null event timer.
254        // FIXME: Throttle null events when the plug-in isn't visible on screen.
255        m_nullEventTimer.startRepeating(nullEventIntervalActive);
256    }
257#endif
258
259    return true;
260}
261
262void NetscapePlugin::platformDestroy()
263{
264#ifndef NP_NO_CARBON
265    if (m_eventModel == NPEventModelCarbon) {
266        if (WindowRef window = windowRef()) {
267            // Destroy the fake Carbon window.
268            DisposeWindow(window);
269
270            ASSERT(windowMap().contains(window));
271            windowMap().remove(window);
272        }
273
274        // Stop the null event timer.
275        m_nullEventTimer.stop();
276    }
277#endif
278}
279
280bool NetscapePlugin::platformInvalidate(const IntRect&)
281{
282    // NPN_InvalidateRect is just a no-op in the Core Animation drawing model.
283    if (m_drawingModel == NPDrawingModelCoreAnimation)
284        return true;
285
286    return false;
287}
288
289void NetscapePlugin::platformGeometryDidChange()
290{
291}
292
293static inline NPCocoaEvent initializeEvent(NPCocoaEventType type)
294{
295    NPCocoaEvent event;
296
297    event.type = type;
298    event.version = 0;
299
300    return event;
301}
302
303#ifndef NP_NO_CARBON
304NetscapePlugin* NetscapePlugin::netscapePluginFromWindow(WindowRef windowRef)
305{
306    return windowMap().get(windowRef);
307}
308
309WindowRef NetscapePlugin::windowRef() const
310{
311    ASSERT(m_eventModel == NPEventModelCarbon);
312
313    return reinterpret_cast<WindowRef>(m_npCGContext.window);
314}
315
316unsigned NetscapePlugin::buttonState()
317{
318    return buttonStateFromLastMouseEvent;
319}
320
321static inline EventRecord initializeEventRecord(EventKind eventKind)
322{
323    EventRecord eventRecord;
324
325    eventRecord.what = eventKind;
326    eventRecord.message = 0;
327    eventRecord.when = TickCount();
328    eventRecord.where = Point();
329    eventRecord.modifiers = 0;
330
331    return eventRecord;
332}
333
334static bool anyMouseButtonIsDown(const WebEvent& event)
335{
336    if (event.type() == WebEvent::MouseDown)
337        return true;
338
339    if (event.type() == WebEvent::MouseMove && static_cast<const WebMouseEvent&>(event).button() != WebMouseEvent::NoButton)
340        return true;
341
342    return false;
343}
344
345static bool rightMouseButtonIsDown(const WebEvent& event)
346{
347    if (event.type() == WebEvent::MouseDown && static_cast<const WebMouseEvent&>(event).button() == WebMouseEvent::RightButton)
348        return true;
349
350    if (event.type() == WebEvent::MouseMove && static_cast<const WebMouseEvent&>(event).button() == WebMouseEvent::RightButton)
351        return true;
352
353    return false;
354}
355
356static EventModifiers modifiersForEvent(const WebEvent& event)
357{
358    EventModifiers modifiers = 0;
359
360    // We only want to set the btnState if a mouse button is _not_ down.
361    if (!anyMouseButtonIsDown(event))
362        modifiers |= btnState;
363
364    if (event.metaKey())
365        modifiers |= cmdKey;
366
367    if (event.shiftKey())
368        modifiers |= shiftKey;
369
370    if (event.altKey())
371        modifiers |= optionKey;
372
373    // Set controlKey if the control key is down or the right mouse button is down.
374    if (event.controlKey() || rightMouseButtonIsDown(event))
375        modifiers |= controlKey;
376
377    return modifiers;
378}
379
380#endif
381
382void NetscapePlugin::platformPaint(GraphicsContext* context, const IntRect& dirtyRect, bool isSnapshot)
383{
384    CGContextRef platformContext = context->platformContext();
385
386    // Translate the context so that the origin is at the top left corner of the plug-in view.
387    context->translate(m_frameRect.x(), m_frameRect.y());
388
389    switch (m_eventModel) {
390        case NPEventModelCocoa: {
391            // Don't send draw events when we're using the Core Animation drawing model.
392            if (!isSnapshot && m_drawingModel == NPDrawingModelCoreAnimation)
393                return;
394
395            NPCocoaEvent event = initializeEvent(NPCocoaEventDrawRect);
396
397            event.data.draw.context = platformContext;
398            event.data.draw.x = dirtyRect.x() - m_frameRect.x();
399            event.data.draw.y = dirtyRect.y() - m_frameRect.y();
400            event.data.draw.width = dirtyRect.width();
401            event.data.draw.height = dirtyRect.height();
402
403            NPP_HandleEvent(&event);
404            break;
405        }
406
407#ifndef NP_NO_CARBON
408        case NPEventModelCarbon: {
409            if (platformContext != m_npCGContext.context) {
410                m_npCGContext.context = platformContext;
411                callSetWindow();
412            }
413
414            EventRecord event = initializeEventRecord(updateEvt);
415            event.message = reinterpret_cast<unsigned long>(windowRef());
416
417            NPP_HandleEvent(&event);
418            break;
419        }
420#endif
421
422        default:
423            ASSERT_NOT_REACHED();
424    }
425}
426
427static uint32_t modifierFlags(const WebEvent& event)
428{
429    uint32_t modifiers = 0;
430
431    if (event.shiftKey())
432        modifiers |= NSShiftKeyMask;
433    if (event.controlKey())
434        modifiers |= NSControlKeyMask;
435    if (event.altKey())
436        modifiers |= NSAlternateKeyMask;
437    if (event.metaKey())
438        modifiers |= NSCommandKeyMask;
439
440    return modifiers;
441}
442
443static int32_t buttonNumber(WebMouseEvent::Button button)
444{
445    switch (button) {
446    case WebMouseEvent::NoButton:
447    case WebMouseEvent::LeftButton:
448        return 0;
449    case WebMouseEvent::RightButton:
450        return 1;
451    case WebMouseEvent::MiddleButton:
452        return 2;
453    }
454
455    ASSERT_NOT_REACHED();
456    return -1;
457}
458
459static void fillInCocoaEventFromMouseEvent(NPCocoaEvent& event, const WebMouseEvent& mouseEvent, const WebCore::IntPoint& pluginLocation)
460{
461    event.data.mouse.modifierFlags = modifierFlags(mouseEvent);
462    event.data.mouse.pluginX = mouseEvent.position().x() - pluginLocation.x();
463    event.data.mouse.pluginY = mouseEvent.position().y() - pluginLocation.y();
464    event.data.mouse.buttonNumber = buttonNumber(mouseEvent.button());
465    event.data.mouse.clickCount = mouseEvent.clickCount();
466    event.data.mouse.deltaX = mouseEvent.deltaX();
467    event.data.mouse.deltaY = mouseEvent.deltaY();
468    event.data.mouse.deltaZ = mouseEvent.deltaZ();
469}
470
471static NPCocoaEvent initializeMouseEvent(const WebMouseEvent& mouseEvent, const WebCore::IntPoint& pluginLocation)
472{
473    NPCocoaEventType eventType;
474
475    switch (mouseEvent.type()) {
476    case WebEvent::MouseDown:
477        eventType = NPCocoaEventMouseDown;
478        break;
479    case WebEvent::MouseUp:
480        eventType = NPCocoaEventMouseUp;
481        break;
482    case WebEvent::MouseMove:
483        if (mouseEvent.button() == WebMouseEvent::NoButton)
484            eventType = NPCocoaEventMouseMoved;
485        else
486            eventType = NPCocoaEventMouseDragged;
487        break;
488    default:
489        ASSERT_NOT_REACHED();
490        return NPCocoaEvent();
491    }
492
493    NPCocoaEvent event = initializeEvent(eventType);
494    fillInCocoaEventFromMouseEvent(event, mouseEvent, pluginLocation);
495    return event;
496}
497
498bool NetscapePlugin::platformHandleMouseEvent(const WebMouseEvent& mouseEvent)
499{
500    switch (m_eventModel) {
501        case NPEventModelCocoa: {
502            NPCocoaEvent event = initializeMouseEvent(mouseEvent, m_frameRect.location());
503
504            NPCocoaEvent* previousMouseEvent = m_currentMouseEvent;
505            m_currentMouseEvent = &event;
506
507            // Protect against NPP_HandleEvent causing the plug-in to be destroyed, since we
508            // access m_currentMouseEvent afterwards.
509            RefPtr<NetscapePlugin> protect(this);
510
511            NPP_HandleEvent(&event);
512
513            m_currentMouseEvent = previousMouseEvent;
514
515            // Some plug-ins return false even if the mouse event has been handled.
516            // This leads to bugs such as <rdar://problem/9167611>. Work around this
517            // by always returning true.
518            return true;
519        }
520
521#ifndef NP_NO_CARBON
522        case NPEventModelCarbon: {
523            EventKind eventKind = nullEvent;
524
525            switch (mouseEvent.type()) {
526            case WebEvent::MouseDown:
527                eventKind = mouseDown;
528                buttonStateFromLastMouseEvent |= (1 << buttonNumber(mouseEvent.button()));
529                break;
530            case WebEvent::MouseUp:
531                eventKind = mouseUp;
532                buttonStateFromLastMouseEvent &= ~(1 << buttonNumber(mouseEvent.button()));
533                break;
534            case WebEvent::MouseMove:
535                eventKind = nullEvent;
536                break;
537            default:
538                ASSERT_NOT_REACHED();
539            }
540
541            EventRecord event = initializeEventRecord(eventKind);
542            event.modifiers = modifiersForEvent(mouseEvent);
543            event.where.h = mouseEvent.globalPosition().x();
544            event.where.v = mouseEvent.globalPosition().y();
545
546            NPP_HandleEvent(&event);
547
548            // Some plug-ins return false even if the mouse event has been handled.
549            // This leads to bugs such as <rdar://problem/9167611>. Work around this
550            // by always returning true.
551            return true;
552        }
553#endif
554
555        default:
556            ASSERT_NOT_REACHED();
557    }
558
559    return false;
560}
561
562bool NetscapePlugin::platformHandleWheelEvent(const WebWheelEvent& wheelEvent)
563{
564    switch (m_eventModel) {
565        case NPEventModelCocoa: {
566            NPCocoaEvent event = initializeEvent(NPCocoaEventScrollWheel);
567
568            event.data.mouse.modifierFlags = modifierFlags(wheelEvent);
569            event.data.mouse.pluginX = wheelEvent.position().x() - m_frameRect.x();
570            event.data.mouse.pluginY = wheelEvent.position().y() - m_frameRect.y();
571            event.data.mouse.buttonNumber = 0;
572            event.data.mouse.clickCount = 0;
573            event.data.mouse.deltaX = wheelEvent.delta().width();
574            event.data.mouse.deltaY = wheelEvent.delta().height();
575            event.data.mouse.deltaZ = 0;
576            return NPP_HandleEvent(&event);
577        }
578
579#ifndef NP_NO_CARBON
580        case NPEventModelCarbon:
581            // Carbon doesn't have wheel events.
582            break;
583#endif
584
585        default:
586            ASSERT_NOT_REACHED();
587    }
588
589    return false;
590}
591
592bool NetscapePlugin::platformHandleMouseEnterEvent(const WebMouseEvent& mouseEvent)
593{
594    switch (m_eventModel) {
595        case NPEventModelCocoa: {
596            NPCocoaEvent event = initializeEvent(NPCocoaEventMouseEntered);
597
598            fillInCocoaEventFromMouseEvent(event, mouseEvent, m_frameRect.location());
599            return NPP_HandleEvent(&event);
600        }
601
602#ifndef NP_NO_CARBON
603        case NPEventModelCarbon: {
604            EventRecord eventRecord = initializeEventRecord(NPEventType_AdjustCursorEvent);
605            eventRecord.modifiers = modifiersForEvent(mouseEvent);
606
607            return NPP_HandleEvent(&eventRecord);
608        }
609#endif
610
611        default:
612            ASSERT_NOT_REACHED();
613    }
614
615    return false;
616}
617
618bool NetscapePlugin::platformHandleMouseLeaveEvent(const WebMouseEvent& mouseEvent)
619{
620    switch (m_eventModel) {
621        case NPEventModelCocoa: {
622            NPCocoaEvent event = initializeEvent(NPCocoaEventMouseExited);
623
624            fillInCocoaEventFromMouseEvent(event, mouseEvent, m_frameRect.location());
625            return NPP_HandleEvent(&event);
626        }
627
628#ifndef NP_NO_CARBON
629        case NPEventModelCarbon: {
630            EventRecord eventRecord = initializeEventRecord(NPEventType_AdjustCursorEvent);
631            eventRecord.modifiers = modifiersForEvent(mouseEvent);
632
633            return NPP_HandleEvent(&eventRecord);
634        }
635#endif
636
637        default:
638            ASSERT_NOT_REACHED();
639    }
640
641    return false;
642}
643
644static unsigned modifierFlags(const WebKeyboardEvent& keyboardEvent)
645{
646    unsigned modifierFlags = 0;
647
648    if (keyboardEvent.capsLockKey())
649        modifierFlags |= NSAlphaShiftKeyMask;
650    if (keyboardEvent.shiftKey())
651        modifierFlags |= NSShiftKeyMask;
652    if (keyboardEvent.controlKey())
653        modifierFlags |= NSControlKeyMask;
654    if (keyboardEvent.altKey())
655        modifierFlags |= NSAlternateKeyMask;
656    if (keyboardEvent.metaKey())
657        modifierFlags |= NSCommandKeyMask;
658
659    return modifierFlags;
660}
661
662static bool isFlagsChangedEvent(const WebKeyboardEvent& keyboardEvent)
663{
664    switch (keyboardEvent.nativeVirtualKeyCode()) {
665    case 54: // Right Command
666    case 55: // Left Command
667
668    case 57: // Capslock
669
670    case 56: // Left Shift
671    case 60: // Right Shift
672
673    case 58: // Left Alt
674    case 61: // Right Alt
675
676    case 59: // Left Ctrl
677    case 62: // Right Ctrl
678        return true;
679    }
680
681    return false;
682}
683
684static NPCocoaEvent initializeKeyboardEvent(const WebKeyboardEvent& keyboardEvent)
685{
686    NPCocoaEventType eventType;
687
688    if (isFlagsChangedEvent(keyboardEvent))
689        eventType = NPCocoaEventFlagsChanged;
690    else {
691        switch (keyboardEvent.type()) {
692            case WebEvent::KeyDown:
693                eventType = NPCocoaEventKeyDown;
694                break;
695            case WebEvent::KeyUp:
696                eventType = NPCocoaEventKeyUp;
697                break;
698            default:
699                ASSERT_NOT_REACHED();
700                return NPCocoaEvent();
701        }
702    }
703
704    NPCocoaEvent event = initializeEvent(eventType);
705    event.data.key.modifierFlags = modifierFlags(keyboardEvent);
706    event.data.key.characters = reinterpret_cast<NPNSString*>(static_cast<NSString*>(keyboardEvent.text()));
707    event.data.key.charactersIgnoringModifiers = reinterpret_cast<NPNSString*>(static_cast<NSString*>(keyboardEvent.unmodifiedText()));
708    event.data.key.isARepeat = keyboardEvent.isAutoRepeat();
709    event.data.key.keyCode = keyboardEvent.nativeVirtualKeyCode();
710
711    return event;
712}
713
714bool NetscapePlugin::platformHandleKeyboardEvent(const WebKeyboardEvent& keyboardEvent)
715{
716    bool handled = false;
717
718    switch (m_eventModel) {
719    case NPEventModelCocoa: {
720        NPCocoaEvent event = initializeKeyboardEvent(keyboardEvent);
721        handled = NPP_HandleEvent(&event);
722        break;
723    }
724
725#ifndef NP_NO_CARBON
726    case NPEventModelCarbon: {
727        EventKind eventKind = nullEvent;
728
729        switch (keyboardEvent.type()) {
730        case WebEvent::KeyDown:
731            eventKind = keyboardEvent.isAutoRepeat() ? autoKey : keyDown;
732            break;
733        case WebEvent::KeyUp:
734            eventKind = keyUp;
735            break;
736        default:
737            ASSERT_NOT_REACHED();
738        }
739
740        EventRecord event = initializeEventRecord(eventKind);
741        event.modifiers = modifiersForEvent(keyboardEvent);
742        event.message = keyboardEvent.nativeVirtualKeyCode() << 8 | keyboardEvent.macCharCode();
743        handled = NPP_HandleEvent(&event);
744        break;
745    }
746#endif
747
748    default:
749        ASSERT_NOT_REACHED();
750    }
751
752    // Most plug-ins simply return true for all keyboard events, even those that aren't handled.
753    // This leads to bugs such as <rdar://problem/8740926>. We work around this by returning false
754    // if the keyboard event has the command modifier pressed.
755    if (keyboardEvent.metaKey())
756        return false;
757
758    return handled;
759}
760
761void NetscapePlugin::platformSetFocus(bool hasFocus)
762{
763    m_pluginHasFocus = hasFocus;
764    m_pluginController->setComplexTextInputEnabled(m_pluginHasFocus && m_windowHasFocus);
765
766    switch (m_eventModel) {
767        case NPEventModelCocoa: {
768            NPCocoaEvent event = initializeEvent(NPCocoaEventFocusChanged);
769
770            event.data.focus.hasFocus = hasFocus;
771            NPP_HandleEvent(&event);
772            break;
773        }
774
775#ifndef NP_NO_CARBON
776        case NPEventModelCarbon: {
777            EventRecord event = initializeEventRecord(hasFocus ? NPEventType_GetFocusEvent : NPEventType_LoseFocusEvent);
778
779            NPP_HandleEvent(&event);
780            break;
781        }
782#endif
783
784        default:
785            ASSERT_NOT_REACHED();
786    }
787}
788
789void NetscapePlugin::windowFocusChanged(bool hasFocus)
790{
791    m_windowHasFocus = hasFocus;
792    m_pluginController->setComplexTextInputEnabled(m_pluginHasFocus && m_windowHasFocus);
793
794    switch (m_eventModel) {
795        case NPEventModelCocoa: {
796            NPCocoaEvent event = initializeEvent(NPCocoaEventWindowFocusChanged);
797
798            event.data.focus.hasFocus = hasFocus;
799            NPP_HandleEvent(&event);
800            break;
801        }
802
803#ifndef NP_NO_CARBON
804        case NPEventModelCarbon: {
805            HiliteWindow(windowRef(), hasFocus);
806            if (hasFocus)
807                SetUserFocusWindow(windowRef());
808
809            EventRecord event = initializeEventRecord(activateEvt);
810            event.message = reinterpret_cast<unsigned long>(windowRef());
811            if (hasFocus)
812                event.modifiers |= activeFlag;
813
814            NPP_HandleEvent(&event);
815            break;
816        }
817#endif
818
819        default:
820            ASSERT_NOT_REACHED();
821    }
822}
823
824#ifndef NP_NO_CARBON
825static Rect computeFakeWindowBoundsRect(const WebCore::IntRect& windowFrameInScreenCoordinates, const WebCore::IntRect& viewFrameInWindowCoordinates)
826{
827    // Carbon global coordinates has the origin set at the top left corner of the main viewing screen, so we want to flip the y coordinate.
828    CGFloat maxY = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]);
829
830    int flippedWindowFrameYCoordinate = maxY - windowFrameInScreenCoordinates.maxY();
831    int flippedViewFrameYCoordinate = windowFrameInScreenCoordinates.height() - viewFrameInWindowCoordinates.maxY();
832
833    Rect bounds;
834
835    bounds.top = flippedWindowFrameYCoordinate + flippedViewFrameYCoordinate;
836    bounds.left = windowFrameInScreenCoordinates.x();
837    bounds.right = bounds.left + viewFrameInWindowCoordinates.width();
838    bounds.bottom = bounds.top + viewFrameInWindowCoordinates.height();
839
840    return bounds;
841}
842#endif
843
844void NetscapePlugin::windowAndViewFramesChanged(const IntRect& windowFrameInScreenCoordinates, const IntRect& viewFrameInWindowCoordinates)
845{
846    m_windowFrameInScreenCoordinates = windowFrameInScreenCoordinates;
847    m_viewFrameInWindowCoordinates = viewFrameInWindowCoordinates;
848
849    switch (m_eventModel) {
850        case NPEventModelCocoa:
851            // Nothing to do.
852            break;
853
854#ifndef NP_NO_CARBON
855        case NPEventModelCarbon: {
856            Rect bounds = computeFakeWindowBoundsRect(windowFrameInScreenCoordinates, viewFrameInWindowCoordinates);
857
858            ::SetWindowBounds(windowRef(), kWindowStructureRgn, &bounds);
859            break;
860        }
861#endif
862
863        default:
864            ASSERT_NOT_REACHED();
865    }
866}
867
868void NetscapePlugin::windowVisibilityChanged(bool)
869{
870    // FIXME: Implement.
871}
872
873uint64_t NetscapePlugin::pluginComplexTextInputIdentifier() const
874{
875    // Just return a dummy value; this is only called for in-process plug-ins, which we don't support on Mac.
876    return static_cast<uint64_t>(reinterpret_cast<uintptr_t>(this));
877}
878
879
880#ifndef NP_NO_CARBON
881static bool convertStringToKeyCodes(const String& string, ScriptCode scriptCode, Vector<UInt8>& keyCodes)
882{
883    // Create the mapping.
884    UnicodeMapping mapping;
885
886    if (GetTextEncodingFromScriptInfo(scriptCode, kTextLanguageDontCare, kTextRegionDontCare, &mapping.otherEncoding) != noErr)
887        return false;
888
889    mapping.unicodeEncoding = CreateTextEncoding(kTextEncodingUnicodeDefault, kTextEncodingDefaultVariant, kTextEncodingDefaultFormat);
890    mapping.mappingVersion = kUnicodeUseLatestMapping;
891
892    // Create the converter
893    UnicodeToTextInfo textInfo;
894
895    if (CreateUnicodeToTextInfo(&mapping, &textInfo) != noErr)
896        return false;
897
898    ByteCount inputLength = string.length() * sizeof(UniChar);
899    ByteCount inputRead;
900    ByteCount outputLength;
901    ByteCount maxOutputLength = string.length() * sizeof(UniChar);
902
903    Vector<UInt8> outputData(maxOutputLength);
904    OSStatus status = ConvertFromUnicodeToText(textInfo, inputLength, string.characters(), kNilOptions, 0, 0, 0, 0, maxOutputLength, &inputRead, &outputLength, outputData.data());
905
906    DisposeUnicodeToTextInfo(&textInfo);
907
908    if (status != noErr)
909        return false;
910
911    outputData.swap(keyCodes);
912    return true;
913}
914#endif
915
916void NetscapePlugin::sendComplexTextInput(const String& textInput)
917{
918    switch (m_eventModel) {
919    case NPEventModelCocoa: {
920        NPCocoaEvent event = initializeEvent(NPCocoaEventTextInput);
921        event.data.text.text = reinterpret_cast<NPNSString*>(static_cast<NSString*>(textInput));
922        NPP_HandleEvent(&event);
923        break;
924    }
925#ifndef NP_NO_CARBON
926    case NPEventModelCarbon: {
927        ScriptCode scriptCode = WKGetScriptCodeFromCurrentKeyboardInputSource();
928        Vector<UInt8> keyCodes;
929
930        if (!convertStringToKeyCodes(textInput, scriptCode, keyCodes))
931            return;
932
933        // Set the script code as the keyboard script. Normally Carbon does this whenever the input source changes.
934        // However, this is only done for the process that has the keyboard focus. We cheat and do it here instead.
935        SetScriptManagerVariable(smKeyScript, scriptCode);
936
937        EventRecord event = initializeEventRecord(keyDown);
938        event.modifiers = 0;
939
940        for (size_t i = 0; i < keyCodes.size(); i++) {
941            event.message = keyCodes[i];
942            NPP_HandleEvent(&event);
943        }
944        break;
945    }
946#endif
947    default:
948        ASSERT_NOT_REACHED();
949    }
950}
951
952PlatformLayer* NetscapePlugin::pluginLayer()
953{
954    return static_cast<PlatformLayer*>(m_pluginLayer.get());
955}
956
957#ifndef NP_NO_CARBON
958void NetscapePlugin::nullEventTimerFired()
959{
960    EventRecord event = initializeEventRecord(nullEvent);
961
962    event.message = 0;
963    CGPoint mousePosition;
964    HIGetMousePosition(kHICoordSpaceScreenPixel, 0, &mousePosition);
965    event.where.h = mousePosition.x;
966    event.where.v = mousePosition.y;
967
968    event.modifiers = GetCurrentKeyModifiers();
969    if (!Button())
970        event.modifiers |= btnState;
971
972    NPP_HandleEvent(&event);
973}
974#endif
975
976} // namespace WebKit
977