| // Copyright (c) 2006-2008 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> |
| |
| #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 "base/timer.h" |
| #include "third_party/WebKit/WebKit/chromium/public/WebInputEvent.h" |
| #include "webkit/default_plugin/plugin_impl.h" |
| #include "webkit/glue/webplugin.h" |
| #include "webkit/glue/plugins/coregraphics_private_symbols_mac.h" |
| #include "webkit/glue/plugins/plugin_constants_win.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/webkit_glue.h" |
| |
| #ifndef NP_NO_CARBON |
| #include "webkit/glue/plugins/carbon_plugin_window_tracker_mac.h" |
| #endif |
| |
| // If we're compiling support for the QuickDraw drawing model, turn off GCC |
| // warnings about deprecated functions (since QuickDraw is a deprecated API). |
| // According to the GCC documentation, this can only be done per file, not |
| // pushed and popped like some options can be. |
| #ifndef NP_NO_QUICKDRAW |
| #pragma GCC diagnostic ignored "-Wdeprecated-declarations" |
| #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; |
| |
| // 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 { |
| |
| base::LazyInstance<std::set<WebPluginDelegateImpl*> > g_active_delegates( |
| base::LINKER_INITIALIZED); |
| |
| 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 |
| |
| WebPluginDelegateImpl::WebPluginDelegateImpl( |
| gfx::PluginWindowHandle containing_view, |
| NPAPI::PluginInstance *instance) |
| : windowless_needs_set_window_(true), |
| // all Mac plugins are "windowless" in the Windows/X11 sense |
| windowless_(true), |
| plugin_(NULL), |
| instance_(instance), |
| parent_(containing_view), |
| quirks_(0), |
| have_focus_(false), |
| focus_notifier_(NULL), |
| containing_window_has_focus_(false), |
| initial_window_focus_(false), |
| container_is_visible_(false), |
| have_called_set_window_(false), |
| handle_event_depth_(0) { |
| memset(&window_, 0, sizeof(window_)); |
| #ifndef NP_NO_CARBON |
| memset(&cg_context_, 0, sizeof(cg_context_)); |
| #endif |
| #ifndef NP_NO_QUICKDRAW |
| memset(&qd_port_, 0, sizeof(qd_port_)); |
| #endif |
| instance->set_windowless(true); |
| |
| std::set<WebPluginDelegateImpl*>* delegates = g_active_delegates.Pointer(); |
| delegates->insert(this); |
| } |
| |
| WebPluginDelegateImpl::~WebPluginDelegateImpl() { |
| std::set<WebPluginDelegateImpl*>* delegates = g_active_delegates.Pointer(); |
| delegates->erase(this); |
| |
| DestroyInstance(); |
| |
| #ifndef NP_NO_CARBON |
| if (cg_context_.window) { |
| CarbonPluginWindowTracker::SharedInstance()->DestroyDummyWindowForDelegate( |
| this, reinterpret_cast<WindowRef>(cg_context_.window)); |
| } |
| #endif |
| } |
| |
| void 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; |
| |
| // Some plugins don't always unload cleanly, so don't unload them at shutdown. |
| if (instance()->mime_type().find("x-silverlight") != std::string::npos || |
| instance()->mime_type().find("audio/x-pn-realaudio") != std::string::npos) |
| instance()->plugin_lib()->PreventLibraryUnload(); |
| |
| #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(); |
| cg_context_.window = window_tracker->CreateDummyWindowForDelegate(this); |
| cg_context_.context = NULL; |
| UpdateDummyWindowBounds(gfx::Point(0, 0)); |
| #ifndef NP_NO_QUICKDRAW |
| qd_port_.port = |
| GetWindowPort(reinterpret_cast<WindowRef>(cg_context_.window)); |
| #endif |
| } |
| #endif |
| |
| switch (instance()->drawing_model()) { |
| #ifndef NP_NO_QUICKDRAW |
| case NPDrawingModelQuickDraw: |
| window_.window = &qd_port_; |
| window_.type = NPWindowTypeDrawable; |
| break; |
| #endif |
| case NPDrawingModelCoreGraphics: |
| #ifndef NP_NO_CARBON |
| if (instance()->event_model() == NPEventModelCarbon) |
| window_.window = &cg_context_; |
| #endif |
| window_.type = NPWindowTypeDrawable; |
| 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; |
| |
| #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 |
| plugin_->SetWindow(NULL); |
| |
| // QuickTime 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. |
| const WebPluginInfo& plugin_info = instance_->plugin_lib()->plugin_info(); |
| if (plugin_info.name.find(L"QuickTime") != std::wstring::npos) |
| WindowlessSetWindow(true); |
| } |
| |
| void WebPluginDelegateImpl::PlatformDestroyInstance() { |
| #ifndef NP_NO_CARBON |
| if (instance()->event_model() == NPEventModelCarbon) |
| CarbonIdleEventSource::SharedInstance()->UnregisterDelegate(this); |
| #endif |
| } |
| |
| void WebPluginDelegateImpl::UpdateContext(CGContextRef context) { |
| #ifndef NP_NO_CARBON |
| // Flash on the Mac apparently caches the context from the struct it receives |
| // in NPP_SetWindow, and continues to use it even when the contents of the |
| // struct have changed, so we need to call NPP_SetWindow again if the context |
| // changes. |
| if (instance()->event_model() == NPEventModelCarbon && |
| context != cg_context_.context) { |
| cg_context_.context = context; |
| WindowlessSetWindow(true); |
| } |
| #endif |
| } |
| |
| void WebPluginDelegateImpl::Paint(CGContextRef context, const gfx::Rect& rect) { |
| DCHECK(windowless_); |
| WindowlessPaint(context, rect); |
| } |
| |
| void WebPluginDelegateImpl::Print(CGContextRef context) { |
| // Disabling the call to NPP_Print as it causes a crash in |
| // flash in some cases. In any case this does not work as expected |
| // as the EMF meta file dc passed in needs to be created with the |
| // the plugin window dc as its sibling dc and the window rect |
| // in .01 mm units. |
| } |
| |
| void WebPluginDelegateImpl::InstallMissingPlugin() { |
| NOTIMPLEMENTED(); |
| } |
| |
| 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(); |
| } |
| |
| void WebPluginDelegateImpl::WindowlessUpdateGeometry( |
| const gfx::Rect& window_rect, |
| const gfx::Rect& clip_rect) { |
| bool old_clip_was_empty = clip_rect_.IsEmpty(); |
| cached_clip_rect_ = clip_rect; |
| if (container_is_visible_) // Remove check when cached_clip_rect_ is removed. |
| clip_rect_ = clip_rect; |
| bool new_clip_is_empty = clip_rect_.IsEmpty(); |
| |
| // Only resend to the instance if the geometry has changed (see note in |
| // WindowlessSetWindow for why we only care about the clip rect switching |
| // empty state). |
| if (window_rect == window_rect_ && old_clip_was_empty == new_clip_is_empty) |
| return; |
| |
| #ifndef NP_NO_CARBON |
| // If visibility has changed, switch our idle event rate. |
| if (instance()->event_model() == NPEventModelCarbon && |
| old_clip_was_empty != new_clip_is_empty) { |
| UpdateIdleEventRate(); |
| } |
| #endif |
| |
| SetPluginRect(window_rect); |
| WindowlessSetWindow(true); |
| } |
| |
| void WebPluginDelegateImpl::WindowlessPaint(gfx::NativeDrawingContext context, |
| const gfx::Rect& damage_rect) { |
| #ifndef NP_NO_CARBON |
| if (instance()->event_model() == NPEventModelCarbon) { |
| // If we somehow get a paint before we've set up the plugin window, bail. |
| if (!cg_context_.context) |
| return; |
| DCHECK(cg_context_.context == context); |
| } |
| #endif |
| |
| 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); |
| |
| switch (instance()->drawing_model()) { |
| #ifndef NP_NO_QUICKDRAW |
| case NPDrawingModelQuickDraw: { |
| // Plugins using the QuickDraw drawing model do not restrict their |
| // drawing to update events the way that CoreGraphics-based plugins |
| // do. When we are asked to paint, we therefore just copy from the |
| // plugin's hidden window into our shared memory bitmap context. |
| CGRect window_bounds = CGRectMake(0, 0, |
| window_rect_.width(), |
| window_rect_.height()); |
| CGWindowID window_id = HIWindowGetCGWindowID( |
| reinterpret_cast<WindowRef>(cg_context_.window)); |
| CGContextSaveGState(context); |
| CGContextTranslateCTM(context, 0, window_rect_.height()); |
| CGContextScaleCTM(context, 1.0, -1.0); |
| CGContextCopyWindowCaptureContentsToRect(context, window_bounds, |
| _CGSDefaultConnection(), |
| window_id, 0); |
| CGContextRestoreGState(context); |
| } |
| #endif |
| case NPDrawingModelCoreGraphics: { |
| 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>(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; |
| } |
| } |
| CGContextRestoreGState(context); |
| } |
| } |
| } |
| |
| void WebPluginDelegateImpl::WindowlessSetWindow(bool force_set_window) { |
| if (!instance()) |
| return; |
| |
| window_.x = 0; |
| window_.y = 0; |
| window_.height = window_rect_.height(); |
| window_.width = window_rect_.width(); |
| window_.clipRect.left = window_.x; |
| window_.clipRect.top = window_.y; |
| window_.clipRect.right = window_.clipRect.left; |
| window_.clipRect.bottom = window_.clipRect.top; |
| if (container_is_visible_ && !clip_rect_.IsEmpty()) { |
| // We never tell plugins that they are only partially visible; because the |
| // drawing target doesn't change size, the positioning of what plugins drew |
| // would be wrong, as would any transforms they did on the context. |
| window_.clipRect.right += window_.width; |
| window_.clipRect.bottom += window_.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_); |
| } |
| |
| DCHECK(err == NPERR_NO_ERROR); |
| } |
| |
| WebPluginDelegateImpl* WebPluginDelegateImpl::GetActiveDelegate() { |
| return g_active_delegate; |
| } |
| |
| std::set<WebPluginDelegateImpl*> WebPluginDelegateImpl::GetActiveDelegates() { |
| std::set<WebPluginDelegateImpl*>* delegates = g_active_delegates.Pointer(); |
| return *delegates; |
| } |
| |
| void WebPluginDelegateImpl::FocusChanged(bool has_focus) { |
| if (has_focus == have_focus_) |
| return; |
| have_focus_ = has_focus; |
| |
| ScopedActiveDelegate active_delegate(this); |
| |
| switch (instance()->event_model()) { |
| #ifndef NP_NO_CARBON |
| case NPEventModelCarbon: { |
| NPEvent focus_event = { 0 }; |
| if (have_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 = have_focus_; |
| instance()->NPP_HandleEvent(&focus_event); |
| break; |
| } |
| } |
| } |
| |
| void WebPluginDelegateImpl::SetFocus() { |
| if (focus_notifier_) |
| focus_notifier_(this); |
| else |
| FocusChanged(true); |
| } |
| |
| 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; |
| |
| 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>(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); |
| } |
| } |
| } |
| |
| 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); |
| } |
| |
| // TODO(stuartmorgan): We may need to remember whether we had focus, and |
| // restore it ourselves when we become visible again. Revisit once SetFocus |
| // is actually being called in all the cases it should be, at which point |
| // we'll know whether or not that's handled for us by WebKit. |
| if (!is_visible) |
| FocusChanged(false); |
| |
| // 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()) { |
| #ifndef NP_NO_CARBON |
| if (instance() && instance()->event_model() == NPEventModelCarbon) |
| UpdateIdleEventRate(); |
| #endif |
| WindowlessSetWindow(true); |
| } |
| } |
| |
| 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); |
| } |
| |
| void WebPluginDelegateImpl::SetPluginRect(const gfx::Rect& rect) { |
| window_rect_ = rect; |
| PluginScreenLocationChanged(); |
| } |
| |
| 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 |
| } |
| |
| #ifndef NP_NO_CARBON |
| void WebPluginDelegateImpl::UpdateDummyWindowBounds( |
| const gfx::Point& plugin_origin) { |
| WindowRef window = reinterpret_cast<WindowRef>(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); |
| } |
| #endif // !NP_NO_CARBON |
| |
| static bool WebInputEventIsWebMouseEvent(const WebInputEvent& event) { |
| switch (event.type) { |
| case WebInputEvent::MouseMove: |
| case WebInputEvent::MouseLeave: |
| case WebInputEvent::MouseEnter: |
| case WebInputEvent::MouseDown: |
| case WebInputEvent::MouseUp: |
| if (event.size < sizeof(WebMouseEvent)) { |
| NOTREACHED(); |
| return false; |
| } |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| static bool WebInputEventIsWebKeyboardEvent(const WebInputEvent& event) { |
| switch (event.type) { |
| case WebInputEvent::KeyDown: |
| case WebInputEvent::KeyUp: |
| if (event.size < sizeof(WebKeyboardEvent)) { |
| NOTREACHED(); |
| return false; |
| } |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| #ifndef NP_NO_CARBON |
| static NSInteger CarbonModifiersFromWebEvent(const WebInputEvent& event) { |
| NSInteger modifiers = 0; |
| if (event.modifiers & WebInputEvent::ControlKey) |
| modifiers |= controlKey; |
| if (event.modifiers & WebInputEvent::ShiftKey) |
| modifiers |= shiftKey; |
| if (event.modifiers & WebInputEvent::AltKey) |
| modifiers |= optionKey; |
| if (event.modifiers & WebInputEvent::MetaKey) |
| modifiers |= cmdKey; |
| return modifiers; |
| } |
| |
| static bool NPEventFromWebMouseEvent(const WebMouseEvent& event, |
| NPEvent *np_event) { |
| np_event->where.h = event.globalX; |
| np_event->where.v = event.globalY; |
| |
| np_event->modifiers |= CarbonModifiersFromWebEvent(event); |
| |
| // default to "button up"; override this for mouse down events below. |
| np_event->modifiers |= btnState; |
| |
| switch (event.button) { |
| case WebMouseEvent::ButtonLeft: |
| break; |
| case WebMouseEvent::ButtonMiddle: |
| np_event->modifiers |= cmdKey; |
| break; |
| case WebMouseEvent::ButtonRight: |
| np_event->modifiers |= controlKey; |
| break; |
| default: |
| NOTIMPLEMENTED(); |
| } |
| switch (event.type) { |
| case WebInputEvent::MouseMove: |
| np_event->what = nullEvent; |
| return true; |
| case WebInputEvent::MouseLeave: |
| case WebInputEvent::MouseEnter: |
| np_event->what = NPEventType_AdjustCursorEvent; |
| return true; |
| case WebInputEvent::MouseDown: |
| np_event->modifiers &= ~btnState; |
| np_event->what = mouseDown; |
| return true; |
| case WebInputEvent::MouseUp: |
| np_event->what = mouseUp; |
| return true; |
| default: |
| NOTREACHED(); |
| return false; |
| } |
| } |
| |
| static bool NPEventFromWebKeyboardEvent(const WebKeyboardEvent& event, |
| NPEvent *np_event) { |
| // TODO: figure out how to handle Unicode input to plugins, if that's |
| // even possible in the NPAPI Carbon event model. |
| np_event->message = (event.nativeKeyCode << 8) & keyCodeMask; |
| np_event->message |= event.text[0] & charCodeMask; |
| np_event->modifiers |= btnState; |
| np_event->modifiers |= CarbonModifiersFromWebEvent(event); |
| |
| switch (event.type) { |
| case WebInputEvent::KeyDown: |
| if (event.modifiers & WebInputEvent::IsAutoRepeat) |
| np_event->what = autoKey; |
| else |
| np_event->what = keyDown; |
| return true; |
| case WebInputEvent::KeyUp: |
| np_event->what = keyUp; |
| return true; |
| default: |
| NOTREACHED(); |
| return false; |
| } |
| } |
| |
| static bool NPEventFromWebInputEvent(const WebInputEvent& event, |
| NPEvent* np_event) { |
| np_event->when = TickCount(); |
| if (WebInputEventIsWebMouseEvent(event)) { |
| return NPEventFromWebMouseEvent(*static_cast<const WebMouseEvent*>(&event), |
| np_event); |
| } else if (WebInputEventIsWebKeyboardEvent(event)) { |
| return NPEventFromWebKeyboardEvent( |
| *static_cast<const WebKeyboardEvent*>(&event), np_event); |
| } |
| DLOG(WARNING) << "unknown event type" << event.type; |
| return false; |
| } |
| #endif // !NP_NO_CARBON |
| |
| static NSInteger CocoaModifiersFromWebEvent(const WebInputEvent& event) { |
| NSInteger modifiers = 0; |
| if (event.modifiers & WebInputEvent::ControlKey) |
| modifiers |= NSControlKeyMask; |
| if (event.modifiers & WebInputEvent::ShiftKey) |
| modifiers |= NSShiftKeyMask; |
| if (event.modifiers & WebInputEvent::AltKey) |
| modifiers |= NSAlternateKeyMask; |
| if (event.modifiers & WebInputEvent::MetaKey) |
| modifiers |= NSCommandKeyMask; |
| return modifiers; |
| } |
| |
| static bool KeyIsModifier(int native_key_code) { |
| switch (native_key_code) { |
| case 55: // Left command |
| case 54: // Right command |
| case 58: // Left option |
| case 61: // Right option |
| case 59: // Left control |
| case 62: // Right control |
| case 56: // Left shift |
| case 60: // Right shift |
| case 57: // Caps lock |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| static bool NPCocoaEventFromWebMouseEvent(const WebMouseEvent& event, |
| NPCocoaEvent *np_cocoa_event) { |
| np_cocoa_event->data.mouse.pluginX = event.x; |
| np_cocoa_event->data.mouse.pluginY = event.y; |
| np_cocoa_event->data.mouse.modifierFlags |= CocoaModifiersFromWebEvent(event); |
| np_cocoa_event->data.mouse.clickCount = event.clickCount; |
| switch (event.button) { |
| case WebMouseEvent::ButtonLeft: |
| np_cocoa_event->data.mouse.buttonNumber = 0; |
| break; |
| case WebMouseEvent::ButtonMiddle: |
| np_cocoa_event->data.mouse.buttonNumber = 2; |
| break; |
| case WebMouseEvent::ButtonRight: |
| np_cocoa_event->data.mouse.buttonNumber = 1; |
| break; |
| default: |
| np_cocoa_event->data.mouse.buttonNumber = event.button; |
| break; |
| } |
| switch (event.type) { |
| case WebInputEvent::MouseDown: |
| np_cocoa_event->type = NPCocoaEventMouseDown; |
| return true; |
| case WebInputEvent::MouseUp: |
| np_cocoa_event->type = NPCocoaEventMouseUp; |
| return true; |
| case WebInputEvent::MouseMove: { |
| bool mouse_is_down = (event.modifiers & WebInputEvent::LeftButtonDown) || |
| (event.modifiers & WebInputEvent::RightButtonDown) || |
| (event.modifiers & WebInputEvent::MiddleButtonDown); |
| np_cocoa_event->type = mouse_is_down ? NPCocoaEventMouseDragged |
| : NPCocoaEventMouseMoved; |
| return true; |
| } |
| case WebInputEvent::MouseEnter: |
| np_cocoa_event->type = NPCocoaEventMouseEntered; |
| return true; |
| case WebInputEvent::MouseLeave: |
| np_cocoa_event->type = NPCocoaEventMouseExited; |
| return true; |
| default: |
| NOTREACHED(); |
| return false; |
| } |
| } |
| |
| static bool NPCocoaEventFromWebMouseWheelEvent(const WebMouseWheelEvent& event, |
| NPCocoaEvent *np_cocoa_event) { |
| np_cocoa_event->type = NPCocoaEventScrollWheel; |
| np_cocoa_event->data.mouse.pluginX = event.x; |
| np_cocoa_event->data.mouse.pluginY = event.y; |
| np_cocoa_event->data.mouse.modifierFlags |= CocoaModifiersFromWebEvent(event); |
| np_cocoa_event->data.mouse.deltaX = event.deltaX; |
| np_cocoa_event->data.mouse.deltaY = event.deltaY; |
| return true; |
| } |
| |
| static bool NPCocoaEventFromWebKeyboardEvent(const WebKeyboardEvent& event, |
| NPCocoaEvent *np_cocoa_event) { |
| np_cocoa_event->data.key.keyCode = event.nativeKeyCode; |
| |
| np_cocoa_event->data.key.modifierFlags |= CocoaModifiersFromWebEvent(event); |
| |
| // Modifier keys have their own event type, and don't get character or |
| // repeat data. |
| if (KeyIsModifier(event.nativeKeyCode)) { |
| np_cocoa_event->type = NPCocoaEventFlagsChanged; |
| return true; |
| } |
| |
| np_cocoa_event->data.key.characters = reinterpret_cast<NPNSString*>( |
| [NSString stringWithFormat:@"%S", event.text]); |
| np_cocoa_event->data.key.charactersIgnoringModifiers = |
| reinterpret_cast<NPNSString*>( |
| [NSString stringWithFormat:@"%S", event.unmodifiedText]); |
| |
| if (event.modifiers & WebInputEvent::IsAutoRepeat) |
| np_cocoa_event->data.key.isARepeat = true; |
| |
| switch (event.type) { |
| case WebInputEvent::KeyDown: |
| np_cocoa_event->type = NPCocoaEventKeyDown; |
| return true; |
| case WebInputEvent::KeyUp: |
| np_cocoa_event->type = NPCocoaEventKeyUp; |
| return true; |
| default: |
| NOTREACHED(); |
| return false; |
| } |
| } |
| |
| static bool NPCocoaEventFromWebInputEvent(const WebInputEvent& event, |
| NPCocoaEvent *np_cocoa_event) { |
| memset(np_cocoa_event, 0, sizeof(NPCocoaEvent)); |
| if (event.type == WebInputEvent::MouseWheel) { |
| return NPCocoaEventFromWebMouseWheelEvent( |
| *static_cast<const WebMouseWheelEvent*>(&event), np_cocoa_event); |
| } else if (WebInputEventIsWebMouseEvent(event)) { |
| return NPCocoaEventFromWebMouseEvent( |
| *static_cast<const WebMouseEvent*>(&event), np_cocoa_event); |
| } else if (WebInputEventIsWebKeyboardEvent(event)) { |
| return NPCocoaEventFromWebKeyboardEvent( |
| *static_cast<const WebKeyboardEvent*>(&event), np_cocoa_event); |
| } |
| DLOG(WARNING) << "unknown event type " << event.type; |
| return false; |
| } |
| |
| bool WebPluginDelegateImpl::PlatformHandleInputEvent( |
| const WebInputEvent& event, WebCursorInfo* cursor_info) { |
| DCHECK(cursor_info != NULL); |
| |
| #ifndef NP_NO_CARBON |
| if (instance()->event_model() == NPEventModelCarbon && |
| !cg_context_.context) { |
| // If we somehow get an event before we've set up the plugin window, bail. |
| return false; |
| } |
| #endif |
| |
| if (WebInputEventIsWebMouseEvent(event)) { |
| // 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 location: " << content_area_origin_ |
| << " instead of " << content_origin; |
| SetContentAreaOrigin(content_origin); |
| } |
| |
| current_windowless_cursor_.GetCursorInfo(cursor_info); |
| } |
| |
| // if we do not currently have focus and this is a mouseDown, trigger a |
| // notification that we are taking the keyboard focus. We can't just key |
| // off of incoming calls to SetFocus, since WebKit may already think we |
| // have it if we were the most recently focused element on our parent tab. |
| if (event.type == WebInputEvent::MouseDown && !have_focus_) { |
| SetFocus(); |
| // Make sure that the plugin is still there after handling the focus event. |
| if (!instance()) |
| return false; |
| } |
| |
| #ifndef NP_NO_CARBON |
| if (instance()->event_model() == NPEventModelCarbon) { |
| if (event.type == WebInputEvent::MouseMove) { |
| return true; // The recurring OnNull will send null events. |
| } |
| |
| switch (instance()->drawing_model()) { |
| #ifndef NP_NO_QUICKDRAW |
| case NPDrawingModelQuickDraw: |
| SetPort(qd_port_.port); |
| break; |
| #endif |
| case NPDrawingModelCoreGraphics: |
| CGContextSaveGState(cg_context_.context); |
| break; |
| } |
| } |
| #endif |
| |
| ScopedActiveDelegate active_delegate(this); |
| |
| #ifndef NP_NO_CARBON |
| // cgcontext_.context can change during event handling (because of a geometry |
| // change triggered by the event); we need to know if that happens so we |
| // don't keep trying to use the context. It is not an owning ref, so shouldn't |
| // be used for anything but pointer comparison. |
| CGContextRef old_context_weak = cg_context_.context; |
| #endif |
| |
| // Create the plugin event structure, and send it to the plugin. |
| bool ret = false; |
| switch (instance()->event_model()) { |
| #ifndef NP_NO_CARBON |
| case NPEventModelCarbon: { |
| NPEvent np_event = {0}; |
| if (!NPEventFromWebInputEvent(event, &np_event)) { |
| LOG(WARNING) << "NPEventFromWebInputEvent failed"; |
| return false; |
| } |
| ret = instance()->NPP_HandleEvent(&np_event) != 0; |
| break; |
| } |
| #endif |
| case NPEventModelCocoa: { |
| NPCocoaEvent np_cocoa_event; |
| if (!NPCocoaEventFromWebInputEvent(event, &np_cocoa_event)) { |
| LOG(WARNING) << "NPCocoaEventFromWebInputEvent failed"; |
| return false; |
| } |
| NPAPI::ScopedCurrentPluginEvent event_scope(instance(), &np_cocoa_event); |
| ret = instance()->NPP_HandleEvent(&np_cocoa_event) != 0; |
| break; |
| } |
| } |
| |
| if (WebInputEventIsWebMouseEvent(event)) { |
| // Plugins are not good about giving accurate information about whether or |
| // not they handled events, and other browsers on the Mac generally ignore |
| // the return value. We may need to expand this to other input types, but |
| // we'll need to be careful about things like Command-keys. |
| ret = true; |
| } |
| |
| #ifndef NP_NO_CARBON |
| if (instance() && instance()->event_model() == NPEventModelCarbon && |
| instance()->drawing_model() == NPDrawingModelCoreGraphics && |
| cg_context_.context == old_context_weak) |
| CGContextRestoreGState(cg_context_.context); |
| #endif |
| |
| return ret; |
| } |
| |
| #ifndef NP_NO_CARBON |
| void WebPluginDelegateImpl::FireIdleEvent() { |
| // Avoid a race condition between IO and UI threads during plugin shutdown |
| if (!instance()) |
| return; |
| |
| ScopedActiveDelegate active_delegate(this); |
| |
| if (!webkit_glue::IsPluginRunningInRendererProcess()) { |
| // If the plugin is running in a subprocess, drain any pending system |
| // events so that the plugin's event handlers will get called on any |
| // windows it has created. Filter out activate/deactivate events on |
| // the fake browser window, but pass everything else through. |
| EventRecord event; |
| while (GetNextEvent(everyEvent, &event)) { |
| if (!instance()) |
| return; |
| if (event.what == activateEvt && cg_context_.window && |
| reinterpret_cast<void *>(event.message) != cg_context_.window) |
| continue; |
| instance()->NPP_HandleEvent(&event); |
| } |
| // If the plugin went away during event handling, we're done. |
| if (!instance()) |
| return; |
| } |
| |
| // 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. |
| // TODO: only do this if the contents of the offscreen window has changed, |
| // so as not to spam the renderer with an unchanging image. |
| if (instance() && instance()->drawing_model() == NPDrawingModelQuickDraw) |
| instance()->webplugin()->Invalidate(); |
| #endif |
| } |
| #endif // !NP_NO_CARBON |