// Copyright (c) 2011 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 #import #include "webkit/plugins/npapi/webplugin_delegate_impl.h" #include #include #include #include "base/file_util.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop.h" #include "base/metrics/stats_counters.h" #include "base/string_util.h" #include "base/utf_string_conversions.h" #include "base/sys_info.h" #include "base/sys_string_conversions.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebInputEvent.h" #include "webkit/glue/webkit_glue.h" #include "webkit/plugins/npapi/plugin_instance.h" #include "webkit/plugins/npapi/plugin_lib.h" #include "webkit/plugins/npapi/plugin_list.h" #include "webkit/plugins/npapi/plugin_stream_url.h" #include "webkit/plugins/npapi/plugin_web_event_converter_mac.h" #include "webkit/plugins/npapi/webplugin.h" #include "webkit/plugins/npapi/webplugin_accelerated_surface_mac.h" #ifndef NP_NO_CARBON #include "webkit/plugins/npapi/carbon_plugin_window_tracker_mac.h" #endif #ifndef NP_NO_QUICKDRAW #include "webkit/plugins/npapi/quickdraw_drawing_manager_mac.h" #endif using WebKit::WebCursorInfo; using WebKit::WebKeyboardEvent; using WebKit::WebInputEvent; using WebKit::WebMouseEvent; using WebKit::WebMouseWheelEvent; // 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 webkit { namespace npapi { namespace { const int kCoreAnimationRedrawPeriodMs = 10; // 100 Hz 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 timer_; std::set delegates_; std::set::iterator iterator_; }; CarbonIdleEventSource() : visible_delegates_(new VisibilityGroup(kVisibleIdlePeriodMs)), hidden_delegates_(new VisibilityGroup(kHiddenIdlePeriodMs)) {} scoped_ptr visible_delegates_; scoped_ptr 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(&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(&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, 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), containing_window_has_focus_(false), initial_window_focus_(false), container_is_visible_(false), have_called_set_window_(false), ime_enabled_(false), keyup_ignore_count_(0), external_drag_tracker_(new ExternalDragTracker()), handle_event_depth_(0), first_set_window_call_(true), plugin_has_focus_(false), has_webkit_focus_(false), containing_view_has_focus_(true), creation_succeeded_(false) { 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(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(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. // Create a surface to host it, and request a "window" handle to identify // the surface. CALayer* layer = nil; NPError err = instance()->NPP_GetValue(NPPVpluginCoreAnimationLayer, reinterpret_cast(&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); } layer_ = layer; surface_ = plugin_->GetAcceleratedSurface(); // If surface initialization fails for some reason, just continue // without any drawing; returning false would be a more confusing user // experience (since it triggers a missing plugin placeholder). if (surface_ && surface_->context()) { renderer_ = [[CARenderer rendererWithCGLContext:surface_->context() options:NULL] retain]; [renderer_ setLayer:layer_]; } plugin_->BindFakePluginWindowHandle(false); } break; } default: NOTREACHED(); break; } // 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; } 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(); } 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) { // 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(&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); } current_windowless_cursor_.GetCursorInfo(cursor_info); } // Per the Cocoa Plugin IME spec, plugins shoudn't receive keydown or keyup // events while composition is in progress. Treat them as handled, however, // since IME is consuming them on behalf of the plugin. if ((event.type == WebInputEvent::KeyDown && ime_enabled_) || (event.type == WebInputEvent::KeyUp && keyup_ignore_count_)) { // Composition ends on a keydown, so ime_enabled_ will be false at keyup; // because the keydown wasn't sent to the plugin, the keyup shouldn't be // either (per the spec). if (event.type == WebInputEvent::KeyDown) ++keyup_ignore_count_; else --keyup_ignore_count_; return true; } #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 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(plugin_event)); enter_event.type = NPCocoaEventMouseEntered; ScopedCurrentPluginEvent event_scope(instance(), &enter_event); instance()->NPP_HandleEvent(&enter_event); } return false; } } // Send the plugin the event. scoped_ptr event_scope(NULL); if (instance()->event_model() == NPEventModelCocoa) { event_scope.reset(new ScopedCurrentPluginEvent( instance(), static_cast(plugin_event))); } int16_t handle_response = instance()->NPP_HandleEvent(plugin_event); bool handled = handle_response != kNPEventNotHandled; // Start IME if requested by the plugin. if (handled && handle_response == kNPEventStartIME && event.type == WebInputEvent::KeyDown) { StartIme(); ++keyup_ignore_count_; } // 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 base::StatsRate plugin_paint("Plugin.Paint"); base::StatsScope 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(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(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; } } } bool WebPluginDelegateImpl::PlatformSetPluginHasFocus(bool focused) { if (!have_called_set_window_) return false; plugin_->FocusChanged(focused); ScopedActiveDelegate active_delegate(this); switch (instance()->event_model()) { #ifndef NP_NO_CARBON case NPEventModelCarbon: { NPEvent focus_event = { 0 }; if (focused) 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 = focused; instance()->NPP_HandleEvent(&focus_event); break; } } return true; } 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 // . 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(const gfx::Rect& window_frame, const gfx::Rect& view_frame) { instance()->set_window_frame(window_frame); SetContentAreaOrigin(gfx::Point(view_frame.x(), view_frame.y())); } void WebPluginDelegateImpl::ImeCompositionCompleted(const string16& text) { if (instance()->event_model() != NPEventModelCocoa) { DLOG(ERROR) << "IME notification receieved in Carbon event model"; return; } ime_enabled_ = false; // If |text| is empty this was just called to tell us composition was // cancelled externally (e.g., the user pressed esc). if (!text.empty()) { NPCocoaEvent text_event; memset(&text_event, 0, sizeof(NPCocoaEvent)); text_event.type = NPCocoaEventTextInput; text_event.data.text.text = reinterpret_cast(base::SysUTF16ToNSString(text)); instance()->NPP_HandleEvent(&text_event); } } #ifndef NP_NO_CARBON void WebPluginDelegateImpl::SetThemeCursor(ThemeCursor cursor) { current_windowless_cursor_.InitFromThemeCursor(cursor); } void WebPluginDelegateImpl::SetCarbonCursor(const Cursor* cursor) { current_windowless_cursor_.InitFromCursor(cursor); } #endif 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(); } } } void WebPluginDelegateImpl::StartIme() { if (instance()->event_model() != NPEventModelCocoa || !IsImeSupported()) { return; } if (ime_enabled_) return; ime_enabled_ = true; plugin_->StartIme(); } bool WebPluginDelegateImpl::IsImeSupported() { // Currently the plugin IME implementation only works on 10.6. static BOOL sImeSupported = NO; static BOOL sHaveCheckedSupport = NO; if (!sHaveCheckedSupport) { int32 major, minor, bugfix; base::SysInfo::OperatingSystemVersionNumbers(&major, &minor, &bugfix); sImeSupported = major > 10 || (major == 10 && minor > 5); sHaveCheckedSupport = YES; } return sImeSupported; } #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() || !renderer_) return; [renderer_ beginFrameAtTime:CACurrentMediaTime() timeStamp:NULL]; if (CGRectIsEmpty([renderer_ updateBounds])) { // If nothing has changed, we are done. [renderer_ endFrame]; return; } surface_->StartDrawing(); CGRect layerRect = [layer_ bounds]; [renderer_ addUpdateRect:layerRect]; [renderer_ render]; [renderer_ endFrame]; surface_->EndDrawing(); } // Update the size of the surface to match the current size of the plug-in. void WebPluginDelegateImpl::UpdateAcceleratedSurface() { // Will only have a window handle when using a Core Animation drawing model. if (!windowed_handle() || !layer_) return; [CATransaction begin]; [CATransaction setValue:[NSNumber numberWithInt:0] forKey:kCATransactionAnimationDuration]; [layer_ setFrame:CGRectMake(0, 0, window_rect_.width(), window_rect_.height())]; [CATransaction commit]; [renderer_ setBounds:[layer_ bounds]]; surface_->SetSize(window_rect_.size()); } void WebPluginDelegateImpl::set_windowed_handle( gfx::PluginWindowHandle handle) { windowed_handle_ = handle; surface_->SetWindowHandle(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(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 } // namespace npapi } // namespace webkit