2008-11-12 Tor Arne Vestbø <tavestbo@trolltech.com>
[WebKit-https.git] / WebCore / plugins / mac / PluginViewMac.cpp
1 /*
2  * Copyright (C) 2006, 2007 Apple Inc.  All rights reserved.
3  * Copyright (C) 2008 Collabora Ltd. All rights reserved.
4  * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
16  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
19  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27
28 #ifndef __LP64__
29
30 #include "config.h"
31 #include "PluginView.h"
32
33 #include <runtime/JSLock.h>
34 #include <runtime/JSValue.h>
35 #include "wtf/RetainPtr.h"
36
37 #include "Document.h"
38 #include "DocumentLoader.h"
39 #include "Element.h"
40 #include "EventNames.h"
41 #include "FocusController.h"
42 #include "FrameLoader.h"
43 #include "FrameLoadRequest.h"
44 #include "FrameTree.h"
45 #include "Frame.h"
46 #include "FrameView.h"
47 #include "GraphicsContext.h"
48 #include "HTMLNames.h"
49 #include "HTMLPlugInElement.h"
50 #include "Image.h"
51 #include "JSDOMBinding.h"
52 #include "KeyboardEvent.h"
53 #include "MouseEvent.h"
54 #include "NotImplemented.h"
55 #include "npruntime_impl.h"
56 #include "Page.h"
57 #include "PlatformMouseEvent.h"
58 #include "PlatformKeyboardEvent.h"
59 #include "PluginDebug.h"
60 #include "PluginPackage.h"
61 #include "PluginMainThreadScheduler.h"
62 #include "RenderLayer.h"
63 #include "runtime.h"
64 #include "runtime_root.h"
65 #include "ScriptController.h"
66 #include "Settings.h"
67
68 using JSC::ExecState;
69 using JSC::Interpreter;
70 using JSC::JSLock;
71 using JSC::JSObject;
72 using JSC::JSValue;
73 using JSC::UString;
74
75 #if PLATFORM(QT)
76 #include <QWidget>
77 #include <QKeyEvent>
78 QT_BEGIN_NAMESPACE
79 extern Q_GUI_EXPORT OSWindowRef qt_mac_window_for(const QWidget *w);
80 QT_END_NAMESPACE
81 #endif
82
83 using std::min;
84
85 using namespace WTF;
86
87 namespace WebCore {
88
89 using namespace HTMLNames;
90
91 static int modifiersForEvent(UIEventWithKeyState *event);
92
93 static inline WindowRef nativeWindowFor(PlatformWidget widget)
94 {
95 #if PLATFORM(QT)
96     if (widget)
97         return static_cast<WindowRef>(qt_mac_window_for(widget));
98 #endif
99     return 0;
100 }
101
102 static inline CGContextRef cgHandleFor(PlatformWidget widget)
103 {
104 #if PLATFORM(QT)
105     if (widget)
106         return (CGContextRef)widget->macCGHandle();
107 #endif
108     return 0;
109 }
110
111 static inline IntPoint topLevelOffsetFor(PlatformWidget widget)
112 {
113 #if PLATFORM(QT)
114     if (widget) {
115         PlatformWidget topLevel = widget->window();
116         return widget->mapTo(topLevel, QPoint(0, 0)) + topLevel->geometry().topLeft() - topLevel->pos();
117     }
118 #endif
119     return IntPoint();
120 }
121
122 // --------------- Lifetime management -----------------
123
124 void PluginView::init()
125 {
126     if (m_haveInitialized)
127         return;
128     m_haveInitialized = true;
129
130     if (!m_plugin) {
131         ASSERT(m_status == PluginStatusCanNotFindPlugin);
132         return;
133     }
134
135     if (!m_plugin->load()) {
136         m_plugin = 0;
137         m_status = PluginStatusCanNotLoadPlugin;
138         return;
139     }
140
141     if (!start()) {
142         m_status = PluginStatusCanNotLoadPlugin;
143         return;
144     }
145
146     setPlatformPluginWidget(m_parentFrame->view()->hostWindow()->platformWindow());
147
148     m_npCgContext.window = 0;
149     m_npCgContext.context = 0;
150     m_npWindow.window = (void*)&m_npCgContext;
151     m_npWindow.type = NPWindowTypeWindow;
152     m_npWindow.x = 0;
153     m_npWindow.y = 0;
154     m_npWindow.width = 0;
155     m_npWindow.height = 0;
156     m_npWindow.clipRect.left = 0;
157     m_npWindow.clipRect.top = 0;
158     m_npWindow.clipRect.right = 0;
159     m_npWindow.clipRect.bottom = 0;
160
161     setIsNPAPIPlugin(true);
162
163     show();
164
165     m_status = PluginStatusLoadedSuccessfully;
166
167     // TODO: Implement null timer throttling depending on plugin activation
168     m_nullEventTimer.set(new Timer<PluginView>(this, &PluginView::nullEventTimerFired));
169     m_nullEventTimer->startRepeating(0.02);
170 }
171
172 PluginView::~PluginView()
173 {
174     stop();
175
176     deleteAllValues(m_requests);
177
178     freeStringArray(m_paramNames, m_paramCount);
179     freeStringArray(m_paramValues, m_paramCount);
180
181     m_parentFrame->script()->cleanupScriptObjectsForPlugin(this);
182
183     if (m_plugin && !(m_plugin->quirks().contains(PluginQuirkDontUnloadPlugin)))
184         m_plugin->unload();
185
186     m_window = 0;
187 }
188
189 void PluginView::stop()
190 {
191     if (!m_isStarted)
192         return;
193
194     HashSet<RefPtr<PluginStream> > streams = m_streams;
195     HashSet<RefPtr<PluginStream> >::iterator end = streams.end();
196     for (HashSet<RefPtr<PluginStream> >::iterator it = streams.begin(); it != end; ++it) {
197         (*it)->stop();
198         disconnectStream((*it).get());
199     }
200
201     ASSERT(m_streams.isEmpty());
202
203     m_isStarted = false;
204
205     JSC::JSLock::DropAllLocks dropAllLocks(false);
206
207     PluginMainThreadScheduler::scheduler().unregisterPlugin(m_instance);
208
209     // Destroy the plugin
210     PluginView::setCurrentPluginView(this);
211     setCallingPlugin(true);
212     m_plugin->pluginFuncs()->destroy(m_instance, 0);
213     setCallingPlugin(false);
214     PluginView::setCurrentPluginView(0);
215
216     m_instance->pdata = 0;
217 }
218
219 NPError PluginView::getValueStatic(NPNVariable variable, void* value)
220 {
221     LOG(Plugin, "PluginView::getValueStatic(%d)", variable);
222
223     switch (variable) {
224     case NPNVToolkit:
225         *((uint32 *)value) = 0;
226         return NPERR_NO_ERROR;
227
228     case NPNVjavascriptEnabledBool:
229         *((uint32 *)value) = true;
230         return NPERR_NO_ERROR;
231
232     default:
233         return NPERR_GENERIC_ERROR;
234     }
235 }
236
237 NPError PluginView::getValue(NPNVariable variable, void* value)
238 {
239     LOG(Plugin, "PluginView::getValue(%d)", variable);
240
241     switch (variable) {
242     case NPNVWindowNPObject: {
243         if (m_isJavaScriptPaused)
244             return NPERR_GENERIC_ERROR;
245
246         NPObject* windowScriptObject = m_parentFrame->script()->windowScriptNPObject();
247
248         // Return value is expected to be retained, as described in
249         // <http://www.mozilla.org/projects/plugin/npruntime.html>
250         if (windowScriptObject)
251             _NPN_RetainObject(windowScriptObject);
252
253         void** v = (void**)value;
254         *v = windowScriptObject;
255
256         return NPERR_NO_ERROR;
257     }
258
259     case NPNVPluginElementNPObject: {
260         if (m_isJavaScriptPaused)
261             return NPERR_GENERIC_ERROR;
262
263         NPObject* pluginScriptObject = 0;
264
265         if (m_element->hasTagName(appletTag) || m_element->hasTagName(embedTag) || m_element->hasTagName(objectTag))
266             pluginScriptObject = static_cast<HTMLPlugInElement*>(m_element)->getNPObject();
267
268         // Return value is expected to be retained, as described in
269         // <http://www.mozilla.org/projects/plugin/npruntime.html>
270         if (pluginScriptObject)
271             _NPN_RetainObject(pluginScriptObject);
272
273         void** v = (void**)value;
274         *v = pluginScriptObject;
275
276         return NPERR_NO_ERROR;
277     }
278
279     case NPNVsupportsCoreGraphicsBool:
280         *((uint32 *)value) = true;
281         return NPERR_NO_ERROR;
282
283     default:
284         return getValueStatic(variable, value);
285     }
286
287 }
288 void PluginView::setParent(ScrollView* parent)
289 {
290     Widget::setParent(parent);
291
292     if (parent)
293         init();
294 }
295
296 // -------------- Geometry and painting ----------------
297
298 void PluginView::show()
299 {
300     LOG(Plugin, "PluginView::show()");
301
302     setSelfVisible(true);
303
304     if (isParentVisible() && platformPluginWidget())
305         platformPluginWidget()->setVisible(true);
306
307     Widget::show();
308 }
309
310 void PluginView::hide()
311 {
312     LOG(Plugin, "PluginView::hide()");
313
314     setSelfVisible(false);
315
316     if (isParentVisible() && platformPluginWidget())
317         platformPluginWidget()->setVisible(false);
318
319     Widget::hide();
320 }
321
322 void PluginView::setFocus()
323 {
324     LOG(Plugin, "PluginView::setFocus()");
325
326     if (platformPluginWidget())
327        platformPluginWidget()->setFocus(Qt::OtherFocusReason);
328    else
329        Widget::setFocus();
330
331     // TODO: Also handle and pass on blur events (focus lost)
332
333     EventRecord record;
334     record.what = getFocusEvent;
335     record.message = 0;
336     record.when = TickCount();
337     record.where = globalMousePosForPlugin();
338     record.modifiers = GetCurrentKeyModifiers();
339
340     if (!dispatchNPEvent(record))
341         LOG(Events, "PluginView::setFocus(): Get-focus event not accepted");
342 }
343
344 void PluginView::setParentVisible(bool visible)
345 {
346     if (isParentVisible() == visible)
347         return;
348
349     Widget::setParentVisible(visible);
350
351     if (isSelfVisible() && platformPluginWidget())
352         platformPluginWidget()->setVisible(visible);
353 }
354
355 void PluginView::setNPWindowRect(const IntRect&)
356 {
357     setNPWindowIfNeeded();
358 }
359
360 void PluginView::setNPWindowIfNeeded()
361 {
362     if (!m_isStarted || !parent() || !m_plugin->pluginFuncs()->setwindow)
363         return;
364
365     CGContextRef newContextRef = cgHandleFor(platformPluginWidget());
366     if (!newContextRef)
367         return;
368
369     WindowRef newWindowRef = nativeWindowFor(platformPluginWidget());
370     if (!newWindowRef)
371         return;
372
373     ASSERT(parent()->isFrameView());
374     FrameView* frameView = static_cast<FrameView*>(parent());
375     IntRect newGeometry = IntRect(frameView->contentsToWindow(frameRect().location()), frameRect().size());
376
377     // TODO: also compare clip rects
378     if (newGeometry == m_windowRect
379         && newWindowRef == m_npCgContext.window
380         && newContextRef == m_npCgContext.context)
381     return;
382
383     m_npWindow.window = (void*)&m_npCgContext;
384     m_npCgContext.window = newWindowRef;
385     m_npCgContext.context = newContextRef;
386
387     m_windowRect = newGeometry;
388     m_npWindow.x = m_windowRect.x();
389     m_npWindow.y = m_windowRect.y();
390     m_npWindow.width = m_windowRect.width();
391     m_npWindow.height = m_windowRect.height();
392
393     // TODO: (also clip against scrollbars, etc.)
394     m_npWindow.clipRect.left = 0;
395     m_npWindow.clipRect.top = 0;
396     m_npWindow.clipRect.right = m_windowRect.width();
397     m_npWindow.clipRect.bottom = m_windowRect.height();
398
399     PluginView::setCurrentPluginView(this);
400     JSC::JSLock::DropAllLocks dropAllLocks(false);
401     setCallingPlugin(true);
402     m_plugin->pluginFuncs()->setwindow(m_instance, &m_npWindow);
403     setCallingPlugin(false);
404     PluginView::setCurrentPluginView(0);
405 }
406
407 void PluginView::updatePluginWidget() const
408 {
409     // Nothing to do here. We update the plugin widget
410     // in paint(), if the NPWindow struct has changed.
411 }
412
413 void PluginView::paint(GraphicsContext* context, const IntRect& rect)
414 {
415     if (!m_isStarted)
416         return; // TODO: Draw the "missing plugin" image
417
418     if (context->paintingDisabled())
419         return;
420
421     setNPWindowIfNeeded();
422
423     EventRecord event;
424     event.what = updateEvt;
425     event.message = (long unsigned int)m_npCgContext.window;
426     event.when = TickCount();
427     event.where.h = 0;
428     event.where.v = 0;
429     event.modifiers = GetCurrentKeyModifiers();
430
431     CGContextRef cg = m_npCgContext.context;
432     CGContextSaveGState(cg);
433     IntPoint offset = frameRect().location();
434     CGContextTranslateCTM(cg, offset.x(), offset.y());
435
436     if (!dispatchNPEvent(event))
437         LOG(Events, "PluginView::paint(): Paint event not accepted");
438
439     CGContextRestoreGState(cg);
440 }
441
442 void PluginView::invalidateRect(const IntRect& rect)
443 {
444     if (platformPluginWidget()) {
445         // TODO: optimize
446         platformPluginWidget()->update();
447         return;
448     }
449 }
450
451 void PluginView::invalidateRect(NPRect* rect)
452 {
453     // TODO: optimize
454     invalidate();
455 }
456
457 void PluginView::invalidateRegion(NPRegion region)
458 {
459     // TODO: optimize
460     invalidate();
461 }
462
463 void PluginView::forceRedraw()
464 {
465     notImplemented();
466 }
467
468
469 // ----------------- Event handling --------------------
470
471 void PluginView::handleMouseEvent(MouseEvent* event)
472 {
473     EventRecord record;
474
475     if (event->type() == eventNames().mousemoveEvent) {
476         // Mouse movement is handled by null timer events
477         return;
478     } else if (event->type() == eventNames().mouseoverEvent) {
479         record.what = adjustCursorEvent;
480     } else if (event->type() == eventNames().mouseoutEvent) {
481         record.what = adjustCursorEvent;
482     } else if (event->type() == eventNames().mousedownEvent) {
483         record.what = mouseDown;
484         // The plugin needs focus to receive keyboard events
485         if (Page* page = m_parentFrame->page())
486             page->focusController()->setFocusedFrame(m_parentFrame);
487         m_parentFrame->document()->setFocusedNode(m_element);
488     } else if (event->type() == eventNames().mouseupEvent) {
489         record.what = mouseUp;
490     } else {
491         return;
492     }
493
494     record.where = globalMousePosForPlugin();
495     record.modifiers = modifiersForEvent(event);
496
497     if (!event->buttonDown())
498         record.modifiers |= btnState;
499
500     if (event->button() == 2)
501         record.modifiers |= controlKey;
502
503     if (!dispatchNPEvent(record)) {
504         if (record.what == adjustCursorEvent)
505             return; // Signals that the plugin wants a normal cursor
506
507         LOG(Events, "PluginView::handleMouseEvent(): Mouse event type %d at %d,%d not accepted",
508                 record.what, record.where.h, record.where.v);
509     } else {
510         event->setDefaultHandled();
511     }
512 }
513
514 void PluginView::handleKeyboardEvent(KeyboardEvent* event)
515 {
516     LOG(Plugin, "PluginView::handleKeyboardEvent() ----------------- ");
517
518     LOG(Plugin, "PV::hKE(): KE.keyCode: 0x%02X, KE.charCode: %d",
519             event->keyCode(), event->charCode());
520
521     EventRecord record;
522
523     if (event->type() == eventNames().keydownEvent) {
524         // This event is the result of a PlatformKeyboardEvent::KeyDown which
525         // was disambiguated into a PlatformKeyboardEvent::RawKeyDown. Since
526         // we don't have access to the text here, we return, and wait for the
527         // corresponding event based on PlatformKeyboardEvent::Char.
528         return;
529     } else if (event->type() == eventNames().keypressEvent) {
530         // Which would be this one. This event was disambiguated from the same
531         // PlatformKeyboardEvent::KeyDown, but to a PlatformKeyboardEvent::Char,
532         // which retains the text from the original event. So, we can safely pass
533         // on the event as a key-down event to the plugin.
534         record.what = keyDown;
535     } else if (event->type() == eventNames().keyupEvent) {
536         // PlatformKeyboardEvent::KeyUp events always have the text, so nothing
537         // fancy here.
538         record.what = keyUp;
539     } else {
540         return;
541     }
542
543     const PlatformKeyboardEvent* platformEvent = event->keyEvent();
544     int keyCode = platformEvent->nativeVirtualKeyCode();
545
546     const String text = platformEvent->text();
547     if (text.length() < 1) {
548         event->setDefaultHandled();
549         return;
550     }
551
552     WTF::RetainPtr<CFStringRef> cfText(WTF::AdoptCF, text.createCFString());
553
554     LOG(Plugin, "PV::hKE(): PKE.text: %s, PKE.unmodifiedText: %s, PKE.keyIdentifier: %s",
555             text.ascii().data(), platformEvent->unmodifiedText().ascii().data(),
556             platformEvent->keyIdentifier().ascii().data());
557
558     char charCodes[2] = { 0, 0 };
559     if (!CFStringGetCString(cfText.get(), charCodes, 2, CFStringGetSystemEncoding())) {
560         LOG_ERROR("Could not resolve character code using system encoding.");
561         event->setDefaultHandled();
562         return;
563     }
564
565     record.where = globalMousePosForPlugin();
566     record.modifiers = modifiersForEvent(event);
567     record.message = ((keyCode & 0xFF) << 8) | (charCodes[0] & 0xFF);
568     record.when = TickCount();
569
570     LOG(Plugin, "PV::hKE(): record.modifiers: %d", record.modifiers);
571
572     LOG(Plugin, "PV::hKE(): PKE.qtEvent()->nativeVirtualKey: 0x%02X, charCode: %d",
573                keyCode, int(uchar(charCodes[0])));
574
575     if (!dispatchNPEvent(record))
576         LOG(Events, "PluginView::handleKeyboardEvent(): Keyboard event type %d not accepted", record.what);
577     else
578         event->setDefaultHandled();
579 }
580
581 void PluginView::nullEventTimerFired(Timer<PluginView>*)
582 {
583     EventRecord record;
584
585     record.what = nullEvent;
586     record.message = 0;
587     record.when = TickCount();
588     record.where = globalMousePosForPlugin();
589     record.modifiers = GetCurrentKeyModifiers();
590     if (!Button())
591         record.modifiers |= btnState;
592
593     if (!dispatchNPEvent(record))
594         LOG(Events, "PluginView::nullEventTimerFired(): Null event not accepted");
595 }
596
597 static int modifiersForEvent(UIEventWithKeyState* event)
598 {
599     int modifiers = 0;
600
601     if (event->ctrlKey())
602         modifiers |= controlKey;
603
604     if (event->altKey())
605         modifiers |= optionKey;
606
607     if (event->metaKey())
608         modifiers |= cmdKey;
609
610     if (event->shiftKey())
611         modifiers |= shiftKey;
612
613      return modifiers;
614 }
615
616 Point PluginView::globalMousePosForPlugin() const
617 {
618     Point pos;
619     GetGlobalMouse(&pos);
620
621     IntPoint offset = topLevelOffsetFor(platformPluginWidget());
622     pos.h -= offset.x();
623     pos.v -= offset.y();
624
625     pos.h = static_cast<short>(pos.h * HIGetScaleFactor());
626     pos.v = static_cast<short>(pos.v * HIGetScaleFactor());
627
628     return pos;
629 }
630
631 bool PluginView::dispatchNPEvent(NPEvent& event)
632 {
633     PluginView::setCurrentPluginView(this);
634     JSC::JSLock::DropAllLocks dropAllLocks(false);
635     setCallingPlugin(true);
636
637     bool accepted = m_plugin->pluginFuncs()->event(m_instance, &event);
638
639     setCallingPlugin(false);
640     PluginView::setCurrentPluginView(0);
641     return accepted;
642 }
643
644 // ------------------- Miscellaneous  ------------------
645
646 static const char* MozillaUserAgent = "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1) Gecko/20061010 Firefox/2.0";
647
648 const char* PluginView::userAgent()
649 {
650     if (m_plugin->quirks().contains(PluginQuirkWantsMozillaUserAgent))
651         return MozillaUserAgent;
652
653     if (m_userAgent.isNull())
654         m_userAgent = m_parentFrame->loader()->userAgent(m_url).utf8();
655
656     return m_userAgent.data();
657 }
658
659 const char* PluginView::userAgentStatic()
660 {
661     return MozillaUserAgent;
662 }
663
664 NPError PluginView::handlePostReadFile(Vector<char>& buffer, uint32 len, const char* buf)
665 {
666     String filename(buf, len);
667
668     if (filename.startsWith("file:///"))
669         filename = filename.substring(8);
670
671     if (!fileExists(filename))
672         return NPERR_FILE_NOT_FOUND;
673
674     FILE* fileHandle = fopen((filename.utf8()).data(), "r");
675
676     if (fileHandle == 0)
677         return NPERR_FILE_NOT_FOUND;
678
679     int bytesRead = fread(buffer.data(), 1, 0, fileHandle);
680
681     fclose(fileHandle);
682
683     if (bytesRead <= 0)
684         return NPERR_FILE_NOT_FOUND;
685
686     return NPERR_NO_ERROR;
687 }
688
689 } // namespace WebCore
690
691 #endif // !__LP64__