diff options
Diffstat (limited to 'webkit/glue/plugins/webplugin_delegate_impl_mac.mm')
-rw-r--r-- | webkit/glue/plugins/webplugin_delegate_impl_mac.mm | 1160 |
1 files changed, 1160 insertions, 0 deletions
diff --git a/webkit/glue/plugins/webplugin_delegate_impl_mac.mm b/webkit/glue/plugins/webplugin_delegate_impl_mac.mm new file mode 100644 index 0000000..efa6bdd --- /dev/null +++ b/webkit/glue/plugins/webplugin_delegate_impl_mac.mm @@ -0,0 +1,1160 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import <Cocoa/Cocoa.h> +#import <QuartzCore/QuartzCore.h> + +#include "webkit/glue/plugins/webplugin_delegate_impl.h" + +#include <string> +#include <unistd.h> +#include <set> + +#include "base/file_util.h" +#include "base/lazy_instance.h" +#include "base/message_loop.h" +#include "base/scoped_ptr.h" +#include "base/stats_counters.h" +#include "base/string_util.h" +#include "third_party/WebKit/WebKit/chromium/public/WebInputEvent.h" +#include "webkit/glue/plugins/plugin_instance.h" +#include "webkit/glue/plugins/plugin_lib.h" +#include "webkit/glue/plugins/plugin_list.h" +#include "webkit/glue/plugins/plugin_stream_url.h" +#include "webkit/glue/plugins/plugin_web_event_converter_mac.h" +#include "webkit/glue/plugins/webplugin.h" +#include "webkit/glue/webkit_glue.h" + +#ifndef NP_NO_CARBON +#include "webkit/glue/plugins/carbon_plugin_window_tracker_mac.h" +#endif + +#ifndef NP_NO_QUICKDRAW +#include "webkit/glue/plugins/quickdraw_drawing_manager_mac.h" +#endif + +using webkit_glue::WebPlugin; +using webkit_glue::WebPluginDelegate; +using webkit_glue::WebPluginResourceClient; +using WebKit::WebCursorInfo; +using WebKit::WebKeyboardEvent; +using WebKit::WebInputEvent; +using WebKit::WebMouseEvent; +using WebKit::WebMouseWheelEvent; + +const int kCoreAnimationRedrawPeriodMs = 10; // 100 Hz + +// Important implementation notes: The Mac definition of NPAPI, particularly +// the distinction between windowed and windowless modes, differs from the +// Windows and Linux definitions. Most of those differences are +// accomodated by the WebPluginDelegate class. + +namespace { + +WebPluginDelegateImpl* g_active_delegate; + +// Helper to simplify correct usage of g_active_delegate. Instantiating will +// set the active delegate to |delegate| for the lifetime of the object, then +// NULL when it goes out of scope. +class ScopedActiveDelegate { +public: + explicit ScopedActiveDelegate(WebPluginDelegateImpl* delegate) { + g_active_delegate = delegate; + } + ~ScopedActiveDelegate() { + g_active_delegate = NULL; + } +private: + DISALLOW_COPY_AND_ASSIGN(ScopedActiveDelegate); +}; + +#ifndef NP_NO_CARBON +// Timer periods for sending idle events to Carbon plugins. The visible value +// (50Hz) matches both Safari and Firefox. The hidden value (8Hz) matches +// Firefox; according to https://bugzilla.mozilla.org/show_bug.cgi?id=525533 +// going lower than that causes issues. +const int kVisibleIdlePeriodMs = 20; // (50Hz) +const int kHiddenIdlePeriodMs = 125; // (8Hz) + +class CarbonIdleEventSource { + public: + // Returns the shared Carbon idle event source. + static CarbonIdleEventSource* SharedInstance() { + DCHECK(MessageLoop::current()->type() == MessageLoop::TYPE_UI); + static CarbonIdleEventSource* event_source = new CarbonIdleEventSource(); + return event_source; + } + + // Registers the plugin delegate as interested in receiving idle events at + // a rate appropriate for the given visibility. A delegate can safely be + // re-registered any number of times, with the latest registration winning. + void RegisterDelegate(WebPluginDelegateImpl* delegate, bool visible) { + if (visible) { + visible_delegates_->RegisterDelegate(delegate); + hidden_delegates_->UnregisterDelegate(delegate); + } else { + hidden_delegates_->RegisterDelegate(delegate); + visible_delegates_->UnregisterDelegate(delegate); + } + } + + // Removes the plugin delegate from the list of plugins receiving idle events. + void UnregisterDelegate(WebPluginDelegateImpl* delegate) { + visible_delegates_->UnregisterDelegate(delegate); + hidden_delegates_->UnregisterDelegate(delegate); + } + + private: + class VisibilityGroup { + public: + explicit VisibilityGroup(int timer_period) + : timer_period_(timer_period), iterator_(delegates_.end()) {} + + // Adds |delegate| to this visibility group. + void RegisterDelegate(WebPluginDelegateImpl* delegate) { + if (delegates_.empty()) { + timer_.Start(base::TimeDelta::FromMilliseconds(timer_period_), + this, &VisibilityGroup::SendIdleEvents); + } + delegates_.insert(delegate); + } + + // Removes |delegate| from this visibility group. + void UnregisterDelegate(WebPluginDelegateImpl* delegate) { + // If a plugin changes visibility during idle event handling, it + // may be removed from this set while SendIdleEvents is still iterating; + // if that happens and it's next on the list, increment the iterator + // before erasing so that the iteration won't be corrupted. + if ((iterator_ != delegates_.end()) && (*iterator_ == delegate)) + ++iterator_; + size_t removed = delegates_.erase(delegate); + if (removed > 0 && delegates_.empty()) + timer_.Stop(); + } + + private: + // Fires off idle events for each delegate in the group. + void SendIdleEvents() { + for (iterator_ = delegates_.begin(); iterator_ != delegates_.end();) { + // Pre-increment so that the skip logic in UnregisterDelegates works. + WebPluginDelegateImpl* delegate = *(iterator_++); + delegate->FireIdleEvent(); + } + } + + int timer_period_; + base::RepeatingTimer<VisibilityGroup> timer_; + std::set<WebPluginDelegateImpl*> delegates_; + std::set<WebPluginDelegateImpl*>::iterator iterator_; + }; + + CarbonIdleEventSource() + : visible_delegates_(new VisibilityGroup(kVisibleIdlePeriodMs)), + hidden_delegates_(new VisibilityGroup(kHiddenIdlePeriodMs)) {} + + scoped_ptr<VisibilityGroup> visible_delegates_; + scoped_ptr<VisibilityGroup> hidden_delegates_; + + DISALLOW_COPY_AND_ASSIGN(CarbonIdleEventSource); +}; +#endif // !NP_NO_CARBON + +} // namespace + +// Helper to build and maintain a model of a drag entering the plugin but not +// starting there. See explanation in PlatformHandleInputEvent. +class ExternalDragTracker { + public: + ExternalDragTracker() : pressed_buttons_(0) {} + + // Returns true if an external drag is in progress. + bool IsDragInProgress() { return pressed_buttons_ != 0; }; + + // Returns true if the given event appears to be related to an external drag. + bool EventIsRelatedToDrag(const WebInputEvent& event); + + // Updates the tracking of whether an external drag is in progress--and if + // so what buttons it involves--based on the given event. + void UpdateDragStateFromEvent(const WebInputEvent& event); + + private: + // Returns the mask for just the button state in a WebInputEvent's modifiers. + static int WebEventButtonModifierMask(); + + // The WebInputEvent modifier flags for any buttons that were down when an + // external drag entered the plugin, and which and are still down now. + int pressed_buttons_; + + DISALLOW_COPY_AND_ASSIGN(ExternalDragTracker); +}; + +void ExternalDragTracker::UpdateDragStateFromEvent(const WebInputEvent& event) { + switch (event.type) { + case WebInputEvent::MouseEnter: + pressed_buttons_ = event.modifiers & WebEventButtonModifierMask(); + break; + case WebInputEvent::MouseUp: { + const WebMouseEvent* mouse_event = + static_cast<const WebMouseEvent*>(&event); + if (mouse_event->button == WebMouseEvent::ButtonLeft) + pressed_buttons_ &= ~WebInputEvent::LeftButtonDown; + if (mouse_event->button == WebMouseEvent::ButtonMiddle) + pressed_buttons_ &= ~WebInputEvent::MiddleButtonDown; + if (mouse_event->button == WebMouseEvent::ButtonRight) + pressed_buttons_ &= ~WebInputEvent::RightButtonDown; + break; + } + default: + break; + } +} + +bool ExternalDragTracker::EventIsRelatedToDrag(const WebInputEvent& event) { + const WebMouseEvent* mouse_event = static_cast<const WebMouseEvent*>(&event); + switch (event.type) { + case WebInputEvent::MouseUp: + // We only care about release of buttons that were part of the drag. + return ((mouse_event->button == WebMouseEvent::ButtonLeft && + (pressed_buttons_ & WebInputEvent::LeftButtonDown)) || + (mouse_event->button == WebMouseEvent::ButtonMiddle && + (pressed_buttons_ & WebInputEvent::MiddleButtonDown)) || + (mouse_event->button == WebMouseEvent::ButtonRight && + (pressed_buttons_ & WebInputEvent::RightButtonDown))); + case WebInputEvent::MouseEnter: + return (event.modifiers & WebEventButtonModifierMask()) != 0; + case WebInputEvent::MouseLeave: + case WebInputEvent::MouseMove: { + int event_buttons = (event.modifiers & WebEventButtonModifierMask()); + return (pressed_buttons_ && + pressed_buttons_ == event_buttons); + } + default: + return false; + } + return false; +} + +int ExternalDragTracker::WebEventButtonModifierMask() { + return WebInputEvent::LeftButtonDown | + WebInputEvent::RightButtonDown | + WebInputEvent::MiddleButtonDown; +} + +#pragma mark - +#pragma mark Core WebPluginDelegate implementation + +WebPluginDelegateImpl::WebPluginDelegateImpl( + gfx::PluginWindowHandle containing_view, + NPAPI::PluginInstance *instance) + : windowed_handle_(NULL), + // all Mac plugins are "windowless" in the Windows/X11 sense + windowless_(true), + plugin_(NULL), + instance_(instance), + parent_(containing_view), + quirks_(0), + buffer_context_(NULL), + layer_(nil), + surface_(NULL), + renderer_(nil), + plugin_has_focus_(false), + has_webkit_focus_(false), + containing_view_has_focus_(false), + containing_window_has_focus_(false), + initial_window_focus_(false), + container_is_visible_(false), + have_called_set_window_(false), + external_drag_tracker_(new ExternalDragTracker()), + handle_event_depth_(0), + first_set_window_call_(true) { + memset(&window_, 0, sizeof(window_)); +#ifndef NP_NO_CARBON + memset(&np_cg_context_, 0, sizeof(np_cg_context_)); +#endif +#ifndef NP_NO_QUICKDRAW + memset(&qd_port_, 0, sizeof(qd_port_)); +#endif + instance->set_windowless(true); +} + +WebPluginDelegateImpl::~WebPluginDelegateImpl() { + DestroyInstance(); + +#ifndef NP_NO_CARBON + if (np_cg_context_.window) { + CarbonPluginWindowTracker::SharedInstance()->DestroyDummyWindowForDelegate( + this, reinterpret_cast<WindowRef>(np_cg_context_.window)); + } +#endif +} + +bool WebPluginDelegateImpl::PlatformInitialize() { + // Don't set a NULL window handle on destroy for Mac plugins. This matches + // Safari and other Mac browsers (see PluginView::stop() in PluginView.cpp, + // where code to do so is surrounded by an #ifdef that excludes Mac OS X, or + // destroyPlugin in WebNetscapePluginView.mm, for examples). + quirks_ |= PLUGIN_QUIRK_DONT_SET_NULL_WINDOW_HANDLE_ON_DESTROY; + + // Mac plugins don't expect to be unloaded, and they don't always do so + // cleanly, so don't unload them at shutdown. + instance()->plugin_lib()->PreventLibraryUnload(); + +#ifndef NP_NO_QUICKDRAW + if (instance()->drawing_model() == NPDrawingModelQuickDraw) { + // For some QuickDraw plugins, we can sometimes get away with giving them + // a port pointing to a pixel buffer instead of a our actual dummy window. + // This gives us much better frame rates, because the window scraping we + // normally use is very slow. + // This breaks down if the plugin does anything complicated with the port + // (as QuickTime seems to during event handling, and sometimes when painting + // its controls), so we switch on the fly as necessary. (It might be + // possible to interpose sufficiently that we wouldn't have to switch back + // and forth, but the current approach gets us most of the benefit.) + // We can't do this at all with plugins that bypass the port entirely and + // attaches their own surface to the window. + // TODO(stuartmorgan): Test other QuickDraw plugins that we support and + // see if any others can use the fast path. + const WebPluginInfo& plugin_info = instance_->plugin_lib()->plugin_info(); + if (plugin_info.name.find(ASCIIToUTF16("QuickTime")) != string16::npos) + quirks_ |= PLUGIN_QUIRK_ALLOW_FASTER_QUICKDRAW_PATH; + } +#endif + +#ifndef NP_NO_CARBON + if (instance()->event_model() == NPEventModelCarbon) { + // Create a stand-in for the browser window so that the plugin will have + // a non-NULL WindowRef to which it can refer. + CarbonPluginWindowTracker* window_tracker = + CarbonPluginWindowTracker::SharedInstance(); + np_cg_context_.window = window_tracker->CreateDummyWindowForDelegate(this); + np_cg_context_.context = NULL; + UpdateDummyWindowBounds(gfx::Point(0, 0)); + } +#endif + + NPDrawingModel drawing_model = instance()->drawing_model(); + switch (drawing_model) { +#ifndef NP_NO_QUICKDRAW + case NPDrawingModelQuickDraw: + if (instance()->event_model() != NPEventModelCarbon) + return false; + qd_manager_.reset(new QuickDrawDrawingManager()); + qd_manager_->SetPluginWindow( + reinterpret_cast<WindowRef>(np_cg_context_.window)); + qd_port_.port = qd_manager_->port(); + window_.window = &qd_port_; + window_.type = NPWindowTypeDrawable; + break; +#endif + case NPDrawingModelCoreGraphics: +#ifndef NP_NO_CARBON + if (instance()->event_model() == NPEventModelCarbon) + window_.window = &np_cg_context_; +#endif + window_.type = NPWindowTypeDrawable; + break; + case NPDrawingModelCoreAnimation: + case NPDrawingModelInvalidatingCoreAnimation: { + if (instance()->event_model() != NPEventModelCocoa) + return false; + window_.type = NPWindowTypeDrawable; + // Ask the plug-in for the CALayer it created for rendering content. Have + // the renderer tell the browser to create a "windowed plugin" to host + // the IOSurface. + CALayer* layer = nil; + NPError err = instance()->NPP_GetValue(NPPVpluginCoreAnimationLayer, + reinterpret_cast<void*>(&layer)); + if (!err) { + if (drawing_model == NPDrawingModelCoreAnimation) { + // Create the timer; it will be started when we get a window handle. + redraw_timer_.reset(new base::RepeatingTimer<WebPluginDelegateImpl>); + } + layer_ = layer; + surface_ = new AcceleratedSurface; + surface_->Initialize(NULL, true); + renderer_ = [[CARenderer rendererWithCGLContext:surface_->context() + options:NULL] retain]; + [renderer_ setLayer:layer_]; + plugin_->BindFakePluginWindowHandle(false); + } + break; + } + default: + NOTREACHED(); + break; + } + + // TODO(stuartmorgan): We need real plugin container visibility information + // when the plugin is initialized; for now, assume it's visible. + // None of the calls SetContainerVisibility would make are useful at this + // point, so we just set the initial state directly. + container_is_visible_ = true; + + // Let the WebPlugin know that we are windowless (unless this is a + // Core Animation plugin, in which case BindFakePluginWindowHandle will take + // care of setting up the appropriate window handle). + if (!layer_) + plugin_->SetWindow(NULL); + +#ifndef NP_NO_CARBON + // If the plugin wants Carbon events, hook up to the source of idle events. + if (instance()->event_model() == NPEventModelCarbon) + UpdateIdleEventRate(); +#endif + + // QuickTime (in QD mode only) can crash if it gets other calls (e.g., + // NPP_Write) before it gets a SetWindow call, so call SetWindow (with a 0x0 + // rect) immediately. +#ifndef NP_NO_QUICKDRAW + if (instance()->drawing_model() == NPDrawingModelQuickDraw) { + const WebPluginInfo& plugin_info = instance_->plugin_lib()->plugin_info(); + if (plugin_info.name.find(ASCIIToUTF16("QuickTime")) != string16::npos) + WindowlessSetWindow(); + } +#endif + + return true; +} + +void WebPluginDelegateImpl::PlatformDestroyInstance() { +#ifndef NP_NO_CARBON + if (instance()->event_model() == NPEventModelCarbon) + CarbonIdleEventSource::SharedInstance()->UnregisterDelegate(this); +#endif + if (redraw_timer_.get()) + redraw_timer_->Stop(); + [renderer_ release]; + renderer_ = nil; + layer_ = nil; + if (surface_) { + surface_->Destroy(); + delete surface_; + surface_ = NULL; + } +} + +void WebPluginDelegateImpl::UpdateGeometryAndContext( + const gfx::Rect& window_rect, const gfx::Rect& clip_rect, + CGContextRef context) { + buffer_context_ = context; +#ifndef NP_NO_CARBON + if (instance()->event_model() == NPEventModelCarbon) { + // Update the structure that is passed to Carbon+CoreGraphics plugins in + // NPP_SetWindow before calling UpdateGeometry, since that will trigger an + // NPP_SetWindow call if the geometry changes (which is the only time the + // context would be different), and some plugins (e.g., Flash) have an + // internal cache of the context that they only update when NPP_SetWindow + // is called. + np_cg_context_.context = context; + } +#endif +#ifndef NP_NO_QUICKDRAW + if (instance()->drawing_model() == NPDrawingModelQuickDraw) + qd_manager_->SetTargetContext(context, window_rect.size()); +#endif + UpdateGeometry(window_rect, clip_rect); +} + +void WebPluginDelegateImpl::Paint(CGContextRef context, const gfx::Rect& rect) { + WindowlessPaint(context, rect); + +#ifndef NP_NO_QUICKDRAW + // Paint events are our cue to dump the current plugin bits into the buffer + // context if we are dealing with a QuickDraw plugin. + if (instance()->drawing_model() == NPDrawingModelQuickDraw) { + qd_manager_->UpdateContext(); + } +#endif +} + +void WebPluginDelegateImpl::Print(CGContextRef context) { + NOTIMPLEMENTED(); +} + +void WebPluginDelegateImpl::SetFocus(bool focused) { + // This is called when internal WebKit focus (the focused element on the page) + // changes, but plugins need to know about actual first responder status, so + // we have an extra layer of focus tracking. + has_webkit_focus_ = focused; + if (containing_view_has_focus_) + SetPluginHasFocus(focused); +} + +bool WebPluginDelegateImpl::PlatformHandleInputEvent( + const WebInputEvent& event, WebCursorInfo* cursor_info) { + DCHECK(cursor_info != NULL); + + // If we get an event before we've set up the plugin, bail. + if (!have_called_set_window_) + return false; +#ifndef NP_NO_CARBON + if (instance()->event_model() == NPEventModelCarbon && + !np_cg_context_.context) { + return false; + } +#endif + + if (WebInputEvent::isMouseEventType(event.type) || + event.type == WebInputEvent::MouseWheel) { + // Ideally we would compute the content origin from the web event using the + // code below as a safety net for missed content area location changes. + // Because of <http://crbug.com/9996>, however, only globalX/Y are right if + // the page has been zoomed, so for now the coordinates we get aren't + // trustworthy enough to use for corrections. +#if PLUGIN_SCALING_FIXED + // Check our plugin location before we send the event to the plugin, just + // in case we somehow missed a plugin frame change. + const WebMouseEvent* mouse_event = + static_cast<const WebMouseEvent*>(&event); + gfx::Point content_origin( + mouse_event->globalX - mouse_event->x - window_rect_.x(), + mouse_event->globalY - mouse_event->y - window_rect_.y()); + if (content_origin.x() != content_area_origin_.x() || + content_origin.y() != content_area_origin_.y()) { + DLOG(WARNING) << "Stale plugin content area location: " + << content_area_origin_ << " instead of " + << content_origin; + SetContentAreaOrigin(content_origin); + } +#endif + + current_windowless_cursor_.GetCursorInfo(cursor_info); + } + +#ifndef NP_NO_CARBON + if (instance()->event_model() == NPEventModelCarbon) { +#ifndef NP_NO_QUICKDRAW + if (instance()->drawing_model() == NPDrawingModelQuickDraw) { + if (quirks_ & PLUGIN_QUIRK_ALLOW_FASTER_QUICKDRAW_PATH) { + // Mouse event handling doesn't work correctly in the fast path mode, + // so any time we get a mouse event turn the fast path off, but set a + // time to switch it on again (we don't rely just on MouseLeave because + // we don't want poor performance in the case of clicking the play + // button and then leaving the mouse there). + // This isn't perfect (specifically, click-and-hold doesn't seem to work + // if the fast path is on), but the slight regression is worthwhile + // for the improved framerates. + if (WebInputEvent::isMouseEventType(event.type)) { + if (event.type == WebInputEvent::MouseLeave) { + SetQuickDrawFastPathEnabled(true); + } else { + SetQuickDrawFastPathEnabled(false); + } + // Make sure the plugin wasn't destroyed during the switch. + if (!instance()) + return false; + } + } + + qd_manager_->MakePortCurrent(); + } +#endif + + if (event.type == WebInputEvent::MouseMove) { + return true; // The recurring FireIdleEvent will send null events. + } + } +#endif + + ScopedActiveDelegate active_delegate(this); + + // Create the plugin event structure. + NPEventModel event_model = instance()->event_model(); + scoped_ptr<PluginWebEventConverter> event_converter( + PluginWebEventConverterFactory::CreateConverterForModel(event_model)); + if (!(event_converter.get() && event_converter->InitWithEvent(event))) { + // Silently consume any keyboard event types that we don't handle, so that + // they don't fall through to the page. + if (WebInputEvent::isKeyboardEventType(event.type)) + return true; + return false; + } + void* plugin_event = event_converter->plugin_event(); + + if (instance()->event_model() == NPEventModelCocoa) { + // We recieve events related to drags starting outside the plugin, but the + // NPAPI Cocoa event model spec says plugins shouldn't receive them, so + // filter them out. + // If we add a page capture mode at the WebKit layer (like the plugin + // capture mode that handles drags starting inside) this can be removed. + bool drag_related = external_drag_tracker_->EventIsRelatedToDrag(event); + external_drag_tracker_->UpdateDragStateFromEvent(event); + if (drag_related) { + if (event.type == WebInputEvent::MouseUp && + !external_drag_tracker_->IsDragInProgress()) { + // When an external drag ends, we need to synthesize a MouseEntered. + NPCocoaEvent enter_event = *(static_cast<NPCocoaEvent*>(plugin_event)); + enter_event.type = NPCocoaEventMouseEntered; + NPAPI::ScopedCurrentPluginEvent event_scope(instance(), &enter_event); + instance()->NPP_HandleEvent(&enter_event); + } + return false; + } + } + +#ifndef PLUGIN_SCALING_FIXED + // Because of <http://crbug.com/9996>, the non-global coordinates we get for + // zoomed pages are wrong. As a temporary hack around that bug, override the + // coordinates we are given with ones computed based on our knowledge of where + // the plugin is on screen. We only need to do this for Cocoa, since Carbon + // only uses the global coordinates. + if (instance()->event_model() == NPEventModelCocoa && + (WebInputEvent::isMouseEventType(event.type) || + event.type == WebInputEvent::MouseWheel)) { + const WebMouseEvent* mouse_event = + static_cast<const WebMouseEvent*>(&event); + NPCocoaEvent* cocoa_event = static_cast<NPCocoaEvent*>(plugin_event); + cocoa_event->data.mouse.pluginX = + mouse_event->globalX - content_area_origin_.x() - window_rect_.x(); + cocoa_event->data.mouse.pluginY = + mouse_event->globalY - content_area_origin_.y() - window_rect_.y(); + } +#endif + + // Send the plugin the event. + scoped_ptr<NPAPI::ScopedCurrentPluginEvent> event_scope(NULL); + if (instance()->event_model() == NPEventModelCocoa) { + event_scope.reset(new NPAPI::ScopedCurrentPluginEvent( + instance(), static_cast<NPCocoaEvent*>(plugin_event))); + } + bool handled = instance()->NPP_HandleEvent(plugin_event) != 0; + + // Plugins don't give accurate information about whether or not they handled + // events, so browsers on the Mac ignore the return value. + // Scroll events are the exception, since the Cocoa spec defines a meaning + // for the return value. + if (WebInputEvent::isMouseEventType(event.type)) { + handled = true; + } else if (WebInputEvent::isKeyboardEventType(event.type)) { + // For Command-key events, trust the return value since eating all menu + // shortcuts is not ideal. + // TODO(stuartmorgan): Implement the advanced key handling spec, and trust + // trust the key event return value from plugins that implement it. + if (!(event.modifiers & WebInputEvent::MetaKey)) + handled = true; + } + + return handled; +} + +void WebPluginDelegateImpl::InstallMissingPlugin() { + NOTIMPLEMENTED(); +} + +#pragma mark - + +void WebPluginDelegateImpl::WindowlessUpdateGeometry( + const gfx::Rect& window_rect, + const gfx::Rect& clip_rect) { + gfx::Rect old_clip_rect = clip_rect_; + cached_clip_rect_ = clip_rect; + if (container_is_visible_) // Remove check when cached_clip_rect_ is removed. + clip_rect_ = clip_rect; + bool clip_rect_changed = (clip_rect_ != old_clip_rect); + bool window_size_changed = (window_rect.size() != window_rect_.size()); + + bool force_set_window = false; +#ifndef NP_NO_QUICKDRAW + // In a QuickDraw plugin, a geometry update might have caused a port change; + // if so, we need to call SetWindow even if nothing else changed. + if (qd_manager_.get() && (qd_port_.port != qd_manager_->port())) { + qd_port_.port = qd_manager_->port(); + force_set_window = true; + } +#endif + + if (window_rect == window_rect_ && !clip_rect_changed && !force_set_window) + return; + + if (old_clip_rect.IsEmpty() != clip_rect_.IsEmpty()) { + PluginVisibilityChanged(); + } + + SetPluginRect(window_rect); + +#ifndef NP_NO_QUICKDRAW + if (window_size_changed && qd_manager_.get() && + qd_manager_->IsFastPathEnabled()) { + // If the window size has changed, we need to turn off the fast path so that + // the full redraw goes to the window and we get a correct baseline paint. + SetQuickDrawFastPathEnabled(false); + return; // SetQuickDrawFastPathEnabled will call SetWindow for us. + } +#endif + + if (window_size_changed || clip_rect_changed || force_set_window) + WindowlessSetWindow(); +} + +void WebPluginDelegateImpl::WindowlessPaint(gfx::NativeDrawingContext context, + const gfx::Rect& damage_rect) { + // If we get a paint event before we are completely set up (e.g., a nested + // call while the plugin is still in NPP_SetWindow), bail. + if (!have_called_set_window_ || !buffer_context_) + return; + DCHECK(buffer_context_ == context); + + static StatsRate plugin_paint("Plugin.Paint"); + StatsScope<StatsRate> scope(plugin_paint); + + // Plugin invalidates trigger asynchronous paints with the original + // invalidation rect; the plugin may be resized before the paint is handled, + // so we need to ensure that the damage rect is still sane. + const gfx::Rect paint_rect(damage_rect.Intersect( + gfx::Rect(0, 0, window_rect_.width(), window_rect_.height()))); + + ScopedActiveDelegate active_delegate(this); + +#ifndef NP_NO_QUICKDRAW + if (instance()->drawing_model() == NPDrawingModelQuickDraw) + qd_manager_->MakePortCurrent(); +#endif + + CGContextSaveGState(context); + + switch (instance()->event_model()) { +#ifndef NP_NO_CARBON + case NPEventModelCarbon: { + NPEvent paint_event = { 0 }; + paint_event.what = updateEvt; + paint_event.message = reinterpret_cast<uint32>(np_cg_context_.window); + paint_event.when = TickCount(); + instance()->NPP_HandleEvent(&paint_event); + break; + } +#endif + case NPEventModelCocoa: { + NPCocoaEvent paint_event; + memset(&paint_event, 0, sizeof(NPCocoaEvent)); + paint_event.type = NPCocoaEventDrawRect; + paint_event.data.draw.context = context; + paint_event.data.draw.x = paint_rect.x(); + paint_event.data.draw.y = paint_rect.y(); + paint_event.data.draw.width = paint_rect.width(); + paint_event.data.draw.height = paint_rect.height(); + instance()->NPP_HandleEvent(&paint_event); + break; + } + } + + // The backing buffer can change during the call to NPP_HandleEvent, in which + // case the old context is (or is about to be) invalid. + if (context == buffer_context_) + CGContextRestoreGState(context); +} + +void WebPluginDelegateImpl::WindowlessSetWindow() { + if (!instance()) + return; + + window_.x = 0; + window_.y = 0; + window_.height = window_rect_.height(); + window_.width = window_rect_.width(); + window_.clipRect.left = clip_rect_.x(); + window_.clipRect.top = clip_rect_.y(); + window_.clipRect.right = clip_rect_.x() + clip_rect_.width(); + window_.clipRect.bottom = clip_rect_.y() + clip_rect_.height(); + + NPError err = instance()->NPP_SetWindow(&window_); + + // Send an appropriate window focus event after the first SetWindow. + if (!have_called_set_window_) { + have_called_set_window_ = true; + SetWindowHasFocus(initial_window_focus_); + } + +#ifndef NP_NO_QUICKDRAW + if ((quirks_ & PLUGIN_QUIRK_ALLOW_FASTER_QUICKDRAW_PATH) && + !qd_manager_->IsFastPathEnabled() && !clip_rect_.IsEmpty()) { + // Give the plugin a few seconds to stabilize so we get a good initial paint + // to use as a baseline, then switch to the fast path. + fast_path_enable_tick_ = base::TimeTicks::Now() + + base::TimeDelta::FromSeconds(3); + } +#endif + + DCHECK(err == NPERR_NO_ERROR); +} + +#pragma mark - + +bool WebPluginDelegateImpl::WindowedCreatePlugin() { + NOTREACHED(); + return false; +} + +void WebPluginDelegateImpl::WindowedDestroyWindow() { + NOTREACHED(); +} + +bool WebPluginDelegateImpl::WindowedReposition(const gfx::Rect& window_rect, + const gfx::Rect& clip_rect) { + NOTREACHED(); + return false; +} + +void WebPluginDelegateImpl::WindowedSetWindow() { + NOTREACHED(); +} + +#pragma mark - +#pragma mark Mac Extensions + +void WebPluginDelegateImpl::PluginDidInvalidate() { + if (instance()->drawing_model() == NPDrawingModelInvalidatingCoreAnimation) + DrawLayerInSurface(); +} + +WebPluginDelegateImpl* WebPluginDelegateImpl::GetActiveDelegate() { + return g_active_delegate; +} + +void WebPluginDelegateImpl::SetWindowHasFocus(bool has_focus) { + // If we get a window focus event before calling SetWindow, just remember the + // states (WindowlessSetWindow will then send it on the first call). + if (!have_called_set_window_) { + initial_window_focus_ = has_focus; + return; + } + + if (has_focus == containing_window_has_focus_) + return; + containing_window_has_focus_ = has_focus; + +#ifndef NP_NO_QUICKDRAW + // Make sure controls repaint with the correct look. + if (quirks_ & PLUGIN_QUIRK_ALLOW_FASTER_QUICKDRAW_PATH) + SetQuickDrawFastPathEnabled(false); +#endif + + ScopedActiveDelegate active_delegate(this); + switch (instance()->event_model()) { +#ifndef NP_NO_CARBON + case NPEventModelCarbon: { + NPEvent focus_event = { 0 }; + focus_event.what = activateEvt; + if (has_focus) + focus_event.modifiers |= activeFlag; + focus_event.message = + reinterpret_cast<unsigned long>(np_cg_context_.window); + focus_event.when = TickCount(); + instance()->NPP_HandleEvent(&focus_event); + break; + } +#endif + case NPEventModelCocoa: { + NPCocoaEvent focus_event; + memset(&focus_event, 0, sizeof(focus_event)); + focus_event.type = NPCocoaEventWindowFocusChanged; + focus_event.data.focus.hasFocus = has_focus; + instance()->NPP_HandleEvent(&focus_event); + break; + } + } +} + +void WebPluginDelegateImpl::SetPluginHasFocus(bool has_focus) { + if (!have_called_set_window_) + return; + + if (has_focus == plugin_has_focus_) + return; + plugin_has_focus_ = has_focus; + + ScopedActiveDelegate active_delegate(this); + + switch (instance()->event_model()) { +#ifndef NP_NO_CARBON + case NPEventModelCarbon: { + NPEvent focus_event = { 0 }; + if (plugin_has_focus_) + focus_event.what = NPEventType_GetFocusEvent; + else + focus_event.what = NPEventType_LoseFocusEvent; + focus_event.when = TickCount(); + instance()->NPP_HandleEvent(&focus_event); + break; + } +#endif + case NPEventModelCocoa: { + NPCocoaEvent focus_event; + memset(&focus_event, 0, sizeof(focus_event)); + focus_event.type = NPCocoaEventFocusChanged; + focus_event.data.focus.hasFocus = plugin_has_focus_; + instance()->NPP_HandleEvent(&focus_event); + break; + } + } +} + +void WebPluginDelegateImpl::SetContentAreaHasFocus(bool has_focus) { + containing_view_has_focus_ = has_focus; + SetPluginHasFocus(containing_view_has_focus_ && has_webkit_focus_); +} + +void WebPluginDelegateImpl::SetContainerVisibility(bool is_visible) { + if (is_visible == container_is_visible_) + return; + container_is_visible_ = is_visible; + + // TODO(stuartmorgan): This is a temporary workarond for + // <http://crbug.com/34266>. When that is fixed, the cached_clip_rect_ code + // should all be removed. + if (is_visible) { + clip_rect_ = cached_clip_rect_; + } else { + clip_rect_.set_width(0); + clip_rect_.set_height(0); + } + + // If the plugin is changing visibility, let the plugin know. If it's scrolled + // off screen (i.e., cached_clip_rect_ is empty), then container visibility + // doesn't change anything. + if (!cached_clip_rect_.IsEmpty()) { + PluginVisibilityChanged(); + WindowlessSetWindow(); + } + + // When the plugin become visible, send an empty invalidate. If there were any + // pending invalidations this will trigger a paint event for the damaged + // region, and if not it's a no-op. This is necessary since higher levels + // that would normally do this weren't responsible for the clip_rect_ change). + if (!clip_rect_.IsEmpty()) + instance()->webplugin()->InvalidateRect(gfx::Rect()); +} + +void WebPluginDelegateImpl::WindowFrameChanged(gfx::Rect window_frame, + gfx::Rect view_frame) { + instance()->set_window_frame(window_frame); + SetContentAreaOrigin(gfx::Point(view_frame.x(), view_frame.y())); +} + +void WebPluginDelegateImpl::SetThemeCursor(ThemeCursor cursor) { + current_windowless_cursor_.InitFromThemeCursor(cursor); +} + +void WebPluginDelegateImpl::SetCursor(const Cursor* cursor) { + current_windowless_cursor_.InitFromCursor(cursor); +} + +void WebPluginDelegateImpl::SetNSCursor(NSCursor* cursor) { + current_windowless_cursor_.InitFromNSCursor(cursor); +} + +#pragma mark - +#pragma mark Internal Tracking + +void WebPluginDelegateImpl::SetPluginRect(const gfx::Rect& rect) { + bool plugin_size_changed = rect.width() != window_rect_.width() || + rect.height() != window_rect_.height(); + window_rect_ = rect; + PluginScreenLocationChanged(); + if (plugin_size_changed) + UpdateAcceleratedSurface(); +} + +void WebPluginDelegateImpl::SetContentAreaOrigin(const gfx::Point& origin) { + content_area_origin_ = origin; + PluginScreenLocationChanged(); +} + +void WebPluginDelegateImpl::PluginScreenLocationChanged() { + gfx::Point plugin_origin(content_area_origin_.x() + window_rect_.x(), + content_area_origin_.y() + window_rect_.y()); + instance()->set_plugin_origin(plugin_origin); + +#ifndef NP_NO_CARBON + if (instance()->event_model() == NPEventModelCarbon) { + UpdateDummyWindowBounds(plugin_origin); + } +#endif +} + +void WebPluginDelegateImpl::PluginVisibilityChanged() { +#ifndef NP_NO_CARBON + if (instance()->event_model() == NPEventModelCarbon) + UpdateIdleEventRate(); +#endif + if (instance()->drawing_model() == NPDrawingModelCoreAnimation) { + bool plugin_visible = container_is_visible_ && !clip_rect_.IsEmpty(); + if (plugin_visible && !redraw_timer_->IsRunning() && windowed_handle()) { + redraw_timer_->Start( + base::TimeDelta::FromMilliseconds(kCoreAnimationRedrawPeriodMs), + this, &WebPluginDelegateImpl::DrawLayerInSurface); + } else if (!plugin_visible) { + redraw_timer_->Stop(); + } + } +} + +#pragma mark - +#pragma mark Core Animation Support + +void WebPluginDelegateImpl::DrawLayerInSurface() { + // If we haven't plumbed up the surface yet, don't try to draw. + if (!windowed_handle()) + return; + + surface_->MakeCurrent(); + + surface_->Clear(window_rect_); + + [renderer_ beginFrameAtTime:CACurrentMediaTime() timeStamp:NULL]; + if (CGRectIsEmpty([renderer_ updateBounds])) { + // If nothing has changed, we are done. + [renderer_ endFrame]; + return; + } + CGRect layerRect = [layer_ bounds]; + [renderer_ addUpdateRect:layerRect]; + [renderer_ render]; + [renderer_ endFrame]; + + surface_->SwapBuffers(); + plugin_->AcceleratedFrameBuffersDidSwap(windowed_handle()); +} + +// Update the size of the IOSurface to match the current size of the plug-in, +// then tell the browser host view so it can adjust its bookkeeping and CALayer +// appropriately. +void WebPluginDelegateImpl::UpdateAcceleratedSurface() { + // Will only have a window handle when using a Core Animation drawing model. + if (!windowed_handle() || !layer_) + return; + + [layer_ setFrame:CGRectMake(0, 0, + window_rect_.width(), window_rect_.height())]; + [renderer_ setBounds:[layer_ bounds]]; + + uint64 io_surface_id = surface_->SetSurfaceSize(window_rect_.size()); + if (io_surface_id) { + plugin_->SetAcceleratedSurface(windowed_handle(), + window_rect_.width(), + window_rect_.height(), + io_surface_id); + } +} + +void WebPluginDelegateImpl::set_windowed_handle( + gfx::PluginWindowHandle handle) { + windowed_handle_ = handle; + UpdateAcceleratedSurface(); + // Kick off the drawing timer, if necessary. + PluginVisibilityChanged(); +} + +#pragma mark - +#pragma mark Carbon Event support + +#ifndef NP_NO_CARBON +void WebPluginDelegateImpl::UpdateDummyWindowBounds( + const gfx::Point& plugin_origin) { + WindowRef window = reinterpret_cast<WindowRef>(np_cg_context_.window); + Rect current_bounds; + GetWindowBounds(window, kWindowContentRgn, ¤t_bounds); + + Rect new_bounds; + // We never want to resize the window to 0x0, so if the plugin is 0x0 just + // move the window without resizing it. + if (window_rect_.width() > 0 && window_rect_.height() > 0) { + SetRect(&new_bounds, 0, 0, window_rect_.width(), window_rect_.height()); + OffsetRect(&new_bounds, plugin_origin.x(), plugin_origin.y()); + } else { + new_bounds = current_bounds; + OffsetRect(&new_bounds, plugin_origin.x() - current_bounds.left, + plugin_origin.y() - current_bounds.top); + } + + if (new_bounds.left != current_bounds.left || + new_bounds.top != current_bounds.top || + new_bounds.right != current_bounds.right || + new_bounds.bottom != current_bounds.bottom) + SetWindowBounds(window, kWindowContentRgn, &new_bounds); +} + +void WebPluginDelegateImpl::UpdateIdleEventRate() { + bool plugin_visible = container_is_visible_ && !clip_rect_.IsEmpty(); + CarbonIdleEventSource::SharedInstance()->RegisterDelegate(this, + plugin_visible); +} + +void WebPluginDelegateImpl::FireIdleEvent() { + // Avoid a race condition between IO and UI threads during plugin shutdown + if (!instance()) + return; + // Don't send idle events until we've called SetWindow. + if (!have_called_set_window_) + return; + +#ifndef NP_NO_QUICKDRAW + // Check whether it's time to turn the QuickDraw fast path back on. + if (!fast_path_enable_tick_.is_null() && + (base::TimeTicks::Now() > fast_path_enable_tick_)) { + SetQuickDrawFastPathEnabled(true); + fast_path_enable_tick_ = base::TimeTicks(); + } +#endif + + ScopedActiveDelegate active_delegate(this); + +#ifndef NP_NO_QUICKDRAW + if (instance()->drawing_model() == NPDrawingModelQuickDraw) + qd_manager_->MakePortCurrent(); +#endif + + // Send an idle event so that the plugin can do background work + NPEvent np_event = {0}; + np_event.what = nullEvent; + np_event.when = TickCount(); + np_event.modifiers = GetCurrentKeyModifiers(); + if (!Button()) + np_event.modifiers |= btnState; + HIPoint mouse_location; + HIGetMousePosition(kHICoordSpaceScreenPixel, NULL, &mouse_location); + np_event.where.h = mouse_location.x; + np_event.where.v = mouse_location.y; + instance()->NPP_HandleEvent(&np_event); + +#ifndef NP_NO_QUICKDRAW + // Quickdraw-based plugins can draw at any time, so tell the renderer to + // repaint. + if (instance() && instance()->drawing_model() == NPDrawingModelQuickDraw) + instance()->webplugin()->Invalidate(); +#endif +} +#endif // !NP_NO_CARBON + +#pragma mark - +#pragma mark QuickDraw Support + +#ifndef NP_NO_QUICKDRAW +void WebPluginDelegateImpl::SetQuickDrawFastPathEnabled(bool enabled) { + if (!enabled) { + // Wait a couple of seconds, then turn the fast path back on. If we're + // turning it off for event handling, that ensures that the common case of + // move-mouse-then-click works (as well as making it likely that a second + // click attempt will work if the first one fails). If we're turning it + // off to force a new baseline image, this leaves plenty of time for the + // plugin to draw. + fast_path_enable_tick_ = base::TimeTicks::Now() + + base::TimeDelta::FromSeconds(2); + } + + if (enabled == qd_manager_->IsFastPathEnabled()) + return; + if (enabled && clip_rect_.IsEmpty()) { + // Don't switch to the fast path while the plugin is completely clipped; + // we can only switch when the window has an up-to-date image for us to + // scrape. We'll automatically switch after we become visible again. + return; + } + + qd_manager_->SetFastPathEnabled(enabled); + qd_port_.port = qd_manager_->port(); + WindowlessSetWindow(); + // Send a paint event so that the new buffer gets updated immediately. + WindowlessPaint(buffer_context_, clip_rect_); +} +#endif // !NP_NO_QUICKDRAW |