diff options
author | chase@chromium.org <chase@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-12-21 00:23:26 +0000 |
---|---|---|
committer | chase@chromium.org <chase@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-12-21 00:23:26 +0000 |
commit | c85b0ba71b5a55647b01de9d345e46896979033d (patch) | |
tree | ae99657ca1440b7540da473e1bbe6156e43e9be3 /webkit/glue/plugins | |
parent | 887ba3adc7b32b7df315ef292ae9395fd75653e5 (diff) | |
download | chromium_src-c85b0ba71b5a55647b01de9d345e46896979033d.zip chromium_src-c85b0ba71b5a55647b01de9d345e46896979033d.tar.gz chromium_src-c85b0ba71b5a55647b01de9d345e46896979033d.tar.bz2 |
Revert "Move the NPAPI files from webkit/glue/plugins to webkit/plugins/npapi"
Manually reverting r69755, which broke the tree.
BUG=none
TEST=none
TBR=dmaclach@chromium.org
Review URL: http://codereview.chromium.org/5998002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@69771 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit/glue/plugins')
120 files changed, 20652 insertions, 13 deletions
diff --git a/webkit/glue/plugins/DEPS b/webkit/glue/plugins/DEPS new file mode 100644 index 0000000..024a4ef --- /dev/null +++ b/webkit/glue/plugins/DEPS @@ -0,0 +1,9 @@ +include_rules = [ + "+ppapi", + + # Files in this directory must not depend on the proxy, because the proxy + # depends on IPC which we don't want to have in /webkit. + "-ppapi/proxy", + + "+printing", +] diff --git a/webkit/glue/plugins/carbon_plugin_window_tracker_mac.cc b/webkit/glue/plugins/carbon_plugin_window_tracker_mac.cc new file mode 100644 index 0000000..c4ae72d --- /dev/null +++ b/webkit/glue/plugins/carbon_plugin_window_tracker_mac.cc @@ -0,0 +1,55 @@ +// Copyright (c) 2009 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. + +#include "base/logging.h" +#include "webkit/glue/plugins/carbon_plugin_window_tracker_mac.h" + +CarbonPluginWindowTracker::CarbonPluginWindowTracker() { +} + +CarbonPluginWindowTracker* CarbonPluginWindowTracker::SharedInstance() { + static CarbonPluginWindowTracker* tracker = new CarbonPluginWindowTracker(); + return tracker; +} + +WindowRef CarbonPluginWindowTracker::CreateDummyWindowForDelegate( + OpaquePluginRef delegate) { + // The real size will be set by the plugin instance, once that size is known. + Rect window_bounds = { 0, 0, 100, 100 }; + WindowRef new_ref = NULL; + if (CreateNewWindow(kDocumentWindowClass, + kWindowNoTitleBarAttribute, + &window_bounds, + &new_ref) == noErr) { + window_to_delegate_map_[new_ref] = delegate; + delegate_to_window_map_[delegate] = new_ref; + } + return new_ref; +} + +OpaquePluginRef CarbonPluginWindowTracker::GetDelegateForDummyWindow( + WindowRef window) const { + WindowToDelegateMap::const_iterator i = window_to_delegate_map_.find(window); + if (i != window_to_delegate_map_.end()) + return i->second; + return NULL; +} + +WindowRef CarbonPluginWindowTracker::GetDummyWindowForDelegate( + OpaquePluginRef delegate) const { + DelegateToWindowMap::const_iterator i = + delegate_to_window_map_.find(delegate); + if (i != delegate_to_window_map_.end()) + return i->second; + return NULL; +} + +void CarbonPluginWindowTracker::DestroyDummyWindowForDelegate( + OpaquePluginRef delegate, WindowRef window) { + DCHECK(GetDelegateForDummyWindow(window) == delegate); + window_to_delegate_map_.erase(window); + delegate_to_window_map_.erase(delegate); + if (window) // Check just in case the initial window creation failed. + DisposeWindow(window); +} diff --git a/webkit/glue/plugins/carbon_plugin_window_tracker_mac.h b/webkit/glue/plugins/carbon_plugin_window_tracker_mac.h new file mode 100644 index 0000000..90fc318 --- /dev/null +++ b/webkit/glue/plugins/carbon_plugin_window_tracker_mac.h @@ -0,0 +1,53 @@ +// Copyright (c) 2009 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. + +#ifndef WEBKIT_GLUE_PLUGINS_CARBON_PLUGIN_WINDOW_TRACKER_MAC_H_ +#define WEBKIT_GLUE_PLUGINS_CARBON_PLUGIN_WINDOW_TRACKER_MAC_H_ + +#include <Carbon/Carbon.h> +#include <map> + +#include "base/basictypes.h" + +// This is really a WebPluginDelegateImpl, but that class is private to the +// framework, and these functions are called from a dylib. +typedef void* OpaquePluginRef; + +// Creates and tracks the invisible windows that are necessary for +// Carbon-event-model plugins. +// +// Serves as a bridge between plugin delegate instances and the Carbon +// interposing library. The Carbon functions we interpose work in terms of +// WindowRefs, and we need to be able to map from those back to the plugin +// delegates that know what we should claim about the state of the window. +class __attribute__((visibility("default"))) CarbonPluginWindowTracker { + public: + CarbonPluginWindowTracker(); + + // Returns the shared window tracker instance. + static CarbonPluginWindowTracker* SharedInstance(); + + // Creates a new carbon window associated with |delegate|. + WindowRef CreateDummyWindowForDelegate(OpaquePluginRef delegate); + + // Returns the WebPluginDelegate associated with the given dummy window. + OpaquePluginRef GetDelegateForDummyWindow(WindowRef window) const; + + // Returns the dummy window associated with |delegate|. + WindowRef GetDummyWindowForDelegate(OpaquePluginRef delegate) const; + + // Destroys the dummy window for |delegate|. + void DestroyDummyWindowForDelegate(OpaquePluginRef delegate, + WindowRef window); + + private: + typedef std::map<WindowRef, OpaquePluginRef> WindowToDelegateMap; + typedef std::map<OpaquePluginRef, WindowRef> DelegateToWindowMap; + WindowToDelegateMap window_to_delegate_map_; + DelegateToWindowMap delegate_to_window_map_; + + DISALLOW_COPY_AND_ASSIGN(CarbonPluginWindowTracker); +}; + +#endif // WEBKIT_GLUE_PLUGINS_CARBON_PLUGIN_WINDOW_TRACKER_MAC_H_ diff --git a/webkit/glue/plugins/coregraphics_private_symbols_mac.h b/webkit/glue/plugins/coregraphics_private_symbols_mac.h new file mode 100644 index 0000000..0342d6f --- /dev/null +++ b/webkit/glue/plugins/coregraphics_private_symbols_mac.h @@ -0,0 +1,27 @@ +// Copyright (c) 2009 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. + +#ifndef WEBKIT_GLUE_PLUGINS_COREGRAPHICS_PRIVATE_SYMBOLS_MAC_H_ +#define WEBKIT_GLUE_PLUGINS_COREGRAPHICS_PRIVATE_SYMBOLS_MAC_H_ + +// These are CoreGraphics SPI, verified to exist in both 10.5 and 10.6. + +#ifdef __cplusplus +extern "C" { +#endif + +// Copies the contents of the window with id |wid| into the given rect in the +// given context +OSStatus CGContextCopyWindowCaptureContentsToRect( + CGContextRef, CGRect, int cid, int wid, int unknown); + +// Returns the connection ID we need for the third argument to +// CGContextCopyWindowCaptureContentsToRect +int _CGSDefaultConnection(void); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBKIT_GLUE_PLUGINS_COREGRAPHICS_PRIVATE_SYMBOLS_MAC_H_ diff --git a/webkit/glue/plugins/default_plugin_shared.h b/webkit/glue/plugins/default_plugin_shared.h new file mode 100644 index 0000000..79d06b3 --- /dev/null +++ b/webkit/glue/plugins/default_plugin_shared.h @@ -0,0 +1,31 @@ +// 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. +// +// Thes file contains stuff that should be shared among projects that do some +// special handling with default plugin + +#ifndef WEBKIT_GLUE_PLUGINS_DEFAULT_PLUGIN_SHARED_H +#define WEBKIT_GLUE_PLUGINS_DEFAULT_PLUGIN_SHARED_H + +namespace default_plugin { + +// We use the NPNGetValue host function to send notification message to host. +// This corresponds to NPNVariable defined in npapi.h, and should be chosen so +// as to not overlap values if NPAPI is updated. + +const int kMissingPluginStatusStart = 5000; + +enum MissingPluginStatus { + MISSING_PLUGIN_AVAILABLE, + MISSING_PLUGIN_USER_STARTED_DOWNLOAD +}; + +#if defined(OS_WIN) +#include <windows.h> +const int kInstallMissingPluginMessage = WM_APP + 117; +#endif + +} // namespace default_plugin + +#endif // WEBKIT_GLUE_PLUGINS_DEFAULT_PLUGIN_SHARED_H diff --git a/webkit/glue/plugins/gtk_plugin_container.cc b/webkit/glue/plugins/gtk_plugin_container.cc new file mode 100644 index 0000000..c80bbf1 --- /dev/null +++ b/webkit/glue/plugins/gtk_plugin_container.cc @@ -0,0 +1,85 @@ +// Copyright (c) 2009 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. + +#include "webkit/glue/plugins/gtk_plugin_container.h" + +#include <gtk/gtk.h> + +#include "base/basictypes.h" + +namespace { + +// NOTE: This class doesn't have constructors/destructors, it is created +// through GLib's object management. +class GtkPluginContainer : public GtkSocket { + public: + // Sets the requested size of the widget. + void set_size(int width, int height) { + width_ = width; + height_ = height; + } + + // Casts a widget into a GtkPluginContainer, after checking the type. + template <class T> + static GtkPluginContainer *CastChecked(T *instance) { + return G_TYPE_CHECK_INSTANCE_CAST(instance, GetType(), GtkPluginContainer); + } + + // Create and register our custom container type with GTK. + static GType GetType() { + static GType type = 0; // We only want to register our type once. + if (!type) { + static const GTypeInfo info = { + sizeof(GtkSocketClass), + NULL, NULL, + static_cast<GClassInitFunc>(&ClassInit), + NULL, NULL, + sizeof(GtkPluginContainer), + 0, &InstanceInit, + }; + type = g_type_register_static(GTK_TYPE_SOCKET, + "GtkPluginContainer", + &info, + static_cast<GTypeFlags>(0)); + } + return type; + } + + // Implementation of the class initializer. + static void ClassInit(gpointer klass, gpointer class_data_unusued) { + GtkWidgetClass* widget_class = reinterpret_cast<GtkWidgetClass*>(klass); + widget_class->size_request = &HandleSizeRequest; + } + + // Implementation of the instance initializer (constructor). + static void InstanceInit(GTypeInstance *instance, gpointer klass) { + GtkPluginContainer *container = CastChecked(instance); + container->set_size(0, 0); + } + + // Report our allocation size during size requisition. + static void HandleSizeRequest(GtkWidget* widget, + GtkRequisition* requisition) { + GtkPluginContainer *container = CastChecked(widget); + requisition->width = container->width_; + requisition->height = container->height_; + } + + int width_; + int height_; + DISALLOW_IMPLICIT_CONSTRUCTORS(GtkPluginContainer); +}; + +} // anonymous namespace + +// Create a new instance of our GTK widget object. +GtkWidget* gtk_plugin_container_new() { + return GTK_WIDGET(g_object_new(GtkPluginContainer::GetType(), NULL)); +} + +void gtk_plugin_container_set_size(GtkWidget *widget, int width, int height) { + GtkPluginContainer::CastChecked(widget)->set_size(width, height); + // Signal the parent that the size request has changed. + gtk_widget_queue_resize_no_redraw(widget); +} diff --git a/webkit/glue/plugins/gtk_plugin_container.h b/webkit/glue/plugins/gtk_plugin_container.h new file mode 100644 index 0000000..eed6b94 --- /dev/null +++ b/webkit/glue/plugins/gtk_plugin_container.h @@ -0,0 +1,26 @@ +// Copyright (c) 2009 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. + +#ifndef WEBKIT_GLUE_PLUGINS_GTK_PLUGIN_CONTAINER_H_ +#define WEBKIT_GLUE_PLUGINS_GTK_PLUGIN_CONTAINER_H_ + +// Windowed plugins are embedded via XEmbed, which is implemented by +// GtkPlug/GtkSocket. But we want to control sizing and positioning +// directly, so we need a subclass of GtkSocket that sidesteps the +// size_request handler. +// +// The custom size_request handler just reports the size set by +// gtk_plugin_container_set_size. + +typedef struct _GtkWidget GtkWidget; + +// Return a new GtkPluginContainer. +// Intentionally GTK-style here since we're creating a custom GTK widget. +// This is a GtkSocket subclass; see its documentation for available methods. +GtkWidget* gtk_plugin_container_new(); + +// Sets the size of the GtkPluginContainer. +void gtk_plugin_container_set_size(GtkWidget *widget, int width, int height); + +#endif // WEBKIT_GLUE_PLUGINS_GTK_PLUGIN_CONTAINER_H_ diff --git a/webkit/glue/plugins/gtk_plugin_container_manager.cc b/webkit/glue/plugins/gtk_plugin_container_manager.cc new file mode 100644 index 0000000..2f82b24 --- /dev/null +++ b/webkit/glue/plugins/gtk_plugin_container_manager.cc @@ -0,0 +1,155 @@ +// 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. + +#include "webkit/glue/plugins/gtk_plugin_container_manager.h" + +#include <gtk/gtk.h> + +#include "base/logging.h" +#include "gfx/gtk_util.h" +#include "webkit/glue/plugins/gtk_plugin_container.h" +#include "webkit/glue/plugins/webplugin.h" + +GtkPluginContainerManager::GtkPluginContainerManager() : host_widget_(NULL) {} + +GtkPluginContainerManager::~GtkPluginContainerManager() {} + +GtkWidget* GtkPluginContainerManager::CreatePluginContainer( + gfx::PluginWindowHandle id) { + DCHECK(host_widget_); + GtkWidget *widget = gtk_plugin_container_new(); + plugin_window_to_widget_map_.insert(std::make_pair(id, widget)); + + // The Realize callback is responsible for adding the plug into the socket. + // The reason is 2-fold: + // - the plug can't be added until the socket is realized, but this may not + // happen until the socket is attached to a top-level window, which isn't the + // case for background tabs. + // - when dragging tabs, the socket gets unrealized, which breaks the XEMBED + // connection. We need to make it again when the tab is reattached, and the + // socket gets realized again. + // + // Note, the RealizeCallback relies on the plugin_window_to_widget_map_ to + // have the mapping. + g_signal_connect(widget, "realize", + G_CALLBACK(RealizeCallback), this); + + // Don't destroy the widget when the plug is removed. + g_signal_connect(widget, "plug-removed", + G_CALLBACK(gtk_true), NULL); + + gtk_container_add(GTK_CONTAINER(host_widget_), widget); + gtk_widget_show(widget); + + return widget; +} + +void GtkPluginContainerManager::DestroyPluginContainer( + gfx::PluginWindowHandle id) { + DCHECK(host_widget_); + GtkWidget* widget = MapIDToWidget(id); + if (widget) + gtk_widget_destroy(widget); + + plugin_window_to_widget_map_.erase(id); +} + +void GtkPluginContainerManager::MovePluginContainer( + const webkit_glue::WebPluginGeometry& move) { + DCHECK(host_widget_); + GtkWidget *widget = MapIDToWidget(move.window); + if (!widget) + return; + + DCHECK(!GTK_WIDGET_NO_WINDOW(widget)); + + if (!move.visible) { + gtk_widget_hide(widget); + return; + } + + gtk_widget_show(widget); + + if (!move.rects_valid) + return; + + // TODO(piman): if the widget hasn't been realized (e.g. the tab has been + // torn off and the parent gtk widget has been detached from the hierarchy), + // we lose the cutout information. + if (GTK_WIDGET_REALIZED(widget)) { + GdkRectangle clip_rect = move.clip_rect.ToGdkRectangle(); + GdkRegion* clip_region = gdk_region_rectangle(&clip_rect); + gfx::SubtractRectanglesFromRegion(clip_region, move.cutout_rects); + gdk_window_shape_combine_region(widget->window, clip_region, 0, 0); + gdk_region_destroy(clip_region); + } + + // Update the window position. Resizing is handled by WebPluginDelegate. + // TODO(deanm): Verify that we only need to move and not resize. + // TODO(evanm): we should cache the last shape and position and skip all + // of this business in the common case where nothing has changed. + int current_x, current_y; + + // Until the above TODO is resolved, we can grab the last position + // off of the GtkFixed with a bit of hackery. + GValue value = {0}; + g_value_init(&value, G_TYPE_INT); + gtk_container_child_get_property(GTK_CONTAINER(host_widget_), widget, + "x", &value); + current_x = g_value_get_int(&value); + gtk_container_child_get_property(GTK_CONTAINER(host_widget_), widget, + "y", &value); + current_y = g_value_get_int(&value); + g_value_unset(&value); + + if (move.window_rect.x() != current_x || + move.window_rect.y() != current_y) { + // Calling gtk_fixed_move unnecessarily is a no-no, as it causes the + // parent window to repaint! + gtk_fixed_move(GTK_FIXED(host_widget_), + widget, + move.window_rect.x(), + move.window_rect.y()); + } + + gtk_plugin_container_set_size(widget, + move.window_rect.width(), + move.window_rect.height()); +} + +GtkWidget* GtkPluginContainerManager::MapIDToWidget( + gfx::PluginWindowHandle id) { + PluginWindowToWidgetMap::const_iterator i = + plugin_window_to_widget_map_.find(id); + if (i != plugin_window_to_widget_map_.end()) + return i->second; + + LOG(ERROR) << "Request for widget host for unknown window id " << id; + + return NULL; +} + +gfx::PluginWindowHandle GtkPluginContainerManager::MapWidgetToID( + GtkWidget* widget) { + for (PluginWindowToWidgetMap::const_iterator i = + plugin_window_to_widget_map_.begin(); + i != plugin_window_to_widget_map_.end(); ++i) { + if (i->second == widget) + return i->first; + } + + LOG(ERROR) << "Request for id for unknown widget"; + return 0; +} + +// static +void GtkPluginContainerManager::RealizeCallback(GtkWidget* widget, + void* user_data) { + GtkPluginContainerManager* plugin_container_manager = + static_cast<GtkPluginContainerManager*>(user_data); + + gfx::PluginWindowHandle id = plugin_container_manager->MapWidgetToID(widget); + if (id) + gtk_socket_add_id(GTK_SOCKET(widget), id); +} diff --git a/webkit/glue/plugins/gtk_plugin_container_manager.h b/webkit/glue/plugins/gtk_plugin_container_manager.h new file mode 100644 index 0000000..7f7db8d --- /dev/null +++ b/webkit/glue/plugins/gtk_plugin_container_manager.h @@ -0,0 +1,57 @@ +// Copyright (c) 2009 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. + +#ifndef WEBKIT_GLUE_PLUGINS_GTK_PLUGIN_CONTAINER_MANAGER_H_ +#define WEBKIT_GLUE_PLUGINS_GTK_PLUGIN_CONTAINER_MANAGER_H_ + +#include <gtk/gtk.h> +#include <map> + +#include "gfx/native_widget_types.h" + +typedef struct _GtkWidget GtkWidget; + +namespace webkit_glue { +struct WebPluginGeometry; +} + +// Helper class that creates and manages plugin containers (GtkSocket). +class GtkPluginContainerManager { + public: + GtkPluginContainerManager(); + ~GtkPluginContainerManager(); + + // Sets the widget that will host the plugin containers. Must be a GtkFixed. + void set_host_widget(GtkWidget *widget) { host_widget_ = widget; } + + // Creates a new plugin container, for a given plugin XID. + GtkWidget* CreatePluginContainer(gfx::PluginWindowHandle id); + + // Destroys a plugin container, given the plugin XID. + void DestroyPluginContainer(gfx::PluginWindowHandle id); + + // Takes an update from WebKit about a plugin's position and side and moves + // the plugin accordingly. + void MovePluginContainer(const webkit_glue::WebPluginGeometry& move); + + private: + // Maps a plugin XID to the corresponding container widget. + GtkWidget* MapIDToWidget(gfx::PluginWindowHandle id); + + // Maps a container widget to the corresponding plugin XID. + gfx::PluginWindowHandle MapWidgetToID(GtkWidget* widget); + + // Callback for when the plugin container gets realized, at which point it + // plugs the plugin XID. + static void RealizeCallback(GtkWidget *widget, void *user_data); + + // Parent of the plugin containers. + GtkWidget* host_widget_; + + // A map that associates plugin containers to the plugin XID. + typedef std::map<gfx::PluginWindowHandle, GtkWidget*> PluginWindowToWidgetMap; + PluginWindowToWidgetMap plugin_window_to_widget_map_; +}; + +#endif // WEBKIT_GLUE_PLUGINS_GTK_PLUGIN_CONTAINER_MANAGER_H_ diff --git a/webkit/glue/plugins/npapi_extension_thunk.cc b/webkit/glue/plugins/npapi_extension_thunk.cc new file mode 100644 index 0000000..05a9c5d --- /dev/null +++ b/webkit/glue/plugins/npapi_extension_thunk.cc @@ -0,0 +1,551 @@ +// 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. + +#include "webkit/glue/plugins/npapi_extension_thunk.h" + +#include "base/logging.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "third_party/npapi/bindings/npapi_extensions.h" +#include "webkit/glue/plugins/plugin_instance.h" +#include "webkit/glue/plugins/webplugin.h" +#include "webkit/glue/plugins/webplugin_delegate.h" +#include "webkit/glue/webkit_glue.h" + +// FindInstance() +// Finds a PluginInstance from an NPP. +// The caller must take a reference if needed. +static NPAPI::PluginInstance* FindInstance(NPP id) { + if (id == NULL) { + NOTREACHED(); + return NULL; + } + return static_cast<NPAPI::PluginInstance*>(id->ndata); +} + +// 2D device API --------------------------------------------------------------- + +static NPError Device2DQueryCapability(NPP id, int32_t capability, + int32_t* value) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin) { + plugin->webplugin()->delegate()->Device2DQueryCapability(capability, value); + return NPERR_NO_ERROR; + } else { + return NPERR_GENERIC_ERROR; + } +} + +static NPError Device2DQueryConfig(NPP id, + const NPDeviceConfig* request, + NPDeviceConfig* obtain) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin) { + return plugin->webplugin()->delegate()->Device2DQueryConfig( + static_cast<const NPDeviceContext2DConfig*>(request), + static_cast<NPDeviceContext2DConfig*>(obtain)); + } + return NPERR_GENERIC_ERROR; +} + +static NPError Device2DInitializeContext(NPP id, + const NPDeviceConfig* config, + NPDeviceContext* context) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin) { + return plugin->webplugin()->delegate()->Device2DInitializeContext( + static_cast<const NPDeviceContext2DConfig*>(config), + static_cast<NPDeviceContext2D*>(context)); + } + return NPERR_GENERIC_ERROR; +} + +static NPError Device2DSetStateContext(NPP id, + NPDeviceContext* context, + int32_t state, + intptr_t value) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin) { + return plugin->webplugin()->delegate()->Device2DSetStateContext( + static_cast<NPDeviceContext2D*>(context), state, value); + } + return NPERR_GENERIC_ERROR; +} + +static NPError Device2DGetStateContext(NPP id, + NPDeviceContext* context, + int32_t state, + intptr_t* value) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin) { + return plugin->webplugin()->delegate()->Device2DGetStateContext( + static_cast<NPDeviceContext2D*>(context), state, value); + } + return NPERR_GENERIC_ERROR; +} + +static NPError Device2DFlushContext(NPP id, + NPDeviceContext* context, + NPDeviceFlushContextCallbackPtr callback, + void* user_data) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin) { + NPError err = plugin->webplugin()->delegate()->Device2DFlushContext( + id, static_cast<NPDeviceContext2D*>(context), callback, user_data); + + // Invoke the callback to inform the caller the work was done. + // TODO(brettw) this is probably not how we want this to work, this should + // happen when the frame is painted so the plugin knows when it can draw + // the next frame. + if (callback != NULL) + (*callback)(id, context, err, user_data); + + // Return any errors. + return err; + } + return NPERR_GENERIC_ERROR; +} + +static NPError Device2DDestroyContext(NPP id, + NPDeviceContext* context) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin) { + return plugin->webplugin()->delegate()->Device2DDestroyContext( + static_cast<NPDeviceContext2D*>(context)); + } + return NPERR_GENERIC_ERROR; +} + +static NPError Device2DCreateBuffer(NPP id, + NPDeviceContext* context, + size_t size, + int32_t* buffer_id) { + return NPERR_GENERIC_ERROR; +} + +static NPError Device2DDestroyBuffer(NPP id, + NPDeviceContext* context, + int32_t buffer_id) { + return NPERR_GENERIC_ERROR; +} + +static NPError Device2DMapBuffer(NPP id, + NPDeviceContext* context, + int32_t buffer_id, + NPDeviceBuffer* buffer) { + return NPERR_GENERIC_ERROR; +} + +// 3D device API --------------------------------------------------------------- + +static NPError Device3DQueryCapability(NPP id, int32_t capability, + int32_t* value) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin) { + plugin->webplugin()->delegate()->Device3DQueryCapability(capability, value); + return NPERR_NO_ERROR; + } else { + return NPERR_GENERIC_ERROR; + } +} + +static NPError Device3DQueryConfig(NPP id, + const NPDeviceConfig* request, + NPDeviceConfig* obtain) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin) { + return plugin->webplugin()->delegate()->Device3DQueryConfig( + static_cast<const NPDeviceContext3DConfig*>(request), + static_cast<NPDeviceContext3DConfig*>(obtain)); + } + return NPERR_GENERIC_ERROR; +} + +static NPError Device3DInitializeContext(NPP id, + const NPDeviceConfig* config, + NPDeviceContext* context) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin) { + return plugin->webplugin()->delegate()->Device3DInitializeContext( + static_cast<const NPDeviceContext3DConfig*>(config), + static_cast<NPDeviceContext3D*>(context)); + } + return NPERR_GENERIC_ERROR; +} + +static NPError Device3DSetStateContext(NPP id, + NPDeviceContext* context, + int32_t state, + intptr_t value) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin) { + return plugin->webplugin()->delegate()->Device3DSetStateContext( + static_cast<NPDeviceContext3D*>(context), state, value); + } + return NPERR_GENERIC_ERROR; +} + +static NPError Device3DGetStateContext(NPP id, + NPDeviceContext* context, + int32_t state, + intptr_t* value) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin) { + return plugin->webplugin()->delegate()->Device3DGetStateContext( + static_cast<NPDeviceContext3D*>(context), state, value); + } + return NPERR_GENERIC_ERROR; +} + +static NPError Device3DFlushContext(NPP id, + NPDeviceContext* context, + NPDeviceFlushContextCallbackPtr callback, + void* user_data) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin) { + return plugin->webplugin()->delegate()->Device3DFlushContext( + id, static_cast<NPDeviceContext3D*>(context), callback, user_data); + } + return NPERR_GENERIC_ERROR; +} + +static NPError Device3DDestroyContext(NPP id, + NPDeviceContext* context) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin) { + return plugin->webplugin()->delegate()->Device3DDestroyContext( + static_cast<NPDeviceContext3D*>(context)); + } + return NPERR_GENERIC_ERROR; +} + +static NPError Device3DCreateBuffer(NPP id, + NPDeviceContext* context, + size_t size, + int32_t* buffer_id) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin) { + return plugin->webplugin()->delegate()->Device3DCreateBuffer( + static_cast<NPDeviceContext3D*>(context), size, buffer_id); + } + return NPERR_GENERIC_ERROR; +} + +static NPError Device3DDestroyBuffer(NPP id, + NPDeviceContext* context, + int32_t buffer_id) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin) { + return plugin->webplugin()->delegate()->Device3DDestroyBuffer( + static_cast<NPDeviceContext3D*>(context), buffer_id); + } + return NPERR_GENERIC_ERROR; +} + +static NPError Device3DMapBuffer(NPP id, + NPDeviceContext* context, + int32_t buffer_id, + NPDeviceBuffer* buffer) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin) { + return plugin->webplugin()->delegate()->Device3DMapBuffer( + static_cast<NPDeviceContext3D*>(context), buffer_id, buffer); + } + return NPERR_GENERIC_ERROR; +} + +// Experimental 3D device API -------------------------------------------------- + +static NPError Device3DGetNumConfigs(NPP id, int32_t* num_configs) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin) { + return plugin->webplugin()->delegate()->Device3DGetNumConfigs(num_configs); + } + return NPERR_GENERIC_ERROR; +} + +static NPError Device3DGetConfigAttribs(NPP id, + int32_t config, + int32_t* attrib_list) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin) { + return plugin->webplugin()->delegate()->Device3DGetConfigAttribs( + config, + attrib_list); + } + return NPERR_GENERIC_ERROR; +} + +static NPError Device3DCreateContext(NPP id, + int32_t config, + const int32_t* attrib_list, + NPDeviceContext** context) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin) { + return plugin->webplugin()->delegate()->Device3DCreateContext( + config, + attrib_list, + reinterpret_cast<NPDeviceContext3D**>(context)); + } + return NPERR_GENERIC_ERROR; +} + +static NPError Device3DSynchronizeContext( + NPP id, + NPDeviceContext* context, + NPDeviceSynchronizationMode mode, + const int32_t* input_attrib_list, + int32_t* output_attrib_list, + NPDeviceSynchronizeContextCallbackPtr callback, + void* callback_data) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin) { + return plugin->webplugin()->delegate()->Device3DSynchronizeContext( + id, + static_cast<NPDeviceContext3D*>(context), + mode, + input_attrib_list, + output_attrib_list, + callback, + callback_data); + } + return NPERR_GENERIC_ERROR; +} + +static NPError Device3DRegisterCallback( + NPP id, + NPDeviceContext* context, + int32_t callback_type, + NPDeviceGenericCallbackPtr callback, + void* callback_data) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin) { + return plugin->webplugin()->delegate()->Device3DRegisterCallback( + id, + static_cast<NPDeviceContext3D*>(context), + callback_type, + callback, + callback_data); + } + return NPERR_GENERIC_ERROR; +} + +// Audio device API ------------------------------------------------------------ + +static NPError DeviceAudioQueryCapability(NPP id, int32_t capability, + int32_t* value) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin) { + plugin->webplugin()->delegate()->DeviceAudioQueryCapability(capability, + value); + return NPERR_NO_ERROR; + } else { + return NPERR_GENERIC_ERROR; + } +} + +static NPError DeviceAudioQueryConfig(NPP id, + const NPDeviceConfig* request, + NPDeviceConfig* obtain) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin) { + return plugin->webplugin()->delegate()->DeviceAudioQueryConfig( + static_cast<const NPDeviceContextAudioConfig*>(request), + static_cast<NPDeviceContextAudioConfig*>(obtain)); + } + return NPERR_GENERIC_ERROR; +} + +static NPError DeviceAudioInitializeContext(NPP id, + const NPDeviceConfig* config, + NPDeviceContext* context) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin) { + return plugin->webplugin()->delegate()->DeviceAudioInitializeContext( + static_cast<const NPDeviceContextAudioConfig*>(config), + static_cast<NPDeviceContextAudio*>(context)); + } + return NPERR_GENERIC_ERROR; +} + +static NPError DeviceAudioSetStateContext(NPP id, + NPDeviceContext* context, + int32_t state, + intptr_t value) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin) { + return plugin->webplugin()->delegate()->DeviceAudioSetStateContext( + static_cast<NPDeviceContextAudio*>(context), state, value); + } + return NPERR_GENERIC_ERROR; +} + +static NPError DeviceAudioGetStateContext(NPP id, + NPDeviceContext* context, + int32_t state, + intptr_t* value) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + return plugin->webplugin()->delegate()->DeviceAudioGetStateContext( + static_cast<NPDeviceContextAudio*>(context), state, value); +} + +static NPError DeviceAudioFlushContext(NPP id, + NPDeviceContext* context, + NPDeviceFlushContextCallbackPtr callback, + void* user_data) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + return plugin->webplugin()->delegate()->DeviceAudioFlushContext( + id, static_cast<NPDeviceContextAudio*>(context), callback, user_data); +} + +static NPError DeviceAudioDestroyContext(NPP id, + NPDeviceContext* context) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + return plugin->webplugin()->delegate()->DeviceAudioDestroyContext( + static_cast<NPDeviceContextAudio*>(context)); +} +// ----------------------------------------------------------------------------- + +static NPDevice* AcquireDevice(NPP id, NPDeviceID device_id) { + static NPDevice device_2d = { + Device2DQueryCapability, + Device2DQueryConfig, + Device2DInitializeContext, + Device2DSetStateContext, + Device2DGetStateContext, + Device2DFlushContext, + Device2DDestroyContext, + Device2DCreateBuffer, + Device2DDestroyBuffer, + Device2DMapBuffer, + NULL, + NULL, + NULL, + NULL, + NULL, + }; + static NPDevice device_3d = { + Device3DQueryCapability, + Device3DQueryConfig, + Device3DInitializeContext, + Device3DSetStateContext, + Device3DGetStateContext, + Device3DFlushContext, + Device3DDestroyContext, + Device3DCreateBuffer, + Device3DDestroyBuffer, + Device3DMapBuffer, + Device3DGetNumConfigs, + Device3DGetConfigAttribs, + Device3DCreateContext, + Device3DRegisterCallback, + Device3DSynchronizeContext, + }; + static NPDevice device_audio = { + DeviceAudioQueryCapability, + DeviceAudioQueryConfig, + DeviceAudioInitializeContext, + DeviceAudioSetStateContext, + DeviceAudioGetStateContext, + DeviceAudioFlushContext, + DeviceAudioDestroyContext, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + }; + + switch (device_id) { + case NPPepper2DDevice: + return const_cast<NPDevice*>(&device_2d); + case NPPepper3DDevice: + return const_cast<NPDevice*>(&device_3d); + case NPPepperAudioDevice: + return const_cast<NPDevice*>(&device_audio); + default: + return NULL; + } +} + +static NPError ChooseFile(NPP id, + const char* mime_types, + NPChooseFileMode mode, + NPChooseFileCallback callback, + void* user_data) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (!plugin) + return NPERR_GENERIC_ERROR; + + if (!plugin->webplugin()->delegate()->ChooseFile(mime_types, + static_cast<int>(mode), + callback, user_data)) + return NPERR_GENERIC_ERROR; + + return NPERR_NO_ERROR; +} + +static void NumberOfFindResultsChanged(NPP id, int total, bool final_result) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin) { + plugin->webplugin()->delegate()->NumberOfFindResultsChanged( + total, final_result); + } +} + +static void SelectedFindResultChanged(NPP id, int index) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin) + plugin->webplugin()->delegate()->SelectedFindResultChanged(index); +} + +static NPWidgetExtensions* GetWidgetExtensions(NPP id) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (!plugin) + return NULL; + + return plugin->webplugin()->delegate()->GetWidgetExtensions(); +} + +static NPError NPSetCursor(NPP id, NPCursorType type) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (!plugin) + return NPERR_GENERIC_ERROR; + + return plugin->webplugin()->delegate()->SetCursor(type) ? + NPERR_NO_ERROR : NPERR_GENERIC_ERROR; +} + +static NPFontExtensions* GetFontExtensions(NPP id) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (!plugin) + return NULL; + + return plugin->webplugin()->delegate()->GetFontExtensions(); +} + +namespace NPAPI { + +NPError GetPepperExtensionsFunctions(void* value) { + static const NPNExtensions kExtensions = { + &AcquireDevice, + &NumberOfFindResultsChanged, + &SelectedFindResultChanged, + &ChooseFile, + &GetWidgetExtensions, + &NPSetCursor, + &GetFontExtensions, + }; + + // Return a pointer to the canonical function table. + NPNExtensions* extensions = const_cast<NPNExtensions*>(&kExtensions); + NPNExtensions** exts = reinterpret_cast<NPNExtensions**>(value); + *exts = extensions; + return NPERR_NO_ERROR; +} + +} // namespace NPAPI diff --git a/webkit/glue/plugins/npapi_extension_thunk.h b/webkit/glue/plugins/npapi_extension_thunk.h new file mode 100644 index 0000000..fada6bc --- /dev/null +++ b/webkit/glue/plugins/npapi_extension_thunk.h @@ -0,0 +1,23 @@ +// Copyright (c) 2009 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. + +#ifndef WEBKIT_GLUE_PLUGINS_NPAPI_EXTENSION_THUNK_H_ +#define WEBKIT_GLUE_PLUGINS_NPAPI_EXTENSION_THUNK_H_ + +#include "third_party/npapi/bindings/npapi_extensions.h" + +// This file implements forwarding for the NPAPI "Pepper" extensions through to +// the WebPluginDelegate associated with the plugin. + +namespace NPAPI { + +// Implements NPN_GetValue for the case of NPNVPepperExtensions. The function +// pointers in the returned structure implement all the extensions. +NPError GetPepperExtensionsFunctions(void* value); + +} // namespace NPAPI + +#endif // WEBKIT_GLUE_PLUGINS_NPAPI_EXTENSION_THUNK_H_ + + diff --git a/webkit/glue/plugins/plugin_constants_win.h b/webkit/glue/plugins/plugin_constants_win.h new file mode 100644 index 0000000..9913e5d --- /dev/null +++ b/webkit/glue/plugins/plugin_constants_win.h @@ -0,0 +1,41 @@ +// Copyright (c) 2006-2009 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. + +#ifndef WEBKIT_GLUE_PLUGIN_CONSTANTS_WIN_H_ +#define WEBKIT_GLUE_PLUGIN_CONSTANTS_WIN_H_ + +// Used by the plugins_test when testing the older WMP plugin to force the new +// plugin to not get loaded. +#define kUseOldWMPPluginSwitch "use-old-wmp" + +// The window class name for a plugin window. +#define kNativeWindowClassName L"NativeWindowClass" + +// The name of the window class name for the wrapper HWND around the actual +// plugin window that's used when running in multi-process mode. This window +// is created on the browser UI thread. +#define kWrapperNativeWindowClassName L"WrapperNativeWindowClass" + +// The name of the custom window message that the browser uses to tell the +// plugin process to paint a window. +#define kPaintMessageName L"Chrome_CustomPaint" + +// The name of the registry key which NPAPI plugins update on installation. +#define kRegistryMozillaPlugins L"SOFTWARE\\MozillaPlugins" + +#define kMozillaActiveXPlugin L"npmozax.dll" +#define kNewWMPPlugin L"np-mswmp.dll" +#define kOldWMPPlugin L"npdsplay.dll" +#define kYahooApplicationStatePlugin L"npystate.dll" +#define kWanWangProtocolHandlerPlugin L"npww.dll" +#define kFlashPlugin L"npswf32.dll" +#define kAcrobatReaderPlugin L"nppdf32.dll" +#define kRealPlayerPlugin L"nppl3260.dll" +#define kSilverlightPlugin L"npctrl.dll" +#define kJavaPlugin1 L"npjp2.dll" +#define kJavaPlugin2 L"npdeploytk.dll" + +#define kGPUPluginMimeType "application/vnd.google.chrome.gpu-plugin" + +#endif // WEBKIT_GLUE_PLUGIN_PLUGIN_LIST_H_ diff --git a/webkit/glue/plugins/plugin_group.cc b/webkit/glue/plugins/plugin_group.cc new file mode 100644 index 0000000..548e624 --- /dev/null +++ b/webkit/glue/plugins/plugin_group.cc @@ -0,0 +1,407 @@ +// 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. + +#include "webkit/glue/plugins/plugin_group.h" + +#include "base/linked_ptr.h" +#include "base/string_util.h" +#include "base/sys_string_conversions.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "base/version.h" +#include "webkit/glue/plugins/plugin_list.h" +#include "webkit/glue/plugins/webplugininfo.h" + +const char* PluginGroup::kAdobeReaderGroupName = "Adobe Reader"; + +/*static*/ +std::set<string16>* PluginGroup::policy_disabled_plugin_patterns_; + +/*static*/ +void PluginGroup::SetPolicyDisabledPluginPatterns( + const std::set<string16>& set) { + if (!policy_disabled_plugin_patterns_) + policy_disabled_plugin_patterns_ = new std::set<string16>(set); + else + *policy_disabled_plugin_patterns_ = set; +} + +/*static*/ +bool PluginGroup::IsPluginNameDisabledByPolicy(const string16& plugin_name) { + if (!policy_disabled_plugin_patterns_) + return false; + + std::set<string16>::const_iterator pattern( + policy_disabled_plugin_patterns_->begin()); + while (pattern != policy_disabled_plugin_patterns_->end()) { + if (MatchPattern(plugin_name, *pattern)) + return true; + ++pattern; + } + + return false; +} + +/*static*/ +bool PluginGroup::IsPluginPathDisabledByPolicy(const FilePath& plugin_path) { + std::vector<WebPluginInfo> plugins; + NPAPI::PluginList::Singleton()->GetPlugins(false, &plugins); + for (std::vector<WebPluginInfo>::const_iterator it = plugins.begin(); + it != plugins.end(); + ++it) { + if (FilePath::CompareEqualIgnoreCase(it->path.value(), + plugin_path.value()) && IsPluginNameDisabledByPolicy(it->name)) { + return true; + } + } + return false; +} + +VersionRange::VersionRange(VersionRangeDefinition definition) + : low_str(definition.version_matcher_low), + high_str(definition.version_matcher_high), + min_str(definition.min_version) { + if (!low_str.empty()) + low.reset(Version::GetVersionFromString(low_str)); + if (!high_str.empty()) + high.reset(Version::GetVersionFromString(high_str)); + if (!min_str.empty()) + min.reset(Version::GetVersionFromString(min_str)); +} + +VersionRange::VersionRange(const VersionRange& other) { + InitFrom(other); +} + +VersionRange& VersionRange::operator=(const VersionRange& other) { + InitFrom(other); + return *this; +} + +VersionRange::~VersionRange() {} + +void VersionRange::InitFrom(const VersionRange& other) { + low_str = other.low_str; + high_str = other.high_str; + min_str = other.min_str; + low.reset(Version::GetVersionFromString(other.low_str)); + high.reset(Version::GetVersionFromString(other.high_str)); + min.reset(Version::GetVersionFromString(other.min_str)); +} + +PluginGroup::PluginGroup(const string16& group_name, + const string16& name_matcher, + const std::string& update_url, + const std::string& identifier) + : identifier_(identifier), + group_name_(group_name), + name_matcher_(name_matcher), + update_url_(update_url), + enabled_(false), + version_(Version::GetVersionFromString("0")) { +} + +void PluginGroup::InitFrom(const PluginGroup& other) { + identifier_ = other.identifier_; + group_name_ = other.group_name_; + name_matcher_ = other.name_matcher_; + description_ = other.description_; + update_url_ = other.update_url_; + enabled_ = other.enabled_; + for (size_t i = 0; i < other.version_ranges_.size(); ++i) + version_ranges_.push_back(other.version_ranges_[i]); + DCHECK_EQ(other.web_plugin_infos_.size(), other.web_plugin_positions_.size()); + for (size_t i = 0; i < other.web_plugin_infos_.size(); ++i) + AddPlugin(other.web_plugin_infos_[i], other.web_plugin_positions_[i]); + if (!version_.get()) + version_.reset(Version::GetVersionFromString("0")); +} + +PluginGroup::PluginGroup(const PluginGroup& other) { + InitFrom(other); +} + +PluginGroup& PluginGroup::operator=(const PluginGroup& other) { + version_ranges_.clear(); + InitFrom(other); + return *this; +} + +/*static*/ +PluginGroup* PluginGroup::FromPluginGroupDefinition( + const PluginGroupDefinition& definition) { + PluginGroup* group = new PluginGroup(ASCIIToUTF16(definition.name), + ASCIIToUTF16(definition.name_matcher), + definition.update_url, + definition.identifier); + for (size_t i = 0; i < definition.num_versions; ++i) + group->version_ranges_.push_back(VersionRange(definition.versions[i])); + return group; +} + +PluginGroup::~PluginGroup() { } + +/*static*/ +std::string PluginGroup::GetIdentifier(const WebPluginInfo& wpi) { +#if defined(OS_POSIX) + return wpi.path.BaseName().value(); +#elif defined(OS_WIN) + return base::SysWideToUTF8(wpi.path.BaseName().value()); +#endif +} + +/*static*/ +std::string PluginGroup::GetLongIdentifier(const WebPluginInfo& wpi) { +#if defined(OS_POSIX) + return wpi.path.value(); +#elif defined(OS_WIN) + return base::SysWideToUTF8(wpi.path.value()); +#endif +} + +/*static*/ +PluginGroup* PluginGroup::FromWebPluginInfo(const WebPluginInfo& wpi) { + // Create a matcher from the name of this plugin. + return new PluginGroup(wpi.name, wpi.name, std::string(), + GetIdentifier(wpi)); +} + +bool PluginGroup::Match(const WebPluginInfo& plugin) const { + if (name_matcher_.empty()) { + return false; + } + + // Look for the name matcher anywhere in the plugin name. + if (plugin.name.find(name_matcher_) == string16::npos) { + return false; + } + + if (version_ranges_.empty()) { + return true; + } + + // There's at least one version range, the plugin's version must be in it. + scoped_ptr<Version> plugin_version( + Version::GetVersionFromString(UTF16ToWide(plugin.version))); + if (plugin_version.get() == NULL) { + // No version could be extracted, assume we don't match the range. + return false; + } + + // Match if the plugin is contained in any of the defined VersionRanges. + for (size_t i = 0; i < version_ranges_.size(); ++i) { + if (IsVersionInRange(*plugin_version, version_ranges_[i])) { + return true; + } + } + // None of the VersionRanges matched. + return false; +} + +/* static */ +Version* PluginGroup::CreateVersionFromString(const string16& version_string) { + // Remove spaces and ')' from the version string, + // Replace any instances of 'r', ',' or '(' with a dot. + std::wstring version = UTF16ToWide(version_string); + RemoveChars(version, L") ", &version); + std::replace(version.begin(), version.end(), 'r', '.'); + std::replace(version.begin(), version.end(), ',', '.'); + std::replace(version.begin(), version.end(), '(', '.'); + + return Version::GetVersionFromString(version); +} + +void PluginGroup::UpdateActivePlugin(const WebPluginInfo& plugin) { + // A group is enabled if any of the files are enabled. + if (plugin.enabled) { + if (!enabled_) { + // If this is the first enabled plugin, use its description. + enabled_ = true; + UpdateDescriptionAndVersion(plugin); + } + } else { + // If this is the first plugin and it's disabled, + // use its description for now. + if (description_.empty()) + UpdateDescriptionAndVersion(plugin); + } +} + +void PluginGroup::UpdateDescriptionAndVersion(const WebPluginInfo& plugin) { + description_ = plugin.desc; + if (Version* new_version = CreateVersionFromString(plugin.version)) + version_.reset(new_version); + else + version_.reset(Version::GetVersionFromString("0")); +} + +void PluginGroup::AddPlugin(const WebPluginInfo& plugin, int position) { + // Check if this group already contains this plugin. + for (size_t i = 0; i < web_plugin_infos_.size(); ++i) { + if (web_plugin_infos_[i].name == plugin.name && + web_plugin_infos_[i].version == plugin.version && + FilePath::CompareEqualIgnoreCase(web_plugin_infos_[i].path.value(), + plugin.path.value())) { + return; + } + } + web_plugin_infos_.push_back(plugin); + // The position of this plugin relative to the global list of plugins. + web_plugin_positions_.push_back(position); + UpdateActivePlugin(plugin); +} + +string16 PluginGroup::GetGroupName() const { + if (!group_name_.empty()) + return group_name_; + DCHECK_EQ(1u, web_plugin_infos_.size()); + FilePath::StringType path = + web_plugin_infos_[0].path.BaseName().RemoveExtension().value(); +#if defined(OS_POSIX) + return UTF8ToUTF16(path); +#elif defined(OS_WIN) + return WideToUTF16(path); +#endif +} + +DictionaryValue* PluginGroup::GetSummary() const { + DictionaryValue* result = new DictionaryValue(); + result->SetString("name", GetGroupName()); + result->SetBoolean("enabled", enabled_); + return result; +} + +DictionaryValue* PluginGroup::GetDataForUI() const { + string16 name = GetGroupName(); + DictionaryValue* result = new DictionaryValue(); + result->SetString("name", name); + result->SetString("description", description_); + result->SetString("version", version_->GetString()); + result->SetString("update_url", update_url_); + result->SetBoolean("critical", IsVulnerable()); + + bool group_disabled_by_policy = IsPluginNameDisabledByPolicy(name); + ListValue* plugin_files = new ListValue(); + bool all_plugins_disabled_by_policy = true; + for (size_t i = 0; i < web_plugin_infos_.size(); ++i) { + const WebPluginInfo& web_plugin = web_plugin_infos_[i]; + int priority = web_plugin_positions_[i]; + DictionaryValue* plugin_file = new DictionaryValue(); + plugin_file->SetString("name", web_plugin.name); + plugin_file->SetString("description", web_plugin.desc); + plugin_file->SetString("path", web_plugin.path.value()); + plugin_file->SetString("version", web_plugin.version); + bool plugin_disabled_by_policy = group_disabled_by_policy || + IsPluginNameDisabledByPolicy(web_plugin.name); + if (plugin_disabled_by_policy) { + plugin_file->SetString("enabledMode", "disabledByPolicy"); + } else { + all_plugins_disabled_by_policy = false; + plugin_file->SetString("enabledMode", + web_plugin.enabled ? "enabled" : "disabledByUser"); + } + plugin_file->SetInteger("priority", priority); + + ListValue* mime_types = new ListValue(); + for (std::vector<WebPluginMimeType>::const_iterator type_it = + web_plugin.mime_types.begin(); + type_it != web_plugin.mime_types.end(); + ++type_it) { + DictionaryValue* mime_type = new DictionaryValue(); + mime_type->SetString("mimeType", type_it->mime_type); + mime_type->SetString("description", type_it->description); + + ListValue* file_extensions = new ListValue(); + for (std::vector<std::string>::const_iterator ext_it = + type_it->file_extensions.begin(); + ext_it != type_it->file_extensions.end(); + ++ext_it) { + file_extensions->Append(new StringValue(*ext_it)); + } + mime_type->Set("fileExtensions", file_extensions); + + mime_types->Append(mime_type); + } + plugin_file->Set("mimeTypes", mime_types); + + plugin_files->Append(plugin_file); + } + + if (group_disabled_by_policy || all_plugins_disabled_by_policy) { + result->SetString("enabledMode", "disabledByPolicy"); + } else { + result->SetString("enabledMode", enabled_ ? "enabled" : "disabledByUser"); + } + result->Set("plugin_files", plugin_files); + + return result; +} + +/*static*/ +bool PluginGroup::IsVersionInRange(const Version& version, + const VersionRange& range) { + DCHECK(range.low.get() != NULL || range.high.get() == NULL) + << "Lower bound of version range must be defined."; + return (range.low.get() == NULL && range.high.get() == NULL) || + (range.low->CompareTo(version) <= 0 && + (range.high.get() == NULL || range.high->CompareTo(version) > 0)); +} + +/*static*/ +bool PluginGroup::IsPluginOutdated(const Version& plugin_version, + const VersionRange& version_range) { + if (IsVersionInRange(plugin_version, version_range)) { + if (version_range.min.get() && + plugin_version.CompareTo(*version_range.min) < 0) { + return true; + } + } + return false; +} + +// Returns true if the latest version of this plugin group is vulnerable. +bool PluginGroup::IsVulnerable() const { + for (size_t i = 0; i < version_ranges_.size(); ++i) { + if (IsPluginOutdated(*version_, version_ranges_[i])) + return true; + } + return false; +} + +void PluginGroup::DisableOutdatedPlugins() { + description_ = string16(); + enabled_ = false; + + for (std::vector<WebPluginInfo>::iterator it = + web_plugin_infos_.begin(); + it != web_plugin_infos_.end(); ++it) { + scoped_ptr<Version> version(CreateVersionFromString(it->version)); + if (version.get()) { + for (size_t i = 0; i < version_ranges_.size(); ++i) { + if (IsPluginOutdated(*version, version_ranges_[i])) { + it->enabled = false; + NPAPI::PluginList::Singleton()->DisablePlugin(it->path); + } + } + } + UpdateActivePlugin(*it); + } +} + +void PluginGroup::Enable(bool enable) { + bool enabled_plugin_exists = false; + for (std::vector<WebPluginInfo>::iterator it = + web_plugin_infos_.begin(); + it != web_plugin_infos_.end(); ++it) { + if (enable && !IsPluginNameDisabledByPolicy(it->name)) { + NPAPI::PluginList::Singleton()->EnablePlugin(it->path); + it->enabled = true; + enabled_plugin_exists = true; + } else { + it->enabled = false; + NPAPI::PluginList::Singleton()->DisablePlugin(it->path); + } + } + enabled_ = enabled_plugin_exists; +} diff --git a/webkit/glue/plugins/plugin_group.h b/webkit/glue/plugins/plugin_group.h new file mode 100644 index 0000000..5098787 --- /dev/null +++ b/webkit/glue/plugins/plugin_group.h @@ -0,0 +1,205 @@ +// 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. + +#ifndef WEBKIT_GLUE_PLUGINS_PLUGIN_GROUP_H_ +#define WEBKIT_GLUE_PLUGINS_PLUGIN_GROUP_H_ +#pragma once + +#include <map> +#include <set> +#include <string> +#include <vector> + +#include "base/gtest_prod_util.h" +#include "base/scoped_ptr.h" +#include "base/string16.h" + +class DictionaryValue; +class FilePath; +class Version; +struct WebPluginInfo; + +namespace NPAPI { +class PluginList; +}; + +// Hard-coded version ranges for plugin groups. +struct VersionRangeDefinition { + // Matcher for lowest version matched by this range (inclusive). May be empty + // to match everything iff |version_matcher_high| is also empty. + const char* version_matcher_low; + // Matcher for highest version matched by this range (exclusive). May be empty + // to match anything higher than |version_matcher_low|. + const char* version_matcher_high; + const char* min_version; // Minimum secure version. +}; + +// Hard-coded definitions of plugin groups. +struct PluginGroupDefinition { + const char* identifier; // Unique identifier for this group. + const char* name; // Name of this group. + const char* name_matcher; // Substring matcher for the plugin name. + const VersionRangeDefinition* versions; // List of version ranges. + const size_t num_versions; // Size of the array |versions| points to. + const char* update_url; // Location of latest secure version. +}; + +// Run-time structure to hold version range information. +struct VersionRange { + public: + explicit VersionRange(VersionRangeDefinition definition); + VersionRange(const VersionRange& other); + VersionRange& operator=(const VersionRange& other); + ~VersionRange(); + + std::string low_str; + std::string high_str; + std::string min_str; + scoped_ptr<Version> low; + scoped_ptr<Version> high; + scoped_ptr<Version> min; + private: + void InitFrom(const VersionRange& other); +}; + +// A PluginGroup can match a range of versions of a specific plugin (as defined +// by matching a substring of its name). +// It contains all WebPluginInfo structs (at least one) matching its definition. +// In addition, it knows about a security "baseline", i.e. the minimum version +// of a plugin that is needed in order not to exhibit known security +// vulnerabilities. + +class PluginGroup { + public: + // Used by about:plugins to disable Reader plugin when internal PDF viewer is + // enabled. + static const char* kAdobeReaderGroupName; + + PluginGroup(const PluginGroup& other); + + ~PluginGroup(); + + PluginGroup& operator=(const PluginGroup& other); + + // Configures the set of plugin name patterns for disabling plugins via + // enterprise configuration management. + static void SetPolicyDisabledPluginPatterns(const std::set<string16>& set); + + // Tests to see if a plugin is on the blacklist using its name as + // the lookup key. + static bool IsPluginNameDisabledByPolicy(const string16& plugin_name); + + // Tests to see if a plugin is on the blacklist using its path as + // the lookup key. + static bool IsPluginPathDisabledByPolicy(const FilePath& plugin_path); + + // Returns true if the given plugin matches this group. + bool Match(const WebPluginInfo& plugin) const; + + // Adds the given plugin to this group. Provide the position of the + // plugin as given by PluginList so we can display its priority. + void AddPlugin(const WebPluginInfo& plugin, int position); + + // Enables/disables this group. This enables/disables all plugins in the + // group. + void Enable(bool enable); + + // Returns whether the plugin group is enabled or not. + bool Enabled() const { return enabled_; } + + // Returns a unique identifier for this group, if one is defined, or the empty + // string otherwise. + const std::string& identifier() const { return identifier_; } + + // Returns this group's name, or the filename without extension if the name + // is empty. + string16 GetGroupName() const; + + // Returns the description of the highest-priority plug-in in the group. + const string16& description() const { return description_; } + + // Returns a DictionaryValue with data to display in the UI. + DictionaryValue* GetDataForUI() const; + + // Returns a DictionaryValue with data to save in the preferences. + DictionaryValue* GetSummary() const; + + // Returns the update URL. + std::string GetUpdateURL() const { return update_url_; } + + // Returns true if the highest-priority plugin in this group has known + // security problems. + bool IsVulnerable() const; + + // Disables all plugins in this group that are older than the + // minimum version. + void DisableOutdatedPlugins(); + + // Parse a version string as used by a plug-in. This method is more lenient + // in accepting weird version strings than Version::GetFromString(). + static Version* CreateVersionFromString(const string16& version_string); + + private: + typedef std::map<std::string, PluginGroup*> PluginMap; + + friend class NPAPI::PluginList; + friend class PluginGroupTest; + friend class TableModelArrayControllerTest; + friend class PluginExceptionsTableModelTest; + + // Generates the (short) identifier string for the given plugin. + static std::string GetIdentifier(const WebPluginInfo& wpi); + + // Generates the long identifier (based on the full file path) for the given + // plugin, to be called when the short identifier is not unique. + static std::string GetLongIdentifier(const WebPluginInfo& wpi); + + // Creates a PluginGroup from a PluginGroupDefinition. The caller takes + // ownership of the created PluginGroup. + static PluginGroup* FromPluginGroupDefinition( + const PluginGroupDefinition& definition); + + // Creates a PluginGroup from a WebPluginInfo. The caller takes ownership of + // the created PluginGroup. + static PluginGroup* FromWebPluginInfo(const WebPluginInfo& wpi); + + // Returns |true| if |version| is contained in [low, high) of |range|. + static bool IsVersionInRange(const Version& version, + const VersionRange& range); + + // Returns |true| iff |plugin_version| is both contained in |version_range| + // and declared outdated (== vulnerable) by it. + static bool IsPluginOutdated(const Version& plugin_version, + const VersionRange& version_range); + + PluginGroup(const string16& group_name, + const string16& name_matcher, + const std::string& update_url, + const std::string& identifier); + + void InitFrom(const PluginGroup& other); + + // Set the description and version for this plugin group from the + // given plug-in. + void UpdateDescriptionAndVersion(const WebPluginInfo& plugin); + + // Updates the active plugin in the group. The active plugin is the first + // enabled one, or if all plugins are disabled, simply the first one. + void UpdateActivePlugin(const WebPluginInfo& plugin); + + static std::set<string16>* policy_disabled_plugin_patterns_; + + std::string identifier_; + string16 group_name_; + string16 name_matcher_; + string16 description_; + std::string update_url_; + bool enabled_; + std::vector<VersionRange> version_ranges_; + scoped_ptr<Version> version_; + std::vector<WebPluginInfo> web_plugin_infos_; + std::vector<int> web_plugin_positions_; +}; + +#endif // WEBKIT_GLUE_PLUGINS_PLUGIN_GROUP_H_ diff --git a/webkit/glue/plugins/plugin_group_unittest.cc b/webkit/glue/plugins/plugin_group_unittest.cc new file mode 100644 index 0000000..31dee1e --- /dev/null +++ b/webkit/glue/plugins/plugin_group_unittest.cc @@ -0,0 +1,224 @@ +// 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. + +#include "webkit/glue/plugins/plugin_group.h" + +#include <string> +#include <vector> + +#include "base/scoped_ptr.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "base/version.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/glue/plugins/webplugininfo.h" +#include "webkit/glue/plugins/plugin_list.h" + +static const VersionRangeDefinition kPluginVersionRange[] = { + { "", "", "3.0.44" } +}; +static const VersionRangeDefinition kPlugin3VersionRange[] = { + { "0", "4", "3.0.44" } +}; +static const VersionRangeDefinition kPlugin4VersionRange[] = { + { "4", "5", "4.0.44" } +}; +static const VersionRangeDefinition kPlugin34VersionRange[] = { + { "0", "4", "3.0.44" }, + { "4", "5", "4.0.44" } +}; + +static const PluginGroupDefinition kPluginDef = { + "myplugin", "MyPlugin", "MyPlugin", kPluginVersionRange, 1, + "http://latest/" }; +static const PluginGroupDefinition kPluginDef3 = { + "myplugin-3", "MyPlugin 3", "MyPlugin", kPlugin3VersionRange, 1, + "http://latest" }; +static const PluginGroupDefinition kPluginDef4 = { + "myplugin-4", "MyPlugin 4", "MyPlugin", kPlugin4VersionRange, 1, + "http://latest" }; +static const PluginGroupDefinition kPluginDef34 = { + "myplugin-34", "MyPlugin 3/4", "MyPlugin", kPlugin34VersionRange, 2, + "http://latest" }; +static const PluginGroupDefinition kPluginDefNotVulnerable = { + "myplugin-latest", "MyPlugin", "MyPlugin", NULL, 0, "http://latest" }; + +// name, path, version, desc, mime_types, enabled. +static WebPluginInfo kPlugin2043 = WebPluginInfo( + ASCIIToUTF16("MyPlugin"), ASCIIToUTF16("2.0.43"), + ASCIIToUTF16("MyPlugin version 2.0.43")); +static WebPluginInfo kPlugin3043 = WebPluginInfo( + ASCIIToUTF16("MyPlugin"), ASCIIToUTF16("3.0.43"), + ASCIIToUTF16("MyPlugin version 3.0.43")); +static WebPluginInfo kPlugin3044 = WebPluginInfo( + ASCIIToUTF16("MyPlugin"), ASCIIToUTF16("3.0.44"), + ASCIIToUTF16("MyPlugin version 3.0.44")); +static WebPluginInfo kPlugin3045 = WebPluginInfo( + ASCIIToUTF16("MyPlugin"), ASCIIToUTF16("3.0.45"), + ASCIIToUTF16("MyPlugin version 3.0.45")); +static WebPluginInfo kPlugin4043 = WebPluginInfo( + ASCIIToUTF16("MyPlugin"), ASCIIToUTF16("4.0.43"), + ASCIIToUTF16("MyPlugin version 4.0.43")); + +class PluginGroupTest : public testing::Test { + public: + static PluginGroup* CreatePluginGroup( + const PluginGroupDefinition& definition) { + return PluginGroup::FromPluginGroupDefinition(definition); + } + static PluginGroup* CreatePluginGroup(const WebPluginInfo& wpi) { + return PluginGroup::FromWebPluginInfo(wpi); + } + protected: + virtual void TearDown() { + PluginGroup::SetPolicyDisabledPluginPatterns(std::set<string16>()); + } +}; + +TEST(PluginGroupTest, PluginGroupMatch) { + scoped_ptr<PluginGroup> group(PluginGroupTest::CreatePluginGroup( + kPluginDef3)); + EXPECT_TRUE(group->Match(kPlugin3045)); + group->AddPlugin(kPlugin3045, 0); + EXPECT_FALSE(group->IsVulnerable()); +} + +TEST(PluginGroupTest, PluginGroupMatchCorrectVersion) { + scoped_ptr<PluginGroup> group(PluginGroupTest::CreatePluginGroup( + kPluginDef3)); + EXPECT_TRUE(group->Match(kPlugin2043)); + EXPECT_TRUE(group->Match(kPlugin3043)); + EXPECT_FALSE(group->Match(kPlugin4043)); + + group.reset(PluginGroupTest::CreatePluginGroup(kPluginDef4)); + EXPECT_FALSE(group->Match(kPlugin2043)); + EXPECT_FALSE(group->Match(kPlugin3043)); + EXPECT_TRUE(group->Match(kPlugin4043)); + + group.reset(PluginGroupTest::CreatePluginGroup(kPluginDef34)); + EXPECT_TRUE(group->Match(kPlugin2043)); + EXPECT_TRUE(group->Match(kPlugin3043)); + EXPECT_TRUE(group->Match(kPlugin4043)); +} + +TEST(PluginGroupTest, PluginGroupDescription) { + string16 desc3043(ASCIIToUTF16("MyPlugin version 3.0.43")); + string16 desc3045(ASCIIToUTF16("MyPlugin version 3.0.45")); + + PluginGroupDefinition plugindefs[] = { kPluginDef3, kPluginDef34 }; + for (size_t i = 0; i < 2; ++i) { + WebPluginInfo plugin3043(kPlugin3043); + WebPluginInfo plugin3045(kPlugin3045); + { + scoped_ptr<PluginGroup> group(PluginGroupTest::CreatePluginGroup( + plugindefs[i])); + EXPECT_TRUE(group->Match(plugin3043)); + group->AddPlugin(plugin3043, 0); + EXPECT_EQ(desc3043, group->description()); + EXPECT_TRUE(group->IsVulnerable()); + EXPECT_TRUE(group->Match(plugin3045)); + group->AddPlugin(plugin3045, 1); + EXPECT_EQ(desc3043, group->description()); + EXPECT_TRUE(group->IsVulnerable()); + } + + { + // Disable the first plugin. + plugin3043.enabled = false; + scoped_ptr<PluginGroup> group(PluginGroupTest::CreatePluginGroup( + plugindefs[i])); + EXPECT_TRUE(group->Match(plugin3043)); + group->AddPlugin(plugin3043, 0); + EXPECT_EQ(desc3043, group->description()); + EXPECT_TRUE(group->IsVulnerable()); + EXPECT_FALSE(group->Enabled()); + EXPECT_TRUE(group->Match(plugin3045)); + group->AddPlugin(plugin3045, 1); + EXPECT_EQ(desc3045, group->description()); + EXPECT_FALSE(group->IsVulnerable()); + } + + { + // Disable the second plugin. + plugin3045.enabled = false; + scoped_ptr<PluginGroup> group(PluginGroupTest::CreatePluginGroup( + plugindefs[i])); + EXPECT_TRUE(group->Match(plugin3043)); + group->AddPlugin(plugin3043, 1); + EXPECT_EQ(desc3043, group->description()); + EXPECT_TRUE(group->IsVulnerable()); + EXPECT_TRUE(group->Match(plugin3045)); + group->AddPlugin(plugin3045, 0); + EXPECT_EQ(desc3043, group->description()); + EXPECT_TRUE(group->IsVulnerable()); + } + } +} + +TEST(PluginGroupTest, PluginGroupDefinition) { + const PluginGroupDefinition* definitions = + NPAPI::PluginList::GetPluginGroupDefinitions(); + for (size_t i = 0; + i < NPAPI::PluginList::GetPluginGroupDefinitionsSize(); + ++i) { + scoped_ptr<PluginGroup> def_group( + PluginGroupTest::CreatePluginGroup(definitions[i])); + ASSERT_TRUE(def_group.get() != NULL); + EXPECT_FALSE(def_group->Match(kPlugin2043)); + } +} + +TEST(PluginGroupTest, DisableOutdated) { + PluginGroupDefinition plugindefs[] = { kPluginDef3, kPluginDef34 }; + for (size_t i = 0; i < 2; ++i) { + scoped_ptr<PluginGroup> group(PluginGroupTest::CreatePluginGroup( + plugindefs[i])); + group->AddPlugin(kPlugin3043, 0); + group->AddPlugin(kPlugin3045, 1); + EXPECT_EQ(ASCIIToUTF16("MyPlugin version 3.0.43"), group->description()); + EXPECT_TRUE(group->IsVulnerable()); + + group->DisableOutdatedPlugins(); + EXPECT_EQ(ASCIIToUTF16("MyPlugin version 3.0.45"), group->description()); + EXPECT_FALSE(group->IsVulnerable()); + } +} + +TEST(PluginGroupTest, VersionExtraction) { + // Some real-world plugin versions (spaces, commata, parentheses, 'r', oh my) + const char* versions[][2] = { + { "7.6.6 (1671)", "7.6.6.1671" }, // Quicktime + { "2, 0, 0, 254", "2.0.0.254" }, // DivX + { "3, 0, 0, 0", "3.0.0.0" }, // Picasa + { "1, 0, 0, 1", "1.0.0.1" }, // Earth + { "10,0,45,2", "10.0.45.2" }, // Flash + { "11.5.7r609", "11.5.7.609"} // Shockwave + }; + + for (size_t i = 0; i < arraysize(versions); i++) { + const WebPluginInfo plugin = WebPluginInfo( + ASCIIToUTF16("Blah Plugin"), ASCIIToUTF16(versions[i][0]), string16()); + scoped_ptr<PluginGroup> group(PluginGroupTest::CreatePluginGroup(plugin)); + EXPECT_TRUE(group->Match(plugin)); + group->AddPlugin(plugin, 0); + scoped_ptr<DictionaryValue> data(group->GetDataForUI()); + std::string version; + data->GetString("version", &version); + EXPECT_EQ(versions[i][1], version); + } +} + +TEST(PluginGroupTest, DisabledByPolicy) { + std::set<string16> disabled_plugins; + disabled_plugins.insert(ASCIIToUTF16("Disable this!")); + disabled_plugins.insert(ASCIIToUTF16("*Google*")); + PluginGroup::SetPolicyDisabledPluginPatterns(disabled_plugins); + + EXPECT_FALSE(PluginGroup::IsPluginNameDisabledByPolicy(ASCIIToUTF16("42"))); + EXPECT_TRUE(PluginGroup::IsPluginNameDisabledByPolicy( + ASCIIToUTF16("Disable this!"))); + EXPECT_TRUE(PluginGroup::IsPluginNameDisabledByPolicy( + ASCIIToUTF16("Google Earth"))); +} diff --git a/webkit/glue/plugins/plugin_host.cc b/webkit/glue/plugins/plugin_host.cc new file mode 100644 index 0000000..28aba02 --- /dev/null +++ b/webkit/glue/plugins/plugin_host.cc @@ -0,0 +1,1111 @@ +// 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. + +#include "webkit/glue/plugins/plugin_host.h" + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/scoped_ptr.h" +#include "base/string_piece.h" +#include "base/string_util.h" +#if defined(OS_MACOSX) +#include "base/sys_info.h" +#endif +#include "base/sys_string_conversions.h" +#include "base/utf_string_conversions.h" +#include "net/base/net_util.h" +#include "third_party/npapi/bindings/npapi_extensions.h" +#include "third_party/npapi/bindings/npruntime.h" +#include "third_party/WebKit/WebKit/chromium/public/WebBindings.h" +#include "third_party/WebKit/WebKit/chromium/public/WebKit.h" +#include "webkit/glue/webkit_glue.h" +#include "webkit/glue/plugins/default_plugin_shared.h" +#include "webkit/glue/plugins/npapi_extension_thunk.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/webplugin_delegate.h" +#include "webkit/glue/plugins/webplugininfo.h" + +using WebKit::WebBindings; + +// Finds a PluginInstance from an NPP. +// The caller must take a reference if needed. +static NPAPI::PluginInstance* FindInstance(NPP id) { + if (id == NULL) { + return NULL; + } + return reinterpret_cast<NPAPI::PluginInstance*>(id->ndata); +} + +#if defined(OS_MACOSX) +// Returns true if the OS supports shared accelerated surfaces via IOSurface. +// This is true on Snow Leopard and higher. +static bool SupportsSharingAcceleratedSurfaces() { + int32 major, minor, bugfix; + base::SysInfo::OperatingSystemVersionNumbers(&major, &minor, &bugfix); + return major > 10 || (major == 10 && minor > 5); +} +#endif + +namespace NPAPI { + +scoped_refptr<PluginHost> PluginHost::singleton_; + +PluginHost::PluginHost() { + InitializeHostFuncs(); +} + +PluginHost::~PluginHost() { +} + +PluginHost *PluginHost::Singleton() { + if (singleton_.get() == NULL) { + singleton_ = new PluginHost(); + } + + DCHECK(singleton_.get() != NULL); + return singleton_; +} + +void PluginHost::InitializeHostFuncs() { + memset(&host_funcs_, 0, sizeof(host_funcs_)); + host_funcs_.size = sizeof(host_funcs_); + host_funcs_.version = (NP_VERSION_MAJOR << 8) | (NP_VERSION_MINOR); + + // The "basic" functions + host_funcs_.geturl = &NPN_GetURL; + host_funcs_.posturl = &NPN_PostURL; + host_funcs_.requestread = &NPN_RequestRead; + host_funcs_.newstream = &NPN_NewStream; + host_funcs_.write = &NPN_Write; + host_funcs_.destroystream = &NPN_DestroyStream; + host_funcs_.status = &NPN_Status; + host_funcs_.uagent = &NPN_UserAgent; + host_funcs_.memalloc = &NPN_MemAlloc; + host_funcs_.memfree = &NPN_MemFree; + host_funcs_.memflush = &NPN_MemFlush; + host_funcs_.reloadplugins = &NPN_ReloadPlugins; + + // We don't implement java yet + host_funcs_.getJavaEnv = &NPN_GetJavaEnv; + host_funcs_.getJavaPeer = &NPN_GetJavaPeer; + + // Advanced functions we implement + host_funcs_.geturlnotify = &NPN_GetURLNotify; + host_funcs_.posturlnotify = &NPN_PostURLNotify; + host_funcs_.getvalue = &NPN_GetValue; + host_funcs_.setvalue = &NPN_SetValue; + host_funcs_.invalidaterect = &NPN_InvalidateRect; + host_funcs_.invalidateregion = &NPN_InvalidateRegion; + host_funcs_.forceredraw = &NPN_ForceRedraw; + + // These come from the Javascript Engine + host_funcs_.getstringidentifier = WebBindings::getStringIdentifier; + host_funcs_.getstringidentifiers = WebBindings::getStringIdentifiers; + host_funcs_.getintidentifier = WebBindings::getIntIdentifier; + host_funcs_.identifierisstring = WebBindings::identifierIsString; + host_funcs_.utf8fromidentifier = WebBindings::utf8FromIdentifier; + host_funcs_.intfromidentifier = WebBindings::intFromIdentifier; + host_funcs_.createobject = WebBindings::createObject; + host_funcs_.retainobject = WebBindings::retainObject; + host_funcs_.releaseobject = WebBindings::releaseObject; + host_funcs_.invoke = WebBindings::invoke; + host_funcs_.invokeDefault = WebBindings::invokeDefault; + host_funcs_.evaluate = WebBindings::evaluate; + host_funcs_.getproperty = WebBindings::getProperty; + host_funcs_.setproperty = WebBindings::setProperty; + host_funcs_.removeproperty = WebBindings::removeProperty; + host_funcs_.hasproperty = WebBindings::hasProperty; + host_funcs_.hasmethod = WebBindings::hasMethod; + host_funcs_.releasevariantvalue = WebBindings::releaseVariantValue; + host_funcs_.setexception = WebBindings::setException; + host_funcs_.pushpopupsenabledstate = NPN_PushPopupsEnabledState; + host_funcs_.poppopupsenabledstate = NPN_PopPopupsEnabledState; + host_funcs_.enumerate = WebBindings::enumerate; + host_funcs_.pluginthreadasynccall = NPN_PluginThreadAsyncCall; + host_funcs_.construct = WebBindings::construct; + host_funcs_.getvalueforurl = NPN_GetValueForURL; + host_funcs_.setvalueforurl = NPN_SetValueForURL; + host_funcs_.getauthenticationinfo = NPN_GetAuthenticationInfo; + host_funcs_.scheduletimer = NPN_ScheduleTimer; + host_funcs_.unscheduletimer = NPN_UnscheduleTimer; + host_funcs_.popupcontextmenu = NPN_PopUpContextMenu; + host_funcs_.convertpoint = NPN_ConvertPoint; + host_funcs_.handleevent = NPN_HandleEvent; + host_funcs_.unfocusinstance = NPN_UnfocusInstance; + host_funcs_.urlredirectresponse = NPN_URLRedirectResponse; +} + +void PluginHost::PatchNPNetscapeFuncs(NPNetscapeFuncs* overrides) { + // When running in the plugin process, we need to patch the NPN functions + // that the plugin calls to interact with NPObjects that we give. Otherwise + // the plugin will call the v8 NPN functions, which won't work since we have + // an NPObjectProxy and not a real v8 implementation. + if (overrides->invoke) + host_funcs_.invoke = overrides->invoke; + + if (overrides->invokeDefault) + host_funcs_.invokeDefault = overrides->invokeDefault; + + if (overrides->evaluate) + host_funcs_.evaluate = overrides->evaluate; + + if (overrides->getproperty) + host_funcs_.getproperty = overrides->getproperty; + + if (overrides->setproperty) + host_funcs_.setproperty = overrides->setproperty; + + if (overrides->removeproperty) + host_funcs_.removeproperty = overrides->removeproperty; + + if (overrides->hasproperty) + host_funcs_.hasproperty = overrides->hasproperty; + + if (overrides->hasmethod) + host_funcs_.hasmethod = overrides->hasmethod; + + if (overrides->setexception) + host_funcs_.setexception = overrides->setexception; + + if (overrides->enumerate) + host_funcs_.enumerate = overrides->enumerate; +} + +bool PluginHost::SetPostData(const char* buf, + uint32 length, + std::vector<std::string>* names, + std::vector<std::string>* values, + std::vector<char>* body) { + // Use a state table to do the parsing. Whitespace must be + // trimmed after the fact if desired. In our case, we actually + // don't care about the whitespace, because we're just going to + // pass this back into another POST. This function strips out the + // "Content-length" header and does not append it to the request. + + // + // This parser takes action only on state changes. + // + // Transition table: + // : \n NULL Other + // 0 GetHeader 1 2 4 0 + // 1 GetValue 1 0 3 1 + // 2 GetData 2 2 3 2 + // 3 DONE + // 4 ERR + // + enum { INPUT_COLON=0, INPUT_NEWLINE, INPUT_NULL, INPUT_OTHER }; + enum { GETNAME, GETVALUE, GETDATA, DONE, ERR }; + int statemachine[3][4] = { { GETVALUE, GETDATA, GETDATA, GETNAME }, + { GETVALUE, GETNAME, DONE, GETVALUE }, + { GETDATA, GETDATA, DONE, GETDATA } }; + std::string name, value; + const char* ptr = static_cast<const char*>(buf); + const char* start = ptr; + int state = GETNAME; // initial state + bool done = false; + bool err = false; + do { + int input; + + // Translate the current character into an input + // for the state table. + switch (*ptr) { + case ':' : + input = INPUT_COLON; + break; + case '\n': + input = INPUT_NEWLINE; + break; + case 0 : + input = INPUT_NULL; + break; + default : + input = INPUT_OTHER; + break; + } + + int newstate = statemachine[state][input]; + + // Take action based on the new state. + if (state != newstate) { + switch (newstate) { + case GETNAME: + // Got a value. + value = std::string(start, ptr - start); + TrimWhitespace(value, TRIM_ALL, &value); + // If the name field is empty, we'll skip this header + // but we won't error out. + if (!name.empty() && name != "content-length") { + names->push_back(name); + values->push_back(value); + } + start = ptr + 1; + break; + case GETVALUE: + // Got a header. + name = StringToLowerASCII(std::string(start, ptr - start)); + TrimWhitespace(name, TRIM_ALL, &name); + start = ptr + 1; + break; + case GETDATA: { + // Finished headers, now get body + if (*ptr) + start = ptr + 1; + size_t previous_size = body->size(); + size_t new_body_size = length - static_cast<int>(start - buf); + body->resize(previous_size + new_body_size); + if (!body->empty()) + memcpy(&body->front() + previous_size, start, new_body_size); + done = true; + break; + } + case ERR: + // error + err = true; + done = true; + break; + } + } + state = newstate; + ptr++; + } while (!done); + + return !err; +} + +} // namespace NPAPI + +extern "C" { + +// Allocates memory from the host's memory space. +void* NPN_MemAlloc(uint32_t size) { + scoped_refptr<NPAPI::PluginHost> host(NPAPI::PluginHost::Singleton()); + if (host != NULL) { + // Note: We must use the same allocator/deallocator + // that is used by the javascript library, as some of the + // JS APIs will pass memory to the plugin which the plugin + // will attempt to free. + return malloc(size); + } + return NULL; +} + +// Deallocates memory from the host's memory space +void NPN_MemFree(void* ptr) { + scoped_refptr<NPAPI::PluginHost> host(NPAPI::PluginHost::Singleton()); + if (host != NULL) { + if (ptr != NULL && ptr != reinterpret_cast<void*>(-1)) + free(ptr); + } +} + +// Requests that the host free a specified amount of memory. +uint32_t NPN_MemFlush(uint32_t size) { + // This is not relevant on Windows; MAC specific + return size; +} + +// This is for dynamic discovery of new plugins. +// Should force a re-scan of the plugins directory to load new ones. +void NPN_ReloadPlugins(NPBool reload_pages) { + WebKit::resetPluginCache(reload_pages ? true : false); +} + +// Requests a range of bytes for a seekable stream. +NPError NPN_RequestRead(NPStream* stream, NPByteRange* range_list) { + if (!stream || !range_list) + return NPERR_GENERIC_ERROR; + + scoped_refptr<NPAPI::PluginInstance> plugin( + reinterpret_cast<NPAPI::PluginInstance*>(stream->ndata)); + if (!plugin.get()) + return NPERR_GENERIC_ERROR; + + plugin->RequestRead(stream, range_list); + return NPERR_NO_ERROR; +} + +// Generic form of GetURL for common code between GetURL and GetURLNotify. +static NPError GetURLNotify(NPP id, + const char* url, + const char* target, + bool notify, + void* notify_data) { + if (!url) + return NPERR_INVALID_URL; + + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (!plugin.get()) { + return NPERR_GENERIC_ERROR; + } + + plugin->RequestURL(url, "GET", target, NULL, 0, notify, notify_data); + return NPERR_NO_ERROR; +} + +// Requests creation of a new stream with the contents of the +// specified URL; gets notification of the result. +NPError NPN_GetURLNotify(NPP id, + const char* url, + const char* target, + void* notify_data) { + // This is identical to NPN_GetURL, but after finishing, the + // browser will call NPP_URLNotify to inform the plugin that + // it has completed. + + // According to the NPAPI documentation, if target == _self + // or a parent to _self, the browser should return NPERR_INVALID_PARAM, + // because it can't notify the plugin once deleted. This is + // absolutely false; firefox doesn't do this, and Flash relies on + // being able to use this. + + // Also according to the NPAPI documentation, we should return + // NPERR_INVALID_URL if the url requested is not valid. However, + // this would require that we synchronously start fetching the + // URL. That just isn't practical. As such, there really is + // no way to return this error. From looking at the Firefox + // implementation, it doesn't look like Firefox does this either. + + return GetURLNotify(id, url, target, true, notify_data); +} + +NPError NPN_GetURL(NPP id, const char* url, const char* target) { + // Notes: + // Request from the Plugin to fetch content either for the plugin + // or to be placed into a browser window. + // + // If target == null, the browser fetches content and streams to plugin. + // otherwise, the browser loads content into an existing browser frame. + // If the target is the window/frame containing the plugin, the plugin + // may be destroyed. + // If the target is _blank, a mailto: or news: url open content in a new + // browser window + // If the target is _self, no other instance of the plugin is created. The + // plugin continues to operate in its own window + + return GetURLNotify(id, url, target, false, 0); +} + +// Generic form of PostURL for common code between PostURL and PostURLNotify. +static NPError PostURLNotify(NPP id, + const char* url, + const char* target, + uint32_t len, + const char* buf, + NPBool file, + bool notify, + void* notify_data) { + if (!url) + return NPERR_INVALID_URL; + + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (!plugin.get()) { + NOTREACHED(); + return NPERR_GENERIC_ERROR; + } + + std::string post_file_contents; + + if (file) { + // Post data to be uploaded from a file. This can be handled in two + // ways. + // 1. Read entire file and send the contents as if it was a post data + // specified in the argument + // 2. Send just the file details and read them in the browser at the + // time of sending the request. + // Approach 2 is more efficient but complicated. Approach 1 has a major + // drawback of sending potentially large data over two IPC hops. In a way + // 'large data over IPC' problem exists as it is in case of plugin giving + // the data directly instead of in a file. + // Currently we are going with the approach 1 to get the feature working. + // We can optimize this later with approach 2. + + // TODO(joshia): Design a scheme to send a file descriptor instead of + // entire file contents across. + + // Security alert: + // --------------- + // Here we are blindly uploading whatever file requested by a plugin. + // This is risky as someone could exploit a plugin to send private + // data in arbitrary locations. + // A malicious (non-sandboxed) plugin has unfeterred access to OS + // resources and can do this anyway without using browser's HTTP stack. + // FWIW, Firefox and Safari don't perform any security checks. + + if (!buf) + return NPERR_FILE_NOT_FOUND; + + std::string file_path_ascii(buf); + FilePath file_path; + static const char kFileUrlPrefix[] = "file:"; + if (StartsWithASCII(file_path_ascii, kFileUrlPrefix, false)) { + GURL file_url(file_path_ascii); + DCHECK(file_url.SchemeIsFile()); + net::FileURLToFilePath(file_url, &file_path); + } else { + file_path = FilePath::FromWStringHack( + base::SysNativeMBToWide(file_path_ascii)); + } + + base::PlatformFileInfo post_file_info = {0}; + if (!file_util::GetFileInfo(file_path, &post_file_info) || + post_file_info.is_directory) + return NPERR_FILE_NOT_FOUND; + + if (!file_util::ReadFileToString(file_path, &post_file_contents)) + return NPERR_FILE_NOT_FOUND; + + buf = post_file_contents.c_str(); + len = post_file_contents.size(); + } + + // The post data sent by a plugin contains both headers + // and post data. Example: + // Content-type: text/html + // Content-length: 200 + // + // <200 bytes of content here> + // + // Unfortunately, our stream needs these broken apart, + // so we need to parse the data and set headers and data + // separately. + plugin->RequestURL(url, "POST", target, buf, len, notify, notify_data); + return NPERR_NO_ERROR; +} + +NPError NPN_PostURLNotify(NPP id, + const char* url, + const char* target, + uint32_t len, + const char* buf, + NPBool file, + void* notify_data) { + return PostURLNotify(id, url, target, len, buf, file, true, notify_data); +} + +NPError NPN_PostURL(NPP id, + const char* url, + const char* target, + uint32_t len, + const char* buf, + NPBool file) { + // POSTs data to an URL, either from a temp file or a buffer. + // If file is true, buf contains a temp file (which host will delete after + // completing), and len contains the length of the filename. + // If file is false, buf contains the data to send, and len contains the + // length of the buffer + // + // If target is null, + // server response is returned to the plugin + // If target is _current, _self, or _top, + // server response is written to the plugin window and plugin is unloaded. + // If target is _new or _blank, + // server response is written to a new browser window + // If target is an existing frame, + // server response goes to that frame. + // + // For protocols other than FTP + // file uploads must be line-end converted from \r\n to \n + // + // Note: you cannot specify headers (even a blank line) in a memory buffer, + // use NPN_PostURLNotify + + return PostURLNotify(id, url, target, len, buf, file, false, 0); +} + +NPError NPN_NewStream(NPP id, + NPMIMEType type, + const char* target, + NPStream** stream) { + // Requests creation of a new data stream produced by the plugin, + // consumed by the browser. + // + // Browser should put this stream into a window target. + // + // TODO: implement me + DVLOG(1) << "NPN_NewStream is not implemented yet."; + return NPERR_GENERIC_ERROR; +} + +int32_t NPN_Write(NPP id, NPStream* stream, int32_t len, void* buffer) { + // Writes data to an existing Plugin-created stream. + + // TODO: implement me + DVLOG(1) << "NPN_Write is not implemented yet."; + return NPERR_GENERIC_ERROR; +} + +NPError NPN_DestroyStream(NPP id, NPStream* stream, NPReason reason) { + // Destroys a stream (could be created by plugin or browser). + // + // Reasons: + // NPRES_DONE - normal completion + // NPRES_USER_BREAK - user terminated + // NPRES_NETWORK_ERROR - network error (all errors fit here?) + // + // + + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin.get() == NULL) { + NOTREACHED(); + return NPERR_GENERIC_ERROR; + } + + return plugin->NPP_DestroyStream(stream, reason); +} + +const char* NPN_UserAgent(NPP id) { +#if defined(OS_WIN) + // Flash passes in a null id during the NP_initialize call. We need to + // default to the Mozilla user agent if we don't have an NPP instance or + // else Flash won't request windowless mode. + bool use_mozilla_user_agent = true; + if (id) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin.get() && !plugin->use_mozilla_user_agent()) + use_mozilla_user_agent = false; + } + + if (use_mozilla_user_agent) + return "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9a1) " + "Gecko/20061103 Firefox/2.0a1"; +#elif defined(OS_MACOSX) + // Silverlight 4 doesn't handle events correctly unless we claim to be Safari. + scoped_refptr<NPAPI::PluginInstance> plugin; + if (id) + plugin = FindInstance(id); + if (plugin.get()) { + WebPluginInfo plugin_info = plugin->plugin_lib()->plugin_info(); + if (plugin_info.name == ASCIIToUTF16("Silverlight Plug-In") && + StartsWith(plugin_info.version, ASCIIToUTF16("4."), false)) { + return "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-us) " + "AppleWebKit/534.1+ (KHTML, like Gecko) Version/5.0 Safari/533.16"; + } + } +#endif + + return webkit_glue::GetUserAgent(GURL()).c_str(); +} + +void NPN_Status(NPP id, const char* message) { + // Displays a message on the status line of the browser window. + + // TODO: implement me + DVLOG(1) << "NPN_Status is not implemented yet."; +} + +void NPN_InvalidateRect(NPP id, NPRect *invalidRect) { + // Invalidates specified drawing area prior to repainting or refreshing a + // windowless plugin + + // Before a windowless plugin can refresh part of its drawing area, it must + // first invalidate it. This function causes the NPP_HandleEvent method to + // pass an update event or a paint message to the plug-in. After calling + // this method, the plug-in recieves a paint message asynchronously. + + // The browser redraws invalid areas of the document and any windowless + // plug-ins at regularly timed intervals. To force a paint message, the + // plug-in can call NPN_ForceRedraw after calling this method. + + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin.get() && plugin->webplugin()) { + if (invalidRect) { +#if defined(OS_WIN) + if (!plugin->windowless()) { + RECT rect = {0}; + rect.left = invalidRect->left; + rect.right = invalidRect->right; + rect.top = invalidRect->top; + rect.bottom = invalidRect->bottom; + ::InvalidateRect(plugin->window_handle(), &rect, false); + return; + } +#endif + gfx::Rect rect(invalidRect->left, + invalidRect->top, + invalidRect->right - invalidRect->left, + invalidRect->bottom - invalidRect->top); + plugin->webplugin()->InvalidateRect(rect); + } else { + plugin->webplugin()->Invalidate(); + } + } +} + +void NPN_InvalidateRegion(NPP id, NPRegion invalidRegion) { + // Invalidates a specified drawing region prior to repainting + // or refreshing a window-less plugin. + // + // Similar to NPN_InvalidateRect. + + // TODO: this is overkill--add platform-specific region handling (at the + // very least, fetch the region's bounding box and pass it to InvalidateRect). + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + DCHECK(plugin.get() != NULL); + if (plugin.get() && plugin->webplugin()) + plugin->webplugin()->Invalidate(); +} + +void NPN_ForceRedraw(NPP id) { + // Forces repaint for a windowless plug-in. + // + // We deliberately do not implement this; we don't want plugins forcing + // synchronous paints. +} + +NPError NPN_GetValue(NPP id, NPNVariable variable, void* value) { + // Allows the plugin to query the browser for information + // + // Variables: + // NPNVxDisplay (unix only) + // NPNVxtAppContext (unix only) + // NPNVnetscapeWindow (win only) - Gets the native window on which the + // plug-in drawing occurs, returns HWND + // NPNVjavascriptEnabledBool: tells whether Javascript is enabled + // NPNVasdEnabledBool: tells whether SmartUpdate is enabled + // NPNVOfflineBool: tells whether offline-mode is enabled + + NPError rv = NPERR_GENERIC_ERROR; + + switch (static_cast<int>(variable)) { + case NPNVWindowNPObject: { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + NPObject *np_object = plugin->webplugin()->GetWindowScriptNPObject(); + // Return value is expected to be retained, as + // described here: + // <http://www.mozilla.org/projects/plugins/npruntime.html#browseraccess> + if (np_object) { + WebBindings::retainObject(np_object); + void **v = (void **)value; + *v = np_object; + rv = NPERR_NO_ERROR; + } else { + NOTREACHED(); + } + break; + } + case NPNVPluginElementNPObject: { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + NPObject *np_object = plugin->webplugin()->GetPluginElement(); + // Return value is expected to be retained, as + // described here: + // <http://www.mozilla.org/projects/plugins/npruntime.html#browseraccess> + if (np_object) { + WebBindings::retainObject(np_object); + void** v = static_cast<void**>(value); + *v = np_object; + rv = NPERR_NO_ERROR; + } else { + NOTREACHED(); + } + break; + } + #if !defined(OS_MACOSX) // OS X doesn't have windowed plugins. + case NPNVnetscapeWindow: { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (!plugin.get()) { + NOTREACHED(); + return NPERR_GENERIC_ERROR; + } + gfx::PluginWindowHandle handle = plugin->window_handle(); + *((void**)value) = (void*)handle; + rv = NPERR_NO_ERROR; + break; + } + #endif + case NPNVjavascriptEnabledBool: { + // yes, JS is enabled. + *((void**)value) = (void*)1; + rv = NPERR_NO_ERROR; + break; + } + #if defined(TOOLKIT_USES_GTK) + case NPNVToolkit: + // Tell them we are GTK2. (The alternative is GTK 1.2.) + *reinterpret_cast<int*>(value) = NPNVGtk2; + rv = NPERR_NO_ERROR; + break; + + case NPNVSupportsXEmbedBool: + *reinterpret_cast<NPBool*>(value) = true; + rv = NPERR_NO_ERROR; + break; + #endif + case NPNVSupportsWindowless: { + NPBool* supports_windowless = reinterpret_cast<NPBool*>(value); + *supports_windowless = true; + rv = NPERR_NO_ERROR; + break; + } + case NPNVprivateModeBool: { + NPBool* private_mode = reinterpret_cast<NPBool*>(value); + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + *private_mode = plugin->webplugin()->IsOffTheRecord(); + rv = NPERR_NO_ERROR; + break; + } + case default_plugin::kMissingPluginStatusStart + + default_plugin::MISSING_PLUGIN_AVAILABLE: + // fall through + case default_plugin::kMissingPluginStatusStart + + default_plugin::MISSING_PLUGIN_USER_STARTED_DOWNLOAD: { + // This is a hack for the default plugin to send notification to + // renderer. Even though we check if the plugin is the default plugin, + // we still need to worry about future standard change that may conflict + // with the variable definition, in order to avoid duplicate case clauses + // in this big switch statement. + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin->plugin_lib()->plugin_info().path.value() == + kDefaultPluginLibraryName) { + plugin->webplugin()->OnMissingPluginStatus( + variable - default_plugin::kMissingPluginStatusStart); + } + break; + } + #if defined(OS_MACOSX) + case NPNVpluginDrawingModel: { + // return the drawing model that was negotiated when we initialized. + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + *reinterpret_cast<int*>(value) = plugin->drawing_model(); + rv = NPERR_NO_ERROR; + break; + } +#ifndef NP_NO_QUICKDRAW + case NPNVsupportsQuickDrawBool: { + // We do not admit to supporting the QuickDraw drawing model. The logic + // here is that our QuickDraw plugin support is so rudimentary that we + // only want to use it as a fallback to keep plugins from crashing: if a + // plugin knows enough to ask, we want them to use CoreGraphics. + NPBool* supports_qd = reinterpret_cast<NPBool*>(value); + *supports_qd = false; + rv = NPERR_NO_ERROR; + break; + } +#endif + case NPNVsupportsCoreGraphicsBool: +#ifndef NP_NO_CARBON + case NPNVsupportsCarbonBool: +#endif + case NPNVsupportsCocoaBool: { + // we do support these drawing and event models. + NPBool* supports_model = reinterpret_cast<NPBool*>(value); + *supports_model = true; + rv = NPERR_NO_ERROR; + break; + } + case NPNVsupportsCoreAnimationBool: { + // We only support the Core Animation model on 10.6 and higher + // TODO(stuartmorgan): Once existing CA plugins have implemented the + // invalidating version, remove support for this one. + NPBool* supports_model = reinterpret_cast<NPBool*>(value); + *supports_model = SupportsSharingAcceleratedSurfaces() ? true : false; + rv = NPERR_NO_ERROR; + break; + } + case NPNVsupportsInvalidatingCoreAnimationBool: { + NPBool* supports_model = reinterpret_cast<NPBool*>(value); + *supports_model = true; + rv = NPERR_NO_ERROR; + break; + } + case NPNVsupportsOpenGLBool: { + // This drawing model was never widely supported, and we don't plan to + // support it. + NPBool* supports_model = reinterpret_cast<NPBool*>(value); + *supports_model = false; + rv = NPERR_NO_ERROR; + break; + } + #endif // OS_MACOSX + case NPNVPepperExtensions: + // Available for any plugin that attempts to get it. + // If the plugin is not started in a Pepper implementation, it + // will likely fail when it tries to use any of the functions + // attached to the extension vector. + rv = NPAPI::GetPepperExtensionsFunctions(value); + break; + default: + DVLOG(1) << "NPN_GetValue(" << variable << ") is not implemented yet."; + break; + } + return rv; +} + +NPError NPN_SetValue(NPP id, NPPVariable variable, void* value) { + // Allows the plugin to set various modes + + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + switch(variable) { + case NPPVpluginWindowBool: { + // Sets windowless mode for display of the plugin + // Note: the documentation at + // http://developer.mozilla.org/en/docs/NPN_SetValue is wrong. When + // value is NULL, the mode is set to true. This is the same way Mozilla + // works. + plugin->set_windowless(value == 0); + return NPERR_NO_ERROR; + } + case NPPVpluginTransparentBool: { + // Sets transparent mode for display of the plugin + // + // Transparent plugins require the browser to paint the background + // before having the plugin paint. By default, windowless plugins + // are transparent. Making a windowless plugin opaque means that + // the plugin does not require the browser to paint the background. + bool mode = (value != 0); + plugin->set_transparent(mode); + return NPERR_NO_ERROR; + } + case NPPVjavascriptPushCallerBool: + // Specifies whether you are pushing or popping the JSContext off. + // the stack + // TODO: implement me + DVLOG(1) << "NPN_SetValue(NPPVJavascriptPushCallerBool) is not " + "implemented."; + return NPERR_GENERIC_ERROR; + case NPPVpluginKeepLibraryInMemory: + // Tells browser that plugin library should live longer than usual. + // TODO: implement me + DVLOG(1) << "NPN_SetValue(NPPVpluginKeepLibraryInMemory) is not " + "implemented."; + return NPERR_GENERIC_ERROR; + #if defined(OS_MACOSX) + case NPPVpluginDrawingModel: { + int model = reinterpret_cast<int>(value); + if (model == NPDrawingModelCoreGraphics || + model == NPDrawingModelInvalidatingCoreAnimation || + (model == NPDrawingModelCoreAnimation && + SupportsSharingAcceleratedSurfaces())) { + plugin->set_drawing_model(static_cast<NPDrawingModel>(model)); + return NPERR_NO_ERROR; + } + return NPERR_GENERIC_ERROR; + } + case NPPVpluginEventModel: { + // we support Carbon and Cocoa event models + int model = reinterpret_cast<int>(value); + switch (model) { +#ifndef NP_NO_CARBON + case NPEventModelCarbon: +#endif + case NPEventModelCocoa: + plugin->set_event_model(static_cast<NPEventModel>(model)); + return NPERR_NO_ERROR; + break; + } + return NPERR_GENERIC_ERROR; + } + #endif + default: + // TODO: implement me + DVLOG(1) << "NPN_SetValue(" << variable << ") is not implemented."; + break; + } + + NOTREACHED(); + return NPERR_GENERIC_ERROR; +} + +void* NPN_GetJavaEnv() { + // TODO: implement me + DVLOG(1) << "NPN_GetJavaEnv is not implemented."; + return NULL; +} + +void* NPN_GetJavaPeer(NPP) { + // TODO: implement me + DVLOG(1) << "NPN_GetJavaPeer is not implemented."; + return NULL; +} + +void NPN_PushPopupsEnabledState(NPP id, NPBool enabled) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin) + plugin->PushPopupsEnabledState(enabled ? true : false); +} + +void NPN_PopPopupsEnabledState(NPP id) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin) + plugin->PopPopupsEnabledState(); +} + +void NPN_PluginThreadAsyncCall(NPP id, + void (*func)(void*), + void* user_data) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin) + plugin->PluginThreadAsyncCall(func, user_data); +} + +NPError NPN_GetValueForURL(NPP id, + NPNURLVariable variable, + const char* url, + char** value, + uint32_t* len) { + if (!id) + return NPERR_INVALID_PARAM; + + if (!url || !*url || !len) + return NPERR_INVALID_URL; + + *len = 0; + std::string result; + + switch (variable) { + case NPNURLVProxy: { + result = "DIRECT"; + if (!webkit_glue::FindProxyForUrl(GURL((std::string(url))), &result)) + return NPERR_GENERIC_ERROR; + + break; + } + case NPNURLVCookie: { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (!plugin) + return NPERR_GENERIC_ERROR; + + webkit_glue::WebPlugin* webplugin = plugin->webplugin(); + if (!webplugin) + return NPERR_GENERIC_ERROR; + + // Bypass third-party cookie blocking by using the url as the + // first_party_for_cookies. + GURL cookies_url((std::string(url))); + result = webplugin->GetCookies(cookies_url, cookies_url); + break; + } + default: + return NPERR_GENERIC_ERROR; + } + + // Allocate this using the NPAPI allocator. The plugin will call + // NPN_Free to free this. + *value = static_cast<char*>(NPN_MemAlloc(result.length() + 1)); + base::strlcpy(*value, result.c_str(), result.length() + 1); + *len = result.length(); + + return NPERR_NO_ERROR; +} + +NPError NPN_SetValueForURL(NPP id, + NPNURLVariable variable, + const char* url, + const char* value, + uint32_t len) { + if (!id) + return NPERR_INVALID_PARAM; + + if (!url || !*url) + return NPERR_INVALID_URL; + + switch (variable) { + case NPNURLVCookie: { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (!plugin) + return NPERR_GENERIC_ERROR; + + webkit_glue::WebPlugin* webplugin = plugin->webplugin(); + if (!webplugin) + return NPERR_GENERIC_ERROR; + + std::string cookie(value, len); + GURL cookies_url((std::string(url))); + webplugin->SetCookie(cookies_url, cookies_url, cookie); + return NPERR_NO_ERROR; + } + case NPNURLVProxy: + // We don't support setting proxy values, fall through... + break; + default: + // Fall through and return an error... + break; + } + + return NPERR_GENERIC_ERROR; +} + +NPError NPN_GetAuthenticationInfo(NPP id, + const char* protocol, + const char* host, + int32_t port, + const char* scheme, + const char* realm, + char** username, + uint32_t* ulen, + char** password, + uint32_t* plen) { + if (!id || !protocol || !host || !scheme || !realm || !username || + !ulen || !password || !plen) + return NPERR_INVALID_PARAM; + + // TODO: implement me (bug 23928) + return NPERR_GENERIC_ERROR; +} + +uint32_t NPN_ScheduleTimer(NPP id, + uint32_t interval, + NPBool repeat, + void (*func)(NPP id, uint32_t timer_id)) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (!plugin) + return 0; + + return plugin->ScheduleTimer(interval, repeat, func); +} + +void NPN_UnscheduleTimer(NPP id, uint32_t timer_id) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin) + plugin->UnscheduleTimer(timer_id); +} + +NPError NPN_PopUpContextMenu(NPP id, NPMenu* menu) { + if (!menu) + return NPERR_INVALID_PARAM; + + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin.get()) { + return plugin->PopUpContextMenu(menu); + } + NOTREACHED(); + return NPERR_GENERIC_ERROR; +} + +NPBool NPN_ConvertPoint(NPP id, double sourceX, double sourceY, + NPCoordinateSpace sourceSpace, + double *destX, double *destY, + NPCoordinateSpace destSpace) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(id)); + if (plugin.get()) { + return plugin->ConvertPoint(sourceX, sourceY, sourceSpace, + destX, destY, destSpace); + } + NOTREACHED(); + return false; +} + +NPBool NPN_HandleEvent(NPP id, void *event, NPBool handled) { + // TODO: Implement advanced key handling: http://crbug.com/46578 + NOTIMPLEMENTED(); + return false; +} + +NPBool NPN_UnfocusInstance(NPP id, NPFocusDirection direction) { + // TODO: Implement advanced key handling: http://crbug.com/46578 + NOTIMPLEMENTED(); + return false; +} + +void NPN_URLRedirectResponse(NPP instance, void* notify_data, NPBool allow) { + scoped_refptr<NPAPI::PluginInstance> plugin(FindInstance(instance)); + if (plugin.get()) { + plugin->URLRedirectResponse(!!allow, notify_data); + } +} + +} // extern "C" diff --git a/webkit/glue/plugins/plugin_host.h b/webkit/glue/plugins/plugin_host.h new file mode 100644 index 0000000..4763df1 --- /dev/null +++ b/webkit/glue/plugins/plugin_host.h @@ -0,0 +1,63 @@ +// 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. + +// TODO: Need mechanism to cleanup the static instance + +#ifndef WEBKIT_GLUE_PLUGIN_PLUGIN_HOST_H__ +#define WEBKIT_GLUE_PLUGIN_PLUGIN_HOST_H__ + +#include <string> +#include <vector> + +#include "base/ref_counted.h" +#include "third_party/npapi/bindings/npapi.h" +#include "third_party/npapi/bindings/nphostapi.h" + +namespace NPAPI +{ +class PluginInstance; + +// The Plugin Host implements the NPN_xxx functions for NPAPI plugins. +// These are the functions exposed from the Plugin Host for use +// by the Plugin. +// +// The PluginHost is managed as a singleton. This isn't strictly +// necessary, but since the callback functions are all global C +// functions, there is really no point in having per-instance PluginHosts. +class PluginHost : public base::RefCounted<PluginHost> { + public: + // Access the single PluginHost instance. Callers + // must call deref() when finished with the object. + static PluginHost *Singleton(); + + // The table of functions provided to the plugin. + NPNetscapeFuncs *host_functions() { return &host_funcs_; } + + // Helper function for parsing post headers, and applying attributes + // to the stream. NPAPI post data include headers + data combined. + // This function parses it out and adds it to the stream in a WebKit + // style. + static bool SetPostData(const char *buf, + uint32 length, + std::vector<std::string>* names, + std::vector<std::string>* values, + std::vector<char>* body); + + void PatchNPNetscapeFuncs(NPNetscapeFuncs* overrides); + + private: + friend class base::RefCounted<PluginHost>; + + virtual ~PluginHost(); + + PluginHost(); + void InitializeHostFuncs(); + static scoped_refptr<PluginHost> singleton_; + NPNetscapeFuncs host_funcs_; + DISALLOW_COPY_AND_ASSIGN(PluginHost); +}; + +} // namespace NPAPI + +#endif // WEBKIT_GLUE_PLUGIN_PLUGIN_HOST_H__ diff --git a/webkit/glue/plugins/plugin_instance.cc b/webkit/glue/plugins/plugin_instance.cc new file mode 100644 index 0000000..4ccbadf --- /dev/null +++ b/webkit/glue/plugins/plugin_instance.cc @@ -0,0 +1,680 @@ +// 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. + +#include "build/build_config.h" + +#include "webkit/glue/plugins/plugin_instance.h" + +#include "base/file_util.h" +#include "base/message_loop.h" +#include "base/string_number_conversions.h" +#include "base/utf_string_conversions.h" +#include "webkit/glue/webkit_glue.h" +#include "webkit/glue/plugins/plugin_host.h" +#include "webkit/glue/plugins/plugin_lib.h" +#include "webkit/glue/plugins/plugin_stream_url.h" +#include "webkit/glue/plugins/plugin_string_stream.h" +#include "webkit/glue/plugins/webplugin.h" +#include "webkit/glue/plugins/webplugin_delegate.h" +#include "net/base/escape.h" + +#if defined(OS_MACOSX) +#include <ApplicationServices/ApplicationServices.h> +#endif + +namespace NPAPI { + +PluginInstance::PluginInstance(PluginLib *plugin, const std::string &mime_type) + : plugin_(plugin), + npp_(0), + host_(PluginHost::Singleton()), + npp_functions_(plugin->functions()), + window_handle_(0), + windowless_(false), + transparent_(true), + webplugin_(0), + mime_type_(mime_type), + use_mozilla_user_agent_(false), +#if defined (OS_MACOSX) +#ifdef NP_NO_QUICKDRAW + drawing_model_(NPDrawingModelCoreGraphics), +#else + drawing_model_(NPDrawingModelQuickDraw), +#endif +#ifdef NP_NO_CARBON + event_model_(NPEventModelCocoa), +#else + event_model_(NPEventModelCarbon), +#endif + currently_handled_event_(NULL), +#endif + message_loop_(MessageLoop::current()), + load_manually_(false), + in_close_streams_(false), + next_timer_id_(1), + next_notify_id_(0), + next_range_request_id_(0), + handles_url_redirects_(false) { + npp_ = new NPP_t(); + npp_->ndata = 0; + npp_->pdata = 0; + + memset(&zero_padding_, 0, sizeof(zero_padding_)); + DCHECK(message_loop_); +} + +PluginInstance::~PluginInstance() { + CloseStreams(); + + if (npp_ != 0) { + delete npp_; + npp_ = 0; + } + + if (plugin_) + plugin_->CloseInstance(); +} + +PluginStreamUrl* PluginInstance::CreateStream(unsigned long resource_id, + const GURL& url, + const std::string& mime_type, + int notify_id) { + + bool notify; + void* notify_data; + GetNotifyData(notify_id, ¬ify, ¬ify_data); + PluginStreamUrl* stream = new PluginStreamUrl( + resource_id, url, this, notify, notify_data); + + AddStream(stream); + return stream; +} + +void PluginInstance::AddStream(PluginStream* stream) { + open_streams_.push_back(make_scoped_refptr(stream)); +} + +void PluginInstance::RemoveStream(PluginStream* stream) { + if (in_close_streams_) + return; + + std::vector<scoped_refptr<PluginStream> >::iterator stream_index; + for (stream_index = open_streams_.begin(); + stream_index != open_streams_.end(); ++stream_index) { + if (*stream_index == stream) { + open_streams_.erase(stream_index); + break; + } + } +} + +bool PluginInstance::IsValidStream(const NPStream* stream) { + std::vector<scoped_refptr<PluginStream> >::iterator stream_index; + for (stream_index = open_streams_.begin(); + stream_index != open_streams_.end(); ++stream_index) { + if ((*stream_index)->stream() == stream) + return true; + } + + return false; +} + +void PluginInstance::CloseStreams() { + in_close_streams_ = true; + for (unsigned int index = 0; index < open_streams_.size(); ++index) { + // Close all streams on the way down. + open_streams_[index]->Close(NPRES_USER_BREAK); + } + open_streams_.clear(); + in_close_streams_ = false; +} + +webkit_glue::WebPluginResourceClient* PluginInstance::GetRangeRequest( + int id) { + PendingRangeRequestMap::iterator iter = pending_range_requests_.find(id); + if (iter == pending_range_requests_.end()) { + NOTREACHED(); + return NULL; + } + + webkit_glue::WebPluginResourceClient* rv = iter->second->AsResourceClient(); + pending_range_requests_.erase(iter); + return rv; +} + +bool PluginInstance::Start(const GURL& url, + char** const param_names, + char** const param_values, + int param_count, + bool load_manually) { + load_manually_ = load_manually; + unsigned short mode = load_manually_ ? NP_FULL : NP_EMBED; + npp_->ndata = this; + + NPError err = NPP_New(mode, param_count, + const_cast<char **>(param_names), const_cast<char **>(param_values)); + + if (err == NPERR_NO_ERROR) { + handles_url_redirects_ = + ((npp_functions_->version >= NPVERS_HAS_URL_REDIRECT_HANDLING) && + (npp_functions_->urlredirectnotify)); + } + return err == NPERR_NO_ERROR; +} + +NPObject *PluginInstance::GetPluginScriptableObject() { + NPObject *value = NULL; + NPError error = NPP_GetValue(NPPVpluginScriptableNPObject, &value); + if (error != NPERR_NO_ERROR || value == NULL) + return NULL; + return value; +} + +// WebPluginLoadDelegate methods +void PluginInstance::DidFinishLoadWithReason( + const GURL& url, NPReason reason, int notify_id) { + bool notify; + void* notify_data; + GetNotifyData(notify_id, ¬ify, ¬ify_data); + if (!notify) { + NOTREACHED(); + return; + } + + NPP_URLNotify(url.spec().c_str(), reason, notify_data); +} + +unsigned PluginInstance::GetBackingTextureId() { + // By default the plugin instance is not backed by an OpenGL texture. + return 0; +} + +// NPAPI methods +NPError PluginInstance::NPP_New(unsigned short mode, + short argc, + char *argn[], + char *argv[]) { + DCHECK(npp_functions_ != 0); + DCHECK(npp_functions_->newp != 0); + DCHECK(argc >= 0); + + if (npp_functions_->newp != 0) { + return npp_functions_->newp( + (NPMIMEType)mime_type_.c_str(), npp_, mode, argc, argn, argv, NULL); + } + return NPERR_INVALID_FUNCTABLE_ERROR; +} + +void PluginInstance::NPP_Destroy() { + DCHECK(npp_functions_ != 0); + DCHECK(npp_functions_->destroy != 0); + + if (npp_functions_->destroy != 0) { + NPSavedData *savedData = 0; + npp_functions_->destroy(npp_, &savedData); + + // TODO: Support savedData. Technically, these need to be + // saved on a per-URL basis, and then only passed + // to new instances of the plugin at the same URL. + // Sounds like a huge security risk. When we do support + // these, we should pass them back to the PluginLib + // to be stored there. + DCHECK(savedData == 0); + } + + for (unsigned int file_index = 0; file_index < files_created_.size(); + file_index++) { + file_util::Delete(files_created_[file_index], false); + } + + // Ensure that no timer callbacks are invoked after NPP_Destroy. + timers_.clear(); +} + +NPError PluginInstance::NPP_SetWindow(NPWindow *window) { + DCHECK(npp_functions_ != 0); + DCHECK(npp_functions_->setwindow != 0); + + if (npp_functions_->setwindow != 0) { + return npp_functions_->setwindow(npp_, window); + } + return NPERR_INVALID_FUNCTABLE_ERROR; +} + +NPError PluginInstance::NPP_NewStream(NPMIMEType type, + NPStream *stream, + NPBool seekable, + unsigned short *stype) { + DCHECK(npp_functions_ != 0); + DCHECK(npp_functions_->newstream != 0); + if (npp_functions_->newstream != 0) { + return npp_functions_->newstream(npp_, type, stream, seekable, stype); + } + return NPERR_INVALID_FUNCTABLE_ERROR; +} + +NPError PluginInstance::NPP_DestroyStream(NPStream *stream, NPReason reason) { + DCHECK(npp_functions_ != 0); + DCHECK(npp_functions_->destroystream != 0); + + if (stream == NULL || !IsValidStream(stream) || (stream->ndata == NULL)) + return NPERR_INVALID_INSTANCE_ERROR; + + if (npp_functions_->destroystream != 0) { + NPError result = npp_functions_->destroystream(npp_, stream, reason); + stream->ndata = NULL; + return result; + } + return NPERR_INVALID_FUNCTABLE_ERROR; +} + +int PluginInstance::NPP_WriteReady(NPStream *stream) { + DCHECK(npp_functions_ != 0); + DCHECK(npp_functions_->writeready != 0); + if (npp_functions_->writeready != 0) { + return npp_functions_->writeready(npp_, stream); + } + return 0; +} + +int PluginInstance::NPP_Write(NPStream *stream, + int offset, + int len, + void *buffer) { + DCHECK(npp_functions_ != 0); + DCHECK(npp_functions_->write != 0); + if (npp_functions_->write != 0) { + return npp_functions_->write(npp_, stream, offset, len, buffer); + } + return 0; +} + +void PluginInstance::NPP_StreamAsFile(NPStream *stream, const char *fname) { + DCHECK(npp_functions_ != 0); + DCHECK(npp_functions_->asfile != 0); + if (npp_functions_->asfile != 0) { + npp_functions_->asfile(npp_, stream, fname); + } + + // Creating a temporary FilePath instance on the stack as the explicit + // FilePath constructor with StringType as an argument causes a compiler + // error when invoked via vector push back. + FilePath file_name = FilePath::FromWStringHack(UTF8ToWide(fname)); + files_created_.push_back(file_name); +} + +void PluginInstance::NPP_URLNotify(const char *url, + NPReason reason, + void *notifyData) { + DCHECK(npp_functions_ != 0); + DCHECK(npp_functions_->urlnotify != 0); + if (npp_functions_->urlnotify != 0) { + npp_functions_->urlnotify(npp_, url, reason, notifyData); + } +} + +NPError PluginInstance::NPP_GetValue(NPPVariable variable, void *value) { + DCHECK(npp_functions_ != 0); + // getvalue is NULL for Shockwave + if (npp_functions_->getvalue != 0) { + return npp_functions_->getvalue(npp_, variable, value); + } + return NPERR_INVALID_FUNCTABLE_ERROR; +} + +NPError PluginInstance::NPP_SetValue(NPNVariable variable, void *value) { + DCHECK(npp_functions_ != 0); + if (npp_functions_->setvalue != 0) { + return npp_functions_->setvalue(npp_, variable, value); + } + return NPERR_INVALID_FUNCTABLE_ERROR; +} + +short PluginInstance::NPP_HandleEvent(void* event) { + DCHECK(npp_functions_ != 0); + DCHECK(npp_functions_->event != 0); + if (npp_functions_->event != 0) { + return npp_functions_->event(npp_, (void*)event); + } + return false; +} + +bool PluginInstance::NPP_Print(NPPrint* platform_print) { + DCHECK(npp_functions_ != 0); + if (npp_functions_->print != 0) { + npp_functions_->print(npp_, platform_print); + return true; + } + return false; +} + +NPError PluginInstance::NPP_ClearSiteData(uint64 flags, + const char* domain, + uint64 max_age) { + DCHECK(npp_functions_ != 0); + // TODO(bauerb): Call NPAPI function when it is defined in the header. + return NPERR_NO_ERROR; +} + +void PluginInstance::NPP_URLRedirectNotify(const char* url, int32_t status, + void* notify_data) { + DCHECK(npp_functions_ != 0); + if (npp_functions_->urlredirectnotify != 0) { + npp_functions_->urlredirectnotify(npp_, url, status, notify_data); + } +} + +void PluginInstance::SendJavaScriptStream(const GURL& url, + const std::string& result, + bool success, + int notify_id) { + bool notify; + void* notify_data; + GetNotifyData(notify_id, ¬ify, ¬ify_data); + + if (success) { + PluginStringStream *stream = + new PluginStringStream(this, url, notify, notify_data); + AddStream(stream); + stream->SendToPlugin(result, "text/html"); + } else { + // NOTE: Sending an empty stream here will crash MacroMedia + // Flash 9. Just send the URL Notify. + if (notify) + NPP_URLNotify(url.spec().c_str(), NPRES_DONE, notify_data); + } +} + +void PluginInstance::DidReceiveManualResponse(const GURL& url, + const std::string& mime_type, + const std::string& headers, + uint32 expected_length, + uint32 last_modified) { + DCHECK(load_manually_); + + plugin_data_stream_ = CreateStream(-1, url, mime_type, 0); + plugin_data_stream_->DidReceiveResponse(mime_type, headers, expected_length, + last_modified, true); +} + +void PluginInstance::DidReceiveManualData(const char* buffer, int length) { + DCHECK(load_manually_); + if (plugin_data_stream_.get() != NULL) { + plugin_data_stream_->DidReceiveData(buffer, length, 0); + } +} + +void PluginInstance::DidFinishManualLoading() { + DCHECK(load_manually_); + if (plugin_data_stream_.get() != NULL) { + plugin_data_stream_->DidFinishLoading(); + plugin_data_stream_->Close(NPRES_DONE); + plugin_data_stream_ = NULL; + } +} + +void PluginInstance::DidManualLoadFail() { + DCHECK(load_manually_); + if (plugin_data_stream_.get() != NULL) { + plugin_data_stream_->DidFail(); + plugin_data_stream_ = NULL; + } +} + +void PluginInstance::PluginThreadAsyncCall(void (*func)(void *), + void *user_data) { + message_loop_->PostTask( + FROM_HERE, NewRunnableMethod( + this, &PluginInstance::OnPluginThreadAsyncCall, func, user_data)); +} + +void PluginInstance::OnPluginThreadAsyncCall(void (*func)(void *), + void *user_data) { + // Do not invoke the callback if NPP_Destroy has already been invoked. + if (webplugin_) + func(user_data); +} + +uint32 PluginInstance::ScheduleTimer(uint32 interval, + NPBool repeat, + void (*func)(NPP id, uint32 timer_id)) { + // Use next timer id. + uint32 timer_id; + timer_id = next_timer_id_; + ++next_timer_id_; + DCHECK(next_timer_id_ != 0); + + // Record timer interval and repeat. + TimerInfo info; + info.interval = interval; + info.repeat = repeat ? true : false; + timers_[timer_id] = info; + + // Schedule the callback. + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + NewRunnableMethod( + this, &PluginInstance::OnTimerCall, func, npp_, timer_id), + interval); + return timer_id; +} + +void PluginInstance::UnscheduleTimer(uint32 timer_id) { + // Remove info about the timer. + TimerMap::iterator it = timers_.find(timer_id); + if (it != timers_.end()) + timers_.erase(it); +} + +#if !defined(OS_MACOSX) +NPError PluginInstance::PopUpContextMenu(NPMenu* menu) { + NOTIMPLEMENTED(); + return NPERR_GENERIC_ERROR; +} +#endif + +void PluginInstance::OnTimerCall(void (*func)(NPP id, uint32 timer_id), + NPP id, + uint32 timer_id) { + // Do not invoke callback if the timer has been unscheduled. + TimerMap::iterator it = timers_.find(timer_id); + if (it == timers_.end()) + return; + + // Get all information about the timer before invoking the callback. The + // callback might unschedule the timer. + TimerInfo info = it->second; + + func(id, timer_id); + + // If the timer was unscheduled by the callback, just free up the timer id. + if (timers_.find(timer_id) == timers_.end()) + return; + + // Reschedule repeating timers after invoking the callback so callback is not + // re-entered if it pumps the messager loop. + if (info.repeat) { + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + NewRunnableMethod( + this, &PluginInstance::OnTimerCall, func, npp_, timer_id), + info.interval); + } else { + timers_.erase(it); + } +} + +void PluginInstance::PushPopupsEnabledState(bool enabled) { + popups_enabled_stack_.push(enabled); +} + +void PluginInstance::PopPopupsEnabledState() { + popups_enabled_stack_.pop(); +} + +void PluginInstance::RequestRead(NPStream* stream, NPByteRange* range_list) { + std::string range_info = "bytes="; + + while (range_list) { + range_info += base::IntToString(range_list->offset); + range_info.push_back('-'); + range_info += + base::IntToString(range_list->offset + range_list->length - 1); + range_list = range_list->next; + if (range_list) + range_info.push_back(','); + } + + if (plugin_data_stream_) { + if (plugin_data_stream_->stream() == stream) { + webplugin_->CancelDocumentLoad(); + plugin_data_stream_ = NULL; + } + } + + // The lifetime of a NPStream instance depends on the PluginStream instance + // which owns it. When a plugin invokes NPN_RequestRead on a seekable stream, + // we don't want to create a new stream when the corresponding response is + // received. We send over a cookie which represents the PluginStream + // instance which is sent back from the renderer when the response is + // received. + std::vector<scoped_refptr<PluginStream> >::iterator stream_index; + for (stream_index = open_streams_.begin(); + stream_index != open_streams_.end(); ++stream_index) { + PluginStream* plugin_stream = *stream_index; + if (plugin_stream->stream() == stream) { + // A stream becomes seekable the first time NPN_RequestRead + // is called on it. + plugin_stream->set_seekable(true); + + pending_range_requests_[++next_range_request_id_] = plugin_stream; + webplugin_->InitiateHTTPRangeRequest( + stream->url, range_info.c_str(), next_range_request_id_); + return; + } + } + NOTREACHED(); +} + +void PluginInstance::RequestURL(const char* url, + const char* method, + const char* target, + const char* buf, + unsigned int len, + bool notify, + void* notify_data) { + int notify_id = 0; + if (notify) { + notify_id = ++next_notify_id_; + pending_requests_[notify_id] = notify_data; + } + + webplugin_->HandleURLRequest( + url, method, target, buf, len, notify_id, popups_allowed(), + notify ? handles_url_redirects_ : false); +} + +bool PluginInstance::ConvertPoint(double source_x, double source_y, + NPCoordinateSpace source_space, + double* dest_x, double* dest_y, + NPCoordinateSpace dest_space) { +#if defined(OS_MACOSX) + CGRect main_display_bounds = CGDisplayBounds(CGMainDisplayID()); + + double flipped_screen_x = source_x; + double flipped_screen_y = source_y; + switch(source_space) { + case NPCoordinateSpacePlugin: + flipped_screen_x += plugin_origin_.x(); + flipped_screen_y += plugin_origin_.y(); + break; + case NPCoordinateSpaceWindow: + flipped_screen_x += containing_window_frame_.x(); + flipped_screen_y = containing_window_frame_.height() - source_y + + containing_window_frame_.y(); + break; + case NPCoordinateSpaceFlippedWindow: + flipped_screen_x += containing_window_frame_.x(); + flipped_screen_y += containing_window_frame_.y(); + break; + case NPCoordinateSpaceScreen: + flipped_screen_y = main_display_bounds.size.height - flipped_screen_y; + break; + case NPCoordinateSpaceFlippedScreen: + break; + default: + NOTREACHED(); + return false; + } + + double target_x = flipped_screen_x; + double target_y = flipped_screen_y; + switch(dest_space) { + case NPCoordinateSpacePlugin: + target_x -= plugin_origin_.x(); + target_y -= plugin_origin_.y(); + break; + case NPCoordinateSpaceWindow: + target_x -= containing_window_frame_.x(); + target_y -= containing_window_frame_.y(); + target_y = containing_window_frame_.height() - target_y; + break; + case NPCoordinateSpaceFlippedWindow: + target_x -= containing_window_frame_.x(); + target_y -= containing_window_frame_.y(); + break; + case NPCoordinateSpaceScreen: + target_y = main_display_bounds.size.height - flipped_screen_y; + break; + case NPCoordinateSpaceFlippedScreen: + break; + default: + NOTREACHED(); + return false; + } + + if (dest_x) + *dest_x = target_x; + if (dest_y) + *dest_y = target_y; + return true; +#else + NOTIMPLEMENTED(); + return false; +#endif +} + +void PluginInstance::GetNotifyData( + int notify_id, bool* notify, void** notify_data) { + PendingRequestMap::iterator iter = pending_requests_.find(notify_id); + if (iter != pending_requests_.end()) { + *notify = true; + *notify_data = iter->second; + pending_requests_.erase(iter); + } else { + *notify = false; + *notify_data = NULL; + } +} + +void PluginInstance::URLRedirectResponse(bool allow, void* notify_data) { + // The notify_data passed in allows us to identify the matching stream. + std::vector<scoped_refptr<PluginStream> >::iterator stream_index; + for (stream_index = open_streams_.begin(); + stream_index != open_streams_.end(); ++stream_index) { + PluginStream* plugin_stream = *stream_index; + if (plugin_stream->notify_data() == notify_data) { + webkit_glue::WebPluginResourceClient* resource_client = + plugin_stream->AsResourceClient(); + webplugin_->URLRedirectResponse(allow, resource_client->ResourceId()); + if (allow) { + plugin_stream->UpdateUrl( + plugin_stream->pending_redirect_url().c_str()); + } + break; + } + } +} + +} // namespace NPAPI diff --git a/webkit/glue/plugins/plugin_instance.h b/webkit/glue/plugins/plugin_instance.h new file mode 100644 index 0000000..fa0320e --- /dev/null +++ b/webkit/glue/plugins/plugin_instance.h @@ -0,0 +1,375 @@ +// 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. + +// TODO: Need to deal with NPAPI's NPSavedData. +// I haven't seen plugins use it yet. + +#ifndef WEBKIT_GLUE_PLUGIN_PLUGIN_INSTANCE_H__ +#define WEBKIT_GLUE_PLUGIN_PLUGIN_INSTANCE_H__ + +#include <map> +#include <stack> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" +#include "gfx/native_widget_types.h" +#include "gfx/point.h" +#include "gfx/rect.h" +#include "googleurl/src/gurl.h" +#include "third_party/npapi/bindings/npapi.h" +#include "third_party/npapi/bindings/nphostapi.h" + +class MessageLoop; + +namespace webkit_glue { +class WebPlugin; +class WebPluginResourceClient; +} + +namespace NPAPI +{ +class PluginLib; +class PluginHost; +class PluginStream; +class PluginStreamUrl; +class PluginDataStream; +#if defined(OS_MACOSX) +class ScopedCurrentPluginEvent; +#endif + +// A PluginInstance is an active, running instance of a Plugin. +// A single plugin may have many PluginInstances. +class PluginInstance : public base::RefCountedThreadSafe<PluginInstance> { + public: + // Create a new instance of a plugin. The PluginInstance + // will hold a reference to the plugin. + PluginInstance(PluginLib *plugin, const std::string &mime_type); + + // Activates the instance by calling NPP_New. + // This should be called after our instance is all + // setup from the host side and we are ready to receive + // requests from the plugin. We must not call any + // functions on the plugin instance until start has + // been called. + // + // url: The instance URL. + // param_names: the list of names of attributes passed via the + // element. + // param_values: the list of values corresponding to param_names + // param_count: number of attributes + // load_manually: if true indicates that the plugin data would be passed + // from webkit. if false indicates that the plugin should + // download the data. + // This also controls whether the plugin is instantiated as + // a full page plugin (NP_FULL) or embedded (NP_EMBED) + // + bool Start(const GURL& url, + char** const param_names, + char** const param_values, + int param_count, + bool load_manually); + + // NPAPI's instance identifier for this instance + NPP npp() { return npp_; } + + // Get/Set for the instance's window handle. + gfx::PluginWindowHandle window_handle() const { return window_handle_; } + void set_window_handle(gfx::PluginWindowHandle value) { + window_handle_ = value; + } + + // Get/Set whether this instance is in Windowless mode. + // Default is false. + bool windowless() { return windowless_; } + void set_windowless(bool value) { windowless_ = value; } + + // Get/Set whether this instance is transparent. + // This only applies to windowless plugins. Transparent + // plugins require that webkit paint the background. + // Default is true. + bool transparent() { return transparent_; } + void set_transparent(bool value) { transparent_ = value; } + + // Get/Set the WebPlugin associated with this instance + webkit_glue::WebPlugin* webplugin() { return webplugin_; } + void set_web_plugin(webkit_glue::WebPlugin* webplugin) { + webplugin_ = webplugin; + } + + // Get the mimeType for this plugin stream + const std::string &mime_type() { return mime_type_; } + + NPAPI::PluginLib* plugin_lib() { return plugin_; } + +#if defined(OS_MACOSX) + // Get/Set the Mac NPAPI drawing and event models + NPDrawingModel drawing_model() { return drawing_model_; } + void set_drawing_model(NPDrawingModel value) { drawing_model_ = value; } + NPEventModel event_model() { return event_model_; } + void set_event_model(NPEventModel value) { event_model_ = value; } + // Updates the instance's tracking of the location of the plugin location + // relative to the upper left of the screen. + void set_plugin_origin(const gfx::Point& origin) { plugin_origin_ = origin; } + // Updates the instance's tracking of the frame of the containing window + // relative to the upper left of the screen. + void set_window_frame(const gfx::Rect& frame) { + containing_window_frame_ = frame; + } +#endif + + // Creates a stream for sending an URL. If notify_id is non-zero, it will + // send a notification to the plugin when the stream is complete; otherwise it + // will not. Set object_url to true if the load is for the object tag's url, + // or false if it's for a url that the plugin fetched through + // NPN_GetUrl[Notify]. + PluginStreamUrl* CreateStream(unsigned long resource_id, + const GURL& url, + const std::string& mime_type, + int notify_id); + + // For each instance, we track all streams. When the + // instance closes, all remaining streams are also + // closed. All streams associated with this instance + // should call AddStream so that they can be cleaned + // up when the instance shuts down. + void AddStream(PluginStream* stream); + + // This is called when a stream is closed. We remove the stream from the + // list, which releases the reference maintained to the stream. + void RemoveStream(PluginStream* stream); + + // Closes all open streams on this instance. + void CloseStreams(); + + // Returns the WebPluginResourceClient object for a stream that has become + // seekable. + webkit_glue::WebPluginResourceClient* GetRangeRequest(int id); + + // Have the plugin create it's script object. + NPObject *GetPluginScriptableObject(); + + // WebViewDelegate methods that we implement. This is for handling + // callbacks during getURLNotify. + void DidFinishLoadWithReason(const GURL& url, NPReason reason, int notify_id); + + // If true, send the Mozilla user agent instead of Chrome's to the plugin. + bool use_mozilla_user_agent() { return use_mozilla_user_agent_; } + void set_use_mozilla_user_agent() { use_mozilla_user_agent_ = true; } + + // If the plugin instance is backed by a texture, return its ID in the + // compositor's namespace. Otherwise return 0. Returns 0 by default. + virtual unsigned GetBackingTextureId(); + + // Helper that implements NPN_PluginThreadAsyncCall semantics + void PluginThreadAsyncCall(void (*func)(void *), + void *userData); + + uint32 ScheduleTimer(uint32 interval, + NPBool repeat, + void (*func)(NPP id, uint32 timer_id)); + + void UnscheduleTimer(uint32 timer_id); + + bool ConvertPoint(double source_x, double source_y, + NPCoordinateSpace source_space, + double* dest_x, double* dest_y, + NPCoordinateSpace dest_space); + + NPError PopUpContextMenu(NPMenu* menu); + + // + // NPAPI methods for calling the Plugin Instance + // + NPError NPP_New(unsigned short, short, char *[], char *[]); + NPError NPP_SetWindow(NPWindow *); + NPError NPP_NewStream(NPMIMEType, NPStream *, NPBool, unsigned short *); + NPError NPP_DestroyStream(NPStream *, NPReason); + int NPP_WriteReady(NPStream *); + int NPP_Write(NPStream *, int, int, void *); + void NPP_StreamAsFile(NPStream *, const char *); + void NPP_URLNotify(const char *, NPReason, void *); + NPError NPP_GetValue(NPPVariable, void *); + NPError NPP_SetValue(NPNVariable, void *); + short NPP_HandleEvent(void*); + void NPP_Destroy(); + bool NPP_Print(NPPrint* platform_print); + NPError NPP_ClearSiteData(uint64, const char*, uint64); + void NPP_URLRedirectNotify(const char* url, int32_t status, + void* notify_data); + + void SendJavaScriptStream(const GURL& url, + const std::string& result, + bool success, + int notify_id); + + void DidReceiveManualResponse(const GURL& url, + const std::string& mime_type, + const std::string& headers, + uint32 expected_length, + uint32 last_modified); + void DidReceiveManualData(const char* buffer, int length); + void DidFinishManualLoading(); + void DidManualLoadFail(); + + void PushPopupsEnabledState(bool enabled); + void PopPopupsEnabledState(); + + bool popups_allowed() const { + return popups_enabled_stack_.empty() ? false : popups_enabled_stack_.top(); + } + + // Initiates byte range reads for plugins. + void RequestRead(NPStream* stream, NPByteRange* range_list); + + // Handles GetURL/GetURLNotify/PostURL/PostURLNotify requests initiated + // by plugins. + void RequestURL(const char* url, + const char* method, + const char* target, + const char* buf, + unsigned int len, + bool notify, + void* notify_data); + + // Handles NPN_URLRedirectResponse calls issued by plugins in response to + // HTTP URL redirect notifications. + void URLRedirectResponse(bool allow, void* notify_data); + + bool handles_url_redirects() const { return handles_url_redirects_; } + + private: + friend class base::RefCountedThreadSafe<PluginInstance>; + +#if defined(OS_MACOSX) + friend class ScopedCurrentPluginEvent; + // Sets the event that the plugin is currently handling. The object is not + // owned or copied, so the caller must call this again with NULL before the + // event pointer becomes invalid. Clients use ScopedCurrentPluginEvent rather + // than calling this directly. + void set_currently_handled_event(NPCocoaEvent* event) { + currently_handled_event_ = event; + } +#endif + + ~PluginInstance(); + void OnPluginThreadAsyncCall(void (*func)(void *), void *userData); + void OnTimerCall(void (*func)(NPP id, uint32 timer_id), + NPP id, uint32 timer_id); + bool IsValidStream(const NPStream* stream); + void GetNotifyData(int notify_id, bool* notify, void** notify_data); + + // This is a hack to get the real player plugin to work with chrome + // The real player plugin dll(nppl3260) when loaded by firefox is loaded via + // the NS COM API which is analogous to win32 COM. So the NPAPI functions in + // the plugin are invoked via an interface by firefox. The plugin instance + // handle which is passed to every NPAPI method is owned by the real player + // plugin, i.e. it expects the ndata member to point to a structure which + // it knows about. Eventually it dereferences this structure and compares + // a member variable at offset 0x24(Version 6.0.11.2888) /2D (Version + // 6.0.11.3088) with 0 and on failing this check, takes a different code + // path which causes a crash. Safari and Opera work with version 6.0.11.2888 + // by chance as their ndata structure contains a 0 at the location which real + // player checks:(. They crash with version 6.0.11.3088 as well. The + // following member just adds a 96 byte padding to our PluginInstance class + // which is passed in the ndata member. This magic number works correctly on + // Vista with UAC on or off :(. + // NOTE: Please dont change the ordering of the member variables + // New members should be added after this padding array. + // TODO(iyengar) : Disassemble the Realplayer ndata structure and look into + // the possiblity of conforming to it (http://b/issue?id=936667). We + // could also log a bug with Real, which would save the effort. + uint8 zero_padding_[96]; + scoped_refptr<NPAPI::PluginLib> plugin_; + NPP npp_; + scoped_refptr<PluginHost> host_; + NPPluginFuncs* npp_functions_; + std::vector<scoped_refptr<PluginStream> > open_streams_; + gfx::PluginWindowHandle window_handle_; + bool windowless_; + bool transparent_; + webkit_glue::WebPlugin* webplugin_; + std::string mime_type_; + GURL get_url_; + intptr_t get_notify_data_; + bool use_mozilla_user_agent_; +#if defined(OS_MACOSX) + NPDrawingModel drawing_model_; + NPEventModel event_model_; + gfx::Point plugin_origin_; + gfx::Rect containing_window_frame_; + NPCocoaEvent* currently_handled_event_; // weak +#endif + MessageLoop* message_loop_; + scoped_refptr<PluginStreamUrl> plugin_data_stream_; + + // This flag if true indicates that the plugin data would be passed from + // webkit. if false indicates that the plugin should download the data. + bool load_manually_; + + // Stack indicating if popups are to be enabled for the outgoing + // NPN_GetURL/NPN_GetURLNotify calls. + std::stack<bool> popups_enabled_stack_; + + // True if in CloseStreams(). + bool in_close_streams_; + + // List of files created for the current plugin instance. File names are + // added to the list every time the NPP_StreamAsFile function is called. + std::vector<FilePath> files_created_; + + // Next unusued timer id. + uint32 next_timer_id_; + + // Map of timer id to settings for timer. + struct TimerInfo { + uint32 interval; + bool repeat; + }; + typedef std::map<uint32, TimerInfo> TimerMap; + TimerMap timers_; + + // Tracks pending GET/POST requests so that the plugin-given data doesn't + // cross process boundaries to an untrusted process. + typedef std::map<int, void*> PendingRequestMap; + PendingRequestMap pending_requests_; + int next_notify_id_; + + // Used to track pending range requests so that when WebPlugin replies to us + // we can match the reply to the stream. + typedef std::map<int, scoped_refptr<PluginStream> > PendingRangeRequestMap; + PendingRangeRequestMap pending_range_requests_; + int next_range_request_id_; + // The plugin handles the NPAPI URL redirect notification API. + // See here https://wiki.mozilla.org/NPAPI:HTTPRedirectHandling + bool handles_url_redirects_; + + DISALLOW_COPY_AND_ASSIGN(PluginInstance); +}; + +#if defined(OS_MACOSX) +// Helper to simplify correct usage of set_currently_handled_event. +// Instantiating will set |instance|'s currently handled to |event| for the +// lifetime of the object, then NULL when it goes out of scope. +class ScopedCurrentPluginEvent { + public: + ScopedCurrentPluginEvent(PluginInstance* instance, NPCocoaEvent* event) + : instance_(instance) { + instance_->set_currently_handled_event(event); + } + ~ScopedCurrentPluginEvent() { + instance_->set_currently_handled_event(NULL); + } + private: + scoped_refptr<PluginInstance> instance_; + DISALLOW_COPY_AND_ASSIGN(ScopedCurrentPluginEvent); +}; +#endif + +} // namespace NPAPI + +#endif // WEBKIT_GLUE_PLUGIN_PLUGIN_INSTANCE_H__ diff --git a/webkit/glue/plugins/plugin_instance_mac.mm b/webkit/glue/plugins/plugin_instance_mac.mm new file mode 100644 index 0000000..9800198 --- /dev/null +++ b/webkit/glue/plugins/plugin_instance_mac.mm @@ -0,0 +1,133 @@ +// 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. + +#include "build/build_config.h" + +#import <AppKit/AppKit.h> + +#include "base/logging.h" +#include "webkit/glue/plugins/plugin_instance.h" + +// When C++ exceptions are disabled, the C++ library defines |try| and +// |catch| so as to allow exception-expecting C++ code to build properly when +// language support for exceptions is not present. These macros interfere +// with the use of |@try| and |@catch| in Objective-C files such as this one. +// Undefine these macros here, after everything has been #included, since +// there will be no C++ uses and only Objective-C uses from this point on. +#undef try +#undef catch + +#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_5 +@interface NSMenu (SnowLeopardMenuPopUpDeclaration) +- (BOOL)popUpMenuPositioningItem:(NSMenuItem*)item + atLocation:(NSPoint)location + inView:(NSView*)view; +@end +#endif + +namespace { + +// Returns an autoreleased NSEvent constructed from the given np_event, +// targeting the given window. +NSEvent* NSEventForNPCocoaEvent(NPCocoaEvent* np_event, NSWindow* window) { + bool mouse_down = 1; + switch (np_event->type) { + case NPCocoaEventMouseDown: + mouse_down = 1; + break; + case NPCocoaEventMouseUp: + mouse_down = 0; + break; + default: + // If plugins start bringing up context menus for things other than + // clicks, this will need more plumbing; for now just log it and proceed + // as if it were a mouse down. + NOTREACHED(); + } + NSEventType event_type = NSLeftMouseDown; + switch (np_event->data.mouse.buttonNumber) { + case 0: + event_type = mouse_down ? NSLeftMouseDown : NSLeftMouseUp; + break; + case 1: + event_type = mouse_down ? NSRightMouseDown : NSRightMouseUp; + break; + default: + event_type = mouse_down ? NSOtherMouseDown : NSOtherMouseUp; + break; + } + + NSInteger click_count = np_event->data.mouse.clickCount; + NSInteger modifiers = np_event->data.mouse.modifierFlags; + // NPCocoaEvent doesn't have a timestamp, so just use the current time. + NSEvent* event = + [NSEvent mouseEventWithType:event_type + location:NSMakePoint(0, 0) + modifierFlags:modifiers + timestamp:[[NSApp currentEvent] timestamp] + windowNumber:[window windowNumber] + context:[NSGraphicsContext currentContext] + eventNumber:0 + clickCount:click_count + pressure:1.0]; + return event; +} + +} // namespace + +namespace NPAPI { + +NPError PluginInstance::PopUpContextMenu(NPMenu* menu) { + if (!currently_handled_event_) + return NPERR_GENERIC_ERROR; + + CGRect main_display_bounds = CGDisplayBounds(CGMainDisplayID()); + NSPoint screen_point = NSMakePoint( + plugin_origin_.x() + currently_handled_event_->data.mouse.pluginX, + plugin_origin_.y() + currently_handled_event_->data.mouse.pluginY); + // Plugin offsets are upper-left based, so flip vertically for Cocoa. + screen_point.y = main_display_bounds.size.height - screen_point.y; + + NSMenu* nsmenu = reinterpret_cast<NSMenu*>(menu); + NPError return_val = NPERR_NO_ERROR; + NSWindow* window = nil; + @try { + if ([nsmenu respondsToSelector: + @selector(popUpMenuPositioningItem:atLocation:inView:)]) { + [nsmenu popUpMenuPositioningItem:nil atLocation:screen_point inView:nil]; + } else { + NSRect dummy_window_rect = NSMakeRect(screen_point.x, screen_point.y, + 1, 1); + window = [[NSWindow alloc] initWithContentRect:dummy_window_rect + styleMask:NSBorderlessWindowMask + backing:NSBackingStoreNonretained + defer:YES]; + [window setTitle:@"PopupMenuDummy"]; // Lets interposing identify it. + [window setAlphaValue:0]; + [window makeKeyAndOrderFront:nil]; + [NSMenu popUpContextMenu:nsmenu + withEvent:NSEventForNPCocoaEvent(currently_handled_event_, + window) + forView:[window contentView]]; + } + } + @catch (NSException* e) { + NSLog(@"Caught exception while handling PopUpContextMenu: %@", e); + return_val = NPERR_GENERIC_ERROR; + } + + if (window) { + @try { + [window orderOut:nil]; + [window release]; + } + @catch (NSException* e) { + NSLog(@"Caught exception while cleaning up in PopUpContextMenu: %@", e); + } + } + + return return_val; +} + +} // namespace NPAPI diff --git a/webkit/glue/plugins/plugin_lib.cc b/webkit/glue/plugins/plugin_lib.cc new file mode 100644 index 0000000..4ae4da4 --- /dev/null +++ b/webkit/glue/plugins/plugin_lib.cc @@ -0,0 +1,349 @@ +// 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. + +#include "webkit/glue/plugins/plugin_lib.h" + +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/metrics/stats_counters.h" +#include "base/string_util.h" +#include "webkit/glue/webkit_glue.h" +#include "webkit/glue/plugins/plugin_instance.h" +#include "webkit/glue/plugins/plugin_host.h" +#include "webkit/glue/plugins/plugin_list.h" + +namespace NPAPI { + +const char kPluginLibrariesLoadedCounter[] = "PluginLibrariesLoaded"; +const char kPluginInstancesActiveCounter[] = "PluginInstancesActive"; + +// A list of all the instantiated plugins. +static std::vector<scoped_refptr<PluginLib> >* g_loaded_libs; + +PluginLib* PluginLib::CreatePluginLib(const FilePath& filename) { + // We can only have one PluginLib object per plugin as it controls the per + // instance function calls (i.e. NP_Initialize and NP_Shutdown). So we keep + // a map of PluginLib objects. + if (!g_loaded_libs) + g_loaded_libs = new std::vector<scoped_refptr<PluginLib> >; + + for (size_t i = 0; i < g_loaded_libs->size(); ++i) { + if ((*g_loaded_libs)[i]->plugin_info().path == filename) + return (*g_loaded_libs)[i]; + } + + WebPluginInfo info; + const PluginEntryPoints* entry_points = NULL; + if (!PluginList::Singleton()->ReadPluginInfo(filename, &info, &entry_points)) + return NULL; + + return new PluginLib(info, entry_points); +} + +void PluginLib::UnloadAllPlugins() { + if (g_loaded_libs) { + // PluginLib::Unload() can remove items from the list and even delete + // the list when it removes the last item, so we must work with a copy + // of the list so that we don't get the carpet removed under our feet. + std::vector<scoped_refptr<PluginLib> > loaded_libs(*g_loaded_libs); + for (size_t i = 0; i < loaded_libs.size(); ++i) + loaded_libs[i]->Unload(); + + if (g_loaded_libs && g_loaded_libs->empty()) { + delete g_loaded_libs; + g_loaded_libs = NULL; + } + } +} + +void PluginLib::ShutdownAllPlugins() { + if (g_loaded_libs) { + for (size_t i = 0; i < g_loaded_libs->size(); ++i) + (*g_loaded_libs)[i]->Shutdown(); + } +} + +PluginLib::PluginLib(const WebPluginInfo& info, + const PluginEntryPoints* entry_points) + : web_plugin_info_(info), + library_(NULL), + initialized_(false), + saved_data_(0), + instance_count_(0), + skip_unload_(false) { + base::StatsCounter(kPluginLibrariesLoadedCounter).Increment(); + memset(static_cast<void*>(&plugin_funcs_), 0, sizeof(plugin_funcs_)); + g_loaded_libs->push_back(make_scoped_refptr(this)); + + if (entry_points) { + internal_ = true; + entry_points_ = *entry_points; + } else { + internal_ = false; + // We will read the entry points from the plugin directly. + memset(&entry_points_, 0, sizeof(entry_points_)); + } +} + +PluginLib::~PluginLib() { + base::StatsCounter(kPluginLibrariesLoadedCounter).Decrement(); + if (saved_data_ != 0) { + // TODO - delete the savedData object here + } +} + +NPPluginFuncs* PluginLib::functions() { + return &plugin_funcs_; +} + +NPError PluginLib::NP_Initialize() { + LOG_IF(ERROR, PluginList::DebugPluginLoading()) + << "PluginLib::NP_Initialize(" << web_plugin_info_.path.value() + << "): initialized=" << initialized_; + if (initialized_) + return NPERR_NO_ERROR; + + if (!Load()) + return NPERR_MODULE_LOAD_FAILED_ERROR; + + PluginHost* host = PluginHost::Singleton(); + if (host == 0) + return NPERR_GENERIC_ERROR; + +#if defined(OS_POSIX) && !defined(OS_MACOSX) + NPError rv = entry_points_.np_initialize(host->host_functions(), + &plugin_funcs_); +#else + NPError rv = entry_points_.np_initialize(host->host_functions()); +#if defined(OS_MACOSX) + // On the Mac, we need to get entry points after calling np_initialize to + // match the behavior of other browsers. + if (rv == NPERR_NO_ERROR) { + rv = entry_points_.np_getentrypoints(&plugin_funcs_); + } +#endif // OS_MACOSX +#endif + LOG_IF(ERROR, PluginList::DebugPluginLoading()) + << "PluginLib::NP_Initialize(" << web_plugin_info_.path.value() + << "): result=" << rv; + initialized_ = (rv == NPERR_NO_ERROR); + return rv; +} + +void PluginLib::NP_Shutdown(void) { + DCHECK(initialized_); + entry_points_.np_shutdown(); +} + +void PluginLib::PreventLibraryUnload() { + skip_unload_ = true; +} + +PluginInstance* PluginLib::CreateInstance(const std::string& mime_type) { + PluginInstance* new_instance = new PluginInstance(this, mime_type); + instance_count_++; + base::StatsCounter(kPluginInstancesActiveCounter).Increment(); + DCHECK_NE(static_cast<PluginInstance*>(NULL), new_instance); + return new_instance; +} + +void PluginLib::CloseInstance() { + base::StatsCounter(kPluginInstancesActiveCounter).Decrement(); + instance_count_--; + // If a plugin is running in its own process it will get unloaded on process + // shutdown. + if ((instance_count_ == 0) && webkit_glue::IsPluginRunningInRendererProcess()) + Unload(); +} + +bool PluginLib::Load() { + if (library_) + return true; + + bool rv = false; + base::NativeLibrary library = 0; + + if (!internal_) { +#if defined(OS_WIN) + // This is to work around a bug in the Real player recorder plugin which + // intercepts LoadLibrary calls from chrome.dll and wraps NPAPI functions + // provided by the plugin. It crashes if the media player plugin is being + // loaded. Workaround is to load the dll dynamically by getting the + // LoadLibrary API address from kernel32.dll which bypasses the recorder + // plugin. + if (web_plugin_info_.name.find(L"Windows Media Player") != + std::wstring::npos) { + library = base::LoadNativeLibraryDynamically(web_plugin_info_.path); + } else { + library = base::LoadNativeLibrary(web_plugin_info_.path); + } +#else // OS_WIN + library = base::LoadNativeLibrary(web_plugin_info_.path); +#endif // OS_WIN + if (library == 0) { + LOG_IF(ERROR, PluginList::DebugPluginLoading()) + << "Couldn't load plugin " << web_plugin_info_.path.value(); + return rv; + } + +#if defined(OS_MACOSX) + // According to the WebKit source, QuickTime at least requires us to call + // UseResFile on the plugin resources before loading. + if (library->bundle_resource_ref != -1) + UseResFile(library->bundle_resource_ref); +#endif + + rv = true; // assume success now + + entry_points_.np_initialize = + (NP_InitializeFunc)base::GetFunctionPointerFromNativeLibrary(library, + "NP_Initialize"); + if (entry_points_.np_initialize == 0) + rv = false; + +#if defined(OS_WIN) || defined(OS_MACOSX) + entry_points_.np_getentrypoints = + (NP_GetEntryPointsFunc)base::GetFunctionPointerFromNativeLibrary( + library, "NP_GetEntryPoints"); + if (entry_points_.np_getentrypoints == 0) + rv = false; +#endif + + entry_points_.np_shutdown = + (NP_ShutdownFunc)base::GetFunctionPointerFromNativeLibrary(library, + "NP_Shutdown"); + if (entry_points_.np_shutdown == 0) + rv = false; + } else { + rv = true; + } + + if (rv) { + plugin_funcs_.size = sizeof(plugin_funcs_); + plugin_funcs_.version = (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR; +#if !defined(OS_POSIX) + if (entry_points_.np_getentrypoints(&plugin_funcs_) != NPERR_NO_ERROR) + rv = false; +#else + // On Linux and Mac, we get the plugin entry points during NP_Initialize. +#endif + } + + if (!internal_) { + if (rv) { + LOG_IF(ERROR, PluginList::DebugPluginLoading()) + << "Plugin " << web_plugin_info_.path.value() + << " loaded successfully."; + library_ = library; + } else { + LOG_IF(ERROR, PluginList::DebugPluginLoading()) + << "Plugin " << web_plugin_info_.path.value() + << " failed to load, unloading."; + base::UnloadNativeLibrary(library); + } + } + + return rv; +} + +// This class implements delayed NP_Shutdown and FreeLibrary on the plugin dll. +class FreePluginLibraryTask : public Task { + public: + FreePluginLibraryTask(const FilePath& path, + base::NativeLibrary library, + NP_ShutdownFunc shutdown_func) + : path_(path), + library_(library), + NP_Shutdown_(shutdown_func) { + } + + ~FreePluginLibraryTask() {} + + void Run() { + if (NP_Shutdown_) { + // Don't call NP_Shutdown if the library has been reloaded since this task + // was posted. + bool reloaded = false; + if (g_loaded_libs) { + for (size_t i = 0; i < g_loaded_libs->size(); ++i) { + if ((*g_loaded_libs)[i]->plugin_info().path == path_) + reloaded = true; + } + } + if (!reloaded) + NP_Shutdown_(); + } + + if (library_) { + // Always call base::UnloadNativeLibrary so that the system reference + // count is decremented. + base::UnloadNativeLibrary(library_); + library_ = NULL; + } + } + + private: + FilePath path_; + base::NativeLibrary library_; + NP_ShutdownFunc NP_Shutdown_; + DISALLOW_COPY_AND_ASSIGN(FreePluginLibraryTask); +}; + +void PluginLib::Unload() { + if (!internal_ && library_) { + // In case of single process mode, a plugin can delete itself + // by executing a script. So delay the unloading of the library + // so that the plugin will have a chance to unwind. + bool defer_unload = webkit_glue::IsPluginRunningInRendererProcess(); + +/* TODO(dglazkov): Revisit when re-enabling the JSC build. +#if USE(JSC) + // The plugin NPAPI instances may still be around. Delay the + // NP_Shutdown and FreeLibrary calls at least till the next + // peek message. + defer_unload = true; +#endif +*/ + + if (defer_unload) { + FreePluginLibraryTask* free_library_task = + new FreePluginLibraryTask(web_plugin_info_.path, + skip_unload_ ? NULL : library_, + entry_points_.np_shutdown); + LOG_IF(ERROR, PluginList::DebugPluginLoading()) + << "Scheduling delayed unload for plugin " + << web_plugin_info_.path.value(); + MessageLoop::current()->PostTask(FROM_HERE, free_library_task); + } else { + Shutdown(); + if (!skip_unload_) { + LOG_IF(ERROR, PluginList::DebugPluginLoading()) + << "Unloading plugin " << web_plugin_info_.path.value(); + base::UnloadNativeLibrary(library_); + } + } + + library_ = NULL; + } + + for (size_t i = 0; i < g_loaded_libs->size(); ++i) { + if ((*g_loaded_libs)[i].get() == this) { + g_loaded_libs->erase(g_loaded_libs->begin() + i); + break; + } + } + if (g_loaded_libs->empty()) { + delete g_loaded_libs; + g_loaded_libs = NULL; + } +} + +void PluginLib::Shutdown() { + if (initialized_ && !internal_) { + NP_Shutdown(); + initialized_ = false; + } +} + +} // namespace NPAPI diff --git a/webkit/glue/plugins/plugin_lib.h b/webkit/glue/plugins/plugin_lib.h new file mode 100644 index 0000000..ca46e41 --- /dev/null +++ b/webkit/glue/plugins/plugin_lib.h @@ -0,0 +1,120 @@ +// 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. + +#ifndef WEBKIT_GLUE_PLUGINS_PLUGIN_LIB_H_ +#define WEBKIT_GLUE_PLUGINS_PLUGIN_LIB_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/native_library.h" +#include "base/ref_counted.h" +#include "build/build_config.h" +#include "webkit/glue/plugins/plugin_list.h" +#include "webkit/glue/plugins/webplugin.h" + +class FilePath; +struct WebPluginInfo; + +namespace NPAPI { + +class PluginInstance; + +// A PluginLib is a single NPAPI Plugin Library, and is the lifecycle +// manager for new PluginInstances. +class PluginLib : public base::RefCounted<PluginLib> { + public: + static PluginLib* CreatePluginLib(const FilePath& filename); + + // Creates a WebPluginInfo structure given a plugin's path. On success + // returns true, with the information being put into "info". + // Returns false if the library couldn't be found, or if it's not a plugin. + static bool ReadWebPluginInfo(const FilePath& filename, WebPluginInfo* info); + +#if defined(OS_POSIX) && !defined(OS_MACOSX) + // Parse the result of an NP_GetMIMEDescription() call. + // This API is only used on Unixes, and is exposed here for testing. + static void ParseMIMEDescription(const std::string& description, + std::vector<WebPluginMimeType>* mime_types); +#endif + + // Unloads all the loaded plugin libraries and cleans up the plugin map. + static void UnloadAllPlugins(); + + // Shuts down all loaded plugin instances. + static void ShutdownAllPlugins(); + + // Get the Plugin's function pointer table. + NPPluginFuncs* functions(); + + // Creates a new instance of this plugin. + PluginInstance* CreateInstance(const std::string& mime_type); + + // Called by the instance when the instance is tearing down. + void CloseInstance(); + + // Gets information about this plugin and the mime types that it + // supports. + const WebPluginInfo& plugin_info() { return web_plugin_info_; } + + bool internal() { return internal_; } + + // + // NPAPI functions + // + + // NPAPI method to initialize a Plugin. + // Initialize can be safely called multiple times + NPError NP_Initialize(); + + // NPAPI method to shutdown a Plugin. + void NP_Shutdown(void); + + int instance_count() const { return instance_count_; } + + // Prevents the library code from being unload when Unload() is called (since + // some plugins crash if unloaded). + void PreventLibraryUnload(); + + // protected for testability. + protected: + friend class base::RefCounted<PluginLib>; + + // Creates a new PluginLib. + // |entry_points| is non-NULL for internal plugins. + PluginLib(const WebPluginInfo& info, + const PluginEntryPoints* entry_points); + + virtual ~PluginLib(); + + // Attempts to load the plugin from the library. + // Returns true if it is a legitimate plugin, false otherwise + bool Load(); + + // Unloads the plugin library. + void Unload(); + + // Shutdown the plugin library. + void Shutdown(); + + private: + bool internal_; // True for plugins that are built-in into chrome binaries. + WebPluginInfo web_plugin_info_; // Supported mime types, description + base::NativeLibrary library_; // The opened library reference. + NPPluginFuncs plugin_funcs_; // The struct of plugin side functions. + bool initialized_; // Is the plugin initialized? + NPSavedData *saved_data_; // Persisted plugin info for NPAPI. + int instance_count_; // Count of plugins in use. + bool skip_unload_; // True if library_ should not be unloaded. + + // Function pointers to entry points into the plugin. + PluginEntryPoints entry_points_; + + DISALLOW_COPY_AND_ASSIGN(PluginLib); +}; + +} // namespace NPAPI + +#endif // WEBKIT_GLUE_PLUGINS_PLUGIN_LIB_H_ diff --git a/webkit/glue/plugins/plugin_lib_mac.mm b/webkit/glue/plugins/plugin_lib_mac.mm new file mode 100644 index 0000000..89444c8 --- /dev/null +++ b/webkit/glue/plugins/plugin_lib_mac.mm @@ -0,0 +1,348 @@ +// 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 <Carbon/Carbon.h> + +#include "webkit/glue/plugins/plugin_lib.h" + +#include "base/mac/scoped_cftyperef.h" +#include "base/native_library.h" +#include "base/scoped_ptr.h" +#include "base/string_split.h" +#include "base/string_util.h" +#include "base/sys_string_conversions.h" +#include "base/utf_string_conversions.h" +#include "webkit/glue/plugins/plugin_list.h" + +static const short kSTRTypeDefinitionResourceID = 128; +static const short kSTRTypeDescriptionResourceID = 127; +static const short kSTRPluginDescriptionResourceID = 126; + +using base::mac::ScopedCFTypeRef; + +namespace NPAPI { + +namespace { + +NSDictionary* GetMIMETypes(CFBundleRef bundle) { + NSString* mime_filename = + (NSString*)CFBundleGetValueForInfoDictionaryKey(bundle, + CFSTR("WebPluginMIMETypesFilename")); + + if (mime_filename) { + + // get the file + + NSString* mime_path = + [NSString stringWithFormat:@"%@/Library/Preferences/%@", + NSHomeDirectory(), mime_filename]; + NSDictionary* mime_file_dict = + [NSDictionary dictionaryWithContentsOfFile:mime_path]; + + // is it valid? + + bool valid_file = false; + if (mime_file_dict) { + NSString* l10n_name = + [mime_file_dict objectForKey:@"WebPluginLocalizationName"]; + NSString* preferred_l10n = [[NSLocale currentLocale] localeIdentifier]; + if ([l10n_name isEqualToString:preferred_l10n]) + valid_file = true; + } + + if (valid_file) + return [mime_file_dict objectForKey:@"WebPluginMIMETypes"]; + + // dammit, I didn't want to have to do this + + typedef void (*CreateMIMETypesPrefsPtr)(void); + CreateMIMETypesPrefsPtr create_prefs_file = + (CreateMIMETypesPrefsPtr)CFBundleGetFunctionPointerForName( + bundle, CFSTR("BP_CreatePluginMIMETypesPreferences")); + if (!create_prefs_file) + return nil; + create_prefs_file(); + + // one more time + + mime_file_dict = [NSDictionary dictionaryWithContentsOfFile:mime_path]; + if (mime_file_dict) + return [mime_file_dict objectForKey:@"WebPluginMIMETypes"]; + else + return nil; + + } else { + return (NSDictionary*)CFBundleGetValueForInfoDictionaryKey(bundle, + CFSTR("WebPluginMIMETypes")); + } +} + +bool ReadPlistPluginInfo(const FilePath& filename, CFBundleRef bundle, + WebPluginInfo* info) { + NSDictionary* mime_types = GetMIMETypes(bundle); + if (!mime_types) + return false; // no type info here; try elsewhere + + for (NSString* mime_type in [mime_types allKeys]) { + NSDictionary* mime_dict = [mime_types objectForKey:mime_type]; + NSString* mime_desc = [mime_dict objectForKey:@"WebPluginTypeDescription"]; + NSArray* mime_exts = [mime_dict objectForKey:@"WebPluginExtensions"]; + + WebPluginMimeType mime; + mime.mime_type = base::SysNSStringToUTF8([mime_type lowercaseString]); + // Remove PDF from the list of types handled by QuickTime, since it provides + // a worse experience than just downloading the PDF. + if (mime.mime_type == "application/pdf" && + StartsWithASCII(filename.BaseName().value(), "QuickTime", false)) { + continue; + } + + if (mime_desc) + mime.description = base::SysNSStringToUTF16(mime_desc); + for (NSString* ext in mime_exts) + mime.file_extensions.push_back( + base::SysNSStringToUTF8([ext lowercaseString])); + + info->mime_types.push_back(mime); + } + + NSString* plugin_name = + (NSString*)CFBundleGetValueForInfoDictionaryKey(bundle, + CFSTR("WebPluginName")); + NSString* plugin_vers = + (NSString*)CFBundleGetValueForInfoDictionaryKey(bundle, + CFSTR("CFBundleShortVersionString")); + NSString* plugin_desc = + (NSString*)CFBundleGetValueForInfoDictionaryKey(bundle, + CFSTR("WebPluginDescription")); + + if (plugin_name) + info->name = base::SysNSStringToUTF16(plugin_name); + else + info->name = UTF8ToUTF16(filename.BaseName().value()); + info->path = filename; + if (plugin_vers) + info->version = base::SysNSStringToUTF16(plugin_vers); + if (plugin_desc) + info->desc = base::SysNSStringToUTF16(plugin_desc); + else + info->desc = UTF8ToUTF16(filename.BaseName().value()); + info->enabled = true; + + return true; +} + +class ScopedBundleResourceFile { + public: + ScopedBundleResourceFile(CFBundleRef bundle) : bundle_(bundle) { + old_ref_num_ = CurResFile(); + bundle_ref_num_ = CFBundleOpenBundleResourceMap(bundle); + UseResFile(bundle_ref_num_); + } + ~ScopedBundleResourceFile() { + UseResFile(old_ref_num_); + CFBundleCloseBundleResourceMap(bundle_, bundle_ref_num_); + } + + private: + CFBundleRef bundle_; + CFBundleRefNum bundle_ref_num_; + ResFileRefNum old_ref_num_; +}; + +bool GetSTRResource(CFBundleRef bundle, short res_id, + std::vector<std::string>* contents) { + Handle res_handle = Get1Resource('STR#', res_id); + if (!res_handle || !*res_handle) + return false; + + char* pointer = *res_handle; + short num_strings = *(short*)pointer; + pointer += sizeof(short); + for (short i = 0; i < num_strings; ++i) { + // Despite being 8-bits wide, these are legacy encoded. Make a round trip. + ScopedCFTypeRef<CFStringRef> str(CFStringCreateWithPascalStringNoCopy( + kCFAllocatorDefault, + (unsigned char*)pointer, + GetApplicationTextEncoding(), // is this right? + kCFAllocatorNull)); // perhaps CFStringGetSystemEncoding? + if (!str.get()) + return false; + contents->push_back(base::SysCFStringRefToUTF8(str.get())); + pointer += 1+*reinterpret_cast<unsigned char*>(pointer); + } + + return true; +} + +bool ReadSTRPluginInfo(const FilePath& filename, CFBundleRef bundle, + WebPluginInfo* info) { + ScopedBundleResourceFile res_file(bundle); + + std::vector<std::string> type_strings; + if (!GetSTRResource(bundle, kSTRTypeDefinitionResourceID, &type_strings)) + return false; + + std::vector<std::string> type_descs; + bool have_type_descs = GetSTRResource(bundle, + kSTRTypeDescriptionResourceID, + &type_descs); + + std::vector<std::string> plugin_descs; + bool have_plugin_descs = GetSTRResource(bundle, + kSTRPluginDescriptionResourceID, + &plugin_descs); + + size_t num_types = type_strings.size()/2; + + for (size_t i = 0; i < num_types; ++i) { + WebPluginMimeType mime; + mime.mime_type = StringToLowerASCII(type_strings[2*i]); + if (have_type_descs && i < type_descs.size()) + mime.description = UTF8ToUTF16(type_descs[i]); + base::SplitString( + StringToLowerASCII(type_strings[2*i+1]), ',', &mime.file_extensions); + + info->mime_types.push_back(mime); + } + + NSString* plugin_vers = + (NSString*)CFBundleGetValueForInfoDictionaryKey(bundle, + CFSTR("CFBundleShortVersionString")); + + if (have_plugin_descs && plugin_descs.size() > 1) + info->name = UTF8ToUTF16(plugin_descs[1]); + else + info->name = UTF8ToUTF16(filename.BaseName().value()); + info->path = filename; + if (plugin_vers) + info->version = base::SysNSStringToUTF16(plugin_vers); + if (have_plugin_descs && plugin_descs.size() > 0) + info->desc = UTF8ToUTF16(plugin_descs[0]); + else + info->desc = UTF8ToUTF16(filename.BaseName().value()); + info->enabled = true; + + return true; +} + +} // anonymous namespace + +bool PluginLib::ReadWebPluginInfo(const FilePath &filename, + WebPluginInfo* info) { + // There are two ways to get information about plugin capabilities. One is an + // Info.plist set of keys, documented at + // http://developer.apple.com/documentation/InternetWeb/Conceptual/WebKit_PluginProgTopic/Concepts/AboutPlugins.html . + // The other is a set of STR# resources, documented at + // https://developer.mozilla.org/En/Gecko_Plugin_API_Reference/Plug-in_Development_Overview . + // + // Historically, the data was maintained in the STR# resources. Apple, with + // the introduction of WebKit, noted the weaknesses of resources and moved the + // information into the Info.plist. Mozilla had always supported a + // NP_GetMIMEDescription() entry point for Unix plugins and also supports it + // on the Mac to supplement the STR# format. WebKit does not support + // NP_GetMIMEDescription() and neither do we. (That entry point is documented + // at https://developer.mozilla.org/en/NP_GetMIMEDescription .) We prefer the + // Info.plist format because it's more extensible and has a defined encoding, + // but will fall back to the STR# format of the data if it is not present in + // the Info.plist. + // + // The parsing of the data lives in the two functions ReadSTRPluginInfo() and + // ReadPlistPluginInfo(), but a summary of the formats follows. + // + // Each data type handled by a plugin has several properties: + // - <<type0mimetype>> + // - <<type0fileextension0>>..<<type0fileextensionk>> + // - <<type0description>> + // + // Each plugin may have any number of types defined. In addition, the plugin + // itself has properties: + // - <<plugindescription>> + // - <<pluginname>> + // + // For the Info.plist version, the data is formatted as follows (in text plist + // format): + // { + // ... the usual plist keys ... + // WebPluginDescription = <<plugindescription>>; + // WebPluginMIMETypes = { + // <<type0mimetype>> = { + // WebPluginExtensions = ( + // <<type0fileextension0>>, + // ... + // <<type0fileextensionk>>, + // ); + // WebPluginTypeDescription = <<type0description>>; + // }; + // <<type1mimetype>> = { ... }; + // ... + // <<typenmimetype>> = { ... }; + // }; + // WebPluginName = <<pluginname>>; + // } + // + // Alternatively (and this is undocumented), rather than a WebPluginMIMETypes + // key, there may be a WebPluginMIMETypesFilename key. If it is present, then + // it is the name of a file in the user's preferences folder in which to find + // the WebPluginMIMETypes key. If the key is present but the file doesn't + // exist, we must load the plugin and call a specific function to have the + // plugin create the file. + // + // If we do not find those keys in the Info.plist, we fall back to the STR# + // resources. In them, the data is formatted as follows: + // STR# 128 + // (1) <<type0mimetype>> + // (2) <<type0fileextension0>>,...,<<type0fileextensionk>> + // (3) <<type1mimetype>> + // (4) <<type1fileextension0>>,...,<<type1fileextensionk>> + // (...) + // (2n+1) <<typenmimetype>> + // (2n+2) <<typenfileextension0>>,...,<<typenfileextensionk>> + // STR# 127 + // (1) <<type0description>> + // (2) <<type1description>> + // (...) + // (n+1) <<typendescription>> + // STR# 126 + // (1) <<plugindescription>> + // (2) <<pluginname>> + // + // Strictly speaking, only STR# 128 is required. + + ScopedCFTypeRef<CFURLRef> bundle_url(CFURLCreateFromFileSystemRepresentation( + kCFAllocatorDefault, (const UInt8*)filename.value().c_str(), + filename.value().length(), true)); + if (!bundle_url) + return false; + ScopedCFTypeRef<CFBundleRef> bundle(CFBundleCreate(kCFAllocatorDefault, + bundle_url.get())); + if (!bundle) + return false; + + // preflight + + OSType type = 0; + CFBundleGetPackageInfo(bundle.get(), &type, NULL); + if (type != FOUR_CHAR_CODE('BRPL')) + return false; + + CFErrorRef error; + Boolean would_load = CFBundlePreflightExecutable(bundle.get(), &error); + if (!would_load) + return false; + + // get the info + + if (ReadPlistPluginInfo(filename, bundle.get(), info)) + return true; + + if (ReadSTRPluginInfo(filename, bundle.get(), info)) + return true; + + // ... or not + + return false; +} + +} // namespace NPAPI diff --git a/webkit/glue/plugins/plugin_lib_posix.cc b/webkit/glue/plugins/plugin_lib_posix.cc new file mode 100644 index 0000000..ac937e1 --- /dev/null +++ b/webkit/glue/plugins/plugin_lib_posix.cc @@ -0,0 +1,256 @@ +// 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. + +#include "webkit/glue/plugins/plugin_lib.h" + +#include <dlfcn.h> +#if defined(OS_OPENBSD) +#include <sys/exec_elf.h> +#else +#include <elf.h> +#include <fcntl.h> +#endif +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "base/eintr_wrapper.h" +#include "base/file_util.h" +#include "base/string_split.h" +#include "base/string_util.h" +#include "base/sys_string_conversions.h" +#include "base/utf_string_conversions.h" +#include "webkit/glue/plugins/plugin_list.h" + +// These headers must be included in this order to make the declaration gods +// happy. +#include "base/third_party/nspr/prcpucfg_linux.h" + +namespace { + +using NPAPI::PluginList; + +// Copied from nsplugindefs.h instead of including the file since it has a bunch +// of dependencies. +enum nsPluginVariable { + nsPluginVariable_NameString = 1, + nsPluginVariable_DescriptionString = 2 +}; + +// Read the ELF header and return true if it is usable on +// the current architecture (e.g. 32-bit ELF on 32-bit build). +// Returns false on other errors as well. +bool ELFMatchesCurrentArchitecture(const FilePath& filename) { + // First make sure we can open the file and it is in fact, a regular file. + struct stat stat_buf; + // Open with O_NONBLOCK so we don't block on pipes. + int fd = open(filename.value().c_str(), O_RDONLY|O_NONBLOCK); + if (fd < 0) + return false; + bool ret = (fstat(fd, &stat_buf) >= 0 && S_ISREG(stat_buf.st_mode)); + if (HANDLE_EINTR(close(fd)) < 0) + return false; + if (!ret) + return false; + + const size_t kELFBufferSize = 5; + char buffer[kELFBufferSize]; + if (!file_util::ReadFile(filename, buffer, kELFBufferSize)) + return false; + + if (buffer[0] != ELFMAG0 || + buffer[1] != ELFMAG1 || + buffer[2] != ELFMAG2 || + buffer[3] != ELFMAG3) { + // Not an ELF file, perhaps? + return false; + } + + int elf_class = buffer[EI_CLASS]; +#if defined(ARCH_CPU_32_BITS) + if (elf_class == ELFCLASS32) + return true; +#elif defined(ARCH_CPU_64_BITS) + if (elf_class == ELFCLASS64) + return true; +#endif + + return false; +} + +// This structure matches enough of nspluginwrapper's NPW_PluginInfo +// for us to extract the real plugin path. +struct __attribute__((packed)) NSPluginWrapperInfo { + char ident[32]; // NSPluginWrapper magic identifier (includes version). + char path[PATH_MAX]; // Path to wrapped plugin. +}; + +// Test a plugin for whether it's been wrapped by NSPluginWrapper, and +// if so attempt to unwrap it. Pass in an opened plugin handle; on +// success, |dl| and |unwrapped_path| will be filled in with the newly +// opened plugin. On failure, params are left unmodified. +void UnwrapNSPluginWrapper(void **dl, FilePath* unwrapped_path) { + NSPluginWrapperInfo* info = + reinterpret_cast<NSPluginWrapperInfo*>(dlsym(*dl, "NPW_Plugin")); + if (!info) + return; // Not a NSPW plugin. + + // Here we could check the NSPW ident field for the versioning + // information, but the path field is available in all versions + // anyway. + + // Grab the path to the wrapped plugin. Just in case the structure + // format changes, protect against the path not being null-terminated. + char* path_end = static_cast<char*>(memchr(info->path, '\0', + sizeof(info->path))); + if (!path_end) + path_end = info->path + sizeof(info->path); + FilePath path = FilePath(std::string(info->path, path_end - info->path)); + + if (!ELFMatchesCurrentArchitecture(path)) { + LOG(WARNING) << path.value() << " is nspluginwrapper wrapping a " + << "plugin for a different architecture; it will " + << "work better if you instead use a native plugin."; + return; + } + + void* newdl = base::LoadNativeLibrary(path); + if (!newdl) { + // We couldn't load the unwrapped plugin for some reason, despite + // being able to load the wrapped one. Just use the wrapped one. + LOG_IF(ERROR, PluginList::DebugPluginLoading()) + << "Could not use unwrapped nspluginwrapper plugin " + << unwrapped_path->value() << ", using the wrapped one."; + return; + } + + // Unload the wrapped plugin, and use the wrapped plugin instead. + LOG_IF(ERROR, PluginList::DebugPluginLoading()) + << "Using unwrapped version " << unwrapped_path->value() + << " of nspluginwrapper-wrapped plugin."; + base::UnloadNativeLibrary(*dl); + *dl = newdl; + *unwrapped_path = path; +} + +} // anonymous namespace + +namespace NPAPI { + +bool PluginLib::ReadWebPluginInfo(const FilePath& filename, + WebPluginInfo* info) { + // The file to reference is: + // http://mxr.mozilla.org/firefox/source/modules/plugin/base/src/nsPluginsDirUnix.cpp + + // Skip files that aren't appropriate for our architecture. + if (!ELFMatchesCurrentArchitecture(filename)) { + LOG_IF(ERROR, PluginList::DebugPluginLoading()) + << "Skipping plugin " << filename.value() + << " because it doesn't match the current architecture."; + return false; + } + + void* dl = base::LoadNativeLibrary(filename); + if (!dl) { + LOG_IF(ERROR, PluginList::DebugPluginLoading()) + << "While reading plugin info, unable to load library " + << filename.value() << ", skipping."; + return false; + } + + info->path = filename; + info->enabled = true; + + // Attempt to swap in the wrapped plugin if this is nspluginwrapper. + UnwrapNSPluginWrapper(&dl, &info->path); + + // See comments in plugin_lib_mac regarding this symbol. + typedef const char* (*NP_GetMimeDescriptionType)(); + NP_GetMimeDescriptionType NP_GetMIMEDescription = + reinterpret_cast<NP_GetMimeDescriptionType>( + dlsym(dl, "NP_GetMIMEDescription")); + const char* mime_description = NULL; + if (NP_GetMIMEDescription) + mime_description = NP_GetMIMEDescription(); + + if (mime_description) + ParseMIMEDescription(mime_description, &info->mime_types); + + // The plugin name and description live behind NP_GetValue calls. + typedef NPError (*NP_GetValueType)(void* unused, + nsPluginVariable variable, + void* value_out); + NP_GetValueType NP_GetValue = + reinterpret_cast<NP_GetValueType>(dlsym(dl, "NP_GetValue")); + if (NP_GetValue) { + const char* name = NULL; + NP_GetValue(NULL, nsPluginVariable_NameString, &name); + if (name) + info->name = UTF8ToUTF16(name); + + const char* description = NULL; + NP_GetValue(NULL, nsPluginVariable_DescriptionString, &description); + if (description) + info->desc = UTF8ToUTF16(description); + + LOG_IF(ERROR, PluginList::DebugPluginLoading()) + << "Got info for plugin " << filename.value() + << " Name = \"" << UTF16ToUTF8(info->name) + << "\", Description = \"" << UTF16ToUTF8(info->desc) << "\"."; + } else { + LOG_IF(ERROR, PluginList::DebugPluginLoading()) + << "Plugin " << filename.value() + << " has no GetValue() and probably won't work."; + } + + // Intentionally not unloading the plugin here, it can lead to crashes. + + return true; +} + +// static +void PluginLib::ParseMIMEDescription( + const std::string& description, + std::vector<WebPluginMimeType>* mime_types) { + // We parse the description here into WebPluginMimeType structures. + // Naively from the NPAPI docs you'd think you could use + // string-splitting, but the Firefox parser turns out to do something + // different: find the first colon, then the second, then a semi. + // + // See ParsePluginMimeDescription near + // http://mxr.mozilla.org/firefox/source/modules/plugin/base/src/nsPluginsDirUtils.h#53 + + std::string::size_type ofs = 0; + for (;;) { + WebPluginMimeType mime_type; + + std::string::size_type end = description.find(':', ofs); + if (end == std::string::npos) + break; + mime_type.mime_type = description.substr(ofs, end - ofs); + ofs = end + 1; + + end = description.find(':', ofs); + if (end == std::string::npos) + break; + const std::string extensions = description.substr(ofs, end - ofs); + base::SplitString(extensions, ',', &mime_type.file_extensions); + ofs = end + 1; + + end = description.find(';', ofs); + // It's ok for end to run off the string here. If there's no + // trailing semicolon we consume the remainder of the string. + if (end != std::string::npos) { + mime_type.description = UTF8ToUTF16(description.substr(ofs, end - ofs)); + } else { + mime_type.description = UTF8ToUTF16(description.substr(ofs)); + } + mime_types->push_back(mime_type); + if (end == std::string::npos) + break; + ofs = end + 1; + } +} + +} // namespace NPAPI diff --git a/webkit/glue/plugins/plugin_lib_unittest.cc b/webkit/glue/plugins/plugin_lib_unittest.cc new file mode 100644 index 0000000..45c4bb6 --- /dev/null +++ b/webkit/glue/plugins/plugin_lib_unittest.cc @@ -0,0 +1,152 @@ +// Copyright (c) 2009 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. + +#include "webkit/glue/plugins/plugin_lib.h" + +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "build/build_config.h" +#include "testing/gtest/include/gtest/gtest.h" + +// Test the unloading of plugin libs. Bug http://crbug.com/46526 showed that +// if UnloadAllPlugins() simply iterates through the g_loaded_libs global +// variable, we can get a crash if no plugin libs were marked as always loaded. +class PluginLibTest : public NPAPI::PluginLib { + public: + PluginLibTest() : NPAPI::PluginLib(WebPluginInfo(), NULL) { + } + using NPAPI::PluginLib::Unload; +}; + +TEST(PluginLibLoading, UnloadAllPlugins) { + // For the creation of the g_loaded_libs global variable. + ASSERT_EQ(static_cast<PluginLibTest*>(NULL), + PluginLibTest::CreatePluginLib(FilePath())); + + // Try with a single plugin lib. + scoped_refptr<PluginLibTest> plugin_lib1(new PluginLibTest()); + NPAPI::PluginLib::UnloadAllPlugins(); + + // Need to create it again, it should have been destroyed above. + ASSERT_EQ(static_cast<PluginLibTest*>(NULL), + PluginLibTest::CreatePluginLib(FilePath())); + + // Try with two plugin libs. + plugin_lib1 = new PluginLibTest(); + scoped_refptr<PluginLibTest> plugin_lib2(new PluginLibTest()); + NPAPI::PluginLib::UnloadAllPlugins(); + + // Need to create it again, it should have been destroyed above. + ASSERT_EQ(static_cast<PluginLibTest*>(NULL), + PluginLibTest::CreatePluginLib(FilePath())); + + // Now try to manually Unload one and then UnloadAll. + plugin_lib1 = new PluginLibTest(); + plugin_lib2 = new PluginLibTest(); + plugin_lib1->Unload(); + NPAPI::PluginLib::UnloadAllPlugins(); + + // Need to create it again, it should have been destroyed above. + ASSERT_EQ(static_cast<PluginLibTest*>(NULL), + PluginLibTest::CreatePluginLib(FilePath())); + + // Now try to manually Unload the only one and then UnloadAll. + plugin_lib1 = new PluginLibTest(); + plugin_lib1->Unload(); + NPAPI::PluginLib::UnloadAllPlugins(); +} + +#if defined(OS_LINUX) + +// Test parsing a simple description: Real Audio. +TEST(MIMEDescriptionParse, Simple) { + std::vector<WebPluginMimeType> types; + NPAPI::PluginLib::ParseMIMEDescription( + "audio/x-pn-realaudio-plugin:rpm:RealAudio document;", + &types); + ASSERT_EQ(1U, types.size()); + const WebPluginMimeType& type = types[0]; + EXPECT_EQ("audio/x-pn-realaudio-plugin", type.mime_type); + ASSERT_EQ(1U, type.file_extensions.size()); + EXPECT_EQ("rpm", type.file_extensions[0]); + EXPECT_EQ(ASCIIToUTF16("RealAudio document"), type.description); +} + +// Test parsing a multi-entry description: QuickTime as provided by Totem. +TEST(MIMEDescriptionParse, Multi) { + std::vector<WebPluginMimeType> types; + NPAPI::PluginLib::ParseMIMEDescription( + "video/quicktime:mov:QuickTime video;video/mp4:mp4:MPEG-4 " + "video;image/x-macpaint:pntg:MacPaint Bitmap image;image/x" + "-quicktime:pict, pict1, pict2:QuickTime image;video/x-m4v" + ":m4v:MPEG-4 video;", + &types); + + ASSERT_EQ(5U, types.size()); + + // Check the x-quicktime one, since it looks tricky with spaces in the + // extension list. + const WebPluginMimeType& type = types[3]; + EXPECT_EQ("image/x-quicktime", type.mime_type); + ASSERT_EQ(3U, type.file_extensions.size()); + EXPECT_EQ("pict2", type.file_extensions[2]); + EXPECT_EQ(ASCIIToUTF16("QuickTime image"), type.description); +} + +// Test parsing a Japanese description, since we got this wrong in the past. +// This comes from loading Totem with LANG=ja_JP.UTF-8. +TEST(MIMEDescriptionParse, JapaneseUTF8) { + std::vector<WebPluginMimeType> types; + NPAPI::PluginLib::ParseMIMEDescription( + "audio/x-ogg:ogg:Ogg \xe3\x82\xaa\xe3\x83\xbc\xe3\x83\x87" + "\xe3\x82\xa3\xe3\x83\xaa", + &types); + + ASSERT_EQ(1U, types.size()); + // Check we got the right number of Unicode characters out of the parse. + EXPECT_EQ(9U, types[0].description.size()); +} + +// Test that we handle corner cases gracefully. +TEST(MIMEDescriptionParse, CornerCases) { + std::vector<WebPluginMimeType> types; + NPAPI::PluginLib::ParseMIMEDescription("mime/type:", &types); + EXPECT_TRUE(types.empty()); + + types.clear(); + NPAPI::PluginLib::ParseMIMEDescription("mime/type:ext1:", &types); + ASSERT_EQ(1U, types.size()); + EXPECT_EQ("mime/type", types[0].mime_type); + EXPECT_EQ(1U, types[0].file_extensions.size()); + EXPECT_EQ("ext1", types[0].file_extensions[0]); + EXPECT_EQ(string16(), types[0].description); +} + +// This Java plugin has embedded semicolons in the mime type. +TEST(MIMEDescriptionParse, ComplicatedJava) { + std::vector<WebPluginMimeType> types; + NPAPI::PluginLib::ParseMIMEDescription( + "application/x-java-vm:class,jar:IcedTea;application/x-java" + "-applet:class,jar:IcedTea;application/x-java-applet;versio" + "n=1.1:class,jar:IcedTea;application/x-java-applet;version=" + "1.1.1:class,jar:IcedTea;application/x-java-applet;version=" + "1.1.2:class,jar:IcedTea;application/x-java-applet;version=" + "1.1.3:class,jar:IcedTea;application/x-java-applet;version=" + "1.2:class,jar:IcedTea;application/x-java-applet;version=1." + "2.1:class,jar:IcedTea;application/x-java-applet;version=1." + "2.2:class,jar:IcedTea;application/x-java-applet;version=1." + "3:class,jar:IcedTea;application/x-java-applet;version=1.3." + "1:class,jar:IcedTea;application/x-java-applet;version=1.4:" + "class,jar:IcedTea", + &types); + + ASSERT_EQ(12U, types.size()); + for (size_t i = 0; i < types.size(); ++i) + EXPECT_EQ(ASCIIToUTF16("IcedTea"), types[i].description); + + // Verify that the mime types with semis are coming through ok. + EXPECT_TRUE(types[4].mime_type.find(';') != std::string::npos); +} + +#endif // defined(OS_LINUX) diff --git a/webkit/glue/plugins/plugin_lib_win.cc b/webkit/glue/plugins/plugin_lib_win.cc new file mode 100644 index 0000000..382c2c8 --- /dev/null +++ b/webkit/glue/plugins/plugin_lib_win.cc @@ -0,0 +1,46 @@ +// 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. + +#include "webkit/glue/plugins/plugin_lib.h" + +#include "base/file_version_info.h" +#include "base/file_version_info_win.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "webkit/glue/plugins/plugin_constants_win.h" +#include "webkit/glue/plugins/plugin_list.h" + +namespace NPAPI { + +bool PluginLib::ReadWebPluginInfo(const FilePath &filename, + WebPluginInfo* info) { + // On windows, the way we get the mime types for the library is + // to check the version information in the DLL itself. This + // will be a string of the format: <type1>|<type2>|<type3>|... + // For example: + // video/quicktime|audio/aiff|image/jpeg + scoped_ptr<FileVersionInfo> version_info( + FileVersionInfo::CreateFileVersionInfo(filename.value())); + if (!version_info.get()) { + LOG_IF(ERROR, PluginList::DebugPluginLoading()) + << "Could not get version info for plugin " + << filename.value(); + return false; + } + + FileVersionInfoWin* version_info_win = + static_cast<FileVersionInfoWin*>(version_info.get()); + PluginVersionInfo pvi; + pvi.mime_types = version_info_win->GetStringValue(L"MIMEType"); + pvi.file_extensions = version_info_win->GetStringValue(L"FileExtents"); + pvi.type_descriptions = version_info_win->GetStringValue(L"FileOpenName"); + pvi.product_name = version_info->product_name(); + pvi.file_description = version_info->file_description(); + pvi.file_version = version_info->file_version(); + pvi.path = filename; + + return PluginList::CreateWebPluginInfo(pvi, info); +} + +} // namespace NPAPI diff --git a/webkit/glue/plugins/plugin_list.cc b/webkit/glue/plugins/plugin_list.cc new file mode 100644 index 0000000..a2b4cf5 --- /dev/null +++ b/webkit/glue/plugins/plugin_list.cc @@ -0,0 +1,781 @@ +// 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. + +#include "webkit/glue/plugins/plugin_list.h" + +#include <algorithm> + +#include "base/command_line.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/string_split.h" +#include "base/string_util.h" +#include "base/sys_string_conversions.h" +#include "base/utf_string_conversions.h" +#include "googleurl/src/gurl.h" +#include "net/base/mime_util.h" +#include "webkit/glue/plugins/plugin_constants_win.h" +#include "webkit/glue/plugins/plugin_lib.h" +#include "webkit/glue/webkit_glue.h" +#include "webkit/plugins/plugin_switches.h" + +#if defined(OS_POSIX) +#include "base/stl_util-inl.h" +#include "base/third_party/valgrind/valgrind.h" +#endif // defined(OS_POSIX) + +namespace NPAPI { + +#if defined(OS_MACOSX) +// Plugin Groups for Mac. +// Plugins are listed here as soon as vulnerabilities and solutions +// (new versions) are published. +// TODO(panayiotis): Get the Real Player version on Mac, somehow. +static const VersionRangeDefinition kQuicktimeVersionRange[] = { + { "", "", "7.6.6" } +}; +static const VersionRangeDefinition kJavaVersionRange[] = { + { "", "", "" } +}; +static const VersionRangeDefinition kFlashVersionRange[] = { + { "", "", "10.1.102" } +}; +static const VersionRangeDefinition kSilverlightVersionRange[] = { + { "0", "4", "3.0.50106.0" }, + { "4", "5", "" } +}; +static const VersionRangeDefinition kFlip4MacVersionRange[] = { + { "", "", "2.2.1" } +}; +static const VersionRangeDefinition kShockwaveVersionRange[] = { + { "", "", "11.5.9.615" } +}; +static const PluginGroupDefinition kGroupDefinitions[] = { + { "apple-quicktime", "Quicktime", "QuickTime Plug-in", kQuicktimeVersionRange, + arraysize(kQuicktimeVersionRange), + "http://www.apple.com/quicktime/download/" }, + { "java-runtime-environment", "Java", "Java", kJavaVersionRange, + arraysize(kJavaVersionRange), "http://support.apple.com/kb/HT1338" }, + { "adobe-flash-player", "Flash", "Shockwave Flash", kFlashVersionRange, + arraysize(kFlashVersionRange), "http://get.adobe.com/flashplayer/" }, + { "silverlight", "Silverlight", "Silverlight", kSilverlightVersionRange, + arraysize(kSilverlightVersionRange), + "http://www.microsoft.com/getsilverlight/" }, + { "flip4mac", "Flip4Mac", "Flip4Mac", kFlip4MacVersionRange, + arraysize(kFlip4MacVersionRange), + "http://www.telestream.net/flip4mac-wmv/overview.htm" }, + { "shockwave", "Shockwave", "Shockwave for Director", kShockwaveVersionRange, + arraysize(kShockwaveVersionRange), + "http://www.adobe.com/shockwave/download/" } +}; + +#elif defined(OS_WIN) +// TODO(panayiotis): We should group "RealJukebox NS Plugin" with the rest of +// the RealPlayer files. +static const VersionRangeDefinition kQuicktimeVersionRange[] = { + { "", "", "7.6.9" } +}; +static const VersionRangeDefinition kJavaVersionRange[] = { + { "0", "7", "6.0.220" } // "220" is not a typo. +}; +static const VersionRangeDefinition kAdobeReaderVersionRange[] = { + { "10", "11", "" }, + { "9", "10", "9.4.1" }, + { "0", "9", "8.2.5" } +}; +static const VersionRangeDefinition kFlashVersionRange[] = { + { "", "", "10.1.102" } +}; +static const VersionRangeDefinition kSilverlightVersionRange[] = { + { "0", "4", "3.0.50106.0" }, + { "4", "5", "" } +}; +static const VersionRangeDefinition kShockwaveVersionRange[] = { + { "", "", "11.5.9.615" } +}; +static const VersionRangeDefinition kDivXVersionRange[] = { + { "", "", "1.4.3.4" } +}; +static const VersionRangeDefinition kRealPlayerVersionRange[] = { + { "", "", "12.0.1.609" } +}; +static const PluginGroupDefinition kGroupDefinitions[] = { + { "apple-quicktime", "Quicktime", "QuickTime Plug-in", kQuicktimeVersionRange, + arraysize(kQuicktimeVersionRange), + "http://www.apple.com/quicktime/download/" }, + { "java-runtime-environment", "Java 6", "Java", kJavaVersionRange, + arraysize(kJavaVersionRange), "http://www.java.com/" }, + { "adobe-reader", PluginGroup::kAdobeReaderGroupName, "Adobe Acrobat", + kAdobeReaderVersionRange, arraysize(kAdobeReaderVersionRange), + "http://get.adobe.com/reader/" }, + { "adobe-flash-player", "Flash", "Shockwave Flash", kFlashVersionRange, + arraysize(kFlashVersionRange), "http://get.adobe.com/flashplayer/" }, + { "silverlight", "Silverlight", "Silverlight", kSilverlightVersionRange, + arraysize(kSilverlightVersionRange), + "http://www.microsoft.com/getsilverlight/" }, + { "shockwave", "Shockwave", "Shockwave for Director", kShockwaveVersionRange, + arraysize(kShockwaveVersionRange), + "http://www.adobe.com/shockwave/download/" }, + { "divx-player", "DivX Player", "DivX Web Player", kDivXVersionRange, + arraysize(kDivXVersionRange), + "http://download.divx.com/divx/autoupdate/player/" + "DivXWebPlayerInstaller.exe" }, + { "realplayer", "RealPlayer", "RealPlayer", kRealPlayerVersionRange, + arraysize(kRealPlayerVersionRange), + "http://www.real.com/realplayer" }, + // These are here for grouping, no vulnerabilities known. + { "windows-media-player", "Windows Media Player", "Windows Media Player", + NULL, 0, "" }, + { "microsoft-office", "Microsoft Office", "Microsoft Office", + NULL, 0, "" }, +}; + +#else +static const PluginGroupDefinition kGroupDefinitions[] = {}; +#endif + +// static +const PluginGroupDefinition* PluginList::GetPluginGroupDefinitions() { + return kGroupDefinitions; +} + +// static +size_t PluginList::GetPluginGroupDefinitionsSize() { + // TODO(viettrungluu): |arraysize()| doesn't work with zero-size arrays. + return ARRAYSIZE_UNSAFE(kGroupDefinitions); +} + +base::LazyInstance<PluginList> g_singleton(base::LINKER_INITIALIZED); + +// static +PluginList* PluginList::Singleton() { + return g_singleton.Pointer(); +} + +// static +bool PluginList::DebugPluginLoading() { + return CommandLine::ForCurrentProcess()->HasSwitch( + switches::kDebugPluginLoading); +} + +bool PluginList::PluginsLoaded() { + AutoLock lock(lock_); + return plugins_loaded_; +} + +void PluginList::RefreshPlugins() { + AutoLock lock(lock_); + plugins_need_refresh_ = true; +} + +void PluginList::AddExtraPluginPath(const FilePath& plugin_path) { + // Chrome OS only loads plugins from /opt/google/chrome/plugins. +#if !defined(OS_CHROMEOS) + AutoLock lock(lock_); + extra_plugin_paths_.push_back(plugin_path); +#endif +} + +void PluginList::RemoveExtraPluginPath(const FilePath& plugin_path) { + AutoLock lock(lock_); + std::vector<FilePath>::iterator it = + std::find(extra_plugin_paths_.begin(), extra_plugin_paths_.end(), + plugin_path); + if (it != extra_plugin_paths_.end()) + extra_plugin_paths_.erase(it); +} + +void PluginList::AddExtraPluginDir(const FilePath& plugin_dir) { + // Chrome OS only loads plugins from /opt/google/chrome/plugins. +#if !defined(OS_CHROMEOS) + AutoLock lock(lock_); + extra_plugin_dirs_.push_back(plugin_dir); +#endif +} + +void PluginList::RegisterInternalPlugin(const PluginVersionInfo& info) { + AutoLock lock(lock_); + internal_plugins_.push_back(info); +} + +void PluginList::UnregisterInternalPlugin(const FilePath& path) { + AutoLock lock(lock_); + for (size_t i = 0; i < internal_plugins_.size(); i++) { + if (internal_plugins_[i].path == path) { + internal_plugins_.erase(internal_plugins_.begin() + i); + return; + } + } + NOTREACHED(); +} + +bool PluginList::ReadPluginInfo(const FilePath& filename, + WebPluginInfo* info, + const PluginEntryPoints** entry_points) { + { + AutoLock lock(lock_); + for (size_t i = 0; i < internal_plugins_.size(); ++i) { + if (filename == internal_plugins_[i].path) { + *entry_points = &internal_plugins_[i].entry_points; + return CreateWebPluginInfo(internal_plugins_[i], info); + } + } + } + + // Not an internal plugin. + *entry_points = NULL; + + return PluginLib::ReadWebPluginInfo(filename, info); +} + +bool PluginList::CreateWebPluginInfo(const PluginVersionInfo& pvi, + WebPluginInfo* info) { + std::vector<std::string> mime_types, file_extensions; + std::vector<string16> descriptions; + base::SplitString(WideToUTF8(pvi.mime_types), '|', &mime_types); + base::SplitString(WideToUTF8(pvi.file_extensions), '|', &file_extensions); + base::SplitString(WideToUTF16(pvi.type_descriptions), '|', &descriptions); + + info->mime_types.clear(); + + if (mime_types.empty()) { + LOG_IF(ERROR, PluginList::DebugPluginLoading()) + << "Plugin " << pvi.product_name << " has no MIME types, skipping"; + return false; + } + + info->name = WideToUTF16(pvi.product_name); + info->desc = WideToUTF16(pvi.file_description); + info->version = WideToUTF16(pvi.file_version); + info->path = pvi.path; + info->enabled = true; + + for (size_t i = 0; i < mime_types.size(); ++i) { + WebPluginMimeType mime_type; + mime_type.mime_type = StringToLowerASCII(mime_types[i]); + if (file_extensions.size() > i) + base::SplitString(file_extensions[i], ',', &mime_type.file_extensions); + + if (descriptions.size() > i) { + mime_type.description = descriptions[i]; + + // On Windows, the description likely has a list of file extensions + // embedded in it (e.g. "SurfWriter file (*.swr)"). Remove an extension + // list from the description if it is present. + size_t ext = mime_type.description.find(ASCIIToUTF16("(*")); + if (ext != string16::npos) { + if (ext > 1 && mime_type.description[ext -1] == ' ') + ext--; + + mime_type.description.erase(ext); + } + } + + info->mime_types.push_back(mime_type); + } + + return true; +} + +PluginList::PluginList() + : plugins_loaded_(false), + plugins_need_refresh_(false), + disable_outdated_plugins_(false), + next_priority_(0) { + PlatformInit(); + AddHardcodedPluginGroups(); +} + +bool PluginList::ShouldDisableGroup(const string16& group_name) { + AutoLock lock(lock_); + if (PluginGroup::IsPluginNameDisabledByPolicy(group_name)) { + disabled_groups_.insert(group_name); + return true; + } + return disabled_groups_.count(group_name) > 0; +} + +void PluginList::LoadPlugins(bool refresh) { + // Don't want to hold the lock while loading new plugins, so we don't block + // other methods if they're called on other threads. + std::vector<FilePath> extra_plugin_paths; + std::vector<FilePath> extra_plugin_dirs; + std::vector<PluginVersionInfo> internal_plugins; + { + AutoLock lock(lock_); + if (plugins_loaded_ && !refresh && !plugins_need_refresh_) + return; + + // Clear the refresh bit now, because it might get set again before we + // reach the end of the method. + plugins_need_refresh_ = false; + extra_plugin_paths = extra_plugin_paths_; + extra_plugin_dirs = extra_plugin_dirs_; + internal_plugins = internal_plugins_; + } + + std::vector<WebPluginInfo> new_plugins; + std::set<FilePath> visited_plugins; + + std::vector<FilePath> directories_to_scan; + GetPluginDirectories(&directories_to_scan); + + // Load internal plugins first so that, if both an internal plugin and a + // "discovered" plugin want to handle the same type, the internal plugin + // will have precedence. + for (size_t i = 0; i < internal_plugins.size(); ++i) { + if (internal_plugins[i].path.value() == kDefaultPluginLibraryName) + continue; + LoadPlugin(internal_plugins[i].path, &new_plugins); + } + + for (size_t i = 0; i < extra_plugin_paths.size(); ++i) { + const FilePath& path = extra_plugin_paths[i]; + if (visited_plugins.find(path) != visited_plugins.end()) + continue; + LoadPlugin(path, &new_plugins); + visited_plugins.insert(path); + } + + for (size_t i = 0; i < extra_plugin_dirs.size(); ++i) { + LoadPluginsFromDir(extra_plugin_dirs[i], &new_plugins, &visited_plugins); + } + + for (size_t i = 0; i < directories_to_scan.size(); ++i) { + LoadPluginsFromDir(directories_to_scan[i], &new_plugins, &visited_plugins); + } + +#if defined(OS_WIN) + LoadPluginsFromRegistry(&new_plugins, &visited_plugins); +#endif + + // Load the default plugin last. + if (webkit_glue::IsDefaultPluginEnabled()) + LoadPlugin(FilePath(kDefaultPluginLibraryName), &new_plugins); + + // Disable all of the plugins and plugin groups that are disabled by policy. + // There's currenly a bug that makes it impossible to correctly re-enable + // plugins or plugin-groups to their original, "pre-policy" state, so + // plugins and groups are only changed to a more "safe" state after a policy + // change, i.e. from enabled to disabled. See bug 54681. + for (PluginGroup::PluginMap::iterator it = plugin_groups_.begin(); + it != plugin_groups_.end(); ++it) { + PluginGroup* group = it->second; + string16 group_name = group->GetGroupName(); + if (ShouldDisableGroup(group_name)) { + group->Enable(false); + } + + if (disable_outdated_plugins_) { + group->DisableOutdatedPlugins(); + } + if (!group->Enabled()) { + AutoLock lock(lock_); + disabled_groups_.insert(group_name); + } + } + + // Only update the data now since loading plugins can take a while. + AutoLock lock(lock_); + + plugins_ = new_plugins; + plugins_loaded_ = true; +} + +void PluginList::LoadPlugin(const FilePath& path, + std::vector<WebPluginInfo>* plugins) { + LOG_IF(ERROR, PluginList::DebugPluginLoading()) + << "Loading plugin " << path.value(); + + WebPluginInfo plugin_info; + const PluginEntryPoints* entry_points; + + if (!ReadPluginInfo(path, &plugin_info, &entry_points)) + return; + + if (!ShouldLoadPlugin(plugin_info, plugins)) + return; + + if (path.value() != kDefaultPluginLibraryName +#if defined(OS_WIN) && !defined(NDEBUG) + && path.BaseName().value() != L"npspy.dll" // Make an exception for NPSPY +#endif + ) { + for (size_t i = 0; i < plugin_info.mime_types.size(); ++i) { + // TODO: don't load global handlers for now. + // WebKit hands to the Plugin before it tries + // to handle mimeTypes on its own. + const std::string &mime_type = plugin_info.mime_types[i].mime_type; + if (mime_type == "*" ) + return; + } + } + + // Mark disabled plugins as such. (This has to happen before calling + // |AddToPluginGroups(plugin_info)|.) + if (disabled_plugins_.count(plugin_info.path)) { + plugin_info.enabled = false; + } else { + plugin_info.enabled = true; + } + + AutoLock lock(lock_); + plugins->push_back(plugin_info); + AddToPluginGroups(plugin_info); +} + +bool PluginList::SupportsType(const WebPluginInfo& info, + const std::string &mime_type, + bool allow_wildcard) { + // Webkit will ask for a plugin to handle empty mime types. + if (mime_type.empty()) + return false; + + for (size_t i = 0; i < info.mime_types.size(); ++i) { + const WebPluginMimeType& mime_info = info.mime_types[i]; + if (net::MatchesMimeType(mime_info.mime_type, mime_type)) { + if (!allow_wildcard && mime_info.mime_type == "*") { + continue; + } + return true; + } + } + return false; +} + +bool PluginList::SupportsExtension(const WebPluginInfo& info, + const std::string &extension, + std::string* actual_mime_type) { + for (size_t i = 0; i < info.mime_types.size(); ++i) { + const WebPluginMimeType& mime_type = info.mime_types[i]; + for (size_t j = 0; j < mime_type.file_extensions.size(); ++j) { + if (mime_type.file_extensions[j] == extension) { + if (actual_mime_type) + *actual_mime_type = mime_type.mime_type; + return true; + } + } + } + + return false; +} + + +void PluginList::GetPlugins(bool refresh, std::vector<WebPluginInfo>* plugins) { + LoadPlugins(refresh); + + AutoLock lock(lock_); + *plugins = plugins_; +} + +void PluginList::GetEnabledPlugins(bool refresh, + std::vector<WebPluginInfo>* plugins) { + LoadPlugins(refresh); + + plugins->clear(); + AutoLock lock(lock_); + for (std::vector<WebPluginInfo>::const_iterator it = plugins_.begin(); + it != plugins_.end(); + ++it) { + if (it->enabled) + plugins->push_back(*it); + } +} + +void PluginList::GetPluginInfoArray( + const GURL& url, + const std::string& mime_type, + bool allow_wildcard, + std::vector<WebPluginInfo>* info, + std::vector<std::string>* actual_mime_types) { + DCHECK(mime_type == StringToLowerASCII(mime_type)); + DCHECK(info); + + LoadPlugins(false); + AutoLock lock(lock_); + info->clear(); + if (actual_mime_types) + actual_mime_types->clear(); + + std::set<FilePath> visited_plugins; + + // Add in enabled plugins by mime type. + WebPluginInfo default_plugin; + for (size_t i = 0; i < plugins_.size(); ++i) { + if (plugins_[i].enabled && + SupportsType(plugins_[i], mime_type, allow_wildcard)) { + FilePath path = plugins_[i].path; + if (path.value() != kDefaultPluginLibraryName && + visited_plugins.insert(path).second) { + info->push_back(plugins_[i]); + if (actual_mime_types) + actual_mime_types->push_back(mime_type); + } + } + } + + // Add in enabled plugins by url. + std::string path = url.path(); + std::string::size_type last_dot = path.rfind('.'); + if (last_dot != std::string::npos) { + std::string extension = StringToLowerASCII(std::string(path, last_dot+1)); + std::string actual_mime_type; + for (size_t i = 0; i < plugins_.size(); ++i) { + if (plugins_[i].enabled && + SupportsExtension(plugins_[i], extension, &actual_mime_type)) { + FilePath path = plugins_[i].path; + if (path.value() != kDefaultPluginLibraryName && + visited_plugins.insert(path).second) { + info->push_back(plugins_[i]); + if (actual_mime_types) + actual_mime_types->push_back(actual_mime_type); + } + } + } + } + + // Add in disabled plugins by mime type. + for (size_t i = 0; i < plugins_.size(); ++i) { + if (!plugins_[i].enabled && + SupportsType(plugins_[i], mime_type, allow_wildcard)) { + FilePath path = plugins_[i].path; + if (path.value() != kDefaultPluginLibraryName && + visited_plugins.insert(path).second) { + info->push_back(plugins_[i]); + if (actual_mime_types) + actual_mime_types->push_back(mime_type); + } + } + } + + // Add the default plugin at the end if it supports the mime type given, + // and the default plugin is enabled. + if (!plugins_.empty() && webkit_glue::IsDefaultPluginEnabled()) { + const WebPluginInfo& default_info = plugins_.back(); + if (SupportsType(default_info, mime_type, allow_wildcard)) { + info->push_back(default_info); + if (actual_mime_types) + actual_mime_types->push_back(mime_type); + } + } +} + +bool PluginList::GetPluginInfo(const GURL& url, + const std::string& mime_type, + bool allow_wildcard, + WebPluginInfo* info, + std::string* actual_mime_type) { + DCHECK(info); + std::vector<WebPluginInfo> info_list; + + // GetPluginInfoArray has slightly less work to do if we can pass + // NULL for the mime type list... + if (actual_mime_type) { + std::vector<std::string> mime_type_list; + GetPluginInfoArray( + url, mime_type, allow_wildcard, &info_list, &mime_type_list); + if (!info_list.empty()) { + *info = info_list[0]; + *actual_mime_type = mime_type_list[0]; + return true; + } + } else { + GetPluginInfoArray(url, mime_type, allow_wildcard, &info_list, NULL); + if (!info_list.empty()) { + *info = info_list[0]; + return true; + } + } + return false; +} + +bool PluginList::GetPluginInfoByPath(const FilePath& plugin_path, + WebPluginInfo* info) { + LoadPlugins(false); + AutoLock lock(lock_); + for (size_t i = 0; i < plugins_.size(); ++i) { + if (plugins_[i].path == plugin_path) { + *info = plugins_[i]; + return true; + } + } + + return false; +} + +void PluginList::GetPluginGroups( + bool load_if_necessary, + std::vector<PluginGroup>* plugin_groups) { + if (load_if_necessary) + LoadPlugins(false); + plugin_groups->clear(); + for (PluginGroup::PluginMap::const_iterator it = plugin_groups_.begin(); + it != plugin_groups_.end(); ++it) { + plugin_groups->push_back(*it->second); + } +} + +const PluginGroup* PluginList::GetPluginGroup( + const WebPluginInfo& web_plugin_info) { + AutoLock lock(lock_); + return AddToPluginGroups(web_plugin_info); +} + +string16 PluginList::GetPluginGroupName(std::string identifier) { + PluginGroup::PluginMap::iterator it = plugin_groups_.find(identifier); + if (it == plugin_groups_.end()) { + return string16(); + } + return it->second->GetGroupName(); +} + +std::string PluginList::GetPluginGroupIdentifier( + const WebPluginInfo& web_plugin_info) { + AutoLock lock(lock_); + PluginGroup* group = AddToPluginGroups(web_plugin_info); + return group->identifier(); +} + +void PluginList::AddHardcodedPluginGroups() { + AutoLock lock(lock_); + const PluginGroupDefinition* definitions = GetPluginGroupDefinitions(); + for (size_t i = 0; i < GetPluginGroupDefinitionsSize(); ++i) { + PluginGroup* definition_group = PluginGroup::FromPluginGroupDefinition( + definitions[i]); + std::string identifier = definition_group->identifier(); + DCHECK(plugin_groups_.find(identifier) == plugin_groups_.end()); + plugin_groups_.insert(std::make_pair(identifier, definition_group)); + } +} + +PluginGroup* PluginList::AddToPluginGroups( + const WebPluginInfo& web_plugin_info) { + PluginGroup* group = NULL; + for (PluginGroup::PluginMap::iterator it = plugin_groups_.begin(); + it != plugin_groups_.end(); ++it) { + if (it->second->Match(web_plugin_info)) + group = it->second; + } + if (!group) { + group = PluginGroup::FromWebPluginInfo(web_plugin_info); + std::string identifier = group->identifier(); + // If the identifier is not unique, use the full path. This means that we + // probably won't be able to search for this group by identifier, but at + // least it's going to be in the set of plugin groups, and if there + // is already a plug-in with the same filename, it's probably going to + // handle the same MIME types (and it has a higher priority), so this one + // is not going to run anyway. + if (plugin_groups_.find(identifier) != plugin_groups_.end()) + identifier = PluginGroup::GetLongIdentifier(web_plugin_info); + DCHECK(plugin_groups_.find(identifier) == plugin_groups_.end()); + plugin_groups_.insert(std::make_pair(identifier, group)); + } + group->AddPlugin(web_plugin_info, next_priority_++); + return group; +} + +bool PluginList::EnablePlugin(const FilePath& filename) { + AutoLock lock(lock_); + + bool did_enable = false; + + std::set<FilePath>::iterator entry = disabled_plugins_.find(filename); + if (entry == disabled_plugins_.end()) + return did_enable; // Early exit if plugin not in disabled list. + + disabled_plugins_.erase(entry); // Remove from disabled list. + + // Set enabled flags if necessary. + for (std::vector<WebPluginInfo>::iterator it = plugins_.begin(); + it != plugins_.end(); + ++it) { + if (it->path == filename) { + DCHECK(!it->enabled); // Should have been disabled. + it->enabled = true; + did_enable = true; + } + } + + return did_enable; +} + +bool PluginList::DisablePlugin(const FilePath& filename) { + AutoLock lock(lock_); + + bool did_disable = false; + + if (disabled_plugins_.find(filename) != disabled_plugins_.end()) + return did_disable; // Early exit if plugin already in disabled list. + + disabled_plugins_.insert(filename); // Add to disabled list. + + // Unset enabled flags if necessary. + for (std::vector<WebPluginInfo>::iterator it = plugins_.begin(); + it != plugins_.end(); + ++it) { + if (it->path == filename) { + DCHECK(it->enabled); // Should have been enabled. + it->enabled = false; + did_disable = true; + } + } + + return did_disable; +} + +bool PluginList::EnableGroup(bool enable, const string16& group_name) { + bool did_change = false; + { + AutoLock lock(lock_); + + std::set<string16>::iterator entry = disabled_groups_.find(group_name); + if (enable) { + if (entry == disabled_groups_.end()) + return did_change; // Early exit if group not in disabled list. + disabled_groups_.erase(entry); // Remove from disabled list. + } else { + if (entry != disabled_groups_.end()) + return did_change; // Early exit if group already in disabled list. + disabled_groups_.insert(group_name); + } + } + + for (PluginGroup::PluginMap::iterator it = plugin_groups_.begin(); + it != plugin_groups_.end(); ++it) { + if (it->second->GetGroupName() == group_name) { + if (it->second->Enabled() != enable) { + it->second->Enable(enable); + did_change = true; + break; + } + } + } + + return did_change; +} + +void PluginList::DisableOutdatedPluginGroups() { + disable_outdated_plugins_ = true; +} + +PluginList::~PluginList() { + Shutdown(); +} + +void PluginList::Shutdown() { + // TODO + // Note: plugin_groups_ contains simple pointers of type PluginGroup*, but + // since this singleton lives until the process is destroyed, no explicit + // cleanup is necessary. + // However, when running on Valgrind, we need to do the cleanup to keep the + // memory tree green. +#if defined(OS_POSIX) + if (RUNNING_ON_VALGRIND) { + STLDeleteContainerPairSecondPointers(plugin_groups_.begin(), + plugin_groups_.end()); + } +#endif +} + +} // namespace NPAPI diff --git a/webkit/glue/plugins/plugin_list.h b/webkit/glue/plugins/plugin_list.h index 111e8fa..734cc6d 100644 --- a/webkit/glue/plugins/plugin_list.h +++ b/webkit/glue/plugins/plugin_list.h @@ -5,26 +5,332 @@ #ifndef WEBKIT_GLUE_PLUGINS_PLUGIN_LIST_H_ #define WEBKIT_GLUE_PLUGINS_PLUGIN_LIST_H_ -// This file is here to keep NativeClient compiling. PluginList was moved to -// webkit/plugins/npapi and into the webkit::npapi namespace. Native Client -// depends on this old location & namespace, so we provide just enough -// definitions here to keep it compiling until it can be updated to use the -// new location & namespace. -// -// TODO(brettw) remove this flie when NaCl is updated. +#include <set> +#include <string> +#include <vector> -#include "webkit/plugins/npapi/plugin_list.h" +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/linked_ptr.h" +#include "base/lock.h" +#include "third_party/npapi/bindings/nphostapi.h" +#include "webkit/glue/plugins/plugin_group.h" +#include "webkit/glue/plugins/webplugininfo.h" + +class GURL; + +namespace base { + +template <typename T> +struct DefaultLazyInstanceTraits; + +} // namespace base namespace NPAPI { -typedef webkit::npapi::PluginEntryPoints PluginEntryPoints; -typedef webkit::npapi::PluginVersionInfo PluginVersionInfo; +#define kDefaultPluginLibraryName FILE_PATH_LITERAL("default_plugin") +#define kGearsPluginLibraryName FILE_PATH_LITERAL("gears") + +class PluginInstance; + +// This struct holds entry points into a plugin. The entry points are +// slightly different between Win/Mac and Unixes. +struct PluginEntryPoints { +#if !defined(OS_POSIX) || defined(OS_MACOSX) + NP_GetEntryPointsFunc np_getentrypoints; +#endif + NP_InitializeFunc np_initialize; + NP_ShutdownFunc np_shutdown; +}; + +// This struct fully describes a plugin. For external plugins, it's read in from +// the version info of the dll; For internal plugins, it's predefined and +// includes addresses of entry functions. (Yes, it's Win32 NPAPI-centric, but +// it'll do for holding descriptions of internal plugins cross-platform.) +struct PluginVersionInfo { + FilePath path; + // Info about the plugin itself. + std::wstring product_name; + std::wstring file_description; + std::wstring file_version; + // Info about the data types that the plugin supports. + std::wstring mime_types; + std::wstring file_extensions; + std::wstring type_descriptions; + // Entry points for internal plugins. Pointers are NULL for external plugins. + PluginEntryPoints entry_points; +}; +// The PluginList is responsible for loading our NPAPI based plugins. It does +// so in whatever manner is appropriate for the platform. On Windows, it loads +// plugins from a known directory by looking for DLLs which start with "NP", +// and checking to see if they are valid NPAPI libraries. On the Mac, it walks +// the machine-wide and user plugin directories and loads anything that has +// the correct types. On Linux, it walks the plugin directories as well +// (e.g. /usr/lib/browser-plugins/). +// This object is thread safe. class PluginList { public: - static inline webkit::npapi::PluginList* Singleton() { - return webkit::npapi::PluginList::Singleton(); - } + // Gets the one instance of the PluginList. + static PluginList* Singleton(); + + // Returns true if we're in debug-plugin-loading mode. This is controlled + // by a command line switch. + static bool DebugPluginLoading(); + + static const PluginGroupDefinition* GetPluginGroupDefinitions(); + static size_t GetPluginGroupDefinitionsSize(); + + // Returns true iff the plugin list has been loaded already. + bool PluginsLoaded(); + + // Cause the plugin list to refresh next time they are accessed, regardless + // of whether they are already loaded. + void RefreshPlugins(); + + // Add/Remove an extra plugin to load when we actually do the loading. Must + // be called before the plugins have been loaded. + void AddExtraPluginPath(const FilePath& plugin_path); + void RemoveExtraPluginPath(const FilePath& plugin_path); + + // Same as above, but specifies a directory in which to search for plugins. + void AddExtraPluginDir(const FilePath& plugin_dir); + + // Register an internal plugin with the specified plugin information and + // function pointers. An internal plugin must be registered before it can + // be loaded using PluginList::LoadPlugin(). + void RegisterInternalPlugin(const PluginVersionInfo& info); + + // Removes a specified internal plugin from the list. The search will match + // on the path from the version info previously registered. + // + // This is generally only necessary for tests. + void UnregisterInternalPlugin(const FilePath& path); + + // Creates a WebPluginInfo structure given a plugin's path. On success + // returns true, with the information being put into "info". If it's an + // internal plugin, "entry_points" is filled in as well with a + // internally-owned PluginEntryPoints pointer. + // Returns false if the library couldn't be found, or if it's not a plugin. + bool ReadPluginInfo(const FilePath& filename, + WebPluginInfo* info, + const PluginEntryPoints** entry_points); + + // Populate a WebPluginInfo from a PluginVersionInfo. + static bool CreateWebPluginInfo(const PluginVersionInfo& pvi, + WebPluginInfo* info); + + // Shutdown all plugins. Should be called at process teardown. + void Shutdown(); + + // Get all the plugins. + void GetPlugins(bool refresh, std::vector<WebPluginInfo>* plugins); + + // Get all the enabled plugins. + void GetEnabledPlugins(bool refresh, std::vector<WebPluginInfo>* plugins); + + // Returns a list in |info| containing plugins that are found for + // the given url and mime type (including disabled plugins, for + // which |info->enabled| is false). The mime type which corresponds + // to the URL is optionally returned back in |actual_mime_types| (if + // it is non-NULL), one for each of the plugin info objects found. + // The |allow_wildcard| parameter controls whether this function + // returns plugins which support wildcard mime types (* as the mime + // type). The |info| parameter is required to be non-NULL. The + // list is in order of "most desirable" to "least desirable", + // meaning that the default plugin is at the end of the list. + void GetPluginInfoArray(const GURL& url, + const std::string& mime_type, + bool allow_wildcard, + std::vector<WebPluginInfo>* info, + std::vector<std::string>* actual_mime_types); + + // Returns the first item from the list returned in GetPluginInfo in |info|. + // Returns true if it found a match. |actual_mime_type| may be NULL. + bool GetPluginInfo(const GURL& url, + const std::string& mime_type, + bool allow_wildcard, + WebPluginInfo* info, + std::string* actual_mime_type); + + // Get plugin info by plugin path (including disabled plugins). Returns true + // if the plugin is found and WebPluginInfo has been filled in |info|. + bool GetPluginInfoByPath(const FilePath& plugin_path, + WebPluginInfo* info); + + // Populates the given vector with all available plugin groups. + void GetPluginGroups(bool load_if_necessary, + std::vector<PluginGroup>* plugin_groups); + + // Returns the PluginGroup corresponding to the given WebPluginInfo. If no + // such group exists, it is created and added to the cache. + // Beware: when calling this from the Browser process, the group that the + // returned pointer points to might disappear suddenly. This happens when + // |RefreshPlugins()| is called and then |LoadPlugins()| is triggered by a + // call to |GetPlugins()|, |GetEnabledPlugins()|, |GetPluginInfoArray()|, + // |GetPluginInfoByPath()|, or |GetPluginGroups(true, _)|. It is the caller's + // responsibility to make sure this doesn't happen. + const PluginGroup* GetPluginGroup(const WebPluginInfo& web_plugin_info); + + // Returns the name of the PluginGroup with the given identifier. + // If no such group exists, an empty string is returned. + string16 GetPluginGroupName(std::string identifier); + + // Returns the identifier string of the PluginGroup corresponding to the given + // WebPluginInfo. If no such group exists, it is created and added to the + // cache. + std::string GetPluginGroupIdentifier(const WebPluginInfo& web_plugin_info); + + // Load a specific plugin with full path. + void LoadPlugin(const FilePath& filename, + std::vector<WebPluginInfo>* plugins); + + // Enable a specific plugin, specified by path. Returns |true| iff a plugin + // currently in the plugin list was actually enabled as a result; regardless + // of return value, if a plugin is found in the future with the given name, it + // will be enabled. Note that plugins are enabled by default as far as + // |PluginList| is concerned. + bool EnablePlugin(const FilePath& filename); + + // Disable a specific plugin, specified by path. Returns |true| iff a plugin + // currently in the plugin list was actually disabled as a result; regardless + // of return value, if a plugin is found in the future with the given name, it + // will be disabled. + bool DisablePlugin(const FilePath& filename); + + // Enable/disable a plugin group, specified by group_name. Returns |true| iff + // a plugin currently in the plugin list was actually enabled/disabled as a + // result; regardless of return value, if a plugin is found in the future with + // the given name, it will be enabled/disabled. Note that plugins are enabled + // by default as far as |PluginList| is concerned. + bool EnableGroup(bool enable, const string16& name); + + // Disable all plugins groups that are known to be outdated, according to + // the information hardcoded in PluginGroup, to make sure that they can't + // be loaded on a web page and instead show a UI to update to the latest + // version. + void DisableOutdatedPluginGroups(); + + ~PluginList(); + + private: + FRIEND_TEST_ALL_PREFIXES(PluginGroupTest, PluginGroupDefinition); + + // Constructors are private for singletons + PluginList(); + + // Creates PluginGroups for the static group definitions, and adds them to + // the PluginGroup cache of this PluginList. + void AddHardcodedPluginGroups(); + + // Adds the given WebPluginInfo to its corresponding group, creating it if + // necessary, and returns the group. + // Callers need to protect calls to this method by a lock themselves. + PluginGroup* AddToPluginGroups(const WebPluginInfo& web_plugin_info); + + // Load all plugins from the default plugins directory + void LoadPlugins(bool refresh); + + // Load all plugins from a specific directory. + // |plugins| is updated with loaded plugin information. + // |visited_plugins| is updated with paths to all plugins that were considered + // (including those we didn't load) + void LoadPluginsFromDir(const FilePath& path, + std::vector<WebPluginInfo>* plugins, + std::set<FilePath>* visited_plugins); + + // Returns true if we should load the given plugin, or false otherwise. + // plugins is the list of plugins we have crawled in the current plugin + // loading run. + bool ShouldLoadPlugin(const WebPluginInfo& info, + std::vector<WebPluginInfo>* plugins); + + // Return whether a plug-in group with the given name should be disabled, + // either because it already is on the list of disabled groups, or because it + // is blacklisted by a policy. In the latter case, add the plugin group to the + // list of disabled groups as well. + bool ShouldDisableGroup(const string16& group_name); + + // Returns true if the given WebPluginInfo supports "mime-type". + // mime_type should be all lower case. + static bool SupportsType(const WebPluginInfo& info, + const std::string &mime_type, + bool allow_wildcard); + + // Returns true if the given WebPluginInfo supports a given file extension. + // extension should be all lower case. + // If mime_type is not NULL, it will be set to the mime type if found. + // The mime type which corresponds to the extension is optionally returned + // back. + static bool SupportsExtension(const WebPluginInfo& info, + const std::string &extension, + std::string* actual_mime_type); + + // + // Platform functions + // + + // Do any initialization. + void PlatformInit(); + + // Get the ordered list of directories from which to load plugins + void GetPluginDirectories(std::vector<FilePath>* plugin_dirs); + + // + // Command-line switches + // + +#if defined(OS_WIN) + // true if we shouldn't load the new WMP plugin. + bool dont_load_new_wmp_; + + // Loads plugins registered under HKCU\Software\MozillaPlugins and + // HKLM\Software\MozillaPlugins. + void LoadPluginsFromRegistry(std::vector<WebPluginInfo>* plugins, + std::set<FilePath>* visited_plugins); +#endif + + // + // Internals + // + + bool plugins_loaded_; + + // If true, we reload plugins even if they've been loaded already. + bool plugins_need_refresh_; + + // Contains information about the available plugins. + std::vector<WebPluginInfo> plugins_; + + // Extra plugin paths that we want to search when loading. + std::vector<FilePath> extra_plugin_paths_; + + // Extra plugin directories that we want to search when loading. + std::vector<FilePath> extra_plugin_dirs_; + + // Holds information about internal plugins. + std::vector<PluginVersionInfo> internal_plugins_; + + // Path names of plugins to disable (the default is to enable them all). + std::set<FilePath> disabled_plugins_; + + // Group names to disable (the default is to enable them all). + std::set<string16> disabled_groups_; + + bool disable_outdated_plugins_; + + // Holds the currently available plugin groups. + PluginGroup::PluginMap plugin_groups_; + + int next_priority_; + + // Need synchronization for the above members since this object can be + // accessed on multiple threads. + Lock lock_; + + friend struct base::DefaultLazyInstanceTraits<PluginList>; + + DISALLOW_COPY_AND_ASSIGN(PluginList); }; } // namespace NPAPI diff --git a/webkit/glue/plugins/plugin_list_mac.mm b/webkit/glue/plugins/plugin_list_mac.mm new file mode 100644 index 0000000..e7a2337 --- /dev/null +++ b/webkit/glue/plugins/plugin_list_mac.mm @@ -0,0 +1,109 @@ +// Copyright (c) 2006-2009 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. + +#include "webkit/glue/plugins/plugin_list.h" + +#import <Foundation/Foundation.h> + +#include "base/file_util.h" +#include "base/mac_util.h" +#include "base/string_number_conversions.h" +#include "base/string_split.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "webkit/glue/plugins/plugin_lib.h" + +namespace { + +void GetPluginCommonDirectory(std::vector<FilePath>* plugin_dirs, + bool user) { + // Note that there are no NSSearchPathDirectory constants for these + // directories so we can't use Cocoa's NSSearchPathForDirectoriesInDomains(). + // Interestingly, Safari hard-codes the location (see + // WebKit/WebKit/mac/Plugins/WebPluginDatabase.mm's +_defaultPlugInPaths). + FSRef ref; + OSErr err = FSFindFolder(user ? kUserDomain : kLocalDomain, + kInternetPlugInFolderType, false, &ref); + + if (err) + return; + + plugin_dirs->push_back(FilePath(mac_util::PathFromFSRef(ref))); +} + +// Returns true if the plugin should be prevented from loading. +bool IsBlacklistedPlugin(const WebPluginInfo& info) { + // We blacklist Gears by included MIME type, since that is more stable than + // its name. Be careful about adding any more plugins to this list though, + // since it's easy to accidentally blacklist plugins that support lots of + // MIME types. + for (std::vector<WebPluginMimeType>::const_iterator i = + info.mime_types.begin(); i != info.mime_types.end(); ++i) { + // The Gears plugin is Safari-specific, so don't load it. + if (i->mime_type == "application/x-googlegears") + return true; + } + + // Versions of Flip4Mac 2.3 before 2.3.6 often hang the renderer, so don't + // load them. + if (StartsWith(info.name, ASCIIToUTF16("Flip4Mac Windows Media"), false) && + StartsWith(info.version, ASCIIToUTF16("2.3"), false)) { + std::vector<string16> components; + base::SplitString(info.version, '.', &components); + int bugfix_version = 0; + return (components.size() >= 3 && + base::StringToInt(components[2], &bugfix_version) && + bugfix_version < 6); + } + + return false; +} + +} // namespace + +namespace NPAPI +{ + +void PluginList::PlatformInit() { +} + +void PluginList::GetPluginDirectories(std::vector<FilePath>* plugin_dirs) { + // Load from the user's area + GetPluginCommonDirectory(plugin_dirs, true); + + // Load from the machine-wide area + GetPluginCommonDirectory(plugin_dirs, false); +} + +void PluginList::LoadPluginsFromDir(const FilePath &path, + std::vector<WebPluginInfo>* plugins, + std::set<FilePath>* visited_plugins) { + file_util::FileEnumerator enumerator(path, + false, // not recursive + file_util::FileEnumerator::DIRECTORIES); + for (FilePath path = enumerator.Next(); !path.value().empty(); + path = enumerator.Next()) { + LoadPlugin(path, plugins); + visited_plugins->insert(path); + } +} + +bool PluginList::ShouldLoadPlugin(const WebPluginInfo& info, + std::vector<WebPluginInfo>* plugins) { + if (IsBlacklistedPlugin(info)) + return false; + + // Hierarchy check + // (we're loading plugins hierarchically from Library folders, so plugins we + // encounter earlier must override plugins we encounter later) + for (size_t i = 0; i < plugins->size(); ++i) { + if ((*plugins)[i].path.BaseName() == info.path.BaseName()) { + return false; // We already have a loaded plugin higher in the hierarchy. + } + } + + return true; +} + +} // namespace NPAPI diff --git a/webkit/glue/plugins/plugin_list_posix.cc b/webkit/glue/plugins/plugin_list_posix.cc new file mode 100644 index 0000000..654c0c5 --- /dev/null +++ b/webkit/glue/plugins/plugin_list_posix.cc @@ -0,0 +1,270 @@ +// 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. + +#include "webkit/glue/plugins/plugin_list.h" + +#include "base/file_util.h" +#include "base/path_service.h" +#include "base/sha1.h" +#include "base/string_split.h" +#include "base/string_util.h" +#include "build/build_config.h" + +namespace { + +// We build up a list of files and mtimes so we can sort them. +typedef std::pair<FilePath, base::Time> FileAndTime; +typedef std::vector<FileAndTime> FileTimeList; + +// Comparator used to sort by descending mtime then ascending filename. +bool CompareTime(const FileAndTime& a, const FileAndTime& b) { + if (a.second == b.second) { + // Fall back on filename sorting, just to make the predicate valid. + return a.first < b.first; + } + + // Sort by mtime, descending. + return a.second > b.second; +} + +// Return true if |path| matches a known (file size, sha1sum) pair. +// The use of the file size is an optimization so we don't have to read in +// the entire file unless we have to. +bool IsBlacklistedBySha1sum(const FilePath& path) { + const struct BadEntry { + int64 size; + std::string sha1; + } bad_entries[] = { + // Flash 9 r31 - http://crbug.com/29237 + { 7040080, "fa5803061125ca47846713b34a26a42f1f1e98bb" }, + // Flash 9 r48 - http://crbug.com/29237 + { 7040036, "0c4b3768a6d4bfba003088e4b9090d381de1af2b" }, + }; + + int64 size; + if (!file_util::GetFileSize(path, &size)) + return false; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(bad_entries); i++) { + if (bad_entries[i].size != size) + continue; + + std::string file_content; + if (!file_util::ReadFileToString(path, &file_content)) + continue; + std::string sha1 = base::SHA1HashString(file_content); + std::string sha1_readable; + for (size_t j = 0; j < sha1.size(); j++) + base::StringAppendF(&sha1_readable, "%02x", sha1[j] & 0xFF); + if (bad_entries[i].sha1 == sha1_readable) + return true; + } + return false; +} + +// Some plugins are shells around other plugins; we prefer to use the +// real plugin directly, if it's available. This function returns +// true if we should prefer other plugins over this one. We'll still +// use a "undesirable" plugin if no other option is available. +bool IsUndesirablePlugin(const WebPluginInfo& info) { + std::string filename = info.path.BaseName().value(); + const char* kUndesiredPlugins[] = { + "npcxoffice", // Crossover + "npwrapper", // nspluginwrapper + }; + for (size_t i = 0; i < arraysize(kUndesiredPlugins); i++) { + if (filename.find(kUndesiredPlugins[i]) != std::string::npos) { + return true; + } + } + return false; +} + +// Return true if we shouldn't load a plugin at all. +// This is an ugly hack to blacklist Adobe Acrobat due to not supporting +// its Xt-based mainloop. +// http://code.google.com/p/chromium/issues/detail?id=38229 +// The gecko-mediaplayer plugins also crashes the entire browser sometimes. +// http://code.google.com/p/chromium/issues/detail?id=24507 +bool IsBlacklistedPlugin(const FilePath& path) { + const char* kBlackListedPlugins[] = { + "nppdf.so", // Adobe PDF + "gecko-mediaplayer", // Gecko Media Player + }; + std::string filename = path.BaseName().value(); + for (size_t i = 0; i < arraysize(kBlackListedPlugins); i++) { + if (filename.find(kBlackListedPlugins[i]) != std::string::npos) { + return true; + } + } + return IsBlacklistedBySha1sum(path); +} + +} // anonymous namespace + +namespace NPAPI { + +void PluginList::PlatformInit() { +} + +void PluginList::GetPluginDirectories(std::vector<FilePath>* plugin_dirs) { + // See http://groups.google.com/group/chromium-dev/browse_thread/thread/7a70e5fcbac786a9 + // for discussion. + // We first consult Chrome-specific dirs, then fall back on the logic + // Mozilla uses. + + // Note: "extra" plugin dirs, including the Plugins subdirectory of + // your Chrome config, are examined before these. See the logic + // related to extra_plugin_dirs in plugin_list.cc. + + // The Chrome binary dir + "plugins/". + FilePath dir; + PathService::Get(base::DIR_EXE, &dir); + plugin_dirs->push_back(dir.Append("plugins")); + + // Chrome OS only loads plugins from /opt/google/chrome/plugins. +#if !defined(OS_CHROMEOS) + // Mozilla code to reference: + // http://mxr.mozilla.org/firefox/ident?i=NS_APP_PLUGINS_DIR_LIST + // and tens of accompanying files (mxr is very helpful). + // This code carefully matches their behavior for compat reasons. + + // 1) MOZ_PLUGIN_PATH env variable. + const char* moz_plugin_path = getenv("MOZ_PLUGIN_PATH"); + if (moz_plugin_path) { + std::vector<std::string> paths; + base::SplitString(moz_plugin_path, ':', &paths); + for (size_t i = 0; i < paths.size(); ++i) + plugin_dirs->push_back(FilePath(paths[i])); + } + + // 2) NS_USER_PLUGINS_DIR: ~/.mozilla/plugins. + // This is a de-facto standard, so even though we're not Mozilla, let's + // look in there too. + FilePath home = file_util::GetHomeDir(); + if (!home.empty()) + plugin_dirs->push_back(home.Append(".mozilla/plugins")); + + // 3) NS_SYSTEM_PLUGINS_DIR: + // This varies across different browsers and versions, so check 'em all. + plugin_dirs->push_back(FilePath("/usr/lib/browser-plugins")); + plugin_dirs->push_back(FilePath("/usr/lib/mozilla/plugins")); + plugin_dirs->push_back(FilePath("/usr/lib/firefox/plugins")); + plugin_dirs->push_back(FilePath("/usr/lib/xulrunner-addons/plugins")); + +#if defined(ARCH_CPU_64_BITS) + // On my Ubuntu system, /usr/lib64 is a symlink to /usr/lib. + // But a user reported on their Fedora system they are separate. + plugin_dirs->push_back(FilePath("/usr/lib64/browser-plugins")); + plugin_dirs->push_back(FilePath("/usr/lib64/mozilla/plugins")); + plugin_dirs->push_back(FilePath("/usr/lib64/firefox/plugins")); + plugin_dirs->push_back(FilePath("/usr/lib64/xulrunner-addons/plugins")); +#endif // defined(ARCH_CPU_64_BITS) +#endif // !defined(OS_CHROMEOS) +} + +void PluginList::LoadPluginsFromDir(const FilePath& dir_path, + std::vector<WebPluginInfo>* plugins, + std::set<FilePath>* visited_plugins) { + // See ScanPluginsDirectory near + // http://mxr.mozilla.org/firefox/source/modules/plugin/base/src/nsPluginHostImpl.cpp#5052 + + // Construct and stat a list of all filenames under consideration, for + // later sorting by mtime. + FileTimeList files; + file_util::FileEnumerator enumerator(dir_path, + false, // not recursive + file_util::FileEnumerator::FILES); + for (FilePath path = enumerator.Next(); !path.value().empty(); + path = enumerator.Next()) { + // Skip over Mozilla .xpt files. + if (path.MatchesExtension(FILE_PATH_LITERAL(".xpt"))) + continue; + + // Java doesn't like being loaded through a symlink, since it uses + // its path to find dependent data files. + // file_util::AbsolutePath calls through to realpath(), which resolves + // symlinks. + FilePath orig_path = path; + file_util::AbsolutePath(&path); + LOG_IF(ERROR, PluginList::DebugPluginLoading()) + << "Resolved " << orig_path.value() << " -> " << path.value(); + + if (visited_plugins->find(path) != visited_plugins->end()) { + LOG_IF(ERROR, PluginList::DebugPluginLoading()) + << "Skipping duplicate instance of " << path.value(); + continue; + } + visited_plugins->insert(path); + + if (IsBlacklistedPlugin(path)) { + LOG_IF(ERROR, PluginList::DebugPluginLoading()) + << "Skipping blacklisted plugin " << path.value(); + continue; + } + + // Flash stops working if the containing directory involves 'netscape'. + // No joke. So use the other path if it's better. + static const char kFlashPlayerFilename[] = "libflashplayer.so"; + static const char kNetscapeInPath[] = "/netscape/"; + if (path.BaseName().value() == kFlashPlayerFilename && + path.value().find(kNetscapeInPath) != std::string::npos) { + if (orig_path.value().find(kNetscapeInPath) == std::string::npos) { + // Go back to the old path. + path = orig_path; + } else { + LOG_IF(ERROR, PluginList::DebugPluginLoading()) + << "Flash misbehaves when used from a directory containing " + << kNetscapeInPath << ", so skipping " << orig_path.value(); + continue; + } + } + + // Get mtime. + base::PlatformFileInfo info; + if (!file_util::GetFileInfo(path, &info)) + continue; + + files.push_back(std::make_pair(path, info.last_modified)); + } + + // Sort the file list by time (and filename). + std::sort(files.begin(), files.end(), CompareTime); + + // Load the files in order. + for (FileTimeList::const_iterator i = files.begin(); i != files.end(); ++i) { + LoadPlugin(i->first, plugins); + } +} + +bool PluginList::ShouldLoadPlugin(const WebPluginInfo& info, + std::vector<WebPluginInfo>* plugins) { + LOG_IF(ERROR, PluginList::DebugPluginLoading()) + << "Considering " << info.path.value() << " (" << info.name << ")"; + + if (IsUndesirablePlugin(info)) { + LOG_IF(ERROR, PluginList::DebugPluginLoading()) + << info.path.value() << " is undesirable."; + + // See if we have a better version of this plugin. + for (size_t i = 0; i < plugins->size(); ++i) { + if (plugins->at(i).name == info.name && + !IsUndesirablePlugin(plugins->at(i))) { + // Skip the current undesirable one so we can use the better one + // we just found. + LOG_IF(ERROR, PluginList::DebugPluginLoading()) + << "Skipping " << info.path.value() << ", preferring " + << plugins->at(i).path.value(); + return false; + } + } + } + + // TODO(evanm): prefer the newest version of flash, etc. here? + + VLOG_IF(1, PluginList::DebugPluginLoading()) << "Using " << info.path.value(); + + return true; +} + +} // namespace NPAPI diff --git a/webkit/glue/plugins/plugin_list_win.cc b/webkit/glue/plugins/plugin_list_win.cc new file mode 100644 index 0000000..4869262 --- /dev/null +++ b/webkit/glue/plugins/plugin_list_win.cc @@ -0,0 +1,410 @@ +// 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. + +#include "webkit/glue/plugins/plugin_list.h" + +#include <tchar.h> + +#include <set> + +#include "base/basictypes.h" +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/path_service.h" +#include "base/scoped_ptr.h" +#include "base/string_number_conversions.h" +#include "base/string_split.h" +#include "base/string_util.h" +#include "base/win/registry.h" +#include "webkit/glue/plugins/plugin_constants_win.h" +#include "webkit/glue/plugins/plugin_lib.h" +#include "webkit/glue/webkit_glue.h" + +namespace { + +const TCHAR kRegistryApps[] = + _T("Software\\Microsoft\\Windows\\CurrentVersion\\App Paths"); +const TCHAR kRegistryFirefox[] = _T("firefox.exe"); +const TCHAR kRegistryAcrobat[] = _T("Acrobat.exe"); +const TCHAR kRegistryAcrobatReader[] = _T("AcroRd32.exe"); +const TCHAR kRegistryWindowsMedia[] = _T("wmplayer.exe"); +const TCHAR kRegistryQuickTime[] = _T("QuickTimePlayer.exe"); +const TCHAR kRegistryPath[] = _T("Path"); +const TCHAR kRegistryFirefoxInstalled[] = + _T("SOFTWARE\\Mozilla\\Mozilla Firefox"); +const TCHAR kRegistryJava[] = + _T("Software\\JavaSoft\\Java Runtime Environment"); +const TCHAR kRegistryBrowserJavaVersion[] = _T("BrowserJavaVersion"); +const TCHAR kRegistryCurrentJavaVersion[] = _T("CurrentVersion"); +const TCHAR kRegistryJavaHome[] = _T("JavaHome"); +const TCHAR kJavaDeploy1[] = _T("npdeploytk.dll"); +const TCHAR kJavaDeploy2[] = _T("npdeployjava1.dll"); + +// The application path where we expect to find plugins. +void GetAppDirectory(std::set<FilePath>* plugin_dirs) { + FilePath app_path; + if (!webkit_glue::GetApplicationDirectory(&app_path)) + return; + + app_path = app_path.AppendASCII("plugins"); + plugin_dirs->insert(app_path); +} + +// The executable path where we expect to find plugins. +void GetExeDirectory(std::set<FilePath>* plugin_dirs) { + FilePath exe_path; + if (!webkit_glue::GetExeDirectory(&exe_path)) + return; + + exe_path = exe_path.AppendASCII("plugins"); + plugin_dirs->insert(exe_path); +} + +// Gets the installed path for a registered app. +bool GetInstalledPath(const TCHAR* app, FilePath* out) { + std::wstring reg_path(kRegistryApps); + reg_path.append(L"\\"); + reg_path.append(app); + + base::win::RegKey key(HKEY_LOCAL_MACHINE, reg_path.c_str(), KEY_READ); + std::wstring path; + if (key.ReadValue(kRegistryPath, &path)) { + *out = FilePath(path); + return true; + } + + return false; +} + +// Search the registry at the given path and detect plugin directories. +void GetPluginsInRegistryDirectory( + HKEY root_key, + const std::wstring& registry_folder, + std::set<FilePath>* plugin_dirs) { + for (base::win::RegistryKeyIterator iter(root_key, registry_folder.c_str()); + iter.Valid(); ++iter) { + // Use the registry to gather plugin across the file system. + std::wstring reg_path = registry_folder; + reg_path.append(L"\\"); + reg_path.append(iter.Name()); + base::win::RegKey key(root_key, reg_path.c_str(), KEY_READ); + + std::wstring path; + if (key.ReadValue(kRegistryPath, &path)) + plugin_dirs->insert(FilePath(path)); + } +} + +// Enumerate through the registry key to find all installed FireFox paths. +// FireFox 3 beta and version 2 can coexist. See bug: 1025003 +void GetFirefoxInstalledPaths(std::vector<FilePath>* out) { + base::win::RegistryKeyIterator it(HKEY_LOCAL_MACHINE, + kRegistryFirefoxInstalled); + for (; it.Valid(); ++it) { + std::wstring full_path = std::wstring(kRegistryFirefoxInstalled) + L"\\" + + it.Name() + L"\\Main"; + base::win::RegKey key(HKEY_LOCAL_MACHINE, full_path.c_str(), KEY_READ); + std::wstring install_dir; + if (!key.ReadValue(L"Install Directory", &install_dir)) + continue; + out->push_back(FilePath(install_dir)); + } +} + +// Get plugin directory locations from the Firefox install path. This is kind +// of a kludge, but it helps us locate the flash player for users that +// already have it for firefox. Not having to download yet-another-plugin +// is a good thing. +void GetFirefoxDirectory(std::set<FilePath>* plugin_dirs) { + std::vector<FilePath> paths; + GetFirefoxInstalledPaths(&paths); + for (unsigned int i = 0; i < paths.size(); ++i) { + plugin_dirs->insert(paths[i].Append(L"plugins")); + } + + FilePath firefox_app_data_plugin_path; + if (PathService::Get(base::DIR_APP_DATA, &firefox_app_data_plugin_path)) { + firefox_app_data_plugin_path = + firefox_app_data_plugin_path.AppendASCII("Mozilla") + .AppendASCII("plugins"); + plugin_dirs->insert(firefox_app_data_plugin_path); + } +} + +// Hardcoded logic to detect Acrobat plugins locations. +void GetAcrobatDirectory(std::set<FilePath>* plugin_dirs) { + FilePath path; + if (!GetInstalledPath(kRegistryAcrobatReader, &path) && + !GetInstalledPath(kRegistryAcrobat, &path)) { + return; + } + + plugin_dirs->insert(path.Append(L"Browser")); +} + +// Hardcoded logic to detect QuickTime plugin location. +void GetQuicktimeDirectory(std::set<FilePath>* plugin_dirs) { + FilePath path; + if (GetInstalledPath(kRegistryQuickTime, &path)) + plugin_dirs->insert(path.Append(L"plugins")); +} + +// Hardcoded logic to detect Windows Media Player plugin location. +void GetWindowsMediaDirectory(std::set<FilePath>* plugin_dirs) { + FilePath path; + if (GetInstalledPath(kRegistryWindowsMedia, &path)) + plugin_dirs->insert(path); + + // If the Windows Media Player Firefox plugin is installed before Firefox, + // the plugin will get written under PFiles\Plugins on one the drives + // (usually, but not always, the last letter). + int size = GetLogicalDriveStrings(0, NULL); + if (size) { + scoped_array<wchar_t> strings(new wchar_t[size]); + if (GetLogicalDriveStrings(size, strings.get())) { + wchar_t* next_drive = strings.get(); + while (*next_drive) { + if (GetDriveType(next_drive) == DRIVE_FIXED) { + FilePath pfiles(next_drive); + pfiles = pfiles.Append(L"PFiles\\Plugins"); + if (file_util::PathExists(pfiles)) + plugin_dirs->insert(pfiles); + } + next_drive = &next_drive[wcslen(next_drive) + 1]; + } + } + } +} + +// Hardcoded logic to detect Java plugin location. +void GetJavaDirectory(std::set<FilePath>* plugin_dirs) { + // Load the new NPAPI Java plugin + // 1. Open the main JRE key under HKLM + base::win::RegKey java_key(HKEY_LOCAL_MACHINE, kRegistryJava, + KEY_QUERY_VALUE); + + // 2. Read the current Java version + std::wstring java_version; + if (!java_key.ReadValue(kRegistryBrowserJavaVersion, &java_version)) + java_key.ReadValue(kRegistryCurrentJavaVersion, &java_version); + + if (!java_version.empty()) { + java_key.OpenKey(java_version.c_str(), KEY_QUERY_VALUE); + + // 3. Install path of the JRE binaries is specified in "JavaHome" + // value under the Java version key. + std::wstring java_plugin_directory; + if (java_key.ReadValue(kRegistryJavaHome, &java_plugin_directory)) { + // 4. The new plugin resides under the 'bin/new_plugin' + // subdirectory. + DCHECK(!java_plugin_directory.empty()); + java_plugin_directory.append(L"\\bin\\new_plugin"); + + // 5. We don't know the exact name of the DLL but it's in the form + // NP*.dll so just invoke LoadPlugins on this path. + plugin_dirs->insert(FilePath(java_plugin_directory)); + } + } +} + +} // anonymous namespace + +namespace NPAPI { + +void PluginList::PlatformInit() { + const CommandLine& command_line = *CommandLine::ForCurrentProcess(); + dont_load_new_wmp_ = command_line.HasSwitch(kUseOldWMPPluginSwitch); +} + +void PluginList::GetPluginDirectories(std::vector<FilePath>* plugin_dirs) { + // We use a set for uniqueness, which we require, over order, which we do not. + std::set<FilePath> dirs; + + // Load from the application-specific area + GetAppDirectory(&dirs); + + // Load from the executable area + GetExeDirectory(&dirs); + + // Load Java + GetJavaDirectory(&dirs); + + // Load firefox plugins too. This is mainly to try to locate + // a pre-installed Flash player. + GetFirefoxDirectory(&dirs); + + // Firefox hard-codes the paths of some popular plugins to ensure that + // the plugins are found. We are going to copy this as well. + GetAcrobatDirectory(&dirs); + GetQuicktimeDirectory(&dirs); + GetWindowsMediaDirectory(&dirs); + + for (std::set<FilePath>::iterator i = dirs.begin(); i != dirs.end(); ++i) + plugin_dirs->push_back(*i); +} + +void PluginList::LoadPluginsFromDir(const FilePath &path, + std::vector<WebPluginInfo>* plugins, + std::set<FilePath>* visited_plugins) { + WIN32_FIND_DATA find_file_data; + HANDLE find_handle; + + std::wstring dir = path.value(); + // FindFirstFile requires that you specify a wildcard for directories. + dir.append(L"\\NP*.DLL"); + + find_handle = FindFirstFile(dir.c_str(), &find_file_data); + if (find_handle == INVALID_HANDLE_VALUE) + return; + + do { + if (!(find_file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { + FilePath filename = path.Append(find_file_data.cFileName); + LoadPlugin(filename, plugins); + visited_plugins->insert(filename); + } + } while (FindNextFile(find_handle, &find_file_data) != 0); + + DCHECK(GetLastError() == ERROR_NO_MORE_FILES); + FindClose(find_handle); +} + +void PluginList::LoadPluginsFromRegistry( + std::vector<WebPluginInfo>* plugins, + std::set<FilePath>* visited_plugins) { + std::set<FilePath> plugin_dirs; + + GetPluginsInRegistryDirectory( + HKEY_CURRENT_USER, kRegistryMozillaPlugins, &plugin_dirs); + GetPluginsInRegistryDirectory( + HKEY_LOCAL_MACHINE, kRegistryMozillaPlugins, &plugin_dirs); + + for (std::set<FilePath>::iterator i = plugin_dirs.begin(); + i != plugin_dirs.end(); ++i) { + LoadPlugin(*i, plugins); + visited_plugins->insert(*i); + } +} + +// Returns true if the given plugins share at least one mime type. This is used +// to differentiate newer versions of a plugin vs two plugins which happen to +// have the same filename. +bool HaveSharedMimeType(const WebPluginInfo& plugin1, + const WebPluginInfo& plugin2) { + for (size_t i = 0; i < plugin1.mime_types.size(); ++i) { + for (size_t j = 0; j < plugin2.mime_types.size(); ++j) { + if (plugin1.mime_types[i].mime_type == plugin2.mime_types[j].mime_type) + return true; + } + } + + return false; +} + +// Compares Windows style version strings (i.e. 1,2,3,4). Returns true if b's +// version is newer than a's, or false if it's equal or older. +bool IsNewerVersion(const std::wstring& a, const std::wstring& b) { + std::vector<std::wstring> a_ver, b_ver; + base::SplitString(a, ',', &a_ver); + base::SplitString(b, ',', &b_ver); + if (a_ver.size() == 1 && b_ver.size() == 1) { + a_ver.clear(); + b_ver.clear(); + base::SplitString(a, '.', &a_ver); + base::SplitString(b, '.', &b_ver); + } + if (a_ver.size() != b_ver.size()) + return false; + for (size_t i = 0; i < a_ver.size(); i++) { + int cur_a, cur_b; + base::StringToInt(a_ver[i], &cur_a); + base::StringToInt(b_ver[i], &cur_b); + + if (cur_a > cur_b) + return false; + if (cur_a < cur_b) + return true; + } + return false; +} + +bool PluginList::ShouldLoadPlugin(const WebPluginInfo& info, + std::vector<WebPluginInfo>* plugins) { + // Version check + + for (size_t i = 0; i < plugins->size(); ++i) { + std::wstring plugin1 = + StringToLowerASCII((*plugins)[i].path.BaseName().ToWStringHack()); + std::wstring plugin2 = + StringToLowerASCII(info.path.BaseName().ToWStringHack()); + if ((plugin1 == plugin2 && HaveSharedMimeType((*plugins)[i], info)) || + (plugin1 == kJavaDeploy1 && plugin2 == kJavaDeploy2) || + (plugin1 == kJavaDeploy2 && plugin2 == kJavaDeploy1)) { + if (!IsNewerVersion((*plugins)[i].version, info.version)) + return false; // We have loaded a plugin whose version is newer. + + plugins->erase(plugins->begin() + i); + break; + } + } + + // Troublemakers + + std::wstring filename = StringToLowerASCII(info.path.BaseName().value()); + // Depends on XPCOM. + if (filename == kMozillaActiveXPlugin) + return false; + + // Disable the Yahoo Application State plugin as it crashes the plugin + // process on return from NPObjectStub::OnInvoke. Please refer to + // http://b/issue?id=1372124 for more information. + if (filename == kYahooApplicationStatePlugin) + return false; + + // Disable the WangWang protocol handler plugin (npww.dll) as it crashes + // chrome during shutdown. Firefox also disables this plugin. + // Please refer to http://code.google.com/p/chromium/issues/detail?id=3953 + // for more information. + if (filename == kWanWangProtocolHandlerPlugin) + return false; + + // We only work with newer versions of the Java plugin which use NPAPI only + // and don't depend on XPCOM. + if (filename == kJavaPlugin1 || filename == kJavaPlugin2) { + std::vector<std::wstring> ver; + base::SplitString(info.version, '.', &ver); + int major, minor, update; + if (ver.size() == 4 && + base::StringToInt(ver[0], &major) && + base::StringToInt(ver[1], &minor) && + base::StringToInt(ver[2], &update)) { + if (major == 6 && minor == 0 && update < 120) + return false; // Java SE6 Update 11 or older. + } + } + + // Special WMP handling + + // If both the new and old WMP plugins exist, only load the new one. + if (filename == kNewWMPPlugin) { + if (dont_load_new_wmp_) + return false; + + for (size_t i = 0; i < plugins->size(); ++i) { + if ((*plugins)[i].path.BaseName().value() == kOldWMPPlugin) { + plugins->erase(plugins->begin() + i); + break; + } + } + } else if (filename == kOldWMPPlugin) { + for (size_t i = 0; i < plugins->size(); ++i) { + if ((*plugins)[i].path.BaseName().value() == kNewWMPPlugin) + return false; + } + } + + return true; +} + +} // namespace NPAPI diff --git a/webkit/glue/plugins/plugin_stream.cc b/webkit/glue/plugins/plugin_stream.cc new file mode 100644 index 0000000..e465e2d --- /dev/null +++ b/webkit/glue/plugins/plugin_stream.cc @@ -0,0 +1,258 @@ +// 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. + +// TODO : Support NP_ASFILEONLY mode +// TODO : Support NP_SEEK mode +// TODO : Support SEEKABLE=true in NewStream + +#include "webkit/glue/plugins/plugin_stream.h" + +#include "base/message_loop.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "net/base/mime_util.h" +#include "webkit/glue/plugins/plugin_instance.h" +#include "googleurl/src/gurl.h" + +namespace NPAPI { + +PluginStream::~PluginStream() { + // always close our temporary files. + CloseTempFile(); + free(const_cast<char*>(stream_.url)); +} + +bool PluginStream::Open(const std::string &mime_type, + const std::string &headers, + uint32 length, + uint32 last_modified, + bool request_is_seekable) { + headers_ = headers; + NPP id = instance_->npp(); + stream_.end = length; + stream_.lastmodified = last_modified; + stream_.pdata = 0; + stream_.ndata = id->ndata; + stream_.notifyData = notify_data_; + if (!headers_.empty()) + stream_.headers = headers_.c_str(); + + bool seekable_stream = false; + if (request_is_seekable) { + std::string headers_lc = StringToLowerASCII(headers); + if (headers_lc.find("accept-ranges: bytes") != std::string::npos) { + seekable_stream = true; + } + } + + const char *char_mime_type = "application/x-unknown-content-type"; + std::string temp_mime_type; + if (!mime_type.empty()) { + char_mime_type = mime_type.c_str(); + } else { + GURL gurl(stream_.url); + +#if defined(OS_WIN) + FilePath path(UTF8ToWide(gurl.path())); +#elif defined(OS_POSIX) + FilePath path(gurl.path()); +#endif + if (net::GetMimeTypeFromFile(path, &temp_mime_type)) + char_mime_type = temp_mime_type.c_str(); + } + + // Silverlight expects a valid mime type + DCHECK(strlen(char_mime_type) != 0); + NPError err = instance_->NPP_NewStream((NPMIMEType)char_mime_type, + &stream_, seekable_stream, + &requested_plugin_mode_); + if (err != NPERR_NO_ERROR) { + Notify(err); + return false; + } + + opened_ = true; + + if (requested_plugin_mode_ == NP_SEEK) { + seekable_stream_ = true; + } + // If the plugin has requested certain modes, then we need a copy + // of this file on disk. Open it and save it as we go. + if (requested_plugin_mode_ == NP_ASFILEONLY || + requested_plugin_mode_ == NP_ASFILE) { + if (OpenTempFile() == false) + return false; + } + + mime_type_ = char_mime_type; + return true; +} + +int PluginStream::Write(const char *buffer, const int length, + int data_offset) { + // There may be two streams to write to - the plugin and the file. + // It is unclear what to do if we cannot write to both. The rules of + // this function are that the plugin must consume at least as many + // bytes as returned by the WriteReady call. So, we will attempt to + // write that many to both streams. If we can't write that many bytes + // to each stream, we'll return failure. + + DCHECK(opened_); + if (WriteToFile(buffer, length) && + WriteToPlugin(buffer, length, data_offset)) + return length; + + return -1; +} + +bool PluginStream::WriteToFile(const char *buf, size_t length) { + // For ASFILEONLY, ASFILE, and SEEK modes, we need to write + // to the disk + if (TempFileIsValid() && + (requested_plugin_mode_ == NP_ASFILE || + requested_plugin_mode_ == NP_ASFILEONLY) ) { + size_t totalBytesWritten = 0, bytes; + do { + bytes = WriteBytes(buf, length); + totalBytesWritten += bytes; + } while (bytes > 0U && totalBytesWritten < length); + + if (totalBytesWritten != length) + return false; + } + + return true; +} + +bool PluginStream::WriteToPlugin(const char *buf, const int length, + const int data_offset) { + // For NORMAL and ASFILE modes, we send the data to the plugin now + if (requested_plugin_mode_ != NP_NORMAL && + requested_plugin_mode_ != NP_ASFILE && + requested_plugin_mode_ != NP_SEEK) + return true; + + int written = TryWriteToPlugin(buf, length, data_offset); + if (written == -1) + return false; + + if (written < length) { + // Buffer the remaining data. + size_t remaining = length - written; + size_t previous_size = delivery_data_.size(); + delivery_data_.resize(previous_size + remaining); + data_offset_ = data_offset; + memcpy(&delivery_data_[previous_size], buf + written, remaining); + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + this, &PluginStream::OnDelayDelivery)); + } + + return true; +} + +void PluginStream::OnDelayDelivery() { + // It is possible that the plugin stream may have closed before the task + // was hit. + if (!opened_) { + return; + } + + int size = static_cast<int>(delivery_data_.size()); + int written = TryWriteToPlugin(&delivery_data_.front(), size, + data_offset_); + if (written > 0) { + // Remove the data that we already wrote. + delivery_data_.erase(delivery_data_.begin(), + delivery_data_.begin() + written); + } +} + +int PluginStream::TryWriteToPlugin(const char *buf, const int length, + const int data_offset) { + int byte_offset = 0; + + if (data_offset > 0) + data_offset_ = data_offset; + + while (byte_offset < length) { + int bytes_remaining = length - byte_offset; + int bytes_to_write = instance_->NPP_WriteReady(&stream_); + if (bytes_to_write > bytes_remaining) + bytes_to_write = bytes_remaining; + + if (bytes_to_write == 0) + return byte_offset; + + int bytes_consumed = instance_->NPP_Write( + &stream_, data_offset_, bytes_to_write, + const_cast<char*>(buf + byte_offset)); + if (bytes_consumed < 0) { + // The plugin failed, which means that we need to close the stream. + Close(NPRES_NETWORK_ERR); + return -1; + } + if (bytes_consumed == 0) { + // The plugin couldn't take all of the data now. + return byte_offset; + } + + // The plugin might report more that we gave it. + bytes_consumed = std::min(bytes_consumed, bytes_to_write); + + data_offset_ += bytes_consumed; + byte_offset += bytes_consumed; + } + + if (close_on_write_data_) + Close(NPRES_DONE); + + return length; +} + +bool PluginStream::Close(NPReason reason) { + if (opened_ == true) { + opened_ = false; + + if (delivery_data_.size()) { + if (reason == NPRES_DONE) { + // There is more data to be streamed, don't destroy the stream now. + close_on_write_data_ = true; + return true; + } else { + // Stop any pending data from being streamed + delivery_data_.resize(0); + } + } + + // If we have a temp file, be sure to close it. + // Also, allow the plugin to access it now. + if (TempFileIsValid()) { + CloseTempFile(); + if (reason == NPRES_DONE) + WriteAsFile(); + } + + if (stream_.ndata != NULL) { + // Stream hasn't been closed yet. + NPError err = instance_->NPP_DestroyStream(&stream_, reason); + DCHECK(err == NPERR_NO_ERROR); + } + } + + Notify(reason); + return true; +} + +webkit_glue::WebPluginResourceClient* PluginStream::AsResourceClient() { + return NULL; +} + +void PluginStream::Notify(NPReason reason) { + if (notify_needed_) { + instance_->NPP_URLNotify(stream_.url, reason, notify_data_); + notify_needed_ = false; + } +} + +} // namespace NPAPI diff --git a/webkit/glue/plugins/plugin_stream.h b/webkit/glue/plugins/plugin_stream.h new file mode 100644 index 0000000..c5975b4 --- /dev/null +++ b/webkit/glue/plugins/plugin_stream.h @@ -0,0 +1,156 @@ +// 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. + +#ifndef WEBKIT_GLUE_PLUGINS_PLUGIN_STREAM_H_ +#define WEBKIT_GLUE_PLUGINS_PLUGIN_STREAM_H_ + +#include "build/build_config.h" + +#include <string> +#include <vector> + +#if defined(OS_POSIX) +#include "base/file_path.h" +#endif +#include "base/ref_counted.h" +#include "third_party/npapi/bindings/npapi.h" + +namespace webkit_glue { +class WebPluginResourceClient; +} + +namespace NPAPI { + +class PluginInstance; + +// Base class for a NPAPI stream. Tracks basic elements +// of a stream for NPAPI notifications and stream position. +class PluginStream : public base::RefCounted<PluginStream> { + public: + // Create a new PluginStream object. If needNotify is true, then the + // plugin will be notified when the stream has been fully sent. + PluginStream(PluginInstance *instance, + const char *url, + bool need_notify, + void *notify_data); + + // In case of a redirect, this can be called to update the url. But it must + // be called before Open(). + void UpdateUrl(const char* url); + + // Opens the stream to the Plugin. + // If the mime-type is not specified, we'll try to find one based on the + // mime-types table and the extension (if any) in the URL. + // If the size of the stream is known, use length to set the size. If + // not known, set length to 0. + // The request_is_seekable parameter indicates whether byte range requests + // can be issued on the stream. + bool Open(const std::string &mime_type, + const std::string &headers, + uint32 length, + uint32 last_modified, + bool request_is_seekable); + + // Writes to the stream. + int Write(const char *buf, const int len, int data_offset); + + // Write the result as a file. + void WriteAsFile(); + + // Notify the plugin that a stream is complete. + void Notify(NPReason reason); + + // Close the stream. + virtual bool Close(NPReason reason); + + virtual webkit_glue::WebPluginResourceClient* AsResourceClient(); + + // Cancels any HTTP requests initiated by the stream. + virtual void CancelRequest() {} + + const NPStream* stream() const { return &stream_; } + + // setter/getter for the seekable attribute on the stream. + bool seekable() const { return seekable_stream_; } + + void set_seekable(bool seekable) { seekable_stream_ = seekable; } + + // getters for reading the notification related attributes on the stream. + bool notify_needed() const { return notify_needed_; } + + void* notify_data() const { return notify_data_; } + + std::string pending_redirect_url() const { return pending_redirect_url_; } + + protected: + friend class base::RefCounted<PluginStream>; + + virtual ~PluginStream(); + + PluginInstance* instance() { return instance_.get(); } + // Check if the stream is open. + bool open() { return opened_; } + + // If the plugin participates in HTTP URL redirect handling then this member + // holds the url being redirected to while we wait for the plugin to make a + // decision on whether to allow or deny the redirect. + std::string pending_redirect_url_; + + private: + + // Open a temporary file for this stream. + // If successful, will set temp_file_name_, temp_file_handle_, and + // return true. + bool OpenTempFile(); + + // Closes the temporary file if it is open. + void CloseTempFile(); + + // Sends the data to the file. Called From WriteToFile. + size_t WriteBytes(const char *buf, size_t length); + + // Sends the data to the file if it's open. + bool WriteToFile(const char *buf, size_t length); + + // Sends the data to the plugin. If it's not ready, handles buffering it + // and retrying later. + bool WriteToPlugin(const char *buf, const int length, const int data_offset); + + // Send the data to the plugin, returning how many bytes it accepted, or -1 + // if an error occurred. + int TryWriteToPlugin(const char *buf, const int length, + const int data_offset); + + // The callback which calls TryWriteToPlugin. + void OnDelayDelivery(); + + // Returns true if the temp file is valid and open for writing. + bool TempFileIsValid(); + + private: + NPStream stream_; + std::string headers_; + scoped_refptr<PluginInstance> instance_; + bool notify_needed_; + void * notify_data_; + bool close_on_write_data_; + uint16 requested_plugin_mode_; + bool opened_; +#if defined(OS_WIN) + char temp_file_name_[MAX_PATH]; + HANDLE temp_file_handle_; +#elif defined(OS_POSIX) + FILE* temp_file_; + FilePath temp_file_path_; +#endif + std::vector<char> delivery_data_; + int data_offset_; + bool seekable_stream_; + std::string mime_type_; + DISALLOW_COPY_AND_ASSIGN(PluginStream); +}; + +} // namespace NPAPI + +#endif // WEBKIT_GLUE_PLUGINS_PLUGIN_STREAM_H_ diff --git a/webkit/glue/plugins/plugin_stream_posix.cc b/webkit/glue/plugins/plugin_stream_posix.cc new file mode 100644 index 0000000..d0e2291 --- /dev/null +++ b/webkit/glue/plugins/plugin_stream_posix.cc @@ -0,0 +1,74 @@ +// 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. + +#include "webkit/glue/plugins/plugin_stream.h" + +#include <string.h> + +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "webkit/glue/plugins/plugin_instance.h" + +namespace NPAPI { + +PluginStream::PluginStream( + PluginInstance *instance, + const char *url, + bool need_notify, + void *notify_data) + : instance_(instance), + notify_needed_(need_notify), + notify_data_(notify_data), + close_on_write_data_(false), + requested_plugin_mode_(NP_NORMAL), + opened_(false), + temp_file_(NULL), + temp_file_path_(), + data_offset_(0), + seekable_stream_(false) { + memset(&stream_, 0, sizeof(stream_)); + stream_.url = strdup(url); +} + +void PluginStream::UpdateUrl(const char* url) { + DCHECK(!opened_); + free(const_cast<char*>(stream_.url)); + stream_.url = strdup(url); +} + +void PluginStream::WriteAsFile() { + if (requested_plugin_mode_ == NP_ASFILE || + requested_plugin_mode_ == NP_ASFILEONLY) + instance_->NPP_StreamAsFile(&stream_, temp_file_path_.value().c_str()); +} + +size_t PluginStream::WriteBytes(const char *buf, size_t length) { + return fwrite(buf, sizeof(char), length, temp_file_); +} + +bool PluginStream::OpenTempFile() { + DCHECK(temp_file_ == NULL); + + if (file_util::CreateTemporaryFile(&temp_file_path_)) + temp_file_ = file_util::OpenFile(temp_file_path_, "a"); + + if (!temp_file_) { + temp_file_path_ = FilePath(""); + return false; + } + + return true; +} + +void PluginStream::CloseTempFile() { + file_util::CloseFile(temp_file_); + temp_file_ = NULL; +} + +bool PluginStream::TempFileIsValid() { + return temp_file_ != NULL; +} + +} // namespace NPAPI diff --git a/webkit/glue/plugins/plugin_stream_url.cc b/webkit/glue/plugins/plugin_stream_url.cc new file mode 100644 index 0000000..7f9f355 --- /dev/null +++ b/webkit/glue/plugins/plugin_stream_url.cc @@ -0,0 +1,130 @@ +// 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. + +#include "webkit/glue/plugins/plugin_stream_url.h" + +#include "net/http/http_response_headers.h" +#include "webkit/glue/plugins/plugin_host.h" +#include "webkit/glue/plugins/plugin_instance.h" +#include "webkit/glue/plugins/plugin_lib.h" +#include "webkit/glue/plugins/webplugin.h" + +namespace NPAPI { + +PluginStreamUrl::PluginStreamUrl( + unsigned long resource_id, + const GURL &url, + PluginInstance *instance, + bool notify_needed, + void *notify_data) + : PluginStream(instance, url.spec().c_str(), notify_needed, notify_data), + url_(url), + id_(resource_id) { +} + +PluginStreamUrl::~PluginStreamUrl() { + if (instance() && instance()->webplugin()) { + instance()->webplugin()->ResourceClientDeleted(AsResourceClient()); + } +} + +bool PluginStreamUrl::Close(NPReason reason) { + // Protect the stream against it being destroyed or the whole plugin instance + // being destroyed within the destroy stream handler. + scoped_refptr<PluginStream> protect(this); + CancelRequest(); + bool result = PluginStream::Close(reason); + instance()->RemoveStream(this); + return result; +} + +webkit_glue::WebPluginResourceClient* PluginStreamUrl::AsResourceClient() { + return static_cast<webkit_glue::WebPluginResourceClient*>(this); +} + +void PluginStreamUrl::WillSendRequest(const GURL& url, int http_status_code) { + if (notify_needed()) { + // If the plugin participates in HTTP url redirect handling then notify it. + if (net::HttpResponseHeaders::IsRedirectResponseCode(http_status_code) && + instance()->handles_url_redirects()) { + pending_redirect_url_ = url.spec(); + instance()->NPP_URLRedirectNotify(url.spec().c_str(), http_status_code, + notify_data()); + return; + } + } + url_ = url; + UpdateUrl(url.spec().c_str()); +} + +void PluginStreamUrl::DidReceiveResponse(const std::string& mime_type, + const std::string& headers, + uint32 expected_length, + uint32 last_modified, + bool request_is_seekable) { + // Protect the stream against it being destroyed or the whole plugin instance + // being destroyed within the new stream handler. + scoped_refptr<PluginStream> protect(this); + + bool opened = Open(mime_type, + headers, + expected_length, + last_modified, + request_is_seekable); + if (!opened) { + CancelRequest(); + instance()->RemoveStream(this); + } else { + if (id_ > 0) + instance()->webplugin()->SetDeferResourceLoading(id_, false); + } +} + +void PluginStreamUrl::DidReceiveData(const char* buffer, int length, + int data_offset) { + if (!open()) + return; + + // Protect the stream against it being destroyed or the whole plugin instance + // being destroyed within the write handlers + scoped_refptr<PluginStream> protect(this); + + if (length > 0) { + // The PluginStreamUrl instance could get deleted if the plugin fails to + // accept data in NPP_Write. + if (Write(const_cast<char*>(buffer), length, data_offset) > 0) { + if (id_ > 0) + instance()->webplugin()->SetDeferResourceLoading(id_, false); + } + } +} + +void PluginStreamUrl::DidFinishLoading() { + if (!seekable()) { + Close(NPRES_DONE); + } +} + +void PluginStreamUrl::DidFail() { + Close(NPRES_NETWORK_ERR); +} + +bool PluginStreamUrl::IsMultiByteResponseExpected() { + return seekable(); +} + +int PluginStreamUrl::ResourceId() { + return id_; +} + +void PluginStreamUrl::CancelRequest() { + if (id_ > 0) { + if (instance()->webplugin()) { + instance()->webplugin()->CancelResource(id_); + } + id_ = 0; + } +} + +} // namespace NPAPI diff --git a/webkit/glue/plugins/plugin_stream_url.h b/webkit/glue/plugins/plugin_stream_url.h new file mode 100644 index 0000000..8642897 --- /dev/null +++ b/webkit/glue/plugins/plugin_stream_url.h @@ -0,0 +1,65 @@ +// 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. + +#ifndef WEBKIT_GLUE_PLUGIN_PLUGIN_STREAM_URL_H__ +#define WEBKIT_GLUE_PLUGIN_PLUGIN_STREAM_URL_H__ + + +#include "webkit/glue/plugins/plugin_stream.h" +#include "webkit/glue/plugins/webplugin.h" +#include "googleurl/src/gurl.h" + +namespace NPAPI { + +class PluginInstance; + +// A NPAPI Stream based on a URL. +class PluginStreamUrl : public PluginStream, + public webkit_glue::WebPluginResourceClient { + public: + // Create a new stream for sending to the plugin by fetching + // a URL. If notifyNeeded is set, then the plugin will be notified + // when the stream has been fully sent to the plugin. Initialize + // must be called before the object is used. + PluginStreamUrl(unsigned long resource_id, + const GURL &url, + PluginInstance *instance, + bool notify_needed, + void *notify_data); + virtual ~PluginStreamUrl(); + + // Stop sending the stream to the client. + // Overrides the base Close so we can cancel our fetching the URL if + // it is still loading. + virtual bool Close(NPReason reason); + + virtual webkit_glue::WebPluginResourceClient* AsResourceClient(); + + virtual void CancelRequest(); + + // + // WebPluginResourceClient methods + // + virtual void WillSendRequest(const GURL& url, int http_status_code); + virtual void DidReceiveResponse(const std::string& mime_type, + const std::string& headers, + uint32 expected_length, + uint32 last_modified, + bool request_is_seekable); + virtual void DidReceiveData(const char* buffer, int length, int data_offset); + virtual void DidFinishLoading(); + virtual void DidFail(); + virtual bool IsMultiByteResponseExpected(); + virtual int ResourceId(); + + private: + GURL url_; + unsigned long id_; + + DISALLOW_COPY_AND_ASSIGN(PluginStreamUrl); +}; + +} // namespace NPAPI + +#endif // WEBKIT_GLUE_PLUGIN_PLUGIN_STREAM_URL_H__ diff --git a/webkit/glue/plugins/plugin_stream_win.cc b/webkit/glue/plugins/plugin_stream_win.cc new file mode 100644 index 0000000..0b6fcbd --- /dev/null +++ b/webkit/glue/plugins/plugin_stream_win.cc @@ -0,0 +1,97 @@ +// 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. + +#include "webkit/glue/plugins/plugin_stream.h" + +#include "base/logging.h" +#include "webkit/glue/plugins/plugin_instance.h" + +namespace NPAPI { + +PluginStream::PluginStream( + PluginInstance *instance, + const char *url, + bool need_notify, + void *notify_data) + : instance_(instance), + notify_needed_(need_notify), + notify_data_(notify_data), + close_on_write_data_(false), + opened_(false), + requested_plugin_mode_(NP_NORMAL), + temp_file_handle_(INVALID_HANDLE_VALUE), + seekable_stream_(false), + data_offset_(0) { + memset(&stream_, 0, sizeof(stream_)); + stream_.url = _strdup(url); + temp_file_name_[0] = '\0'; +} + +void PluginStream::UpdateUrl(const char* url) { + DCHECK(!opened_); + free(const_cast<char*>(stream_.url)); + stream_.url = _strdup(url); + pending_redirect_url_.clear(); +} + +void PluginStream::WriteAsFile() { + if (requested_plugin_mode_ == NP_ASFILE || + requested_plugin_mode_ == NP_ASFILEONLY) + instance_->NPP_StreamAsFile(&stream_, temp_file_name_); +} + +size_t PluginStream::WriteBytes(const char *buf, size_t length) { + DWORD bytes; + + if (!WriteFile(temp_file_handle_, buf, length, &bytes, 0)) + return 0U; + + return static_cast<size_t>(bytes); +} + +bool PluginStream::OpenTempFile() { + DCHECK(temp_file_handle_ == INVALID_HANDLE_VALUE); + + // The reason for using all the Ascii versions of these filesystem + // calls is that the filename which we pass back to the plugin + // via NPAPI is an ascii filename. Otherwise, we'd use wide-chars. + // + // TODO: + // This is a bug in NPAPI itself, and it needs to be fixed. + // The case which will fail is if a user has a multibyte name, + // but has the system locale set to english. GetTempPathA will + // return junk in this case, causing us to be unable to open the + // file. + + char temp_directory[MAX_PATH]; + if (GetTempPathA(MAX_PATH, temp_directory) == 0) + return false; + if (GetTempFileNameA(temp_directory, "npstream", 0, temp_file_name_) == 0) + return false; + temp_file_handle_ = CreateFileA(temp_file_name_, + FILE_ALL_ACCESS, + FILE_SHARE_READ, + 0, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + 0); + if (temp_file_handle_ == INVALID_HANDLE_VALUE) { + temp_file_name_[0] = '\0'; + return false; + } + return true; +} + +void PluginStream::CloseTempFile() { + if (temp_file_handle_ != INVALID_HANDLE_VALUE) { + CloseHandle(temp_file_handle_); + temp_file_handle_ = INVALID_HANDLE_VALUE; + } +} + +bool PluginStream::TempFileIsValid() { + return temp_file_handle_ != INVALID_HANDLE_VALUE; +} + +} // namespace NPAPI diff --git a/webkit/glue/plugins/plugin_string_stream.cc b/webkit/glue/plugins/plugin_string_stream.cc new file mode 100644 index 0000000..f174267 --- /dev/null +++ b/webkit/glue/plugins/plugin_string_stream.cc @@ -0,0 +1,37 @@ +// 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. + +#include "webkit/glue/plugins/plugin_string_stream.h" + +#include "googleurl/src/gurl.h" + +namespace NPAPI { + +PluginStringStream::PluginStringStream( + PluginInstance* instance, + const GURL& url, + bool notify_needed, + void* notify_data) + : PluginStream(instance, url.spec().c_str(), notify_needed, notify_data) { +} + +PluginStringStream::~PluginStringStream() { +} + +void PluginStringStream::SendToPlugin(const std::string &data, + const std::string &mime_type) { + // Protect the stream against it being destroyed or the whole plugin instance + // being destroyed within the plugin stream callbacks. + scoped_refptr<PluginStringStream> protect(this); + + int length = static_cast<int>(data.length()); + if (Open(mime_type, std::string(), length, 0, false)) { + // TODO - check if it was not fully sent, and figure out a backup plan. + int written = Write(data.c_str(), length, 0); + NPReason reason = written == length ? NPRES_DONE : NPRES_NETWORK_ERR; + Close(reason); + } +} + +} diff --git a/webkit/glue/plugins/plugin_string_stream.h b/webkit/glue/plugins/plugin_string_stream.h new file mode 100644 index 0000000..68db2bf --- /dev/null +++ b/webkit/glue/plugins/plugin_string_stream.h @@ -0,0 +1,39 @@ +// 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. + +#ifndef WEBKIT_GLUE_PLUGIN_PLUGIN_STRING_STREAM_H_ +#define WEBKIT_GLUE_PLUGIN_PLUGIN_STRING_STREAM_H_ + +#include "webkit/glue/plugins/plugin_stream.h" + +class GURL; + +namespace NPAPI { + +class PluginInstance; + +// An NPAPI stream from a string. +class PluginStringStream : public PluginStream { + public: + // Create a new stream for sending to the plugin. + // If notify_needed, will notify the plugin after the data has + // all been sent. + PluginStringStream(PluginInstance* instance, + const GURL& url, + bool notify_needed, + void* notify_data); + + // Initiates the sending of data to the plugin. + void SendToPlugin(const std::string& data, + const std::string& mime_type); + + private: + virtual ~PluginStringStream(); + + DISALLOW_COPY_AND_ASSIGN(PluginStringStream); +}; + +} // namespace NPAPI + +#endif // WEBKIT_GLUE_PLUGIN_PLUGIN_STRING_STREAM_H_ diff --git a/webkit/glue/plugins/plugin_stubs.cc b/webkit/glue/plugins/plugin_stubs.cc new file mode 100644 index 0000000..f8210c30 --- /dev/null +++ b/webkit/glue/plugins/plugin_stubs.cc @@ -0,0 +1,30 @@ +// 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. + +// This file stubs out some functions needed to make the linker happy +// without linking in all the plugin code. It should be removed once +// we have plugins working on all platforms. + +// TODO(port): remove this file. + +#include "base/logging.h" +#include "webkit/glue/plugins/plugin_instance.h" +#include "webkit/glue/plugins/plugin_stream.h" + +namespace NPAPI { + +PluginStream::~PluginStream() { + NOTIMPLEMENTED(); +} + +bool PluginStream::Close(NPReason reason) { + NOTIMPLEMENTED(); + return false; +} + +void PluginInstance::NPP_StreamAsFile(NPStream*, const char*) { + NOTIMPLEMENTED(); +} + +} // namespace NPAPI diff --git a/webkit/glue/plugins/plugin_web_event_converter_mac.h b/webkit/glue/plugins/plugin_web_event_converter_mac.h new file mode 100644 index 0000000..ec5b86f --- /dev/null +++ b/webkit/glue/plugins/plugin_web_event_converter_mac.h @@ -0,0 +1,60 @@ +// 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. + +#ifndef WEBKIT_GLUE_PLUGIN_PLUGIN_WEB_EVENT_CONVERTER_MAC_H_ +#define WEBKIT_GLUE_PLUGIN_PLUGIN_WEB_EVENT_CONVERTER_MAC_H_ + +#include "third_party/npapi/bindings/npapi.h" + +namespace WebKit { +class WebInputEvent; +class WebKeyboardEvent; +class WebMouseEvent; +class WebMouseWheelEvent; +} + +// Utility class to translating WebInputEvent structs to equivalent structures +// suitable for sending to Mac plugins (via NPP_HandleEvent). +class PluginWebEventConverter { + public: + PluginWebEventConverter() {} + virtual ~PluginWebEventConverter() {} + + // Initializes a converter for the given web event. Returns false if the event + // could not be converted. + virtual bool InitWithEvent(const WebKit::WebInputEvent& web_event); + + // Returns a pointer to a plugin event--suitable for passing to + // NPP_HandleEvent--corresponding to the the web event this converter was + // created with. The pointer is valid only as long as this object is. + // Returns NULL iff InitWithEvent returned false. + virtual void* plugin_event() = 0; + +protected: + // To be overridden by subclasses to store a converted plugin representation + // of the given web event, suitable for returning from plugin_event. + // Returns true if the event was successfully converted. + virtual bool ConvertKeyboardEvent( + const WebKit::WebKeyboardEvent& web_event) = 0; + virtual bool ConvertMouseEvent(const WebKit::WebMouseEvent& web_event) = 0; + virtual bool ConvertMouseWheelEvent( + const WebKit::WebMouseWheelEvent& web_event) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(PluginWebEventConverter); +}; + +// Factory for generating PluginWebEventConverter objects by event model. +class PluginWebEventConverterFactory { + public: + // Returns a new PluginWebEventConverter corresponding to the given plugin + // event model. + static PluginWebEventConverter* + CreateConverterForModel(NPEventModel event_model); + + private: + DISALLOW_COPY_AND_ASSIGN(PluginWebEventConverterFactory); +}; + +#endif // WEBKIT_GLUE_PLUGIN_PLUGIN_WEB_EVENT_CONVERTER_MAC_H_ diff --git a/webkit/glue/plugins/plugin_web_event_converter_mac.mm b/webkit/glue/plugins/plugin_web_event_converter_mac.mm new file mode 100644 index 0000000..12d5cc6 --- /dev/null +++ b/webkit/glue/plugins/plugin_web_event_converter_mac.mm @@ -0,0 +1,359 @@ +// 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> + +#include "base/logging.h" +#include "webkit/glue/plugins/plugin_web_event_converter_mac.h" +#include "third_party/WebKit/WebKit/chromium/public/WebInputEvent.h" + +using WebKit::WebInputEvent; +using WebKit::WebKeyboardEvent; +using WebKit::WebMouseEvent; +using WebKit::WebMouseWheelEvent; + +namespace { + +// Returns true if the caps lock flag should be set for the given event. +bool CapsLockIsActive(const WebInputEvent& event) { + // Only key events have accurate information for the caps lock flag; see + // <https://bugs.webkit.org/show_bug.cgi?id=46518>. + // For other types, use the live state. + if (WebInputEvent::isKeyboardEventType(event.type)) + return (event.modifiers & WebInputEvent::CapsLockOn) != 0; + else + return ([[NSApp currentEvent] modifierFlags] & NSAlphaShiftKeyMask) != 0; +} + +} // namespace + +#pragma mark - + +#ifndef NP_NO_CARBON + +// Converter implementation for the Carbon event model. +class CarbonPluginWebEventConverter : public PluginWebEventConverter { + public: + CarbonPluginWebEventConverter() {} + virtual ~CarbonPluginWebEventConverter() {} + + virtual bool InitWithEvent(const WebInputEvent& web_event); + + virtual void* plugin_event() { return &carbon_event_; } + + protected: + virtual bool ConvertKeyboardEvent(const WebKeyboardEvent& key_event); + virtual bool ConvertMouseEvent(const WebMouseEvent& mouse_event); + virtual bool ConvertMouseWheelEvent(const WebMouseWheelEvent& wheel_event); + + private: + // Returns the Carbon translation of web_event's modifiers. + static EventModifiers CarbonModifiers(const WebInputEvent& web_event); + + NPEvent carbon_event_; + + DISALLOW_COPY_AND_ASSIGN(CarbonPluginWebEventConverter); +}; + +bool CarbonPluginWebEventConverter::InitWithEvent( + const WebInputEvent& web_event) { + memset(&carbon_event_, 0, sizeof(carbon_event_)); + // Set the fields common to all event types. + carbon_event_.when = TickCount(); + carbon_event_.modifiers |= CarbonModifiers(web_event); + + return PluginWebEventConverter::InitWithEvent(web_event); +} + +bool CarbonPluginWebEventConverter::ConvertKeyboardEvent( + const WebKeyboardEvent& key_event) { + // TODO: Figure out how to handle Unicode input to plugins, if that's + // even possible in the NPAPI Carbon event model. + carbon_event_.message = (key_event.nativeKeyCode << 8) & keyCodeMask; + carbon_event_.message |= key_event.text[0] & charCodeMask; + carbon_event_.modifiers |= btnState; + + switch (key_event.type) { + case WebInputEvent::KeyDown: + if (key_event.modifiers & WebInputEvent::IsAutoRepeat) + carbon_event_.what = autoKey; + else + carbon_event_.what = keyDown; + return true; + case WebInputEvent::KeyUp: + carbon_event_.what = keyUp; + return true; + case WebInputEvent::RawKeyDown: + case WebInputEvent::Char: + // May be used eventually for IME, but currently not needed. + return false; + default: + NOTREACHED(); + return false; + } +} + +bool CarbonPluginWebEventConverter::ConvertMouseEvent( + const WebMouseEvent& mouse_event) { + carbon_event_.where.h = mouse_event.globalX; + carbon_event_.where.v = mouse_event.globalY; + + // Default to "button up"; override this for mouse down events below. + carbon_event_.modifiers |= btnState; + + switch (mouse_event.button) { + case WebMouseEvent::ButtonLeft: + break; + case WebMouseEvent::ButtonMiddle: + carbon_event_.modifiers |= cmdKey; + break; + case WebMouseEvent::ButtonRight: + carbon_event_.modifiers |= controlKey; + break; + default: + NOTIMPLEMENTED(); + } + switch (mouse_event.type) { + case WebInputEvent::MouseMove: + carbon_event_.what = nullEvent; + return true; + case WebInputEvent::MouseLeave: + case WebInputEvent::MouseEnter: + carbon_event_.what = NPEventType_AdjustCursorEvent; + return true; + case WebInputEvent::MouseDown: + carbon_event_.modifiers &= ~btnState; + carbon_event_.what = mouseDown; + return true; + case WebInputEvent::MouseUp: + carbon_event_.what = mouseUp; + return true; + default: + NOTREACHED(); + return false; + } +} + +bool CarbonPluginWebEventConverter::ConvertMouseWheelEvent( + const WebMouseWheelEvent& wheel_event) { + return false; // The Carbon NPAPI event model has no "mouse wheel" concept. +} + +EventModifiers CarbonPluginWebEventConverter::CarbonModifiers( + const WebInputEvent& web_event) { + NSInteger modifiers = 0; + if (web_event.modifiers & WebInputEvent::ControlKey) + modifiers |= controlKey; + if (web_event.modifiers & WebInputEvent::ShiftKey) + modifiers |= shiftKey; + if (web_event.modifiers & WebInputEvent::AltKey) + modifiers |= optionKey; + if (web_event.modifiers & WebInputEvent::MetaKey) + modifiers |= cmdKey; + if (CapsLockIsActive(web_event)) + modifiers |= alphaLock; + return modifiers; +} + +#endif // !NP_NO_CARBON + +#pragma mark - + +// Converter implementation for the Cocoa event model. +class CocoaPluginWebEventConverter : public PluginWebEventConverter { +public: + CocoaPluginWebEventConverter() {} + virtual ~CocoaPluginWebEventConverter() {} + + virtual bool InitWithEvent(const WebInputEvent& web_event); + + virtual void* plugin_event() { return &cocoa_event_; } + +protected: + virtual bool ConvertKeyboardEvent(const WebKeyboardEvent& key_event); + virtual bool ConvertMouseEvent(const WebMouseEvent& mouse_event); + virtual bool ConvertMouseWheelEvent(const WebMouseWheelEvent& wheel_event); + +private: + // Returns the Cocoa translation of web_event's modifiers. + static NSUInteger CocoaModifiers(const WebInputEvent& web_event); + + // Returns true if the given key is a modifier key. + static bool KeyIsModifier(int native_key_code); + + NPCocoaEvent cocoa_event_; + + DISALLOW_COPY_AND_ASSIGN(CocoaPluginWebEventConverter); +}; + +bool CocoaPluginWebEventConverter::InitWithEvent( + const WebInputEvent& web_event) { + memset(&cocoa_event_, 0, sizeof(cocoa_event_)); + return PluginWebEventConverter::InitWithEvent(web_event); +} + +bool CocoaPluginWebEventConverter::ConvertKeyboardEvent( + const WebKeyboardEvent& key_event) { + cocoa_event_.data.key.keyCode = key_event.nativeKeyCode; + + cocoa_event_.data.key.modifierFlags |= CocoaModifiers(key_event); + + // Modifier keys have their own event type, and don't get character or + // repeat data. + if (KeyIsModifier(key_event.nativeKeyCode)) { + cocoa_event_.type = NPCocoaEventFlagsChanged; + return true; + } + + cocoa_event_.data.key.characters = reinterpret_cast<NPNSString*>( + [NSString stringWithFormat:@"%S", key_event.text]); + cocoa_event_.data.key.charactersIgnoringModifiers = + reinterpret_cast<NPNSString*>( + [NSString stringWithFormat:@"%S", key_event.unmodifiedText]); + + if (key_event.modifiers & WebInputEvent::IsAutoRepeat) + cocoa_event_.data.key.isARepeat = true; + + switch (key_event.type) { + case WebInputEvent::KeyDown: + cocoa_event_.type = NPCocoaEventKeyDown; + return true; + case WebInputEvent::KeyUp: + cocoa_event_.type = NPCocoaEventKeyUp; + return true; + case WebInputEvent::RawKeyDown: + case WebInputEvent::Char: + // May be used eventually for IME, but currently not needed. + return false; + default: + NOTREACHED(); + return false; + } +} + +bool CocoaPluginWebEventConverter::ConvertMouseEvent( + const WebMouseEvent& mouse_event) { + cocoa_event_.data.mouse.pluginX = mouse_event.x; + cocoa_event_.data.mouse.pluginY = mouse_event.y; + cocoa_event_.data.mouse.modifierFlags |= CocoaModifiers(mouse_event); + cocoa_event_.data.mouse.clickCount = mouse_event.clickCount; + switch (mouse_event.button) { + case WebMouseEvent::ButtonLeft: + cocoa_event_.data.mouse.buttonNumber = 0; + break; + case WebMouseEvent::ButtonMiddle: + cocoa_event_.data.mouse.buttonNumber = 2; + break; + case WebMouseEvent::ButtonRight: + cocoa_event_.data.mouse.buttonNumber = 1; + break; + default: + cocoa_event_.data.mouse.buttonNumber = mouse_event.button; + break; + } + switch (mouse_event.type) { + case WebInputEvent::MouseDown: + cocoa_event_.type = NPCocoaEventMouseDown; + return true; + case WebInputEvent::MouseUp: + cocoa_event_.type = NPCocoaEventMouseUp; + return true; + case WebInputEvent::MouseMove: { + bool mouse_is_down = + (mouse_event.modifiers & WebInputEvent::LeftButtonDown) || + (mouse_event.modifiers & WebInputEvent::RightButtonDown) || + (mouse_event.modifiers & WebInputEvent::MiddleButtonDown); + cocoa_event_.type = mouse_is_down ? NPCocoaEventMouseDragged + : NPCocoaEventMouseMoved; + return true; + } + case WebInputEvent::MouseEnter: + cocoa_event_.type = NPCocoaEventMouseEntered; + return true; + case WebInputEvent::MouseLeave: + cocoa_event_.type = NPCocoaEventMouseExited; + return true; + default: + NOTREACHED(); + return false; + } +} + +bool CocoaPluginWebEventConverter::ConvertMouseWheelEvent( + const WebMouseWheelEvent& wheel_event) { + cocoa_event_.type = NPCocoaEventScrollWheel; + cocoa_event_.data.mouse.pluginX = wheel_event.x; + cocoa_event_.data.mouse.pluginY = wheel_event.y; + cocoa_event_.data.mouse.modifierFlags |= CocoaModifiers(wheel_event); + cocoa_event_.data.mouse.deltaX = wheel_event.deltaX; + cocoa_event_.data.mouse.deltaY = wheel_event.deltaY; + return true; +} + +NSUInteger CocoaPluginWebEventConverter::CocoaModifiers( + const WebInputEvent& web_event) { + NSInteger modifiers = 0; + if (web_event.modifiers & WebInputEvent::ControlKey) + modifiers |= NSControlKeyMask; + if (web_event.modifiers & WebInputEvent::ShiftKey) + modifiers |= NSShiftKeyMask; + if (web_event.modifiers & WebInputEvent::AltKey) + modifiers |= NSAlternateKeyMask; + if (web_event.modifiers & WebInputEvent::MetaKey) + modifiers |= NSCommandKeyMask; + if (CapsLockIsActive(web_event)) + modifiers |= NSAlphaShiftKeyMask; + return modifiers; +} + +bool CocoaPluginWebEventConverter::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; + } +} + +#pragma mark - + +bool PluginWebEventConverter::InitWithEvent(const WebInputEvent& web_event) { + if (web_event.type == WebInputEvent::MouseWheel) { + return ConvertMouseWheelEvent( + *static_cast<const WebMouseWheelEvent*>(&web_event)); + } else if (WebInputEvent::isMouseEventType(web_event.type)) { + return ConvertMouseEvent(*static_cast<const WebMouseEvent*>(&web_event)); + } else if (WebInputEvent::isKeyboardEventType(web_event.type)) { + return ConvertKeyboardEvent( + *static_cast<const WebKeyboardEvent*>(&web_event)); + } + DLOG(WARNING) << "Unknown event type " << web_event.type; + return false; +} + +#pragma mark - + +PluginWebEventConverter* + PluginWebEventConverterFactory::CreateConverterForModel( + NPEventModel event_model) { + switch (event_model) { + case NPEventModelCocoa: + return new CocoaPluginWebEventConverter(); +#ifndef NP_NO_CARBON + case NPEventModelCarbon: + return new CarbonPluginWebEventConverter(); +#endif + default: + NOTIMPLEMENTED(); + return NULL; + } +} diff --git a/webkit/glue/plugins/quickdraw_drawing_manager_mac.cc b/webkit/glue/plugins/quickdraw_drawing_manager_mac.cc new file mode 100644 index 0000000..424cc1e --- /dev/null +++ b/webkit/glue/plugins/quickdraw_drawing_manager_mac.cc @@ -0,0 +1,154 @@ +// 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. + +#ifndef NP_NO_QUICKDRAW + +#include "webkit/glue/plugins/quickdraw_drawing_manager_mac.h" + +#include "webkit/glue/plugins/coregraphics_private_symbols_mac.h" + +// 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. +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + +QuickDrawDrawingManager::QuickDrawDrawingManager() + : plugin_window_(NULL), target_context_(NULL), fast_path_enabled_(false), + current_port_(NULL), target_world_(NULL), plugin_world_(NULL) {} + +QuickDrawDrawingManager::~QuickDrawDrawingManager() { + DestroyGWorlds(); +} + +void QuickDrawDrawingManager::SetFastPathEnabled(bool enabled) { + if (fast_path_enabled_ == enabled) + return; + + fast_path_enabled_ = enabled; + if (enabled) { + if (!target_world_) + UpdateGWorlds(); + // Copy our last window snapshot into our new source, since the plugin + // may not repaint everything. + CopyGWorldBits(target_world_, plugin_world_, plugin_size_); + current_port_ = plugin_world_; + } else { + current_port_ = GetWindowPort(plugin_window_); + } +} + +void QuickDrawDrawingManager::SetTargetContext(CGContextRef context, + const gfx::Size& plugin_size) { + target_context_ = context; + if (plugin_size != plugin_size_) { + plugin_size_ = plugin_size; + // Pitch the old GWorlds, since they are the wrong size now. + DestroyGWorlds(); + if (fast_path_enabled_) + UpdateGWorlds(); + } +} + +void QuickDrawDrawingManager::SetPluginWindow(WindowRef window) { + plugin_window_ = window; + if (!fast_path_enabled_) + current_port_ = GetWindowPort(window); +} + +void QuickDrawDrawingManager::UpdateContext() { + if (fast_path_enabled_) + CopyGWorldBits(plugin_world_, target_world_, plugin_size_); + else + ScrapeWindow(plugin_window_, target_context_, plugin_size_); +} + +bool QuickDrawDrawingManager::IsFastPathEnabled() { + return fast_path_enabled_; +} + +void QuickDrawDrawingManager::MakePortCurrent() { + if (fast_path_enabled_) + SetGWorld(current_port_, NULL); + else + SetPort(current_port_); +} + +void QuickDrawDrawingManager::DestroyGWorlds() { + if (plugin_world_) { + DisposeGWorld(plugin_world_); + plugin_world_ = NULL; + } + if (target_world_) { + DisposeGWorld(target_world_); + target_world_ = NULL; + } +} + +void QuickDrawDrawingManager::UpdateGWorlds() { + DestroyGWorlds(); + if (!target_context_) + return; + + Rect window_bounds = { + 0, 0, plugin_size_.height(), plugin_size_.width() + }; + // Create a GWorld pointing at the same bits as our target context. + if (target_context_) { + NewGWorldFromPtr( + &target_world_, k32BGRAPixelFormat, &window_bounds, NULL, NULL, 0, + static_cast<Ptr>(CGBitmapContextGetData(target_context_)), + static_cast<SInt32>(CGBitmapContextGetBytesPerRow(target_context_))); + } + // Create a GWorld for the plugin to paint into whenever it wants; since + // QuickDraw plugins don't draw at known times, they can't be allowed to draw + // directly into the shared memory. + NewGWorld(&plugin_world_, k32ARGBPixelFormat, &window_bounds, + NULL, NULL, kNativeEndianPixMap); + if (fast_path_enabled_) + current_port_ = plugin_world_; +} + +void QuickDrawDrawingManager::ScrapeWindow(WindowRef window, + CGContextRef target_context, + const gfx::Size& plugin_size) { + if (!target_context) + return; + + CGRect window_bounds = CGRectMake(0, 0, + plugin_size.width(), + plugin_size.height()); + CGWindowID window_id = HIWindowGetCGWindowID(window); + CGContextSaveGState(target_context); + CGContextTranslateCTM(target_context, 0, plugin_size.height()); + CGContextScaleCTM(target_context, 1.0, -1.0); + CGContextCopyWindowCaptureContentsToRect(target_context, window_bounds, + _CGSDefaultConnection(), + window_id, 0); + CGContextRestoreGState(target_context); +} + +void QuickDrawDrawingManager::CopyGWorldBits(GWorldPtr source, GWorldPtr dest, + const gfx::Size& plugin_size) { + if (!(source && dest)) + return; + + Rect window_bounds = { 0, 0, plugin_size.height(), plugin_size.width() }; + PixMapHandle source_pixmap = GetGWorldPixMap(source); + if (LockPixels(source_pixmap)) { + PixMapHandle dest_pixmap = GetGWorldPixMap(dest); + if (LockPixels(dest_pixmap)) { + SetGWorld(dest, NULL); + // Set foreground and background colors to avoid "colorizing" the image. + ForeColor(blackColor); + BackColor(whiteColor); + CopyBits(reinterpret_cast<BitMap*>(*source_pixmap), + reinterpret_cast<BitMap*>(*dest_pixmap), + &window_bounds, &window_bounds, srcCopy, NULL); + UnlockPixels(dest_pixmap); + } + UnlockPixels(source_pixmap); + } +} + +#endif // !NP_NO_QUICKDRAW diff --git a/webkit/glue/plugins/quickdraw_drawing_manager_mac.h b/webkit/glue/plugins/quickdraw_drawing_manager_mac.h new file mode 100644 index 0000000..8163f92 --- /dev/null +++ b/webkit/glue/plugins/quickdraw_drawing_manager_mac.h @@ -0,0 +1,83 @@ +// 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. + +#ifndef WEBKIT_GLUE_QUICKDRAW_DRAWING_MANAGER_MAC_H_ +#define WEBKIT_GLUE_QUICKDRAW_DRAWING_MANAGER_MAC_H_ + +#ifndef NP_NO_QUICKDRAW + +#import <Carbon/Carbon.h> + +#include "gfx/rect.h" + +// Plugin helper class encapsulating the details of capturing what a QuickDraw +// drawing model plugin draws, then drawing it into a CGContext. +class QuickDrawDrawingManager { + public: + QuickDrawDrawingManager(); + ~QuickDrawDrawingManager(); + + // Sets the mode used for plugin drawing. If enabled is true the plugin draws + // into a GWorld that's not connected to a window, otherwise the plugin draws + // into our the plugin's dummy window (which is slower, since the call we use + // to scrape the window contents is much more expensive than copying between + // GWorlds). + void SetFastPathEnabled(bool enabled); + + // Returns true if the fast path is currently enabled. + bool IsFastPathEnabled(); + + // Sets the context that the plugin bits should be copied into when + // UpdateContext is called. This object does not retain |context|, so the + // caller must call SetTargetContext again if the context changes. + // If the fast path is currently enabled, this call will cause the port to + // change. + void SetTargetContext(CGContextRef context, const gfx::Size& plugin_size); + + // Sets the window that is used by the plugin. This object does not own the + // window, so the caler must call SetPluginWindow again if the window changes. + void SetPluginWindow(WindowRef window); + + // Updates the target context with the current plugin bits. + void UpdateContext(); + + // Returns the port that the plugin should draw into. This returned port is + // only valid until the next call to SetFastPathEnabled (or SetTargetContext + // while the fast path is enabled). + CGrafPtr port() { return current_port_; } + + // Makes the QuickDraw port current; should be called before calls where the + // plugin might draw. + void MakePortCurrent(); + + private: + // Updates the GWorlds used by the faster path. + void UpdateGWorlds(); + + // Deletes the GWorlds used by the faster path. + void DestroyGWorlds(); + + // Scrapes the contents of the window into the given context. + // Used for the slower path. + static void ScrapeWindow(WindowRef window, CGContextRef target_context, + const gfx::Size& plugin_size); + + // Copies the source GWorld's bits into the target GWorld. + // Used for the faster path. + static void CopyGWorldBits(GWorldPtr source, GWorldPtr dest, + const gfx::Size& plugin_size); + + WindowRef plugin_window_; // Weak reference. + CGContextRef target_context_; // Weak reference. + gfx::Size plugin_size_; + bool fast_path_enabled_; + CGrafPtr current_port_; + // Variables used for the faster path: + GWorldPtr target_world_; // Created lazily; may be NULL. + GWorldPtr plugin_world_; // Created lazily; may be NULL. +}; + +#endif // !NP_NO_QUICKDRAW + +#endif // QUICKDRAW_DRAWING_MANAGER_MAC diff --git a/webkit/glue/plugins/test/Info.plist b/webkit/glue/plugins/test/Info.plist new file mode 100644 index 0000000..37145fd --- /dev/null +++ b/webkit/glue/plugins/test/Info.plist @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleExecutable</key> + <string>NPAPITestPlugIn</string> + <key>CFBundleIdentifier</key> + <string>org.chromium.npapi_test_plugin</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleExecutable</key> + <string>${EXECUTABLE_NAME}</string> + <key>CFBundleName</key> + <string>${PRODUCT_NAME}</string> + <key>CFBundlePackageType</key> + <string>BRPL</string> + <key>CFBundleShortVersionString</key> + <string>1.0</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleVersion</key> + <string>1.0</string> + <key>CFPlugInDynamicRegisterFunction</key> + <string></string> + <key>CFPlugInDynamicRegistration</key> + <string>NO</string> + <key>WebPluginDescription</key> + <string>Simple NPAPI plug-in for Chromium unit tests</string> + <key>WebPluginMIMETypes</key> + <dict> + <key>application/vnd.npapi-test</key> + <dict> + <key>WebPluginExtensions</key> + <array> + <string>npapitest</string> + </array> + <key>WebPluginTypeDescription</key> + <string>test npapi</string> + </dict> + </dict> + <key>WebPluginName</key> + <string>Chromium NPAPI Test Plugin</string> +</dict> +</plist> diff --git a/webkit/glue/plugins/test/npapi_constants.cc b/webkit/glue/plugins/test/npapi_constants.cc new file mode 100644 index 0000000..75cc68f --- /dev/null +++ b/webkit/glue/plugins/test/npapi_constants.cc @@ -0,0 +1,10 @@ +// 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. + +#include "webkit/glue/plugins/test/npapi_constants.h" + +namespace NPAPIClient { +const char kTestCompleteCookie[] = "status"; +const char kTestCompleteSuccess[] = "OK"; +} diff --git a/webkit/glue/plugins/test/npapi_constants.h b/webkit/glue/plugins/test/npapi_constants.h new file mode 100644 index 0000000..6570c35 --- /dev/null +++ b/webkit/glue/plugins/test/npapi_constants.h @@ -0,0 +1,19 @@ +// 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. + +// Constants for the NPAPI test + +#ifndef WEBKIT_PORT_PLUGINS_TEST_NPAPI_CONSTANTS_H__ +#define WEBKIT_PORT_PLUGINS_TEST_NPAPI_CONSTANTS_H__ + +namespace NPAPIClient { +// The name of the cookie which will be used to communicate between +// the plugin and the test harness. +extern const char kTestCompleteCookie[]; + +// The cookie value which will be sent to the client upon successful +// test. +extern const char kTestCompleteSuccess[]; +} +#endif // WEBKIT_PORT_PLUGINS_TEST_NPAPI_CONSTANTS_H__ diff --git a/webkit/glue/plugins/test/npapi_test.cc b/webkit/glue/plugins/test/npapi_test.cc new file mode 100644 index 0000000..895a842 --- /dev/null +++ b/webkit/glue/plugins/test/npapi_test.cc @@ -0,0 +1,122 @@ +// 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. + +// +// npapitest +// +// This is a NPAPI Plugin Program which is used to test the Browser's NPAPI +// host implementation. It is used in conjunction with the npapi_unittest. +// +// As a NPAPI Plugin, you can invoke it by creating a web page of the following +// type: +// +// <embed src="content-to-load" type="application/vnd.npapi-test" +// name="test-name"> +// +// arguments: +// src: This is the initial content which will be sent to the plugin. +// type: Must be "application/vnd.npapi-test" +// name: The testcase to run when invoked +// id: The id of the test being run (for testing concurrent plugins) +// +// The Plugin drives the actual test, calling host functions and validating the +// Host callbacks which it receives. It is the duty of the plugin to record +// all errors. +// +// To indicate test completion, the plugin expects the containing HTML page to +// implement two javascript functions: +// onSuccess(string testname); +// onFailure(string testname, string results); +// The HTML host pages used in this test will then set a document cookie +// which the automated test framework can poll for and discover that the +// test has completed. +// +// +// TESTS +// When the PluginClient receives a NPP_New callback from the browser, +// it looks at the "name" argument which is passed in. It verifies that +// the name matches a known test, and instantiates that test. The test is +// a subclass of PluginTest. +// +// + +#include "base/basictypes.h" + +#if defined(OS_WIN) +#include <windows.h> +#endif + +#if defined(__GNUC__) && __GNUC__ >= 4 +#define EXPORT __attribute__((visibility ("default"))) +#else +#define EXPORT +#endif + +#include "webkit/glue/plugins/test/plugin_client.h" + +#if defined(OS_WIN) +BOOL API_CALL DllMain(HINSTANCE hDll, DWORD dwReason, LPVOID lpReserved) { + return TRUE; +} +#endif + +extern "C" { +EXPORT NPError API_CALL NP_GetEntryPoints(NPPluginFuncs* pFuncs) { + return NPAPIClient::PluginClient::GetEntryPoints(pFuncs); +} + +EXPORT NPError API_CALL NP_Shutdown() { + return NPAPIClient::PluginClient::Shutdown(); +} + +#if defined(OS_WIN) || defined(OS_MACOSX) +EXPORT NPError API_CALL NP_Initialize(NPNetscapeFuncs* npnFuncs) { + return NPAPIClient::PluginClient::Initialize(npnFuncs); +} +#elif defined(OS_POSIX) +EXPORT NPError API_CALL NP_Initialize(NPNetscapeFuncs* npnFuncs, + NPPluginFuncs* nppFuncs) { + NPError error = NPAPIClient::PluginClient::Initialize(npnFuncs); + if (error == NPERR_NO_ERROR) { + error = NP_GetEntryPoints(nppFuncs); + } + return error; +} + +EXPORT NPError API_CALL NP_GetValue(NPP instance, NPPVariable variable, + void* value) { + NPError err = NPERR_NO_ERROR; + + switch (variable) { + case NPPVpluginNameString: + *(static_cast<const char**>(value)) = "NPAPI Test Plugin"; + break; + case NPPVpluginDescriptionString: + *(static_cast<const char**>(value)) = + "Simple NPAPI plug-in for Chromium unit tests"; + break; + case NPPVpluginNeedsXEmbed: + *(static_cast<NPBool*>(value)) = true; + break; + default: + err = NPERR_GENERIC_ERROR; + break; + } + + return err; +} + +EXPORT const char* API_CALL NP_GetMIMEDescription(void) { + // The layout test LayoutTests/fast/js/navigator-mimeTypes-length.html + // asserts that the number of mimetypes handled by plugins should be + // greater than the number of plugins. We specify a mimetype here so + // this plugin has at least one. + return "application/vnd.npapi-test:npapitest:test npapi"; +} +#endif // OS_POSIX +} // extern "C" + +namespace WebCore { + const char* currentTextBreakLocaleID() { return "en_us"; } +} diff --git a/webkit/glue/plugins/test/npapi_test.def b/webkit/glue/plugins/test/npapi_test.def new file mode 100644 index 0000000..4481c16 --- /dev/null +++ b/webkit/glue/plugins/test/npapi_test.def @@ -0,0 +1,6 @@ +LIBRARY npapi_test_plugin + +EXPORTS + NP_GetEntryPoints @1 + NP_Initialize @2 + NP_Shutdown @3 diff --git a/webkit/glue/plugins/test/npapi_test.rc b/webkit/glue/plugins/test/npapi_test.rc new file mode 100644 index 0000000..524dda4 --- /dev/null +++ b/webkit/glue/plugins/test/npapi_test.rc @@ -0,0 +1,102 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x17L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "FileDescription", "NPAPI Test Plugin" + VALUE "FileVersion", "1, 0, 0, 1" + VALUE "InternalName", "npapi_test_plugin" + VALUE "LegalCopyright", "Copyright (C) 2007" + VALUE "MIMEType", "application/vnd.npapi-test" + VALUE "OriginalFilename", "npapi_test_plugin.dll" + VALUE "ProductName", "NPAPI Test Plugin" + VALUE "ProductVersion", "1, 0, 0, 1" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/webkit/glue/plugins/test/plugin_arguments_test.cc b/webkit/glue/plugins/test/plugin_arguments_test.cc new file mode 100644 index 0000000..46ccf43 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_arguments_test.cc @@ -0,0 +1,69 @@ +// 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. + +#include "base/basictypes.h" +#include "base/string_util.h" +#include "base/stringprintf.h" + +#include "webkit/glue/plugins/test/plugin_arguments_test.h" + +namespace NPAPIClient { + +PluginArgumentsTest::PluginArgumentsTest(NPP id, + NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions) { +} + +NPError PluginArgumentsTest::New(uint16 mode, int16 argc, + const char* argn[], const char* argv[], + NPSavedData* saved) { + // mode: should be the string either "NP_EMBED" or "NP_FULL", + // depending on the mode passed in. + // count: the count of "val" arguments. If the value is + // 2, then we'll find arguments "val1" and "val2". If + // the value is 0, then there will be no "val" arguments. + // size: each val string will be this size * the value's + // index. E.g if size is "10", val1 will be 10bytes, + // and val2 will be 20bytes. + const char *mode_string = GetArgValue("mode", argc, argn, argv); + ExpectAsciiStringNotEqual(mode_string, (const char *)NULL); + if (mode_string != NULL) { + std::string mode_dep_string = mode_string; + if (mode == NP_EMBED) + ExpectStringLowerCaseEqual(mode_dep_string, "np_embed"); + else if (mode == NP_FULL) + ExpectStringLowerCaseEqual(mode_dep_string, "np_full"); + } + + const char *count_string = GetArgValue("count", argc, argn, argv); + if (count_string != NULL) { + int max_args = atoi(count_string); + + const char *size_string = GetArgValue("size", argc, argn, argv); + ExpectAsciiStringNotEqual(size_string, (const char *)NULL); + if (size_string != NULL) { + int size = atoi(size_string); + + for (int index = 1; index <= max_args; index++) { + std::string arg_name = base::StringPrintf("%s%d", "val", index); + const char *val_string = GetArgValue(arg_name.c_str(), argc, argn, + argv); + ExpectAsciiStringNotEqual(val_string, (const char*)NULL); + if (val_string != NULL) + ExpectIntegerEqual((int)strlen(val_string), (index*size)); + } + } + } + + return PluginTest::New(mode, argc, argn, argv, saved); +} + +NPError PluginArgumentsTest::SetWindow(NPWindow* pNPWindow) { + // This test just tests the arguments. We're done now. + this->SignalTestCompleted(); + + return NPERR_NO_ERROR; +} + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_arguments_test.h b/webkit/glue/plugins/test/plugin_arguments_test.h new file mode 100644 index 0000000..aa05f19 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_arguments_test.h @@ -0,0 +1,43 @@ +// 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. + +#ifndef WEBKIT_PORT_PLUGINS_TEST_PLUGIN_ARGUMENTS_TEST_H__ +#define WEBKIT_PORT_PLUGINS_TEST_PLUGIN_ARGUMENTS_TEST_H__ + +#include "webkit/glue/plugins/test/plugin_test.h" + +namespace NPAPIClient { + +// The PluginArgumentsTest test that we properly receive arguments +// intended for the plugin. +// +// This is basically overkill for testing that the arguments passed +// to the plugin match what we expect. +// +// We expect to find the following arguments: +// mode: should be the string either "NP_EMBED" or "NP_FULL", +// depending on the mode passed in. +// count: the count of "val" arguments. If the value is +// 2, then we'll find arguments "val1" and "val2". If +// the value is 0, then there will be no "val" arguments. +// size: each val string will be this size * the value's +// index. E.g if size is "10", val1 will be 10bytes, +// and val2 will be 20bytes. +// +class PluginArgumentsTest : public PluginTest { + public: + // Constructor. + PluginArgumentsTest(NPP id, NPNetscapeFuncs *host_functions); + + // Initialize this PluginTest based on the arguments from NPP_New. + virtual NPError New(uint16 mode, int16 argc, const char* argn[], + const char* argv[], NPSavedData* saved); + + // NPAPI SetWindow handler. + virtual NPError SetWindow(NPWindow* pNPWindow); +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_PORT_PLUGINS_TEST_PLUGIN_ARGUMENTS_TEST_H__ diff --git a/webkit/glue/plugins/test/plugin_client.cc b/webkit/glue/plugins/test/plugin_client.cc new file mode 100644 index 0000000..8358340 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_client.cc @@ -0,0 +1,240 @@ +// Copyright (c) 2009 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. + +#include "webkit/glue/plugins/test/plugin_client.h" + +#include "base/string_util.h" +#include "webkit/glue/plugins/test/plugin_test.h" +#include "webkit/glue/plugins/test/plugin_test_factory.h" + +namespace NPAPIClient { + +NPNetscapeFuncs* PluginClient::host_functions_; + +NPError PluginClient::GetEntryPoints(NPPluginFuncs* pFuncs) { + if (pFuncs == NULL) + return NPERR_INVALID_FUNCTABLE_ERROR; + + if (pFuncs->size < sizeof(NPPluginFuncs)) + return NPERR_INVALID_FUNCTABLE_ERROR; + + pFuncs->version = (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR; + pFuncs->newp = NPP_New; + pFuncs->destroy = NPP_Destroy; + pFuncs->setwindow = NPP_SetWindow; + pFuncs->newstream = NPP_NewStream; + pFuncs->destroystream = NPP_DestroyStream; + pFuncs->asfile = NPP_StreamAsFile; + pFuncs->writeready = NPP_WriteReady; + pFuncs->write = NPP_Write; + pFuncs->print = NPP_Print; + pFuncs->event = NPP_HandleEvent; + pFuncs->urlnotify = NPP_URLNotify; + pFuncs->getvalue = NPP_GetValue; + pFuncs->setvalue = NPP_SetValue; + pFuncs->javaClass = NULL; + pFuncs->urlredirectnotify = NPP_URLRedirectNotify; + + return NPERR_NO_ERROR; +} + +NPError PluginClient::Initialize(NPNetscapeFuncs* pFuncs) { + if (pFuncs == NULL) { + return NPERR_INVALID_FUNCTABLE_ERROR; + } + + if (static_cast<unsigned char>((pFuncs->version >> 8) & 0xff) > + NP_VERSION_MAJOR) { + return NPERR_INCOMPATIBLE_VERSION_ERROR; + } + +#if defined(OS_WIN) + // Check if we should crash. + HANDLE crash_event = CreateEvent(NULL, TRUE, FALSE, L"TestPluginCrashOnInit"); + if (WaitForSingleObject(crash_event, 0) == WAIT_OBJECT_0) { + int *zero = NULL; + *zero = 0; + } + CloseHandle(crash_event); +#endif + + host_functions_ = pFuncs; + + return NPERR_NO_ERROR; +} + +NPError PluginClient::Shutdown() { + return NPERR_NO_ERROR; +} + +} // namespace NPAPIClient + +extern "C" { +NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16 mode, + int16 argc, char* argn[], char* argv[], NPSavedData* saved) { + if (instance == NULL) + return NPERR_INVALID_INSTANCE_ERROR; + + // We look at the test name requested via the plugin arguments. We match + // that against a given test and try to instantiate it. + + // lookup the name parameter + std::string test_name; + for (int name_index = 0; name_index < argc; name_index++) { + if (base::strcasecmp(argn[name_index], "name") == 0) { + test_name = argv[name_index]; + break; + } + } + if (test_name.empty()) + return NPERR_GENERIC_ERROR; // no name found + + NPAPIClient::PluginTest* new_test = NPAPIClient::CreatePluginTest(test_name, + instance, NPAPIClient::PluginClient::HostFunctions()); + if (new_test == NULL) { + // If we don't have a test case for this, create a + // generic one which basically never fails. + LOG(WARNING) << "Unknown test name '" << test_name + << "'; using default test."; + new_test = new NPAPIClient::PluginTest(instance, + NPAPIClient::PluginClient::HostFunctions()); + } + + NPError ret = new_test->New(mode, argc, (const char**)argn, + (const char**)argv, saved); + if ((ret == NPERR_NO_ERROR) && new_test->IsWindowless()) { + NPAPIClient::PluginClient::HostFunctions()->setvalue( + instance, NPPVpluginWindowBool, NULL); + } + + return ret; +} + +NPError NPP_Destroy(NPP instance, NPSavedData** save) { + if (instance == NULL) + return NPERR_INVALID_INSTANCE_ERROR; + + NPAPIClient::PluginTest* plugin = + reinterpret_cast<NPAPIClient::PluginTest*>(instance->pdata); + + NPError rv = plugin->Destroy(); + delete plugin; + return rv; +} + +NPError NPP_SetWindow(NPP instance, NPWindow* pNPWindow) { + if (instance == NULL) + return NPERR_INVALID_INSTANCE_ERROR; + + NPAPIClient::PluginTest* plugin = + reinterpret_cast<NPAPIClient::PluginTest*>(instance->pdata); + + return plugin->SetWindow(pNPWindow); +} + +NPError NPP_NewStream(NPP instance, NPMIMEType type, + NPStream* stream, NPBool seekable, uint16* stype) { + if (instance == NULL) + return NPERR_INVALID_INSTANCE_ERROR; + + NPAPIClient::PluginTest* plugin = + reinterpret_cast<NPAPIClient::PluginTest*>(instance->pdata); + + return plugin->NewStream(type, stream, seekable, stype); +} + +int32 NPP_WriteReady(NPP instance, NPStream *stream) { + if (instance == NULL) + return NPERR_INVALID_INSTANCE_ERROR; + + NPAPIClient::PluginTest* plugin = + reinterpret_cast<NPAPIClient::PluginTest*>(instance->pdata); + + return plugin->WriteReady(stream); +} + +int32 NPP_Write(NPP instance, NPStream *stream, int32 offset, + int32 len, void *buffer) { + if (instance == NULL) + return NPERR_INVALID_INSTANCE_ERROR; + + NPAPIClient::PluginTest* plugin = + reinterpret_cast<NPAPIClient::PluginTest*>(instance->pdata); + + return plugin->Write(stream, offset, len, buffer); +} + +NPError NPP_DestroyStream(NPP instance, NPStream *stream, NPError reason) { + if (instance == NULL) + return NPERR_INVALID_INSTANCE_ERROR; + + NPAPIClient::PluginTest* plugin = + reinterpret_cast<NPAPIClient::PluginTest*>(instance->pdata); + + return plugin->DestroyStream(stream, reason); +} + +void NPP_StreamAsFile(NPP instance, NPStream* stream, const char* fname) { + if (instance == NULL) + return; + + NPAPIClient::PluginTest* plugin = + reinterpret_cast<NPAPIClient::PluginTest*>(instance->pdata); + + return plugin->StreamAsFile(stream, fname); +} + +void NPP_Print(NPP instance, NPPrint* printInfo) { + if (instance == NULL) + return; + + // XXXMB - do work here. +} + +void NPP_URLNotify(NPP instance, const char* url, NPReason reason, + void* notifyData) { + if (instance == NULL) + return; + + NPAPIClient::PluginTest* plugin = + reinterpret_cast<NPAPIClient::PluginTest*>(instance->pdata); + + return plugin->URLNotify(url, reason, notifyData); +} + +NPError NPP_GetValue(NPP instance, NPPVariable variable, void *value) { + if (instance == NULL) + return NPERR_INVALID_INSTANCE_ERROR; + + // XXXMB - do work here. + return NPERR_GENERIC_ERROR; +} + +NPError NPP_SetValue(NPP instance, NPNVariable variable, void *value) { + if (instance == NULL) + return NPERR_INVALID_INSTANCE_ERROR; + + // XXXMB - do work here. + return NPERR_GENERIC_ERROR; +} + +int16 NPP_HandleEvent(NPP instance, void* event) { + if (instance == NULL) + return 0; + + NPAPIClient::PluginTest* plugin = + reinterpret_cast<NPAPIClient::PluginTest*>(instance->pdata); + + return plugin->HandleEvent(event); +} + +void NPP_URLRedirectNotify(NPP instance, const char* url, int32_t status, + void* notify_data) { + if (instance) { + NPAPIClient::PluginTest* plugin = + reinterpret_cast<NPAPIClient::PluginTest*>(instance->pdata); + plugin->URLRedirectNotify(url, status, notify_data); + } +} +} // extern "C" diff --git a/webkit/glue/plugins/test/plugin_client.h b/webkit/glue/plugins/test/plugin_client.h new file mode 100644 index 0000000..a6291b0 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_client.h @@ -0,0 +1,45 @@ +// 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. + +#ifndef WEBKIT_PORT_PLUGINS_TEST_PLUGIN_CLIENT_H__ +#define WEBKIT_PORT_PLUGINS_TEST_PLUGIN_CLIENT_H__ + +#include "third_party/npapi/bindings/npapi.h" +#include "third_party/npapi/bindings/nphostapi.h" + +namespace NPAPIClient { + +// A PluginClient is a NPAPI Plugin. This class contains +// the bootstrapping functions used by the browser to load +// the plugin. +class PluginClient { + public: + // Although not documented in the NPAPI specification, this function + // gets the list of entry points in the NPAPI Plugin (client) for the + // NPAPI Host to call. + static NPError GetEntryPoints(NPPluginFuncs* pFuncs); + + // The browser calls this function only once: when a plug-in is loaded, + // before the first instance is created. This is the first function that + // the browser calls. NP_Initialize tells the plug-in that the browser has + // loaded it and provides global initialization. Allocate any memory or + // resources shared by all instances of your plug-in at this time. + static NPError Initialize(NPNetscapeFuncs* pFuncs); + + // The browser calls this function once after the last instance of your + // plug-in is destroyed, before unloading the plug-in library itself. Use + // NP_Shutdown to delete any data allocated in NP_Initialize to be shared + // by all instances of a plug-in. + static NPError Shutdown(); + + // The table of functions provided by the host. + static NPNetscapeFuncs *HostFunctions() { return host_functions_; } + + private: + static NPNetscapeFuncs* host_functions_; +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_PORT_PLUGINS_TEST_PLUGIN_CLIENT_H__ diff --git a/webkit/glue/plugins/test/plugin_create_instance_in_paint.cc b/webkit/glue/plugins/test/plugin_create_instance_in_paint.cc new file mode 100644 index 0000000..f98f89b --- /dev/null +++ b/webkit/glue/plugins/test/plugin_create_instance_in_paint.cc @@ -0,0 +1,78 @@ +// Copyright (c) 2009 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. + +#include "webkit/glue/plugins/test/plugin_create_instance_in_paint.h" + +#include "webkit/glue/plugins/test/plugin_client.h" + +namespace NPAPIClient { + +CreateInstanceInPaintTest::CreateInstanceInPaintTest( + NPP id, NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions), + window_(NULL), created_(false) { +} + +NPError CreateInstanceInPaintTest::SetWindow(NPWindow* pNPWindow) { + if (pNPWindow->window == NULL) + return NPERR_NO_ERROR; + + if (test_id() == "1") { + if (!window_) { + static ATOM window_class = 0; + if (!window_class) { + WNDCLASSEX wcex; + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.style = CS_DBLCLKS; + wcex.lpfnWndProc = + &NPAPIClient::CreateInstanceInPaintTest::WindowProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = GetModuleHandle(NULL); + wcex.hIcon = 0; + wcex.hCursor = 0; + wcex.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW+1); + wcex.lpszMenuName = 0; + wcex.lpszClassName = L"CreateInstanceInPaintTestWindowClass"; + wcex.hIconSm = 0; + window_class = RegisterClassEx(&wcex); + } + + HWND parent = reinterpret_cast<HWND>(pNPWindow->window); + window_ = CreateWindowEx( + WS_EX_LEFT | WS_EX_LTRREADING | WS_EX_RIGHTSCROLLBAR, + MAKEINTATOM(window_class), 0, + WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE , + 0, 0, 100, 100, parent, 0, GetModuleHandle(NULL), 0); + DCHECK(window_); + // TODO: this property leaks. + ::SetProp(window_, L"Plugin_Instance", this); + } + } else if (test_id() == "2") { + SignalTestCompleted(); + } else { + NOTREACHED(); + } + return NPERR_NO_ERROR; +} + +LRESULT CALLBACK CreateInstanceInPaintTest::WindowProc( + HWND window, UINT message, WPARAM wparam, LPARAM lparam) { + if (message == WM_PAINT) { + CreateInstanceInPaintTest* this_instance = + reinterpret_cast<CreateInstanceInPaintTest*> + (::GetProp(window, L"Plugin_Instance")); + if (this_instance->test_id() == "1" && !this_instance->created_) { + ::RemoveProp(window, L"Plugin_Instance"); + this_instance->created_ = true; + this_instance->HostFunctions()->geturlnotify( + this_instance->id(), "javascript:CreateNewInstance()", NULL, + reinterpret_cast<void*>(1)); + } + } + + return DefWindowProc(window, message, wparam, lparam); +} + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_create_instance_in_paint.h b/webkit/glue/plugins/test/plugin_create_instance_in_paint.h new file mode 100644 index 0000000..84d7a94 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_create_instance_in_paint.h @@ -0,0 +1,33 @@ +// Copyright (c) 2009 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. + +#ifndef WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_CREATE_INSTANCE_IN_PAINT_H +#define WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_CREATE_INSTANCE_IN_PAINT_H + +#include "webkit/glue/plugins/test/plugin_test.h" + +namespace NPAPIClient { + +// This class tests that creating a new plugin via script while handling a +// Windows message doesn't cause a deadlock. +class CreateInstanceInPaintTest : public PluginTest { + public: + // Constructor. + CreateInstanceInPaintTest(NPP id, NPNetscapeFuncs *host_functions); + // + // NPAPI functions + // + virtual NPError SetWindow(NPWindow* pNPWindow); + + private: + static LRESULT CALLBACK WindowProc( + HWND window, UINT message, WPARAM wparam, LPARAM lparam); + + HWND window_; + bool created_; +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_CREATE_INSTANCE_IN_PAINT_H diff --git a/webkit/glue/plugins/test/plugin_delete_plugin_in_stream_test.cc b/webkit/glue/plugins/test/plugin_delete_plugin_in_stream_test.cc new file mode 100644 index 0000000..15318b4 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_delete_plugin_in_stream_test.cc @@ -0,0 +1,45 @@ +// 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. + +#include "webkit/glue/plugins/test/plugin_delete_plugin_in_stream_test.h" + +#include "webkit/glue/plugins/test/plugin_client.h" + +namespace NPAPIClient { + +#define kUrl "javascript:window.location+\"\"" +#define kUrlStreamId 1 + +DeletePluginInStreamTest::DeletePluginInStreamTest(NPP id, NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions), + test_started_(false) { +} + +NPError DeletePluginInStreamTest::SetWindow(NPWindow* pNPWindow) { + if (pNPWindow->window == NULL) + return NPERR_NO_ERROR; + + if (!test_started_) { + std::string url = "self_delete_plugin_stream.html"; + HostFunctions()->geturlnotify(id(), url.c_str(), NULL, + reinterpret_cast<void*>(kUrlStreamId)); + test_started_ = true; + } + return NPERR_NO_ERROR; +} + +NPError DeletePluginInStreamTest::NewStream(NPMIMEType type, NPStream* stream, + NPBool seekable, uint16* stype) { + NPIdentifier delete_id = HostFunctions()->getstringidentifier("DeletePluginWithinScript"); + + NPObject *window_obj = NULL; + HostFunctions()->getvalue(id(), NPNVWindowNPObject, &window_obj); + + NPVariant rv; + HostFunctions()->invoke(id(), window_obj, delete_id, NULL, 0, &rv); + + return NPERR_NO_ERROR; +} + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_delete_plugin_in_stream_test.h b/webkit/glue/plugins/test/plugin_delete_plugin_in_stream_test.h new file mode 100644 index 0000000..418e976 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_delete_plugin_in_stream_test.h @@ -0,0 +1,30 @@ +// 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. + +#ifndef WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_DELETE_PLUGIN_IN_STREAM_TEST_H +#define WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_DELETE_PLUGIN_IN_STREAM_TEST_H + +#include "webkit/glue/plugins/test/plugin_test.h" + +namespace NPAPIClient { + +// This class tests +class DeletePluginInStreamTest : public PluginTest { + public: + // Constructor. + DeletePluginInStreamTest(NPP id, NPNetscapeFuncs *host_functions); + // + // NPAPI functions + // + virtual NPError SetWindow(NPWindow* pNPWindow); + virtual NPError NewStream(NPMIMEType type, NPStream* stream, + NPBool seekable, uint16* stype); + private: + bool test_started_; + std::string self_url_; +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_DELETE_PLUGIN_IN_STREAM_TEST_H diff --git a/webkit/glue/plugins/test/plugin_get_javascript_url2_test.cc b/webkit/glue/plugins/test/plugin_get_javascript_url2_test.cc new file mode 100644 index 0000000..d17dced --- /dev/null +++ b/webkit/glue/plugins/test/plugin_get_javascript_url2_test.cc @@ -0,0 +1,134 @@ +// Copyright (c) 2006-2009 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. + +#include "webkit/glue/plugins/test/plugin_get_javascript_url2_test.h" + +#include "base/basictypes.h" + +// url for "self". +#define SELF_URL "javascript:window.location+\"\"" +// The identifier for the self url stream. +#define SELF_URL_STREAM_ID 1 + +// The identifier for the fetched url stream. +#define FETCHED_URL_STREAM_ID 2 + +// The maximum chunk size of stream data. +#define STREAM_CHUNK 197 + +const int kNPNEvaluateTimerID = 100; +const int kNPNEvaluateTimerElapse = 50; + +namespace NPAPIClient { + +ExecuteGetJavascriptUrl2Test::ExecuteGetJavascriptUrl2Test( + NPP id, NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions), + test_started_(false) { +} + +NPError ExecuteGetJavascriptUrl2Test::SetWindow(NPWindow* pNPWindow) { + if (pNPWindow->window == NULL) + return NPERR_NO_ERROR; + + if (!test_started_) { + std::string url = SELF_URL; + HostFunctions()->geturlnotify(id(), url.c_str(), "_self", + reinterpret_cast<void*>(SELF_URL_STREAM_ID)); + test_started_ = true; + } + return NPERR_NO_ERROR; +} + +NPError ExecuteGetJavascriptUrl2Test::NewStream(NPMIMEType type, NPStream* stream, + NPBool seekable, uint16* stype) { + if (stream == NULL) { + SetError("NewStream got null stream"); + return NPERR_INVALID_PARAM; + } + + COMPILE_ASSERT(sizeof(unsigned long) <= sizeof(stream->notifyData), + cast_validity_check); + unsigned long stream_id = reinterpret_cast<unsigned long>(stream->notifyData); + switch (stream_id) { + case SELF_URL_STREAM_ID: + break; + default: + SetError("Unexpected NewStream callback"); + break; + } + return NPERR_NO_ERROR; +} + +int32 ExecuteGetJavascriptUrl2Test::WriteReady(NPStream *stream) { + return STREAM_CHUNK; +} + +int32 ExecuteGetJavascriptUrl2Test::Write(NPStream *stream, int32 offset, int32 len, + void *buffer) { + if (stream == NULL) { + SetError("Write got null stream"); + return -1; + } + if (len < 0 || len > STREAM_CHUNK) { + SetError("Write got bogus stream chunk size"); + return -1; + } + + COMPILE_ASSERT(sizeof(unsigned long) <= sizeof(stream->notifyData), + cast_validity_check); + unsigned long stream_id = reinterpret_cast<unsigned long>(stream->notifyData); + switch (stream_id) { + case SELF_URL_STREAM_ID: + self_url_.append(static_cast<char*>(buffer), len); + break; + default: + SetError("Unexpected write callback"); + break; + } + // Pretend that we took all the data. + return len; +} + + +NPError ExecuteGetJavascriptUrl2Test::DestroyStream(NPStream *stream, NPError reason) { + if (stream == NULL) { + SetError("NewStream got null stream"); + return NPERR_INVALID_PARAM; + } + + COMPILE_ASSERT(sizeof(unsigned long) <= sizeof(stream->notifyData), + cast_validity_check); + unsigned long stream_id = reinterpret_cast<unsigned long>(stream->notifyData); + switch (stream_id) { + case SELF_URL_STREAM_ID: + // don't care + break; + default: + SetError("Unexpected NewStream callback"); + break; + } + return NPERR_NO_ERROR; +} + +void ExecuteGetJavascriptUrl2Test::URLNotify(const char* url, NPReason reason, void* data) { + COMPILE_ASSERT(sizeof(unsigned long) <= sizeof(data), + cast_validity_check); + + unsigned long stream_id = reinterpret_cast<unsigned long>(data); + switch (stream_id) { + case SELF_URL_STREAM_ID: + if (strcmp(url, SELF_URL) != 0) + SetError("URLNotify reported incorrect url for SELF_URL"); + if (self_url_.empty()) + SetError("Failed to obtain window location."); + SignalTestCompleted(); + break; + default: + SetError("Unexpected NewStream callback"); + break; + } +} + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_get_javascript_url2_test.h b/webkit/glue/plugins/test/plugin_get_javascript_url2_test.h new file mode 100644 index 0000000..557da76 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_get_javascript_url2_test.h @@ -0,0 +1,38 @@ +// 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. + +#ifndef WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_GET_JAVASCRIPT_URL2_H +#define WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_GET_JAVASCRIPT_URL2_H + +#include "webkit/glue/plugins/test/plugin_test.h" + +namespace NPAPIClient { + +// This class tests NPP_GetURLNotify for a javascript URL with _top +// as the target frame. +class ExecuteGetJavascriptUrl2Test : public PluginTest { + public: + // Constructor. + ExecuteGetJavascriptUrl2Test(NPP id, NPNetscapeFuncs *host_functions); + + // + // NPAPI functions + // + virtual NPError SetWindow(NPWindow* pNPWindow); + virtual NPError NewStream(NPMIMEType type, NPStream* stream, + NPBool seekable, uint16* stype); + virtual int32 WriteReady(NPStream *stream); + virtual int32 Write(NPStream *stream, int32 offset, int32 len, + void *buffer); + virtual NPError DestroyStream(NPStream *stream, NPError reason); + virtual void URLNotify(const char* url, NPReason reason, void* data); + + private: + bool test_started_; + std::string self_url_; +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_GET_JAVASCRIPT_URL2_H diff --git a/webkit/glue/plugins/test/plugin_get_javascript_url_test.cc b/webkit/glue/plugins/test/plugin_get_javascript_url_test.cc new file mode 100644 index 0000000..50f5e5a --- /dev/null +++ b/webkit/glue/plugins/test/plugin_get_javascript_url_test.cc @@ -0,0 +1,218 @@ +// 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. + +#include "webkit/glue/plugins/test/plugin_get_javascript_url_test.h" + +#include "base/basictypes.h" + +// url for "self". +#define SELF_URL "javascript:window.location+\"\"" +// The identifier for the self url stream. +#define SELF_URL_STREAM_ID 1 + +// The identifier for the fetched url stream. +#define FETCHED_URL_STREAM_ID 2 + +// The maximum chunk size of stream data. +#define STREAM_CHUNK 197 + +const int kNPNEvaluateTimerID = 100; +const int kNPNEvaluateTimerElapse = 50; + + +namespace NPAPIClient { + +ExecuteGetJavascriptUrlTest::ExecuteGetJavascriptUrlTest( + NPP id, NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions), + test_started_(false), +#ifdef OS_WIN + window_(NULL), +#endif + npn_evaluate_context_(false) { +} + +NPError ExecuteGetJavascriptUrlTest::SetWindow(NPWindow* pNPWindow) { + if (pNPWindow->window == NULL) + return NPERR_NO_ERROR; + + if (!test_started_) { + std::string url = SELF_URL; + HostFunctions()->geturlnotify(id(), url.c_str(), "_top", + reinterpret_cast<void*>(SELF_URL_STREAM_ID)); + test_started_ = true; + +#ifdef OS_WIN + HWND window_handle = reinterpret_cast<HWND>(pNPWindow->window); + if (!::GetProp(window_handle, L"Plugin_Instance")) { + // TODO: this propery leaks. + ::SetProp(window_handle, L"Plugin_Instance", this); + // We attempt to retreive the NPObject for the plugin instance identified + // by the NPObjectLifetimeTestInstance2 class as it may not have been + // instantiated yet. + SetTimer(window_handle, kNPNEvaluateTimerID, kNPNEvaluateTimerElapse, + TimerProc); + } + window_ = window_handle; +#endif + } + + return NPERR_NO_ERROR; +} + +#ifdef OS_WIN +void CALLBACK ExecuteGetJavascriptUrlTest::TimerProc( + HWND window, UINT message, UINT timer_id, unsigned long elapsed_time) { + ExecuteGetJavascriptUrlTest* this_instance = + reinterpret_cast<ExecuteGetJavascriptUrlTest*> + (::GetProp(window, L"Plugin_Instance")); + + ::RemoveProp(window, L"Plugin_Instance"); + + NPObject *window_obj = NULL; + this_instance->HostFunctions()->getvalue(this_instance->id(), + NPNVWindowNPObject, + &window_obj); + if (!window_obj) { + this_instance->SetError("Failed to get NPObject for plugin instance2"); + this_instance->SignalTestCompleted(); + return; + } + + std::string script = "javascript:window.location"; + NPString script_string; + script_string.UTF8Characters = script.c_str(); + script_string.UTF8Length = static_cast<unsigned int>(script.length()); + NPVariant result_var; + + this_instance->npn_evaluate_context_ = true; + NPError result = this_instance->HostFunctions()->evaluate( + this_instance->id(), window_obj, &script_string, &result_var); + this_instance->npn_evaluate_context_ = false; +} +#endif + +NPError ExecuteGetJavascriptUrlTest::NewStream(NPMIMEType type, + NPStream* stream, + NPBool seekable, + uint16* stype) { + if (stream == NULL) { + SetError("NewStream got null stream"); + return NPERR_INVALID_PARAM; + } + + if (npn_evaluate_context_) { + SetError("NewStream received in context of NPN_Evaluate"); + return NPERR_NO_ERROR; + } + + COMPILE_ASSERT(sizeof(unsigned long) <= sizeof(stream->notifyData), + cast_validity_check); + unsigned long stream_id = reinterpret_cast<unsigned long>(stream->notifyData); + switch (stream_id) { + case SELF_URL_STREAM_ID: + break; + default: + SetError("Unexpected NewStream callback"); + break; + } + return NPERR_NO_ERROR; +} + +int32 ExecuteGetJavascriptUrlTest::WriteReady(NPStream *stream) { + if (npn_evaluate_context_) { + SetError("WriteReady received in context of NPN_Evaluate"); + return NPERR_NO_ERROR; + } + return STREAM_CHUNK; +} + +int32 ExecuteGetJavascriptUrlTest::Write(NPStream *stream, int32 offset, + int32 len, void *buffer) { + if (stream == NULL) { + SetError("Write got null stream"); + return -1; + } + if (len < 0 || len > STREAM_CHUNK) { + SetError("Write got bogus stream chunk size"); + return -1; + } + + if (npn_evaluate_context_) { + SetError("Write received in context of NPN_Evaluate"); + return len; + } + + COMPILE_ASSERT(sizeof(unsigned long) <= sizeof(stream->notifyData), + cast_validity_check); + unsigned long stream_id = reinterpret_cast<unsigned long>(stream->notifyData); + switch (stream_id) { + case SELF_URL_STREAM_ID: + self_url_.append(static_cast<char*>(buffer), len); + break; + default: + SetError("Unexpected write callback"); + break; + } + // Pretend that we took all the data. + return len; +} + + +NPError ExecuteGetJavascriptUrlTest::DestroyStream(NPStream *stream, + NPError reason) { + if (stream == NULL) { + SetError("NewStream got null stream"); + return NPERR_INVALID_PARAM; + } + +#ifdef OS_WIN + KillTimer(window_, kNPNEvaluateTimerID); +#endif + + if (npn_evaluate_context_) { + SetError("DestroyStream received in context of NPN_Evaluate"); + return NPERR_NO_ERROR; + } + + COMPILE_ASSERT(sizeof(unsigned long) <= sizeof(stream->notifyData), + cast_validity_check); + unsigned long stream_id = reinterpret_cast<unsigned long>(stream->notifyData); + switch (stream_id) { + case SELF_URL_STREAM_ID: + // don't care + break; + default: + SetError("Unexpected NewStream callback"); + break; + } + return NPERR_NO_ERROR; +} + +void ExecuteGetJavascriptUrlTest::URLNotify(const char* url, NPReason reason, + void* data) { + COMPILE_ASSERT(sizeof(unsigned long) <= sizeof(data), + cast_validity_check); + + if (npn_evaluate_context_) { + SetError("URLNotify received in context of NPN_Evaluate"); + return; + } + + unsigned long stream_id = reinterpret_cast<unsigned long>(data); + switch (stream_id) { + case SELF_URL_STREAM_ID: + if (strcmp(url, SELF_URL) != 0) + SetError("URLNotify reported incorrect url for SELF_URL"); + if (self_url_.empty()) + SetError("Failed to obtain window location."); + SignalTestCompleted(); + break; + default: + SetError("Unexpected NewStream callback"); + break; + } +} + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_get_javascript_url_test.h b/webkit/glue/plugins/test/plugin_get_javascript_url_test.h new file mode 100644 index 0000000..5c2540d --- /dev/null +++ b/webkit/glue/plugins/test/plugin_get_javascript_url_test.h @@ -0,0 +1,47 @@ +// 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. + +#ifndef WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_GET_JAVASCRIPT_URL_H +#define WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_GET_JAVASCRIPT_URL_H + +#include "webkit/glue/plugins/test/plugin_test.h" + +namespace NPAPIClient { + +// This class tests NPP_GetURLNotify for a javascript URL with _top +// as the target frame. +class ExecuteGetJavascriptUrlTest : public PluginTest { + public: + // Constructor. + ExecuteGetJavascriptUrlTest(NPP id, NPNetscapeFuncs *host_functions); + // + // NPAPI functions + // + virtual NPError SetWindow(NPWindow* pNPWindow); + virtual NPError NewStream(NPMIMEType type, NPStream* stream, + NPBool seekable, uint16* stype); + virtual int32 WriteReady(NPStream *stream); + virtual int32 Write(NPStream *stream, int32 offset, int32 len, + void *buffer); + virtual NPError DestroyStream(NPStream *stream, NPError reason); + virtual void URLNotify(const char* url, NPReason reason, void* data); + + private: +#if defined(OS_WIN) + static void CALLBACK TimerProc(HWND window, UINT message, UINT timer_id, + unsigned long elapsed_time); +#endif + bool test_started_; + // This flag is set to true in the context of the NPN_Evaluate call. + bool npn_evaluate_context_; + std::string self_url_; + +#if defined(OS_WIN) + HWND window_; +#endif +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_GET_JAVASCRIPT_URL_H diff --git a/webkit/glue/plugins/test/plugin_geturl_test.cc b/webkit/glue/plugins/test/plugin_geturl_test.cc new file mode 100644 index 0000000..5363a66 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_geturl_test.cc @@ -0,0 +1,414 @@ +// 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. + +#include "webkit/glue/plugins/test/plugin_geturl_test.h" + +#include <stdio.h> + +#include "base/basictypes.h" +#include "base/file_util.h" +#include "base/string_number_conversions.h" +#include "base/utf_string_conversions.h" + +// url for "self". The %22%22 is to make a statement for javascript to +// evaluate and return. +#define SELF_URL "javascript:window.location+\"\"" + +// The identifier for the self url stream. +#define SELF_URL_STREAM_ID 1 + +// The identifier for the fetched url stream. +#define FETCHED_URL_STREAM_ID 2 + +// url for testing GetURL with a bogus URL. +#define BOGUS_URL "bogoproto:///x:/asdf.xysdhffieasdf.asdhj/" + +// url for testing redirect notifications sent to plugins. +#define REDIRECT_SRC_URL \ + "http://mock.http/npapi/plugin_read_page_redirect_src.html" + +// The notification id for the redirect notification url. +#define REDIRECT_SRC_URL_NOTIFICATION_ID 4 + +// The identifier for the bogus url stream. +#define BOGUS_URL_STREAM_ID 3 + +// The maximum chunk size of stream data. +#define STREAM_CHUNK 197 + +namespace NPAPIClient { + +PluginGetURLTest::PluginGetURLTest(NPP id, NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions), + tests_started_(false), + tests_in_progress_(0), + test_file_(NULL), + expect_404_response_(false), + npn_evaluate_context_(false), + handle_url_redirects_(false), + received_url_redirect_notification_(false) { +} + +NPError PluginGetURLTest::New(uint16 mode, int16 argc, const char* argn[], + const char* argv[], NPSavedData* saved) { + const char* page_not_found_url = GetArgValue("page_not_found_url", argc, + argn, argv); + if (page_not_found_url) { + page_not_found_url_ = page_not_found_url; + expect_404_response_ = true; + } + + const char* fail_write_url = GetArgValue("fail_write_url", argc, + argn, argv); + if (fail_write_url) { + fail_write_url_ = fail_write_url; + } + + const char* referrer_target_url = GetArgValue("ref_target", argc, + argn, argv); + if (referrer_target_url) { + referrer_target_url_ = referrer_target_url; + } + + if (!base::strcasecmp(GetArgValue("name", argc, argn, argv), + "geturlredirectnotify")) { + handle_url_redirects_ = true; + } + return PluginTest::New(mode, argc, argn, argv, saved); +} + +NPError PluginGetURLTest::SetWindow(NPWindow* pNPWindow) { + if (pNPWindow->window == NULL) + return NPERR_NO_ERROR; + + if (!tests_started_) { + tests_started_ = true; + + tests_in_progress_++; + + if (expect_404_response_) { + HostFunctions()->geturl(id(), page_not_found_url_.c_str(), NULL); + return NPERR_NO_ERROR; + } else if (!fail_write_url_.empty()) { + HostFunctions()->geturl(id(), fail_write_url_.c_str(), NULL); + return NPERR_NO_ERROR; + } else if (!referrer_target_url_.empty()) { + HostFunctions()->pushpopupsenabledstate(id(), true); + HostFunctions()->geturl(id(), referrer_target_url_.c_str(), "_blank"); + HostFunctions()->poppopupsenabledstate(id()); + return NPERR_NO_ERROR; + } else if (handle_url_redirects_) { + HostFunctions()->geturlnotify( + id(), REDIRECT_SRC_URL, NULL, + reinterpret_cast<void*>(REDIRECT_SRC_URL_NOTIFICATION_ID)); + return NPERR_NO_ERROR; + } + + std::string url = SELF_URL; + HostFunctions()->geturlnotify(id(), url.c_str(), NULL, + reinterpret_cast<void*>(SELF_URL_STREAM_ID)); + + tests_in_progress_++; + std::string bogus_url = BOGUS_URL; + HostFunctions()->geturlnotify(id(), bogus_url.c_str(), NULL, + reinterpret_cast<void*>(BOGUS_URL_STREAM_ID)); + } + return NPERR_NO_ERROR; +} + +NPError PluginGetURLTest::NewStream(NPMIMEType type, NPStream* stream, + NPBool seekable, uint16* stype) { + if (stream == NULL) { + SetError("NewStream got null stream"); + return NPERR_INVALID_PARAM; + } + + if (test_completed()) { + return PluginTest::NewStream(type, stream, seekable, stype); + } + + if (!referrer_target_url_.empty()) { + return NPERR_NO_ERROR; + } + + COMPILE_ASSERT(sizeof(unsigned long) <= sizeof(stream->notifyData), + cast_validity_check); + + if (expect_404_response_) { + NPObject *window_obj = NULL; + HostFunctions()->getvalue(id(), NPNVWindowNPObject, &window_obj); + if (!window_obj) { + SetError("Failed to get NPObject for plugin instance2"); + SignalTestCompleted(); + return NPERR_NO_ERROR; + } + + std::string script = "javascript:alert('Hi there from plugin');"; + NPString script_string; + script_string.UTF8Characters = script.c_str(); + script_string.UTF8Length = static_cast<unsigned int>(script.length()); + NPVariant result_var; + + npn_evaluate_context_ = true; + HostFunctions()->evaluate(id(), window_obj, &script_string, &result_var); + npn_evaluate_context_ = false; + return NPERR_NO_ERROR; + } + + if (!fail_write_url_.empty()) { + return NPERR_NO_ERROR; + } + + + unsigned long stream_id = reinterpret_cast<unsigned long>( + stream->notifyData); + + switch (stream_id) { + case SELF_URL_STREAM_ID: + break; + case FETCHED_URL_STREAM_ID: + { + std::string filename = self_url_; + if (filename.find("file:///", 0) != 0) { + SetError("Test expects a file-url."); + break; + } + + // TODO(evanm): use the net:: functions to convert file:// URLs to + // on-disk file paths. But it probably doesn't actually matter in + // this test. + +#if defined(OS_WIN) + filename = filename.substr(8); // remove "file:///" + // Assume an ASCII path on Windows. + FilePath path = FilePath(ASCIIToWide(filename)); +#else + filename = filename.substr(7); // remove "file://" + FilePath path = FilePath(filename); +#endif + + test_file_ = file_util::OpenFile(path, "r"); + if (!test_file_) { + SetError("Could not open source file"); + } + } + break; + case BOGUS_URL_STREAM_ID: + SetError("Unexpected NewStream for BOGUS_URL"); + break; + case REDIRECT_SRC_URL_NOTIFICATION_ID: + SetError("Should not redirect to URL when plugin denied it."); + break; + default: + SetError("Unexpected NewStream callback"); + break; + } + return NPERR_NO_ERROR; +} + +int32 PluginGetURLTest::WriteReady(NPStream *stream) { + if (test_completed()) { + return PluginTest::WriteReady(stream); + } + + if (!referrer_target_url_.empty()) { + return STREAM_CHUNK; + } + + COMPILE_ASSERT(sizeof(unsigned long) <= sizeof(stream->notifyData), + cast_validity_check); + unsigned long stream_id = reinterpret_cast<unsigned long>( + stream->notifyData); + if (stream_id == BOGUS_URL_STREAM_ID) + SetError("Received WriteReady for BOGUS_URL"); + + return STREAM_CHUNK; +} + +int32 PluginGetURLTest::Write(NPStream *stream, int32 offset, int32 len, + void *buffer) { + if (test_completed()) { + return PluginTest::Write(stream, offset, len, buffer); + } + + if (!fail_write_url_.empty()) { + SignalTestCompleted(); + return -1; + } + + if (!referrer_target_url_.empty()) { + return len; + } + + if (stream == NULL) { + SetError("Write got null stream"); + return -1; + } + if (len < 0 || len > STREAM_CHUNK) { + SetError("Write got bogus stream chunk size"); + return -1; + } + + COMPILE_ASSERT(sizeof(unsigned long) <= sizeof(stream->notifyData), + cast_validity_check); + unsigned long stream_id = reinterpret_cast<unsigned long>( + stream->notifyData); + switch (stream_id) { + case SELF_URL_STREAM_ID: + self_url_.append(static_cast<char*>(buffer), len); + break; + case FETCHED_URL_STREAM_ID: + { + char read_buffer[STREAM_CHUNK]; + int32 bytes = fread(read_buffer, 1, len, test_file_); + // Technically, fread could return fewer than len + // bytes. But this is not likely. + if (bytes != len) + SetError("Did not read correct bytelength from source file"); + if (memcmp(read_buffer, buffer, len)) + SetError("Content mismatch between data and source!"); + } + break; + case BOGUS_URL_STREAM_ID: + SetError("Unexpected write callback for BOGUS_URL"); + break; + default: + SetError("Unexpected write callback"); + break; + } + // Pretend that we took all the data. + return len; +} + + +NPError PluginGetURLTest::DestroyStream(NPStream *stream, NPError reason) { + if (test_completed()) { + return PluginTest::DestroyStream(stream, reason); + } + + if (stream == NULL) { + SetError("NewStream got null stream"); + return NPERR_INVALID_PARAM; + } + + COMPILE_ASSERT(sizeof(unsigned long) <= sizeof(stream->notifyData), + cast_validity_check); + + if (expect_404_response_) { + if (npn_evaluate_context_) { + SetError("Received destroyStream in the context of NPN_Evaluate."); + } + + SignalTestCompleted(); + return NPERR_NO_ERROR; + } + + if (!referrer_target_url_.empty()) { + return NPERR_NO_ERROR; + } + + unsigned long stream_id = + reinterpret_cast<unsigned long>(stream->notifyData); + switch (stream_id) { + case SELF_URL_STREAM_ID: + // don't care + break; + case FETCHED_URL_STREAM_ID: + { + char read_buffer[STREAM_CHUNK]; + size_t bytes = fread(read_buffer, 1, sizeof(read_buffer), test_file_); + if (bytes != 0) + SetError("Data and source mismatch on length"); + file_util::CloseFile(test_file_); + } + break; + default: + SetError("Unexpected NewStream callback"); + break; + } + return NPERR_NO_ERROR; +} + +void PluginGetURLTest::StreamAsFile(NPStream* stream, const char* fname) { + if (stream == NULL) { + SetError("NewStream got null stream"); + return; + } + + COMPILE_ASSERT(sizeof(unsigned long) <= sizeof(stream->notifyData), + cast_validity_check); + unsigned long stream_id = + reinterpret_cast<unsigned long>(stream->notifyData); + switch (stream_id) { + case SELF_URL_STREAM_ID: + // don't care + break; + default: + SetError("Unexpected NewStream callback"); + break; + } +} + +void PluginGetURLTest::URLNotify(const char* url, NPReason reason, void* data) { + if (!tests_in_progress_) { + SetError("URLNotify received after tests completed"); + return; + } + + if (!url) { + SetError("URLNotify received NULL url"); + return; + } + + COMPILE_ASSERT(sizeof(unsigned long) <= sizeof(data), cast_validity_check); + unsigned long stream_id = reinterpret_cast<unsigned long>(data); + switch (stream_id) { + case SELF_URL_STREAM_ID: + if (strcmp(url, SELF_URL) != 0) + SetError("URLNotify reported incorrect url for SELF_URL"); + + // We have our stream url. Go fetch it. + HostFunctions()->geturlnotify(id(), self_url_.c_str(), NULL, + reinterpret_cast<void*>(FETCHED_URL_STREAM_ID)); + break; + case FETCHED_URL_STREAM_ID: + if (!url || strcmp(url, self_url_.c_str()) != 0) + SetError("URLNotify reported incorrect url for FETCHED_URL"); + tests_in_progress_--; + break; + case BOGUS_URL_STREAM_ID: + if (reason != NPRES_NETWORK_ERR) { + std::string err = "BOGUS_URL received unexpected URLNotify status: "; + err.append(base::IntToString(reason)); + SetError(err); + } + tests_in_progress_--; + break; + case REDIRECT_SRC_URL_NOTIFICATION_ID: { + if (!received_url_redirect_notification_) { + SetError("Failed to receive URLRedirect notification"); + } + tests_in_progress_--; + break; + } + default: + SetError("Unexpected NewStream callback"); + break; + } + + if (tests_in_progress_ == 0) + SignalTestCompleted(); +} + +void PluginGetURLTest::URLRedirectNotify(const char* url, + int32_t status, + void* notify_data) { + if (!base::strcasecmp(url, "http://mock.http/npapi/plugin_read_page.html")) { + received_url_redirect_notification_ = true; + // Disallow redirect notification. + HostFunctions()->urlredirectresponse(id(), notify_data, false); + } +} + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_geturl_test.h b/webkit/glue/plugins/test/plugin_geturl_test.h new file mode 100644 index 0000000..df8d741 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_geturl_test.h @@ -0,0 +1,61 @@ +// 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. + +#ifndef WEBKIT_PORT_PLUGINS_TEST_PLUGIN_GETURL_TEST_H__ +#define WEBKIT_PORT_PLUGINS_TEST_PLUGIN_GETURL_TEST_H__ + +#include <stdio.h> + +#include "webkit/glue/plugins/test/plugin_test.h" + +namespace NPAPIClient { + +// The PluginGetURLTest test functionality of the NPN_GetURL +// and NPN_GetURLNotify methods. +// +// This test first discovers it's URL by sending a GetURL request +// for 'javascript:top.location'. After receiving that, the +// test will request the url itself (again via GetURL). +class PluginGetURLTest : public PluginTest { + public: + // Constructor. + PluginGetURLTest(NPP id, NPNetscapeFuncs *host_functions); + + // + // NPAPI functions + // + virtual NPError New(uint16 mode, int16 argc, const char* argn[], + const char* argv[], NPSavedData* saved); + virtual NPError SetWindow(NPWindow* pNPWindow); + virtual NPError NewStream(NPMIMEType type, NPStream* stream, + NPBool seekable, uint16* stype); + virtual int32 WriteReady(NPStream *stream); + virtual int32 Write(NPStream *stream, int32 offset, int32 len, + void *buffer); + virtual NPError DestroyStream(NPStream *stream, NPError reason); + virtual void StreamAsFile(NPStream* stream, const char* fname); + virtual void URLNotify(const char* url, NPReason reason, void* data); + virtual void URLRedirectNotify(const char* url, int32_t status, + void* notify_data); + + private: + bool tests_started_; + int tests_in_progress_; + std::string self_url_; + FILE* test_file_; + bool expect_404_response_; + // This flag is set to true in the context of the NPN_Evaluate call. + bool npn_evaluate_context_; + // The following two flags handle URL redirect notifications received by + // plugins. + bool handle_url_redirects_; + bool received_url_redirect_notification_; + std::string page_not_found_url_; + std::string fail_write_url_; + std::string referrer_target_url_; +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_PORT_PLUGINS_TEST_PLUGIN_GETURL_TEST_H__ diff --git a/webkit/glue/plugins/test/plugin_javascript_open_popup.cc b/webkit/glue/plugins/test/plugin_javascript_open_popup.cc new file mode 100644 index 0000000..0f93bf4 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_javascript_open_popup.cc @@ -0,0 +1,103 @@ +// 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. + +#include "build/build_config.h" +#include "webkit/glue/plugins/test/plugin_javascript_open_popup.h" + +#if defined(USE_X11) +#include "third_party/npapi/bindings/npapi_x11.h" +#endif +#include "webkit/glue/plugins/test/plugin_client.h" + +namespace NPAPIClient { + +ExecuteJavascriptOpenPopupWithPluginTest:: + ExecuteJavascriptOpenPopupWithPluginTest(NPP id, + NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions), + popup_window_test_started_(false) { +} + +int16 ExecuteJavascriptOpenPopupWithPluginTest::SetWindow( + NPWindow* window) { + if (window->window == NULL) + return NPERR_NO_ERROR; + + if (!popup_window_test_started_) { + popup_window_test_started_ = true; + HostFunctions()->geturl( + id(), "popup_window_with_target_plugin.html", "_blank"); + } + return NPERR_NO_ERROR; +} + +// ExecuteJavascriptPopupWindowTargetPluginTest member defines. +ExecuteJavascriptPopupWindowTargetPluginTest:: + ExecuteJavascriptPopupWindowTargetPluginTest( + NPP id, NPNetscapeFuncs* host_functions) + : PluginTest(id, host_functions), + test_completed_(false) { +} + +int16 ExecuteJavascriptPopupWindowTargetPluginTest::SetWindow( + NPWindow* window) { + if (window->window == NULL) + return NPERR_NO_ERROR; + + if (!test_completed_) { + if (CheckWindow(window)) { + SignalTestCompleted(); + test_completed_ = true; + } + } + return PluginTest::SetWindow(window); +} + +#if defined(OS_WIN) +bool ExecuteJavascriptPopupWindowTargetPluginTest::CheckWindow( + NPWindow* window) { + HWND window_handle = reinterpret_cast<HWND>(window->window); + + if (IsWindow(window_handle)) { + HWND parent_window = GetParent(window_handle); + if (!IsWindow(parent_window) || parent_window == GetDesktopWindow()) + SetError("Windowed plugin instantiated with NULL parent"); + return true; + } + + return false; +} + +#elif defined(USE_X11) +// This code blindly follows the same sorts of verifications done on +// the Windows side. Does it make sense on X? Maybe not really, but +// it can't hurt to do extra validations. +bool ExecuteJavascriptPopupWindowTargetPluginTest::CheckWindow( + NPWindow* window) { + Window xwindow = reinterpret_cast<Window>(window->window); + // Grab a pointer to the extra SetWindow data so we can grab the display out. + NPSetWindowCallbackStruct* extra = + static_cast<NPSetWindowCallbackStruct*>(window->ws_info); + + if (xwindow) { + Window root, parent; + Status status = XQueryTree(extra->display, xwindow, &root, &parent, + NULL, NULL); // NULL children info. + DCHECK(status != 0); + if (!parent || parent == root) + SetError("Windowed plugin instantiated with NULL parent"); + return true; + } + + return false; +} +#elif defined(OS_MACOSX) +bool ExecuteJavascriptPopupWindowTargetPluginTest::CheckWindow( + NPWindow* window) { + // TODO(port) scaffolding--replace with a real test once NPWindow is done. + return false; +} +#endif + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_javascript_open_popup.h b/webkit/glue/plugins/test/plugin_javascript_open_popup.h new file mode 100644 index 0000000..552397a --- /dev/null +++ b/webkit/glue/plugins/test/plugin_javascript_open_popup.h @@ -0,0 +1,47 @@ +// 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. + +#ifndef WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_JAVASCRIPT_OPEN_POPUP_H +#define WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_JAVASCRIPT_OPEN_POPUP_H + +#include "webkit/glue/plugins/test/plugin_test.h" + +namespace NPAPIClient { + +// This class tests the case where a windowed plugin instance is +// instantiated in a popup window. The plugin instance needs to +// have a valid parent window. +class ExecuteJavascriptOpenPopupWithPluginTest : public PluginTest { + public: + // Constructor. + ExecuteJavascriptOpenPopupWithPluginTest( + NPP id, NPNetscapeFuncs *host_functions); + // NPAPI SetWindow handler. + virtual NPError SetWindow(NPWindow* window); + + private: + bool popup_window_test_started_; +}; + +// This class represents a windowed plugin instance instantiated within a +// popup window. It verifies that the plugin instance has a valid parent. +class ExecuteJavascriptPopupWindowTargetPluginTest : public PluginTest { + public: + ExecuteJavascriptPopupWindowTargetPluginTest( + NPP id, NPNetscapeFuncs *host_functions); + // NPAPI SetWindow handler. + virtual NPError SetWindow(NPWindow* window); + + private: + // Do a platform-specific validation of the passed-in |window|. + // E.g. on Windows, verifies window->window is a reasonable HWND. + // Returns true if the test should be marked complete. + bool CheckWindow(NPWindow* window); + + bool test_completed_; +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_JAVASCRIPT_OPEN_POPUP_H diff --git a/webkit/glue/plugins/test/plugin_new_fails_test.cc b/webkit/glue/plugins/test/plugin_new_fails_test.cc new file mode 100644 index 0000000..2feeec6 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_new_fails_test.cc @@ -0,0 +1,18 @@ +// 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. + +#include "webkit/glue/plugins/test/plugin_new_fails_test.h" + +namespace NPAPIClient { + +NewFailsTest::NewFailsTest(NPP id, NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions) { +} + +NPError NewFailsTest::New(uint16 mode, int16 argc, const char* argn[], + const char* argv[], NPSavedData* saved) { + return NPERR_GENERIC_ERROR; +} + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_new_fails_test.h b/webkit/glue/plugins/test/plugin_new_fails_test.h new file mode 100644 index 0000000..1acf9e5 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_new_fails_test.h @@ -0,0 +1,21 @@ +// 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. + +#ifndef WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_PLUGIN_NEW_FAILS_TEST_H__ +#define WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_PLUGIN_NEW_FAILS_TEST_H__ + +#include "webkit/glue/plugins/test/plugin_test.h" + +namespace NPAPIClient { + +class NewFailsTest : public PluginTest { + public: + NewFailsTest(NPP id, NPNetscapeFuncs *host_functions); + virtual NPError New(uint16 mode, int16 argc, const char* argn[], + const char* argv[], NPSavedData* saved); +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_PLUGIN_NPP_NEW_FAILS_TEST_H__ diff --git a/webkit/glue/plugins/test/plugin_npobject_lifetime_test.cc b/webkit/glue/plugins/test/plugin_npobject_lifetime_test.cc new file mode 100644 index 0000000..4564506 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_npobject_lifetime_test.cc @@ -0,0 +1,174 @@ +// 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. + +#include "webkit/glue/plugins/test/plugin_npobject_lifetime_test.h" + +namespace NPAPIClient { + +const int kNPObjectLifetimeTimer = 100; +const int kNPObjectLifetimeTimerElapse = 2000; + +NPObject* NPObjectLifetimeTestInstance2::plugin_instance_object_ = NULL; + +NPObjectDeletePluginInNPN_Evaluate* + NPObjectDeletePluginInNPN_Evaluate::g_npn_evaluate_test_instance_ = NULL; + +NPObjectLifetimeTest::NPObjectLifetimeTest(NPP id, + NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions), + other_plugin_instance_object_(NULL), + timer_id_(0) { +} + +NPError NPObjectLifetimeTest::SetWindow(NPWindow* pNPWindow) { + if (pNPWindow->window == NULL) + return NPERR_NO_ERROR; + + HWND window_handle = reinterpret_cast<HWND>(pNPWindow->window); + if (!::GetProp(window_handle, L"Plugin_Instance")) { + // TODO: this propery leaks. + ::SetProp(window_handle, L"Plugin_Instance", this); + // We attempt to retreive the NPObject for the plugin instance identified + // by the NPObjectLifetimeTestInstance2 class as it may not have been + // instantiated yet. + timer_id_ = SetTimer(window_handle, kNPObjectLifetimeTimer, + kNPObjectLifetimeTimerElapse, TimerProc); + } + return NPERR_NO_ERROR; +} + +void CALLBACK NPObjectLifetimeTest::TimerProc( + HWND window, UINT message, UINT timer_id, + unsigned long elapsed_milli_seconds) { + + NPObjectLifetimeTest* this_instance = + reinterpret_cast<NPObjectLifetimeTest*> + (::GetProp(window, L"Plugin_Instance")); + KillTimer(window, this_instance->timer_id_); + ::RemoveProp(window, L"Plugin_Instance"); + + this_instance->timer_id_ = 0; + + this_instance->other_plugin_instance_object_ = + NPObjectLifetimeTestInstance2::plugin_instance_object_; + this_instance->HostFunctions()->retainobject( + this_instance->other_plugin_instance_object_); + + NPString script; + script.UTF8Characters = "javascript:DeleteSecondPluginInstance()"; + script.UTF8Length = static_cast<uint32_t>(strlen(script.UTF8Characters)); + + this_instance->HostFunctions()->geturlnotify( + this_instance->id(), "javascript:DeleteSecondPluginInstance()", NULL, + reinterpret_cast<void*>(1)); +} + +void NPObjectLifetimeTest::URLNotify(const char* url, NPReason reason, + void* data) { + // Create a "location" identifier. + NPIdentifier identifier = HostFunctions()->getstringidentifier("location"); + // Declare a local variant value. + NPVariant variantValue; + // Get the location property from the window object (which is another object). + bool b1 = HostFunctions()->getproperty(id(), other_plugin_instance_object_, + identifier, &variantValue ); + HostFunctions()->releaseobject(other_plugin_instance_object_); + other_plugin_instance_object_ = NULL; + // If this test failed, then we'd have crashed by now. + SignalTestCompleted(); +} + +NPObjectLifetimeTestInstance2::NPObjectLifetimeTestInstance2( + NPP id, NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions) { +} + +NPObjectLifetimeTestInstance2::~NPObjectLifetimeTestInstance2() { + if (plugin_instance_object_) { + HostFunctions()->releaseobject(plugin_instance_object_); + plugin_instance_object_ = NULL; + } +} + +NPError NPObjectLifetimeTestInstance2::SetWindow(NPWindow* pNPWindow) { + if (pNPWindow->window == NULL) + return NPERR_NO_ERROR; + + if (!plugin_instance_object_) { + if (!HostFunctions()->getvalue(id(), NPNVWindowNPObject, + &plugin_instance_object_)) { + SetError("Failed to get NPObject for plugin instance2"); + SignalTestCompleted(); + return NPERR_GENERIC_ERROR; + } + } + + return NPERR_NO_ERROR; +} + + +NPObjectDeletePluginInNPN_Evaluate::NPObjectDeletePluginInNPN_Evaluate( + NPP id, NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions), + plugin_instance_object_(NULL), + timer_id_(0) { + g_npn_evaluate_test_instance_ = this; +} + +NPObjectDeletePluginInNPN_Evaluate::~NPObjectDeletePluginInNPN_Evaluate() { + if (plugin_instance_object_) { + HostFunctions()->releaseobject(plugin_instance_object_); + plugin_instance_object_ = NULL; + } +} + +NPError NPObjectDeletePluginInNPN_Evaluate::SetWindow(NPWindow* np_window) { + if (np_window->window == NULL) + return NPERR_NO_ERROR; + + HWND window_handle = reinterpret_cast<HWND>(np_window->window); + // We setup a timerproc to invoke NPN_Evaluate to destroy this plugin + // instance. This is to ensure that we don't destroy the plugin instance + // while it is being used in webkit as this leads to crashes and is a + // more accurate representation of the renderer crash as described in + // http://b/issue?id=1134683. + if (!timer_id_) { + timer_id_ = SetTimer(window_handle, kNPObjectLifetimeTimer, + kNPObjectLifetimeTimerElapse, TimerProc); + } + return NPERR_NO_ERROR; +} + +void CALLBACK NPObjectDeletePluginInNPN_Evaluate::TimerProc( + HWND window, UINT message, UINT timer_id, + unsigned long elapsed_milli_seconds) { + + KillTimer(window, g_npn_evaluate_test_instance_->timer_id_); + g_npn_evaluate_test_instance_->timer_id_ = 0; + NPObject *window_obj = NULL; + g_npn_evaluate_test_instance_->HostFunctions()->getvalue( + g_npn_evaluate_test_instance_->id(), NPNVWindowNPObject, + &window_obj); + + if (!window_obj) { + g_npn_evaluate_test_instance_->SetError( + "Failed to get NPObject for plugin instance2"); + g_npn_evaluate_test_instance_->SignalTestCompleted(); + return; + } + + std::string script = "javascript:DeletePluginWithinScript()"; + NPString script_string; + script_string.UTF8Characters = script.c_str(); + script_string.UTF8Length = + static_cast<unsigned int>(script.length()); + + NPVariant result_var; + bool result = g_npn_evaluate_test_instance_->HostFunctions()->evaluate( + g_npn_evaluate_test_instance_->id(), window_obj, + &script_string, &result_var); + // If this test failed we would have crashed by now. +} + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_npobject_lifetime_test.h b/webkit/glue/plugins/test/plugin_npobject_lifetime_test.h new file mode 100644 index 0000000..60d0314 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_npobject_lifetime_test.h @@ -0,0 +1,82 @@ +// 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. + +#ifndef WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_NPOBJECT_LIFETIME_TEST_H__ +#define WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_NPOBJECT_LIFETIME_TEST_H__ + +#include "build/build_config.h" +#include "webkit/glue/plugins/test/plugin_test.h" + +namespace NPAPIClient { + +// The NPObjectLifeTime class tests the case where a plugin has an NPObject +// which points to a different plugin instance on a different frame in the +// page and whether refcounts on this npobject are valid when the source frame +// is destroyed. +class NPObjectLifetimeTest : public PluginTest { + public: + // Constructor. + NPObjectLifetimeTest(NPP id, NPNetscapeFuncs *host_functions); + + // NPAPI SetWindow handler. + virtual NPError SetWindow(NPWindow* pNPWindow); + + virtual void URLNotify(const char* url, NPReason reason, void* data); + + protected: + NPObject* other_plugin_instance_object_; + +#if defined(OS_WIN) + static void CALLBACK TimerProc(HWND window, UINT message, UINT timer_id, + unsigned long elapsed_milli_seconds); + UINT_PTR timer_id_; +#endif + DISALLOW_IMPLICIT_CONSTRUCTORS(NPObjectLifetimeTest); +}; + +// The NPObjectLifetimeTestInstance2 class represents the plugin instance +// which is deleted by the NPObjectLifeTime class via a javascript function. +class NPObjectLifetimeTestInstance2 : public PluginTest { + public: + // Constructor. + NPObjectLifetimeTestInstance2(NPP id, NPNetscapeFuncs *host_functions); + ~NPObjectLifetimeTestInstance2(); + + // NPAPI SetWindow handler. + virtual NPError SetWindow(NPWindow* pNPWindow); + protected: + static NPObject* plugin_instance_object_; + friend class NPObjectLifetimeTest; + + DISALLOW_IMPLICIT_CONSTRUCTORS(NPObjectLifetimeTestInstance2); +}; + +// The NPObjectLifeTime class tests the case where a plugin instance is +// destroyed in NPN_Evaluate +class NPObjectDeletePluginInNPN_Evaluate : public PluginTest { + public: + // Constructor. + NPObjectDeletePluginInNPN_Evaluate(NPP id, NPNetscapeFuncs *host_functions); + ~NPObjectDeletePluginInNPN_Evaluate(); + + // NPAPI SetWindow handler. + virtual NPError SetWindow(NPWindow* pNPWindow); + + protected: + NPObject* plugin_instance_object_; +#if defined(OS_WIN) + static void CALLBACK TimerProc(HWND window, UINT message, UINT timer_id, + unsigned long elapsed_milli_seconds); + UINT_PTR timer_id_; +#endif + + private: + static NPObjectDeletePluginInNPN_Evaluate* g_npn_evaluate_test_instance_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(NPObjectDeletePluginInNPN_Evaluate); +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_NPOBJECT_LIFETIME_TEST_H__ diff --git a/webkit/glue/plugins/test/plugin_npobject_proxy_test.cc b/webkit/glue/plugins/test/plugin_npobject_proxy_test.cc new file mode 100644 index 0000000..5b3a2ca --- /dev/null +++ b/webkit/glue/plugins/test/plugin_npobject_proxy_test.cc @@ -0,0 +1,51 @@ +// 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. + +#include "base/basictypes.h" +#include "base/compiler_specific.h" + +#if defined(OS_WIN) +#define STRSAFE_NO_DEPRECATE +#include <strsafe.h> +#endif +#include "webkit/glue/plugins/test/plugin_npobject_proxy_test.h" + +namespace NPAPIClient { + +NPObjectProxyTest::NPObjectProxyTest(NPP id, NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions) { +} + +NPError NPObjectProxyTest::SetWindow(NPWindow* pNPWindow) { + if (pNPWindow->window == NULL) + return NPERR_NO_ERROR; + + NPIdentifier document_id = HostFunctions()->getstringidentifier("document"); + NPIdentifier create_text_node_id = HostFunctions()->getstringidentifier("createTextNode"); + NPIdentifier append_child_id = HostFunctions()->getstringidentifier("appendChild"); + + NPVariant docv; + NPObject *window_obj = NULL; + HostFunctions()->getvalue(id(), NPNVWindowNPObject, &window_obj); + + HostFunctions()->getproperty(id(), window_obj, document_id, &docv); + NPObject *doc = NPVARIANT_TO_OBJECT(docv); + + NPVariant strv; + MSVC_SUPPRESS_WARNING(4267); + STRINGZ_TO_NPVARIANT("div", strv); + + NPVariant textv; + HostFunctions()->invoke(id(), doc, create_text_node_id, &strv, 1, &textv); + + NPVariant v; + HostFunctions()->invoke(id(), doc, append_child_id, &textv, 1, &v); + + // If this test failed, then we'd have crashed by now. + SignalTestCompleted(); + + return NPERR_NO_ERROR; +} + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_npobject_proxy_test.h b/webkit/glue/plugins/test/plugin_npobject_proxy_test.h new file mode 100644 index 0000000..3d14ddb --- /dev/null +++ b/webkit/glue/plugins/test/plugin_npobject_proxy_test.h @@ -0,0 +1,27 @@ +// 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. + +#ifndef WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_NPOBJECT_PROXY_TEST_H__ +#define WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_NPOBJECT_PROXY_TEST_H__ + +#include "webkit/glue/plugins/test/plugin_test.h" + +namespace NPAPIClient { + +// The NPObjectProxyTest tests that when we proxy an NPObject that is itself +// a proxy, we don't create a new proxy but instead just use the original +// pointer. + +class NPObjectProxyTest : public PluginTest { + public: + // Constructor. + NPObjectProxyTest(NPP id, NPNetscapeFuncs *host_functions); + + // NPAPI SetWindow handler. + virtual NPError SetWindow(NPWindow* pNPWindow); +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_NPOBJECT_PROXY_TEST_H__ diff --git a/webkit/glue/plugins/test/plugin_private_test.cc b/webkit/glue/plugins/test/plugin_private_test.cc new file mode 100644 index 0000000..cdab7ce --- /dev/null +++ b/webkit/glue/plugins/test/plugin_private_test.cc @@ -0,0 +1,57 @@ +// Copyright (c) 2009 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. + +#include "webkit/glue/plugins/test/plugin_private_test.h" + +#include "base/basictypes.h" +#include "base/string_util.h" +#include "webkit/glue/plugins/test/plugin_client.h" + +namespace NPAPIClient { + +PrivateTest::PrivateTest(NPP id, NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions) { +} + +NPError PrivateTest::New(uint16 mode, int16 argc, + const char* argn[], const char* argv[], + NPSavedData* saved) { + PluginTest::New(mode, argc, argn, argv, saved); + + NPBool private_mode = 0; + NPNetscapeFuncs* browser = NPAPIClient::PluginClient::HostFunctions(); + NPError result = browser->getvalue( + id(), NPNVprivateModeBool, &private_mode); + if (result != NPERR_NO_ERROR) { + SetError("Failed to read NPNVprivateModeBool value."); + } else { + NPIdentifier location = HostFunctions()->getstringidentifier("location"); + NPIdentifier href = HostFunctions()->getstringidentifier("href"); + + NPObject *window_obj = NULL; + HostFunctions()->getvalue(id(), NPNVWindowNPObject, &window_obj); + + NPVariant location_var; + HostFunctions()->getproperty(id(), window_obj, location, &location_var); + + NPVariant href_var; + HostFunctions()->getproperty(id(), NPVARIANT_TO_OBJECT(location_var), href, + &href_var); + std::string href_str(href_var.value.stringValue.UTF8Characters, + href_var.value.stringValue.UTF8Length); + bool private_expected = href_str.find("?private") != href_str.npos; + if (private_mode != static_cast<NPBool>(private_expected)) + SetError("NPNVprivateModeBool returned incorrect value."); + + HostFunctions()->releasevariantvalue(&href_var); + HostFunctions()->releasevariantvalue(&location_var); + HostFunctions()->releaseobject(window_obj); + } + + SignalTestCompleted(); + + return NPERR_NO_ERROR; +} + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_private_test.h b/webkit/glue/plugins/test/plugin_private_test.h new file mode 100644 index 0000000..db6b5d1 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_private_test.h @@ -0,0 +1,25 @@ +// Copyright (c) 2009 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. + +#ifndef WEBKIT_PORT_PLUGINS_TEST_PLUGIN_PRIVATE_TEST_H_ +#define WEBKIT_PORT_PLUGINS_TEST_PLUGIN_PRIVATE_TEST_H_ + +#include "webkit/glue/plugins/test/plugin_test.h" + +namespace NPAPIClient { + +// The PluginPrivateTest tests that a plugin can query if the browser is in +// private browsing mode. +class PrivateTest : public PluginTest { + public: + PrivateTest(NPP id, NPNetscapeFuncs *host_functions); + + // Initialize this PluginTest based on the arguments from NPP_New. + virtual NPError New(uint16 mode, int16 argc, const char* argn[], + const char* argv[], NPSavedData* saved); +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_PORT_PLUGINS_TEST_PLUGIN_PRIVATE_TEST_H_ diff --git a/webkit/glue/plugins/test/plugin_schedule_timer_test.cc b/webkit/glue/plugins/test/plugin_schedule_timer_test.cc new file mode 100644 index 0000000..fbfce34 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_schedule_timer_test.cc @@ -0,0 +1,116 @@ +// 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. + +#include "webkit/glue/plugins/test/plugin_schedule_timer_test.h" +#include "webkit/glue/plugins/test/plugin_client.h" + +using base::Time; + +namespace NPAPIClient { + +// The times below are accurate but they are not tested against because it +// might make the test flakey. +ScheduleTimerTest::Event + ScheduleTimerTest::schedule_[ScheduleTimerTest::kNumEvents] = { + { 0, -1, 0, 100, false, -1 }, // schedule 0 100ms no-repeat + { 100, 0, 0, 200, false, -1 }, // schedule 0 200ms no-repeat + { 300, 0, 0, 100, true, -1 }, // schedule 0 100ms repeat + { 400, 0, 1, 50, true, -1 }, // schedule 1 50ms repeat + { 450, 1, -1, 0, true, -1 }, // receive 1 repeating + { 500, 0, -1, 0, true, -1 }, // receive 0 repeating + { 500, 1, -1, 0, true, -1 }, // receive 1 repeating + { 550, 1, -1, 0, true, -1 }, // receive 1 repeating + { 600, 0, -1, 0, true, 0 }, // receive 0 repeating and unschedule + { 600, 1, 2, 400, true, 1 }, // receive 1 repeating and unschedule + { 1000, 2, -1, 0, true, 2 }, // receive final and unschedule +}; + +ScheduleTimerTest::ScheduleTimerTest( + NPP id, NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions), + num_received_events_(0) { + for (int i = 0; i < kNumTimers; ++i) { + timer_ids_[i] = 0; + } + for (int i = 0; i < kNumEvents; ++i) { + received_events_[i] = false; + } +} + +NPError ScheduleTimerTest::New( + uint16 mode, int16 argc, const char* argn[], const char* argv[], + NPSavedData* saved) { + NPError error = PluginTest::New(mode, argc, argn, argv, saved); + if (error != NPERR_NO_ERROR) + return error; + + start_time_ = Time::Now(); + HandleEvent(0); + + return NPERR_NO_ERROR; +} + +void ScheduleTimerTest::OnTimer(uint32 timer_id) { + Time current_time = Time::Now(); + int relative_time = static_cast<int>( + (current_time - start_time_).InMilliseconds()); + + // See if there is a matching unreceived event. + int event_index = FindUnreceivedEvent(relative_time, timer_id); + if (event_index < 0) { + SetError("Received unexpected timer event"); + SignalTestCompleted(); + return; + } + + HandleEvent(event_index); + + // Finish test if all events have happened. + if (num_received_events_ == kNumEvents) + SignalTestCompleted(); +} + +int ScheduleTimerTest::FindUnreceivedEvent(int time, uint32 timer_id) { + for (int i = 0; i < kNumEvents; ++i) { + const Event& event = schedule_[i]; + if (!received_events_[i] && + timer_ids_[event.received_index] == timer_id) { + return i; + } + } + return -1; +} + +namespace { +void OnTimerHelper(NPP id, uint32 timer_id) { + ScheduleTimerTest* plugin_object = + static_cast<ScheduleTimerTest*>(id->pdata); + if (plugin_object) { + plugin_object->OnTimer(timer_id); + } +} +} + +void ScheduleTimerTest::HandleEvent(int event_index) { + const Event& event = schedule_[event_index]; + + // Mark event as received. + DCHECK(!received_events_[event_index]); + received_events_[event_index] = true; + ++num_received_events_; + + // Unschedule timer if present. + if (event.unscheduled_index >= 0) { + HostFunctions()->unscheduletimer( + id(), timer_ids_[event.unscheduled_index]); + } + + // Schedule timer if present. + if (event.scheduled_index >= 0) { + timer_ids_[event.scheduled_index] = HostFunctions()->scheduletimer( + id(), event.scheduled_interval, event.schedule_repeated, OnTimerHelper); + } +} + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_schedule_timer_test.h b/webkit/glue/plugins/test/plugin_schedule_timer_test.h new file mode 100644 index 0000000..e3e6505a --- /dev/null +++ b/webkit/glue/plugins/test/plugin_schedule_timer_test.h @@ -0,0 +1,68 @@ +// 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. + +#ifndef WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_SCHEDULE_TIMER_TEST_H +#define WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_SCHEDULE_TIMER_TEST_H + +#include "base/at_exit.h" +#include "base/time.h" +#include "webkit/glue/plugins/test/plugin_test.h" + +namespace NPAPIClient { + +// This class tests scheduling and unscheduling of timers using +// NPN_ScheduleTimer and NPN_UnscheduleTimer. +class ScheduleTimerTest : public PluginTest { + public: + ScheduleTimerTest(NPP id, NPNetscapeFuncs *host_functions); + + virtual NPError New(uint16 mode, int16 argc, const char* argn[], + const char* argv[], NPSavedData* saved); + + void OnTimer(uint32 timer_id); + + private: + // base::Time needs one of these. + base::AtExitManager at_exit_manager_; + + // Table mapping timer index (as used in event schedule) to timer id. + static const int kNumTimers = 3; + uint32 timer_ids_[kNumTimers]; + + // Schedule of events for test. + static const int kNumEvents = 11; + struct Event { + int time; + + // The index of the timer that triggered the event or -1 for the first + // event. + int received_index; + + // The index of the timer to schedule on this event or -1. + int scheduled_index; + + // Info about the timer to be scheduled (if any). + uint32 scheduled_interval; + bool schedule_repeated; + + // The index of the timer to unschedule on this event or -1. + int unscheduled_index; + }; + static Event schedule_[kNumEvents]; + int num_received_events_; + + // Set of events that have been received (by index). + bool received_events_[kNumEvents]; + + // Time of initial event. + base::Time start_time_; + + // Returns index of matching unreceived event or -1 if not found. + int FindUnreceivedEvent(int time, uint32 timer_id); + void HandleEvent(int event_index); +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_SCHEDULE_TIMER_TEST_H diff --git a/webkit/glue/plugins/test/plugin_setup_test.cc b/webkit/glue/plugins/test/plugin_setup_test.cc new file mode 100644 index 0000000..e4c4903 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_setup_test.cc @@ -0,0 +1,22 @@ +// 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. + +#include "base/basictypes.h" +#include "base/string_util.h" + +#include "webkit/glue/plugins/test/plugin_setup_test.h" + +namespace NPAPIClient { + +PluginSetupTest::PluginSetupTest(NPP id, NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions) { +} + +NPError PluginSetupTest::SetWindow(NPWindow* pNPWindow) { + this->SignalTestCompleted(); + + return NPERR_NO_ERROR; +} + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_setup_test.h b/webkit/glue/plugins/test/plugin_setup_test.h new file mode 100644 index 0000000..b01bc42 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_setup_test.h @@ -0,0 +1,24 @@ +// 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. + +#ifndef WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_SETUP_TEST_H__ +#define WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_SETUP_TEST_H__ + +#include "webkit/glue/plugins/test/plugin_test.h" + +namespace NPAPIClient { + +// A very simple test that just sets up a new plug-in. +class PluginSetupTest : public PluginTest { + public: + // Constructor. + PluginSetupTest(NPP id, NPNetscapeFuncs *host_functions); + + // NPAPI SetWindow handler. + virtual NPError SetWindow(NPWindow* pNPWindow); +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_SETUP_TEST_H__ diff --git a/webkit/glue/plugins/test/plugin_test.cc b/webkit/glue/plugins/test/plugin_test.cc new file mode 100644 index 0000000..6717e4b --- /dev/null +++ b/webkit/glue/plugins/test/plugin_test.cc @@ -0,0 +1,155 @@ +// 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. + +#include "webkit/glue/plugins/test/plugin_test.h" + +#include "base/string_util.h" +#include "webkit/glue/plugins/test/npapi_constants.h" + +namespace NPAPIClient { + +PluginTest::PluginTest(NPP id, NPNetscapeFuncs *host_functions) { + id_ = id; + id_->pdata = this; + host_functions_ = host_functions; + test_completed_ = false; +} + +NPError PluginTest::New(uint16 mode, int16 argc, const char* argn[], + const char* argv[], NPSavedData* saved) { + test_name_ = this->GetArgValue("name", argc, argn, argv); + test_id_ = this->GetArgValue("id", argc, argn, argv); + return NPERR_NO_ERROR; +} + +NPError PluginTest::Destroy() { + return NPERR_NO_ERROR; +} + +NPError PluginTest::SetWindow(NPWindow* pNPWindow) { + return NPERR_NO_ERROR; +} + +// It's a shame I have to implement URLEncode. But, using webkit's +// or using chrome's means a ball of string of dlls and dependencies that +// is very very long. After spending far too much time on it, +// I'll just encode it myself. Too bad Microsoft doesn't implement +// this in a reusable way either. Both webkit and chrome will +// end up using libicu, which is a string of dependencies we don't +// want. + +inline unsigned char toHex(const unsigned char x) { + return x > 9 ? (x + 'A' - 10) : (x + '0'); +} + +std::string URLEncode(const std::string &sIn) { + std::string sOut; + + const size_t length = sIn.length(); + for (size_t idx = 0; idx < length;) { + const char ch = sIn.at(idx); + if (isalnum(ch)) { + sOut.append(1, ch); + } else if (isspace(ch) && ((ch != '\n') && (ch != '\r'))) { + sOut.append(1, '+'); + } else { + sOut.append(1, '%'); + sOut.append(1, toHex(ch>>4)); + sOut.append(1, toHex(ch%16)); + } + idx++; + } + return sOut; +} + +void PluginTest::SignalTestCompleted() { + NPObject *window_obj = NULL; + host_functions_->getvalue(id_, NPNVWindowNPObject, &window_obj); + if (!window_obj) + return; + + test_completed_ = true; + // To signal test completion, we expect a couple of + // javascript functions to be defined in the webpage + // which hosts this plugin: + // onSuccess(test_name, test_id) + // onFailure(test_name, test_id, error_message) + std::string script("javascript:"); + if (Succeeded()) { + script.append("onSuccess(\""); + script.append(test_name_); + script.append("\",\""); + script.append(test_id_); + script.append("\");"); + } else { + script.append("onFailure(\""); + script.append(test_name_); + script.append("\",\""); + script.append(test_id_); + script.append("\",\""); + script.append(test_status_); + script.append("\");"); + } + + NPString script_string; + script_string.UTF8Characters = script.c_str(); + script_string.UTF8Length = static_cast<unsigned int>(script.length()); + + NPVariant result_var; + host_functions_->evaluate(id_, window_obj, &script_string, &result_var); +} + +const char *PluginTest::GetArgValue(const char *name, const int16 argc, + const char *argn[], const char *argv[]) { + for (int idx = 0; idx < argc; idx++) + if (base::strcasecmp(argn[idx], name) == 0) + return argv[idx]; + return NULL; +} + +void PluginTest::SetError(const std::string &msg) { + test_status_.append(msg); +} + +NPError PluginTest::NewStream(NPMIMEType type, NPStream* stream, + NPBool seekable, uint16* stype) { + // There is no default action here. + return NPERR_NO_ERROR; +} + +int32 PluginTest::WriteReady(NPStream *stream) { + // Take data in small chunks + return 4096; +} + +int32 PluginTest::Write(NPStream *stream, int32 offset, int32 len, + void *buffer) { + // Pretend that we took all the data. + return len; +} + +NPError PluginTest::DestroyStream(NPStream *stream, NPError reason) { + // There is no default action. + return NPERR_NO_ERROR; +} + +void PluginTest::StreamAsFile(NPStream* stream, const char* fname) { + // There is no default action. +} + +void PluginTest::URLNotify(const char* url, NPReason reason, void* data) { + // There is no default action +} + +int16 PluginTest::HandleEvent(void* event) { + // There is no default action + return 0; +} + +void PluginTest::URLRedirectNotify(const char* url, int32_t status, + void* notify_data) { + // There is no default action +} + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_test.h b/webkit/glue/plugins/test/plugin_test.h new file mode 100644 index 0000000..f3f8937 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_test.h @@ -0,0 +1,134 @@ +// 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. + +#ifndef WEBKIT_PORT_PLUGINS_TEST_PLUGIN_TEST_H_ +#define WEBKIT_PORT_PLUGINS_TEST_PLUGIN_TEST_H_ + +#include <string> + +#include "base/string_number_conversions.h" +#include "base/string_util.h" +#include "third_party/npapi/bindings/npapi.h" +#include "third_party/npapi/bindings/nphostapi.h" + +namespace NPAPIClient { + +// A PluginTest represents an instance of the plugin, which in +// our case is a test case. +class PluginTest { + public: + // Constructor. + PluginTest(NPP id, NPNetscapeFuncs *host_functions); + + // Destructor + virtual ~PluginTest() {} + + // Returns true if the test runs in windowless plugin mode. + virtual bool IsWindowless() const { return false; } + + // + // NPAPI Functions + // + virtual NPError New(uint16 mode, int16 argc, const char* argn[], + const char* argv[], NPSavedData* saved); + virtual NPError Destroy(); + virtual NPError SetWindow(NPWindow* pNPWindow); + virtual NPError NewStream(NPMIMEType type, NPStream* stream, + NPBool seekable, uint16* stype); + virtual int32 WriteReady(NPStream *stream); + virtual int32 Write(NPStream *stream, int32 offset, int32 len, + void *buffer); + virtual NPError DestroyStream(NPStream *stream, NPError reason); + virtual void StreamAsFile(NPStream* stream, const char* fname); + virtual void URLNotify(const char* url, NPReason reason, void* data); + virtual int16 HandleEvent(void* event); + virtual void URLRedirectNotify(const char* url, int32_t status, + void* notify_data); + // Returns true if the test has not had any errors. + bool Succeeded() { return test_status_.length() == 0; } + + // Sets an error for the test case. Appends the msg to the + // error that will be returned from the test. + void SetError(const std::string &msg); + + // Expect two string values are equal, and if not, logs an + // appropriate error about it. + void ExpectStringLowerCaseEqual(const std::string &val1, const std::string &val2) { + if (!LowerCaseEqualsASCII(val1, val2.c_str())) { + std::string err; + err = "Expected Equal for '"; + err.append(val1); + err.append("' and '"); + err.append(val2); + err.append("'"); + SetError(err); + } + }; + + // Expect two values to not be equal, and if they are + // logs an appropriate error about it. + void ExpectAsciiStringNotEqual(const char *val1, const char *val2) { + if (val1 == val2) { + std::string err; + err = "Expected Not Equal for '"; + err.append(val1); + err.append("' and '"); + err.append(val2); + err.append("'"); + SetError(err); + } + } + // Expect two integer values are equal, and if not, logs an + // appropriate error about it. + void ExpectIntegerEqual(int val1, int val2) { + if (val1 != val2) { + std::string err; + err = "Expected Equal for '"; + err.append(base::IntToString(val1)); + err.append("' and '"); + err.append(base::IntToString(val2)); + err.append("'"); + SetError(err); + } + } + + + protected: + // Signals to the Test that invoked us that the test is + // completed. This is done by forcing the plugin to + // set a cookie in the browser window, which the test program + // is waiting for. Note - because this is done by + // using javascript, the browser must have the frame + // setup before the plugin calls this function. So plugin + // tests MUST NOT call this function prior to having + // received the SetWindow() callback from the browser. + void SignalTestCompleted(); + + // Helper function to lookup names in the input array. + // If the name is found, returns the value, otherwise + // returns NULL. + const char *GetArgValue(const char *name, const int16 argc, + const char *argn[], const char *argv[]); + + // Access to the list of functions provided + // by the NPAPI host. + NPNetscapeFuncs *HostFunctions() { return host_functions_; } + + // The NPP Identifier for this plugin instance. + NPP id() { return id_; } + std::string test_id() const { return test_id_; } + std::string test_name() const { return test_name_; } + bool test_completed() const { return test_completed_; } + private: + NPP id_; + NPNetscapeFuncs * host_functions_; + std::string test_name_; + std::string test_id_; + std::string test_status_; + bool test_completed_; +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_PORT_PLUGINS_TEST_PLUGIN_TEST_H_ diff --git a/webkit/glue/plugins/test/plugin_test_factory.cc b/webkit/glue/plugins/test/plugin_test_factory.cc new file mode 100644 index 0000000..b4ae4f1 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_test_factory.cc @@ -0,0 +1,104 @@ +// 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. + +#include "webkit/glue/plugins/test/plugin_test_factory.h" + +#include "webkit/glue/plugins/test/plugin_arguments_test.h" +#include "webkit/glue/plugins/test/plugin_delete_plugin_in_stream_test.h" +#include "webkit/glue/plugins/test/plugin_get_javascript_url_test.h" +#include "webkit/glue/plugins/test/plugin_get_javascript_url2_test.h" +#include "webkit/glue/plugins/test/plugin_geturl_test.h" +#include "webkit/glue/plugins/test/plugin_javascript_open_popup.h" +#include "webkit/glue/plugins/test/plugin_new_fails_test.h" +#include "webkit/glue/plugins/test/plugin_npobject_lifetime_test.h" +#include "webkit/glue/plugins/test/plugin_npobject_proxy_test.h" +#include "webkit/glue/plugins/test/plugin_private_test.h" +#include "webkit/glue/plugins/test/plugin_schedule_timer_test.h" +#include "webkit/glue/plugins/test/plugin_setup_test.h" +#include "webkit/glue/plugins/test/plugin_thread_async_call_test.h" +#include "webkit/glue/plugins/test/plugin_window_size_test.h" +#if defined(OS_WIN) +#include "webkit/glue/plugins/test/plugin_windowed_test.h" +#endif +#include "webkit/glue/plugins/test/plugin_windowless_test.h" + +namespace NPAPIClient { + +PluginTest* CreatePluginTest(const std::string& test_name, + NPP instance, + NPNetscapeFuncs* host_functions) { + PluginTest* new_test = NULL; + + if (test_name == "arguments") { + new_test = new PluginArgumentsTest(instance, host_functions); + } else if (test_name == "geturl" || test_name == "geturl_404_response" || + test_name == "geturl_fail_write" || + test_name == "plugin_referrer_test" || + test_name == "geturlredirectnotify") { + new_test = new PluginGetURLTest(instance, host_functions); + } else if (test_name == "npobject_proxy") { + new_test = new NPObjectProxyTest(instance, host_functions); +#if defined(OS_WIN) || defined(OS_MACOSX) + // TODO(port): plugin_windowless_test.*. + } else if (test_name == "execute_script_delete_in_paint" || + test_name == "execute_script_delete_in_mouse_move" || + test_name == "delete_frame_test" || + test_name == "multiple_instances_sync_calls" || + test_name == "no_hang_if_init_crashes" || + test_name == "convert_point") { + new_test = new WindowlessPluginTest(instance, host_functions); +#endif + } else if (test_name == "getjavascripturl") { + new_test = new ExecuteGetJavascriptUrlTest(instance, host_functions); + } else if (test_name == "getjavascripturl2") { + new_test = new ExecuteGetJavascriptUrl2Test(instance, host_functions); +#if defined(OS_WIN) + // TODO(port): plugin_window_size_test.*. + } else if (test_name == "checkwindowrect") { + new_test = new PluginWindowSizeTest(instance, host_functions); +#endif + } else if (test_name == "self_delete_plugin_stream") { + new_test = new DeletePluginInStreamTest(instance, host_functions); +#if defined(OS_WIN) + // TODO(port): plugin_npobject_lifetime_test.*. + } else if (test_name == "npobject_lifetime_test") { + new_test = new NPObjectLifetimeTest(instance, host_functions); + } else if (test_name == "npobject_lifetime_test_second_instance") { + new_test = new NPObjectLifetimeTestInstance2(instance, host_functions); + } else if (test_name == "new_fails") { + new_test = new NewFailsTest(instance, host_functions); + } else if (test_name == "npobject_delete_plugin_in_evaluate" || + test_name == "npobject_delete_create_plugin_in_evaluate") { + new_test = new NPObjectDeletePluginInNPN_Evaluate(instance, host_functions); +#endif + } else if (test_name == "plugin_javascript_open_popup_with_plugin") { + new_test = new ExecuteJavascriptOpenPopupWithPluginTest( + instance, host_functions); + } else if (test_name == "plugin_popup_with_plugin_target") { + new_test = new ExecuteJavascriptPopupWindowTargetPluginTest( + instance, host_functions); + } else if (test_name == "plugin_thread_async_call") { + new_test = new PluginThreadAsyncCallTest(instance, host_functions); + } else if (test_name == "private") { + new_test = new PrivateTest(instance, host_functions); + } else if (test_name == "schedule_timer") { + new_test = new ScheduleTimerTest(instance, host_functions); +#if defined(OS_WIN) + // TODO(port): plugin_windowed_test.*. + } else if (test_name == "hidden_plugin" || + test_name == "create_instance_in_paint" || + test_name == "alert_in_window_message" || + test_name == "ensure_scripting_works_in_destroy" || + test_name == "invoke_js_function_on_create") { + new_test = new WindowedPluginTest(instance, host_functions); +#endif + } else if (test_name == "setup") { + // "plugin" is the name for plugin documents. + new_test = new PluginSetupTest(instance, host_functions); + } + + return new_test; +} + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_test_factory.h b/webkit/glue/plugins/test/plugin_test_factory.h new file mode 100644 index 0000000..3fd38d5 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_test_factory.h @@ -0,0 +1,22 @@ +// 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. + +#ifndef WEBKIT_PORT_PLUGINS_TEST_PLUGIN_TEST_FACTROY_H__ +#define WEBKIT_PORT_PLUGINS_TEST_PLUGIN_TEST_FACTROY_H__ + +#include <string> + +#include "third_party/npapi/bindings/nphostapi.h" + +namespace NPAPIClient { + +class PluginTest; + +extern PluginTest* CreatePluginTest(const std::string& test_name, + NPP instance, + NPNetscapeFuncs* host_functions); + +} // namespace NPAPIClient + +#endif // WEBKIT_PORT_PLUGINS_TEST_PLUGIN_TEST_FACTROY_H__ diff --git a/webkit/glue/plugins/test/plugin_thread_async_call_test.cc b/webkit/glue/plugins/test/plugin_thread_async_call_test.cc new file mode 100644 index 0000000..c01a49e --- /dev/null +++ b/webkit/glue/plugins/test/plugin_thread_async_call_test.cc @@ -0,0 +1,117 @@ +// 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. + +#include "webkit/glue/plugins/test/plugin_thread_async_call_test.h" + +#include "base/at_exit.h" +#include "base/message_loop.h" +#include "base/thread.h" +#include "webkit/glue/plugins/test/plugin_client.h" + +namespace NPAPIClient { + +namespace { + +// There are two plugin instances in this test. The long lived instance is used +// for reporting errors and signalling test completion. The short lived one is +// used to verify that async callbacks are not invoked after NPP_Destroy. +PluginThreadAsyncCallTest* g_short_lived_instance; +PluginThreadAsyncCallTest* g_long_lived_instance; + +void OnCallSucceededHelper(void* data) { + static_cast<PluginThreadAsyncCallTest*>(data)->OnCallSucceeded(); +} + +class AsyncCallTask : public Task { + public: + AsyncCallTask(PluginThreadAsyncCallTest* test_class) + : test_class_(test_class) {} + + void Run() { + test_class_->AsyncCall(); + } + + private: + PluginThreadAsyncCallTest* test_class_; +}; + +void OnCallFailed(void* data) { + g_long_lived_instance->SetError("Async callback invoked after NPP_Destroy"); +} + +void OnCallCompletedHelper(void* data) { + static_cast<PluginThreadAsyncCallTest*>(data)->OnCallCompleted(); +} +} + +PluginThreadAsyncCallTest::PluginThreadAsyncCallTest( + NPP id, NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions) { +} + +NPError PluginThreadAsyncCallTest::New( + uint16 mode, int16 argc, const char* argn[], const char* argv[], + NPSavedData* saved) { + NPError error = PluginTest::New(mode, argc, argn, argv, saved); + if (error != NPERR_NO_ERROR) + return error; + + // Determine whether this is the short lived instance. + for (int i = 0; i < argc; ++i) { + if (base::strcasecmp(argn[i], "short_lived") == 0) { + if (base::strcasecmp(argv[i], "true") == 0) { + g_short_lived_instance = this; + } else { + g_long_lived_instance = this; + } + } + } + + // Schedule an async call that will succeed. Make sure to call that API from + // a different thread to fully test it. + if (this == g_short_lived_instance) { + at_exit_manager_.reset(new base::AtExitManager()); + base::Thread random_thread("random_thread"); + random_thread.Start(); + random_thread.message_loop()->PostTask(FROM_HERE, new AsyncCallTask(this)); + } + + return NPERR_NO_ERROR; +} + +void PluginThreadAsyncCallTest::AsyncCall() { + HostFunctions()->pluginthreadasynccall(id(), OnCallSucceededHelper, this); +} + +void PluginThreadAsyncCallTest::OnCallSucceeded() { + // Delete the short lived instance. + NPIdentifier delete_id = HostFunctions()->getstringidentifier( + "deleteShortLivedInstance"); + + NPObject *window_obj = NULL; + HostFunctions()->getvalue(id(), NPNVWindowNPObject, &window_obj); + + NPVariant result; + HostFunctions()->invoke(id(), window_obj, delete_id, NULL, 0, &result); +} + +NPError PluginThreadAsyncCallTest::Destroy() { + if (this == g_short_lived_instance) { + // Schedule an async call that should not be called. + HostFunctions()->pluginthreadasynccall(id(), OnCallFailed, NULL); + + // Schedule an async call to end the test using the long lived instance. + HostFunctions()->pluginthreadasynccall(g_long_lived_instance->id(), + OnCallCompletedHelper, + g_long_lived_instance); + } + + return NPERR_NO_ERROR; +} + +void PluginThreadAsyncCallTest::OnCallCompleted() { + SignalTestCompleted(); +} + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_thread_async_call_test.h b/webkit/glue/plugins/test/plugin_thread_async_call_test.h new file mode 100644 index 0000000..78e4e8d --- /dev/null +++ b/webkit/glue/plugins/test/plugin_thread_async_call_test.h @@ -0,0 +1,39 @@ +// 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. + +#ifndef WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_THREAD_ASYNC_CALL_TEST_H_ +#define WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_THREAD_ASYNC_CALL_TEST_H_ + +#include "base/scoped_ptr.h" +#include "webkit/glue/plugins/test/plugin_test.h" + +namespace base { +class AtExitManager; +} + +namespace NPAPIClient { + +// This class tests scheduling and unscheduling of async callbacks using +// NPN_PluginThreadAsyncCall. +class PluginThreadAsyncCallTest : public PluginTest { + public: + PluginThreadAsyncCallTest(NPP id, NPNetscapeFuncs *host_functions); + + virtual NPError New(uint16 mode, int16 argc, const char* argn[], + const char* argv[], NPSavedData* saved); + + virtual NPError Destroy(); + + void AsyncCall(); + void OnCallSucceeded(); + void OnCallCompleted(); + + private: + // base::Thread needs one of these. + scoped_ptr<base::AtExitManager> at_exit_manager_; +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_THREAD_ASYNC_CALL_TEST_H_ diff --git a/webkit/glue/plugins/test/plugin_window_size_test.cc b/webkit/glue/plugins/test/plugin_window_size_test.cc new file mode 100644 index 0000000..9bfabca --- /dev/null +++ b/webkit/glue/plugins/test/plugin_window_size_test.cc @@ -0,0 +1,55 @@ +// 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. + +#include "webkit/glue/plugins/test/plugin_window_size_test.h" +#include "webkit/glue/plugins/test/plugin_client.h" + +namespace NPAPIClient { + +PluginWindowSizeTest::PluginWindowSizeTest(NPP id, + NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions) { +} + +NPError PluginWindowSizeTest::SetWindow(NPWindow* pNPWindow) { + if (pNPWindow->window == NULL) + return NPERR_NO_ERROR; + + HWND window = reinterpret_cast<HWND>(pNPWindow->window); + if (!pNPWindow || !::IsWindow(window)) { + SetError("Invalid arguments passed in"); + return NPERR_INVALID_PARAM; + } + + RECT window_rect = {0}; + window_rect.left = pNPWindow->x; + window_rect.top = pNPWindow->y; + window_rect.right = pNPWindow->width; + window_rect.bottom = pNPWindow->height; + + if (!::IsRectEmpty(&window_rect)) { + RECT client_rect = {0}; + ::GetClientRect(window, &client_rect); + if (::IsRectEmpty(&client_rect)) { + SetError("The client rect of the plugin window is empty. Test failed"); + } + + // Bug 6742: ensure that the coordinates passed in are relative to the + // parent HWND. + POINT origin_from_os; + RECT window_rect_from_os; + ::GetWindowRect(window, &window_rect_from_os); + origin_from_os.x = window_rect_from_os.left; + origin_from_os.y = window_rect_from_os.top; + ::ScreenToClient(GetParent(window), &origin_from_os); + if (origin_from_os.x != pNPWindow->x || origin_from_os.y != pNPWindow->y) + SetError("Wrong position passed in to SetWindow! Test failed"); + + SignalTestCompleted(); + } + + return NPERR_NO_ERROR; +} + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_window_size_test.h b/webkit/glue/plugins/test/plugin_window_size_test.h new file mode 100644 index 0000000..3650671 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_window_size_test.h @@ -0,0 +1,24 @@ +// 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. + +#ifndef WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_WINDOW_SIZE_TEST_H +#define WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_WINDOW_SIZE_TEST_H + +#include "webkit/glue/plugins/test/plugin_test.h" + +namespace NPAPIClient { + +// This class tests whether the plugin window has a non zero rect +// on the second SetWindow call. +class PluginWindowSizeTest : public PluginTest { + public: + // Constructor. + PluginWindowSizeTest(NPP id, NPNetscapeFuncs *host_functions); + // NPAPI SetWindow handler + virtual NPError SetWindow(NPWindow* pNPWindow); +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_WINDOW_SIZE_TEST_H diff --git a/webkit/glue/plugins/test/plugin_windowed_test.cc b/webkit/glue/plugins/test/plugin_windowed_test.cc new file mode 100644 index 0000000..c82aa55 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_windowed_test.cc @@ -0,0 +1,150 @@ +// Copyright (c) 2009 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. + +#include "webkit/glue/plugins/test/plugin_windowed_test.h" +#include "webkit/glue/plugins/test/plugin_client.h" + +namespace NPAPIClient { + +WindowedPluginTest::WindowedPluginTest(NPP id, NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions), + window_(NULL), done_(false) { +} + +WindowedPluginTest::~WindowedPluginTest() { + if (window_) + DestroyWindow(window_); +} + +NPError WindowedPluginTest::SetWindow(NPWindow* pNPWindow) { + if (pNPWindow->window == NULL) + return NPERR_NO_ERROR; + + if (test_name() == "create_instance_in_paint" && test_id() == "2") { + SignalTestCompleted(); + return NPERR_NO_ERROR; + } + + if (window_) + return NPERR_NO_ERROR; + + HWND parent = reinterpret_cast<HWND>(pNPWindow->window); + if (!pNPWindow || !::IsWindow(parent)) { + SetError("Invalid arguments passed in"); + return NPERR_INVALID_PARAM; + } + + if ((test_name() == "create_instance_in_paint" && test_id() == "1") || + test_name() == "alert_in_window_message" || + test_name() == "invoke_js_function_on_create") { + static ATOM window_class = 0; + if (!window_class) { + WNDCLASSEX wcex; + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.style = CS_DBLCLKS; + wcex.lpfnWndProc = &NPAPIClient::WindowedPluginTest::WindowProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = GetModuleHandle(NULL); + wcex.hIcon = 0; + wcex.hCursor = 0; + wcex.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW+1); + wcex.lpszMenuName = 0; + wcex.lpszClassName = L"CreateInstanceInPaintTestWindowClass"; + wcex.hIconSm = 0; + window_class = RegisterClassEx(&wcex); + } + + window_ = CreateWindowEx( + WS_EX_LEFT | WS_EX_LTRREADING | WS_EX_RIGHTSCROLLBAR, + MAKEINTATOM(window_class), 0, + WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE , + 0, 0, 100, 100, parent, 0, GetModuleHandle(NULL), 0); + DCHECK(window_); + // TODO: this propery leaks. + ::SetProp(window_, L"Plugin_Instance", this); + } + + return NPERR_NO_ERROR; +} + +NPError WindowedPluginTest::Destroy() { + if (test_name() != "ensure_scripting_works_in_destroy") + return NPERR_NO_ERROR; + + // Bug 23706: ensure that scripting works with no asserts. + NPObject *window_obj = NULL; + HostFunctions()->getvalue(id(), NPNVWindowNPObject,&window_obj); + + if (!window_obj) { + SetError("Failed to get NPObject for plugin instance"); + } else { + std::string script = "javascript:GetMagicNumber()"; + NPString script_string; + script_string.UTF8Characters = script.c_str(); + script_string.UTF8Length = + static_cast<unsigned int>(script.length()); + + NPVariant result_var; + bool result = HostFunctions()->evaluate( + id(), window_obj, &script_string, &result_var); + if (!result || + result_var.type != NPVariantType_Double || + result_var.value.doubleValue != 42.0) { + SetError("Failed to script during NPP_Destroy"); + } + } + + SignalTestCompleted(); + return NPERR_NO_ERROR; +} + +void WindowedPluginTest::CallJSFunction( + WindowedPluginTest* this_ptr, const char* function) { + NPIdentifier function_id = this_ptr->HostFunctions()->getstringidentifier( + function); + + NPObject *window_obj = NULL; + this_ptr->HostFunctions()->getvalue( + this_ptr->id(), NPNVWindowNPObject, &window_obj); + + NPVariant rv; + this_ptr->HostFunctions()->invoke( + this_ptr->id(), window_obj, function_id, NULL, 0, &rv); +} + +LRESULT CALLBACK WindowedPluginTest::WindowProc( + HWND window, UINT message, WPARAM wparam, LPARAM lparam) { + WindowedPluginTest* this_ptr = + reinterpret_cast<WindowedPluginTest*> + (::GetProp(window, L"Plugin_Instance")); + + if (this_ptr && !this_ptr->done_) { + if (this_ptr->test_name() == "create_instance_in_paint" && + message == WM_PAINT) { + this_ptr->done_ = true; + CallJSFunction(this_ptr, "CreateNewInstance"); + } else if (this_ptr->test_name() == "alert_in_window_message" && + message == WM_PAINT) { + this_ptr->done_ = true; + // We call this function twice as we want to display two alerts + // and verify that we don't hang the browser. + CallJSFunction(this_ptr, "CallAlert"); + CallJSFunction(this_ptr, "CallAlert"); + } else if (this_ptr->test_name() == + "invoke_js_function_on_create" && + message == WM_PAINT) { + this_ptr->done_ = true; + CallJSFunction(this_ptr, "PluginCreated"); + } + + if (this_ptr->done_) { + ::RemoveProp(window, L"Plugin_Instance"); + } + } + + return DefWindowProc(window, message, wparam, lparam); +} + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_windowed_test.h b/webkit/glue/plugins/test/plugin_windowed_test.h new file mode 100644 index 0000000..949ea86 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_windowed_test.h @@ -0,0 +1,33 @@ +// Copyright (c) 2009 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. + +#ifndef WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_WINDOWED_TEST_H +#define WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_WINDOWED_TEST_H + +#include "webkit/glue/plugins/test/plugin_test.h" + +namespace NPAPIClient { + +// This class contains a list of windowed plugin tests. Please add additional +// tests to this class. +class WindowedPluginTest : public PluginTest { + public: + WindowedPluginTest(NPP id, NPNetscapeFuncs *host_functions); + ~WindowedPluginTest(); + + private: + static LRESULT CALLBACK WindowProc( + HWND window, UINT message, WPARAM wparam, LPARAM lparam); + static void CallJSFunction(WindowedPluginTest*, const char*); + + virtual NPError SetWindow(NPWindow* pNPWindow); + virtual NPError Destroy(); + + HWND window_; + bool done_; +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_WINDOWED_TEST_H diff --git a/webkit/glue/plugins/test/plugin_windowless_test.cc b/webkit/glue/plugins/test/plugin_windowless_test.cc new file mode 100644 index 0000000..aa6a9d7 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_windowless_test.cc @@ -0,0 +1,261 @@ +// 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. + +#define STRSAFE_NO_DEPRECATE +#include "base/string_number_conversions.h" +#include "base/string_util.h" +#include "webkit/glue/plugins/test/plugin_windowless_test.h" +#include "webkit/glue/plugins/test/plugin_client.h" + +#if defined(OS_MACOSX) +#include <ApplicationServices/ApplicationServices.h> +#include <Carbon/Carbon.h> +#endif + +namespace NPAPIClient { + +// Remember the first plugin instance for tests involving multiple instances +WindowlessPluginTest* g_other_instance = NULL; + +WindowlessPluginTest::WindowlessPluginTest(NPP id, + NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions) { + if (!g_other_instance) + g_other_instance = this; +} + +static bool IsPaintEvent(NPEvent* np_event) { +#if defined(OS_WIN) + return WM_PAINT == np_event->event; +#elif defined(OS_MACOSX) + return np_event->what == updateEvt; +#endif +} + +static bool IsMouseMoveEvent(NPEvent* np_event) { +#if defined(OS_WIN) + return WM_MOUSEMOVE == np_event->event; +#elif defined(OS_MACOSX) + return np_event->what == nullEvent; +#endif +} + +static bool IsMouseUpEvent(NPEvent* np_event) { +#if defined(OS_WIN) + return WM_LBUTTONUP == np_event->event; +#elif defined(OS_MACOSX) + return np_event->what == mouseUp; +#endif +} + +static bool IsWindowActivationEvent(NPEvent* np_event) { +#if defined(OS_WIN) + NOTIMPLEMENTED(); + return false; +#elif defined(OS_MACOSX) + return np_event->what == activateEvt; +#endif +} + +int16 WindowlessPluginTest::HandleEvent(void* event) { + + NPNetscapeFuncs* browser = NPAPIClient::PluginClient::HostFunctions(); + + NPBool supports_windowless = 0; + NPError result = browser->getvalue(id(), NPNVSupportsWindowless, + &supports_windowless); + if ((result != NPERR_NO_ERROR) || (supports_windowless != TRUE)) { + SetError("Failed to read NPNVSupportsWindowless value"); + SignalTestCompleted(); + return PluginTest::HandleEvent(event); + } + + NPEvent* np_event = reinterpret_cast<NPEvent*>(event); + if (IsPaintEvent(np_event)) { +#if defined(OS_WIN) + HDC paint_dc = reinterpret_cast<HDC>(np_event->wParam); + if (paint_dc == NULL) { + SetError("Invalid Window DC passed to HandleEvent for WM_PAINT"); + SignalTestCompleted(); + return NPERR_GENERIC_ERROR; + } + + HRGN clipping_region = CreateRectRgn(0, 0, 0, 0); + if (!GetClipRgn(paint_dc, clipping_region)) { + SetError("No clipping region set in window DC"); + DeleteObject(clipping_region); + SignalTestCompleted(); + return NPERR_GENERIC_ERROR; + } + + DeleteObject(clipping_region); +#endif + + if (test_name() == "execute_script_delete_in_paint") { + ExecuteScriptDeleteInPaint(browser); + } else if (test_name() == "multiple_instances_sync_calls") { + MultipleInstanceSyncCalls(browser); + } +#if OS_MACOSX + } else if (IsWindowActivationEvent(np_event) && + test_name() == "convert_point") { + ConvertPoint(browser); +#endif + } else if (IsMouseMoveEvent(np_event) && + test_name() == "execute_script_delete_in_mouse_move") { + ExecuteScript(browser, id(), "DeletePluginWithinScript();", NULL); + SignalTestCompleted(); + } else if (IsMouseUpEvent(np_event) && + test_name() == "delete_frame_test") { + ExecuteScript( + browser, id(), + "parent.document.getElementById('frame').outerHTML = ''", NULL); + } + // If this test failed, then we'd have crashed by now. + return PluginTest::HandleEvent(event); +} + +NPError WindowlessPluginTest::ExecuteScript(NPNetscapeFuncs* browser, NPP id, + const std::string& script, NPVariant* result) { + std::string script_url = "javascript:"; + script_url += script; + + NPString script_string = { script_url.c_str(), script_url.length() }; + NPObject *window_obj = NULL; + browser->getvalue(id, NPNVWindowNPObject, &window_obj); + + NPVariant unused_result; + if (!result) + result = &unused_result; + + return browser->evaluate(id, window_obj, &script_string, result); +} + +void WindowlessPluginTest::ExecuteScriptDeleteInPaint( + NPNetscapeFuncs* browser) { + const NPUTF8* urlString = "javascript:DeletePluginWithinScript()"; + const NPUTF8* targetString = NULL; + browser->geturl(id(), urlString, targetString); + SignalTestCompleted(); +} + +void WindowlessPluginTest::MultipleInstanceSyncCalls(NPNetscapeFuncs* browser) { + if (this == g_other_instance) + return; + + DCHECK(g_other_instance); + ExecuteScript(browser, g_other_instance->id(), "TestCallback();", NULL); + SignalTestCompleted(); +} + +#if defined(OS_MACOSX) +std::string StringForPoint(int x, int y) { + std::string point_string("("); + point_string.append(base::IntToString(x)); + point_string.append(", "); + point_string.append(base::IntToString(y)); + point_string.append(")"); + return point_string; +} +#endif + +void WindowlessPluginTest::ConvertPoint(NPNetscapeFuncs* browser) { +#if defined(OS_MACOSX) + // First, just sanity-test that round trips work. + NPCoordinateSpace spaces[] = { NPCoordinateSpacePlugin, + NPCoordinateSpaceWindow, + NPCoordinateSpaceFlippedWindow, + NPCoordinateSpaceScreen, + NPCoordinateSpaceFlippedScreen }; + for (unsigned int i = 0; i < arraysize(spaces); ++i) { + for (unsigned int j = 0; j < arraysize(spaces); ++j) { + double x, y, round_trip_x, round_trip_y; + if (!(browser->convertpoint(id(), 0, 0, spaces[i], &x, &y, spaces[j])) || + !(browser->convertpoint(id(), x, y, spaces[j], &round_trip_x, + &round_trip_y, spaces[i]))) { + SetError("Conversion failed"); + SignalTestCompleted(); + return; + } + if (i != j && x == 0 && y == 0) { + SetError("Converting a coordinate should change it"); + SignalTestCompleted(); + return; + } + if (round_trip_x != 0 || round_trip_y != 0) { + SetError("Round-trip conversion should return the original point"); + SignalTestCompleted(); + return; + } + } + } + + // Now, more extensive testing on a single point. + double screen_x, screen_y; + browser->convertpoint(id(), 0, 0, NPCoordinateSpacePlugin, + &screen_x, &screen_y, NPCoordinateSpaceScreen); + double flipped_screen_x, flipped_screen_y; + browser->convertpoint(id(), 0, 0, NPCoordinateSpacePlugin, + &flipped_screen_x, &flipped_screen_y, + NPCoordinateSpaceFlippedScreen); + double window_x, window_y; + browser->convertpoint(id(), 0, 0, NPCoordinateSpacePlugin, + &window_x, &window_y, NPCoordinateSpaceWindow); + double flipped_window_x, flipped_window_y; + browser->convertpoint(id(), 0, 0, NPCoordinateSpacePlugin, + &flipped_window_x, &flipped_window_y, + NPCoordinateSpaceFlippedWindow); + + CGRect main_display_bounds = CGDisplayBounds(CGMainDisplayID()); + + // Check that all the coordinates are right. The constants below are based on + // the window frame set in the UI test and the content offset in the test + // html. Y-coordinates are not checked exactly so that the test is robust + // against toolbar changes, info and bookmark bar visibility, etc. + const int kWindowHeight = 400; + const int kWindowXOrigin = 50; + const int kWindowYOrigin = 50; + const int kPluginXContentOffset = 50; + const int kPluginYContentOffset = 50; + const int kChromeYTolerance = 200; + + std::string error_string; + if (screen_x != flipped_screen_x) + error_string = "Flipping screen coordinates shouldn't change x"; + else if (flipped_screen_y != main_display_bounds.size.height - screen_y) + error_string = "Flipped screen coordinates should be flipped vertically"; + else if (screen_x != kWindowXOrigin + kPluginXContentOffset) + error_string = "Screen x location is wrong"; + else if (flipped_screen_y < kWindowYOrigin + kPluginYContentOffset || + flipped_screen_y > kWindowYOrigin + kPluginYContentOffset + + kChromeYTolerance) + error_string = "Screen y location is wrong"; + else if (window_x != flipped_window_x) + error_string = "Flipping window coordinates shouldn't change x"; + else if (flipped_window_y != kWindowHeight - window_y) + error_string = "Flipped window coordinates should be flipped vertically"; + else if (window_x != kPluginXContentOffset) + error_string = "Window x location is wrong"; + else if (flipped_window_y < kPluginYContentOffset || + flipped_window_y > kPluginYContentOffset + kChromeYTolerance) + error_string = "Window y location is wrong"; + + if (!error_string.empty()) { + error_string.append(" - "); + error_string.append(StringForPoint(screen_x, screen_y)); + error_string.append(" - "); + error_string.append(StringForPoint(flipped_screen_x, flipped_screen_y)); + error_string.append(" - "); + error_string.append(StringForPoint(window_x, window_y)); + error_string.append(" - "); + error_string.append(StringForPoint(flipped_window_x, flipped_window_y)); + SetError(error_string); + } +#else + SetError("Unimplemented"); +#endif + SignalTestCompleted(); +} + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_windowless_test.h b/webkit/glue/plugins/test/plugin_windowless_test.h new file mode 100644 index 0000000..f336653 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_windowless_test.h @@ -0,0 +1,35 @@ +// 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. + +#ifndef WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_EXECUTE_SCRIPT_DELETE_TEST_H +#define WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_EXECUTE_SCRIPT_DELETE_TEST_H + +#include "webkit/glue/plugins/test/plugin_test.h" + +namespace NPAPIClient { + +// This class contains a list of windowless plugin tests. Please add additional +// tests to this class. +class WindowlessPluginTest : public PluginTest { + public: + // Constructor. + WindowlessPluginTest(NPP id, NPNetscapeFuncs *host_functions); + + // These tests run in windowless plugin mode. + virtual bool IsWindowless() const { return true; } + + // NPAPI HandleEvent handler + virtual int16 HandleEvent(void* event); + + protected: + NPError ExecuteScript(NPNetscapeFuncs* browser, NPP id, + const std::string& script, NPVariant* result); + void ExecuteScriptDeleteInPaint(NPNetscapeFuncs* browser); + void MultipleInstanceSyncCalls(NPNetscapeFuncs* browser); + void ConvertPoint(NPNetscapeFuncs* browser); +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_EXECUTE_SCRIPT_DELETE_TEST_H diff --git a/webkit/glue/plugins/test/resource.h b/webkit/glue/plugins/test/resource.h new file mode 100644 index 0000000..c52fa82 --- /dev/null +++ b/webkit/glue/plugins/test/resource.h @@ -0,0 +1,15 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by npapi_test.rc +// + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/webkit/glue/plugins/url_request_info_unittest.cc b/webkit/glue/plugins/url_request_info_unittest.cc new file mode 100644 index 0000000..341eeb2 --- /dev/null +++ b/webkit/glue/plugins/url_request_info_unittest.cc @@ -0,0 +1,249 @@ +// 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. + +#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrameClient.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLRequest.h" +#include "third_party/WebKit/WebKit/chromium/public/WebView.h" + +#include "webkit/plugins/ppapi/ppapi_plugin_instance.h" +#include "webkit/plugins/ppapi/ppb_url_request_info_impl.h" +#include "webkit/plugins/ppapi/ppapi_unittest.h" + +using WebKit::WebCString; +using WebKit::WebFrame; +using WebKit::WebFrameClient; +using WebKit::WebString; +using WebKit::WebView; +using WebKit::WebURL; +using WebKit::WebURLRequest; + +namespace { + +bool IsExpected(const WebCString& web_string, const char* expected) { + const char* result = web_string.data(); + return strcmp(result, expected) == 0; +} + +bool IsExpected(const WebString& web_string, const char* expected) { + return IsExpected(web_string.utf8(), expected); +} + +bool IsNullOrEmpty(const WebString& web_string) { + return web_string.isNull() || web_string.isEmpty(); +} + +// The base class destructor is protected, so derive. +class TestWebFrameClient : public WebFrameClient { +}; + +} // namespace + +namespace webkit { +namespace ppapi { + +class URLRequestInfoTest : public PpapiUnittest { + public: + URLRequestInfoTest() : info_(new PPB_URLRequestInfo_Impl(module())) { + } + + static void SetUpTestCase() { + web_view_ = WebView::create(0, 0); + web_view_->initializeMainFrame(&web_frame_client_); + WebURL web_url(GURL("")); + WebURLRequest url_request; + url_request.initialize(); + url_request.setURL(web_url); + frame_ = web_view_->mainFrame(); + frame_->loadRequest(url_request); + } + + static void TearDownTestCase() { + web_view_->close(); + } + + bool GetDownloadToFile() { + WebURLRequest web_request = info_->ToWebURLRequest(frame_); + return web_request.downloadToFile(); + } + + WebCString GetURL() { + WebURLRequest web_request = info_->ToWebURLRequest(frame_); + return web_request.url().spec(); + } + + WebString GetMethod() { + WebURLRequest web_request = info_->ToWebURLRequest(frame_); + return web_request.httpMethod(); + } + + WebString GetHeaderValue(const char* field) { + WebURLRequest web_request = info_->ToWebURLRequest(frame_); + return web_request.httpHeaderField(WebString::fromUTF8(field)); + } + + scoped_refptr<PPB_URLRequestInfo_Impl> info_; + + static TestWebFrameClient web_frame_client_; + static WebView* web_view_; + static WebFrame* frame_; +}; + +TestWebFrameClient URLRequestInfoTest::web_frame_client_; +WebView* URLRequestInfoTest::web_view_; +WebFrame* URLRequestInfoTest::frame_; + +TEST_F(URLRequestInfoTest, GetInterface) { + const PPB_URLRequestInfo* interface = info_->GetInterface(); + ASSERT_TRUE(interface); + ASSERT_TRUE(interface->Create); + ASSERT_TRUE(interface->IsURLRequestInfo); + ASSERT_TRUE(interface->SetProperty); + ASSERT_TRUE(interface->AppendDataToBody); + ASSERT_TRUE(interface->AppendFileToBody); + ASSERT_TRUE(interface->Create); + ASSERT_TRUE(interface->Create); +} + +TEST_F(URLRequestInfoTest, AsURLRequestInfo) { + ASSERT_EQ(info_, info_->AsPPB_URLRequestInfo_Impl()); +} + +TEST_F(URLRequestInfoTest, StreamToFile) { + info_->SetStringProperty(PP_URLREQUESTPROPERTY_URL, "http://www.google.com"); + + ASSERT_FALSE(GetDownloadToFile()); + + ASSERT_TRUE(info_->SetBooleanProperty( + PP_URLREQUESTPROPERTY_STREAMTOFILE, true)); + ASSERT_TRUE(GetDownloadToFile()); + + ASSERT_TRUE(info_->SetBooleanProperty( + PP_URLREQUESTPROPERTY_STREAMTOFILE, false)); + ASSERT_FALSE(GetDownloadToFile()); +} + +TEST_F(URLRequestInfoTest, FollowRedirects) { + ASSERT_TRUE(info_->follow_redirects()); + + ASSERT_TRUE(info_->SetBooleanProperty( + PP_URLREQUESTPROPERTY_FOLLOWREDIRECTS, false)); + ASSERT_FALSE(info_->follow_redirects()); + + ASSERT_TRUE(info_->SetBooleanProperty( + PP_URLREQUESTPROPERTY_FOLLOWREDIRECTS, true)); + ASSERT_TRUE(info_->follow_redirects()); +} + +TEST_F(URLRequestInfoTest, RecordDownloadProgress) { + ASSERT_FALSE(info_->record_download_progress()); + + ASSERT_TRUE(info_->SetBooleanProperty( + PP_URLREQUESTPROPERTY_RECORDDOWNLOADPROGRESS, true)); + ASSERT_TRUE(info_->record_download_progress()); + + ASSERT_TRUE(info_->SetBooleanProperty( + PP_URLREQUESTPROPERTY_RECORDDOWNLOADPROGRESS, false)); + ASSERT_FALSE(info_->record_download_progress()); +} + +TEST_F(URLRequestInfoTest, RecordUploadProgress) { + ASSERT_FALSE(info_->record_upload_progress()); + + ASSERT_TRUE(info_->SetBooleanProperty( + PP_URLREQUESTPROPERTY_RECORDUPLOADPROGRESS, true)); + ASSERT_TRUE(info_->record_upload_progress()); + + ASSERT_TRUE(info_->SetBooleanProperty( + PP_URLREQUESTPROPERTY_RECORDUPLOADPROGRESS, false)); + ASSERT_FALSE(info_->record_upload_progress()); +} + +TEST_F(URLRequestInfoTest, SetURL) { + // Test default URL is "about:blank". + ASSERT_TRUE(IsExpected(GetURL(), "about:blank")); + + const char* url = "http://www.google.com/"; + ASSERT_TRUE(info_->SetStringProperty( + PP_URLREQUESTPROPERTY_URL, url)); + ASSERT_TRUE(IsExpected(GetURL(), url)); +} + +TEST_F(URLRequestInfoTest, SetMethod) { + // Test default method is "GET". + ASSERT_TRUE(IsExpected(GetMethod(), "GET")); + ASSERT_TRUE(info_->SetStringProperty( + PP_URLREQUESTPROPERTY_METHOD, "POST")); + ASSERT_TRUE(IsExpected(GetMethod(), "POST")); +} + +TEST_F(URLRequestInfoTest, SetValidHeaders) { + // Test default header field. + ASSERT_TRUE(IsExpected( + GetHeaderValue("foo"), "")); + // Test that we can set a header field. + ASSERT_TRUE(info_->SetStringProperty( + PP_URLREQUESTPROPERTY_HEADERS, "foo: bar")); + ASSERT_TRUE(IsExpected( + GetHeaderValue("foo"), "bar")); + // Test that we can set multiple header fields using \n delimiter. + ASSERT_TRUE(info_->SetStringProperty( + PP_URLREQUESTPROPERTY_HEADERS, "foo: bar\nbar: baz")); + ASSERT_TRUE(IsExpected( + GetHeaderValue("foo"), "bar")); + ASSERT_TRUE(IsExpected( + GetHeaderValue("bar"), "baz")); +} + +TEST_F(URLRequestInfoTest, SetInvalidHeaders) { + const char* const kForbiddenHeaderFields[] = { + "accept-charset", + "accept-encoding", + "connection", + "content-length", + "cookie", + "cookie2", + "content-transfer-encoding", + "date", + "expect", + "host", + "keep-alive", + "origin", + "referer", + "te", + "trailer", + "transfer-encoding", + "upgrade", + "user-agent", + "via", + + "proxy-foo", // Test for any header starting with proxy- or sec-. + "sec-foo", + }; + + // Test that no forbidden header fields can be set. + for (size_t i = 0; i < arraysize(kForbiddenHeaderFields); ++i) { + std::string headers(kForbiddenHeaderFields[i]); + headers.append(": foo"); + ASSERT_FALSE(info_->SetStringProperty( + PP_URLREQUESTPROPERTY_HEADERS, headers.c_str())); + ASSERT_TRUE(IsNullOrEmpty(GetHeaderValue(kForbiddenHeaderFields[i]))); + } + + // Test that forbidden header can't be set in various ways. + ASSERT_FALSE(info_->SetStringProperty( + PP_URLREQUESTPROPERTY_HEADERS, "cookie : foo")); + ASSERT_TRUE(IsNullOrEmpty(GetHeaderValue("cookie"))); + + // Test that forbidden header can't be set with an allowed one. + ASSERT_FALSE(info_->SetStringProperty( + PP_URLREQUESTPROPERTY_HEADERS, "foo: bar\ncookie: foo")); + ASSERT_TRUE(IsNullOrEmpty(GetHeaderValue("cookie"))); +} + +// TODO(bbudge) Unit tests for AppendDataToBody, AppendFileToBody. + +} // namespace ppapi +} // namespace webkit + diff --git a/webkit/glue/plugins/webplugin.cc b/webkit/glue/plugins/webplugin.cc new file mode 100644 index 0000000..f780e18f --- /dev/null +++ b/webkit/glue/plugins/webplugin.cc @@ -0,0 +1,31 @@ +// 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. + +#include "webkit/glue/plugins/webplugin.h" + +namespace webkit_glue { + +WebPluginGeometry::WebPluginGeometry() + : window(gfx::kNullPluginWindow), + rects_valid(false), + visible(false) { +} + +WebPluginGeometry::~WebPluginGeometry() { +} + +bool WebPluginGeometry::Equals(const WebPluginGeometry& rhs) const { + return window == rhs.window && + window_rect == rhs.window_rect && + clip_rect == rhs.clip_rect && + cutout_rects == rhs.cutout_rects && + rects_valid == rhs.rects_valid && + visible == rhs.visible; +} + +WebPluginDelegate* WebPlugin::delegate() { + return NULL; +} + +} // namespace webkit_glue diff --git a/webkit/glue/plugins/webplugin.h b/webkit/glue/plugins/webplugin.h new file mode 100644 index 0000000..8a61027 --- /dev/null +++ b/webkit/glue/plugins/webplugin.h @@ -0,0 +1,200 @@ +// Copyright (c) 2006-2009 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. + +#ifndef WEBKIT_GLUE_WEBPLUGIN_H_ +#define WEBKIT_GLUE_WEBPLUGIN_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "gfx/native_widget_types.h" +#include "gfx/rect.h" + +// TODO(port): this typedef is obviously incorrect on non-Windows +// platforms, but now a lot of code now accidentally depends on them +// existing. #ifdef out these declarations and fix all the users. +typedef void* HANDLE; + +class GURL; +struct NPObject; + +namespace WebKit { +class WebFrame; +} + +namespace webkit_glue { + +class WebPluginDelegate; +class WebPluginParentView; +class WebPluginResourceClient; +#if defined(OS_MACOSX) +class WebPluginAcceleratedSurface; +#endif + +// Describes the new location for a plugin window. +struct WebPluginGeometry { + WebPluginGeometry(); + ~WebPluginGeometry(); + + bool Equals(const WebPluginGeometry& rhs) const; + + // On Windows, this is the plugin window in the plugin process. + // On X11, this is the XID of the plugin-side GtkPlug containing the + // GtkSocket hosting the actual plugin window. + // + // On Mac OS X, all of the plugin types are currently "windowless" + // (window == 0) except for the special case of the GPU plugin, + // which currently performs rendering on behalf of the Pepper 3D API + // and WebGL. The GPU plugin uses a simple integer for the + // PluginWindowHandle which is used to map to a side data structure + // containing information about the plugin. Soon this plugin will be + // generalized, at which point this mechanism will be rethought or + // removed. + gfx::PluginWindowHandle window; + gfx::Rect window_rect; + // Clip rect (include) and cutouts (excludes), relative to + // window_rect origin. + gfx::Rect clip_rect; + std::vector<gfx::Rect> cutout_rects; + bool rects_valid; + bool visible; +}; + +// The WebKit side of a plugin implementation. It provides wrappers around +// operations that need to interact with the frame and other WebCore objects. +class WebPlugin { + public: + virtual ~WebPlugin() {} + + // Called by the plugin delegate to let the WebPlugin know if the plugin is + // windowed (i.e. handle is not NULL) or windowless (handle is NULL). This + // tells the WebPlugin to send mouse/keyboard events to the plugin delegate, + // as well as the information about the HDC for paint operations. + virtual void SetWindow(gfx::PluginWindowHandle window) = 0; + + // Whether input events should be sent to the delegate. + virtual void SetAcceptsInputEvents(bool accepts) = 0; + + // Called by the plugin delegate to let it know that the window is being + // destroyed. + virtual void WillDestroyWindow(gfx::PluginWindowHandle window) = 0; +#if defined(OS_WIN) + // The pump_messages_event is a event handle which is valid only for + // windowless plugins and is used in NPP_HandleEvent calls to pump messages + // if the plugin enters a modal loop. + // Cancels a pending request. + virtual void SetWindowlessPumpEvent(HANDLE pump_messages_event) = 0; +#endif + virtual void CancelResource(unsigned long id) = 0; + virtual void Invalidate() = 0; + virtual void InvalidateRect(const gfx::Rect& rect) = 0; + + // Returns the NPObject for the browser's window object. + virtual NPObject* GetWindowScriptNPObject() = 0; + + // Returns the DOM element that loaded the plugin. + virtual NPObject* GetPluginElement() = 0; + + // Cookies + virtual void SetCookie(const GURL& url, + const GURL& first_party_for_cookies, + const std::string& cookie) = 0; + virtual std::string GetCookies(const GURL& url, + const GURL& first_party_for_cookies) = 0; + + // Shows a modal HTML dialog containing the given URL. json_arguments are + // passed to the dialog via the DOM 'window.chrome.dialogArguments', and the + // retval is the string returned by 'window.chrome.send("DialogClose", + // retval)'. + virtual void ShowModalHTMLDialog(const GURL& url, int width, int height, + const std::string& json_arguments, + std::string* json_retval) = 0; + + // When a default plugin has downloaded the plugin list and finds it is + // available, it calls this method to notify the renderer. Also it will update + // the status when user clicks on the plugin to install. + virtual void OnMissingPluginStatus(int status) = 0; + + // Handles GetURL/GetURLNotify/PostURL/PostURLNotify requests initiated + // by plugins. If the plugin wants notification of the result, notify_id will + // be non-zero. + virtual void HandleURLRequest(const char* url, + const char* method, + const char* target, + const char* buf, + unsigned int len, + int notify_id, + bool popups_allowed, + bool notify_redirects) = 0; + + // Cancels document load. + virtual void CancelDocumentLoad() = 0; + + // Initiates a HTTP range request for an existing stream. + virtual void InitiateHTTPRangeRequest(const char* url, + const char* range_info, + int range_request_id) = 0; + + // Returns true iff in off the record (Incognito) mode. + virtual bool IsOffTheRecord() = 0; + + // Called when the WebPluginResourceClient instance is deleted. + virtual void ResourceClientDeleted( + WebPluginResourceClient* resource_client) {} + + // Defers the loading of the resource identified by resource_id. This is + // controlled by the defer parameter. + virtual void SetDeferResourceLoading(unsigned long resource_id, + bool defer) = 0; + +#if defined(OS_MACOSX) + // Enables/disables plugin IME. + virtual void SetImeEnabled(bool enabled) {}; + + // Synthesize a fake window handle for the plug-in to identify the instance + // to the browser, allowing mapping to a surface for hardware accelleration + // of plug-in content. The browser generates the handle which is then set on + // the plug-in. |opaque| indicates whether the content should be treated as + // opaque or translucent. + // TODO(stuartmorgan): Move this into WebPluginProxy. + virtual void BindFakePluginWindowHandle(bool opaque) {} + + // Returns the accelerated surface abstraction for accelerated plugins. + virtual WebPluginAcceleratedSurface* GetAcceleratedSurface() { return NULL; } +#endif + + // Gets the WebPluginDelegate that implements the interface. + // This API is only for use with Pepper, and is only overridden + // by in-renderer implementations. + virtual WebPluginDelegate* delegate(); + + // Handles NPN_URLRedirectResponse calls issued by plugins in response to + // HTTP URL redirect notifications. + virtual void URLRedirectResponse(bool allow, int resource_id) = 0; +}; + +// Simpler version of ResourceHandleClient that lends itself to proxying. +class WebPluginResourceClient { + public: + virtual ~WebPluginResourceClient() {} + virtual void WillSendRequest(const GURL& url, int http_status_code) = 0; + // The request_is_seekable parameter indicates whether byte range requests + // can be issued for the underlying stream. + virtual void DidReceiveResponse(const std::string& mime_type, + const std::string& headers, + uint32 expected_length, + uint32 last_modified, + bool request_is_seekable) = 0; + virtual void DidReceiveData(const char* buffer, int length, + int data_offset) = 0; + virtual void DidFinishLoading() = 0; + virtual void DidFail() = 0; + virtual bool IsMultiByteResponseExpected() = 0; + virtual int ResourceId() = 0; +}; + +} // namespace webkit_glue + +#endif // #ifndef WEBKIT_GLUE_WEBPLUGIN_H_ diff --git a/webkit/glue/plugins/webplugin_2d_device_delegate.cc b/webkit/glue/plugins/webplugin_2d_device_delegate.cc new file mode 100644 index 0000000..f971e20 --- /dev/null +++ b/webkit/glue/plugins/webplugin_2d_device_delegate.cc @@ -0,0 +1,53 @@ +// 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. + +#include "webkit/glue/plugins/webplugin_2d_device_delegate.h" + +namespace webkit_glue { + +NPError WebPlugin2DDeviceDelegate::Device2DQueryCapability(int32 capability, + int32* value) { + return NPERR_GENERIC_ERROR; +} + +NPError WebPlugin2DDeviceDelegate::Device2DQueryConfig( + const NPDeviceContext2DConfig* request, + NPDeviceContext2DConfig* obtain) { + return NPERR_GENERIC_ERROR; +} + +NPError WebPlugin2DDeviceDelegate::Device2DInitializeContext( + const NPDeviceContext2DConfig* config, + NPDeviceContext2D* context) { + return NPERR_GENERIC_ERROR; +} + +NPError WebPlugin2DDeviceDelegate::Device2DSetStateContext( + NPDeviceContext2D* context, + int32 state, + intptr_t value) { + return NPERR_GENERIC_ERROR; +} + +NPError WebPlugin2DDeviceDelegate::Device2DGetStateContext( + NPDeviceContext2D* context, + int32 state, + intptr_t* value) { + return NPERR_GENERIC_ERROR; +} + +NPError WebPlugin2DDeviceDelegate::Device2DFlushContext( + NPP id, + NPDeviceContext2D* context, + NPDeviceFlushContextCallbackPtr callback, + void* user_data) { + return NPERR_GENERIC_ERROR; +} + +NPError WebPlugin2DDeviceDelegate::Device2DDestroyContext( + NPDeviceContext2D* context) { + return NPERR_GENERIC_ERROR; +} + +} // namespace webkit_glue diff --git a/webkit/glue/plugins/webplugin_2d_device_delegate.h b/webkit/glue/plugins/webplugin_2d_device_delegate.h new file mode 100644 index 0000000..e18c2fd --- /dev/null +++ b/webkit/glue/plugins/webplugin_2d_device_delegate.h @@ -0,0 +1,43 @@ +// Copyright (c) 2009 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. + +#ifndef WEBKIT_GLUE_PLUGINS_WEBPLUGIN_2D_DEVICE_DELEGATE_H_ +#define WEBKIT_GLUE_PLUGINS_WEBPLUGIN_2D_DEVICE_DELEGATE_H_ + +#include "base/basictypes.h" +#include "third_party/npapi/bindings/npapi_extensions.h" + +namespace webkit_glue { + +// Interface for the NPAPI 2D device extension. This class implements "NOP" +// versions of all these functions so it can be used seamlessly by the +// "regular" plugin delegate while being overridden by the "pepper" one. +class WebPlugin2DDeviceDelegate { + public: + virtual NPError Device2DQueryCapability(int32 capability, int32* value); + virtual NPError Device2DQueryConfig(const NPDeviceContext2DConfig* request, + NPDeviceContext2DConfig* obtain); + virtual NPError Device2DInitializeContext( + const NPDeviceContext2DConfig* config, + NPDeviceContext2D* context); + virtual NPError Device2DSetStateContext(NPDeviceContext2D* context, + int32 state, + intptr_t value); + virtual NPError Device2DGetStateContext(NPDeviceContext2D* context, + int32 state, + intptr_t* value); + virtual NPError Device2DFlushContext(NPP id, + NPDeviceContext2D* context, + NPDeviceFlushContextCallbackPtr callback, + void* user_data); + virtual NPError Device2DDestroyContext(NPDeviceContext2D* context); + + protected: + WebPlugin2DDeviceDelegate() {} + virtual ~WebPlugin2DDeviceDelegate() {} +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_PLUGINS_WEBPLUGIN_2D_DEVICE_DELEGATE_H_ diff --git a/webkit/glue/plugins/webplugin_3d_device_delegate.cc b/webkit/glue/plugins/webplugin_3d_device_delegate.cc new file mode 100644 index 0000000..93dffa6 --- /dev/null +++ b/webkit/glue/plugins/webplugin_3d_device_delegate.cc @@ -0,0 +1,110 @@ +// 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. + +#include "webkit/glue/plugins/webplugin_3d_device_delegate.h" + +namespace webkit_glue { + +NPError WebPlugin3DDeviceDelegate::Device3DQueryCapability(int32 capability, + int32* value) { + return NPERR_GENERIC_ERROR; +} + +NPError WebPlugin3DDeviceDelegate::Device3DQueryConfig( + const NPDeviceContext3DConfig* request, + NPDeviceContext3DConfig* obtain) { + return NPERR_GENERIC_ERROR; +} + +NPError WebPlugin3DDeviceDelegate::Device3DInitializeContext( + const NPDeviceContext3DConfig* config, + NPDeviceContext3D* context) { + return NPERR_GENERIC_ERROR; +} + +NPError WebPlugin3DDeviceDelegate::Device3DSetStateContext( + NPDeviceContext3D* context, + int32 state, + intptr_t value) { + return NPERR_GENERIC_ERROR; +} + +NPError WebPlugin3DDeviceDelegate::Device3DGetStateContext( + NPDeviceContext3D* context, + int32 state, + intptr_t* value) { + return NPERR_GENERIC_ERROR; +} + +NPError WebPlugin3DDeviceDelegate::Device3DFlushContext( + NPP id, + NPDeviceContext3D* context, + NPDeviceFlushContextCallbackPtr callback, + void* user_data) { + return NPERR_GENERIC_ERROR; +} + +NPError WebPlugin3DDeviceDelegate::Device3DDestroyContext( + NPDeviceContext3D* context) { + return NPERR_GENERIC_ERROR; +} + +NPError WebPlugin3DDeviceDelegate::Device3DCreateBuffer(NPDeviceContext3D* context, + size_t size, + int32* id) { + return NPERR_GENERIC_ERROR; +} + +NPError WebPlugin3DDeviceDelegate::Device3DDestroyBuffer( + NPDeviceContext3D* context, + int32 id) { + return NPERR_GENERIC_ERROR; +} + +NPError WebPlugin3DDeviceDelegate::Device3DMapBuffer(NPDeviceContext3D* context, + int32 id, + NPDeviceBuffer* buffer) { + return NPERR_GENERIC_ERROR; +} + +NPError WebPlugin3DDeviceDelegate::Device3DGetNumConfigs(int32* num_configs) { + return NPERR_GENERIC_ERROR; +} + +NPError WebPlugin3DDeviceDelegate::Device3DGetConfigAttribs( + int32 config, + int32* attrib_list) { + return NPERR_GENERIC_ERROR; +} + +NPError WebPlugin3DDeviceDelegate::Device3DCreateContext( + int32 config, + const int32* attrib_list, + NPDeviceContext3D** context) { + return NPERR_GENERIC_ERROR; +} + +NPError WebPlugin3DDeviceDelegate::Device3DRegisterCallback( + NPP id, + NPDeviceContext* context, + int32 callback_type, + NPDeviceGenericCallbackPtr callback, + void* callback_data) { + return NPERR_GENERIC_ERROR; +} + +NPError WebPlugin3DDeviceDelegate::Device3DSynchronizeContext( + NPP id, + NPDeviceContext3D* context, + NPDeviceSynchronizationMode mode, + const int32* input_attrib_list, + int32* output_attrib_list, + NPDeviceSynchronizeContextCallbackPtr callback, + void* callback_data) { + return NPERR_GENERIC_ERROR; +} + + +} // namespace webkit_glue + diff --git a/webkit/glue/plugins/webplugin_3d_device_delegate.h b/webkit/glue/plugins/webplugin_3d_device_delegate.h new file mode 100644 index 0000000..2f64b45 --- /dev/null +++ b/webkit/glue/plugins/webplugin_3d_device_delegate.h @@ -0,0 +1,71 @@ +// Copyright (c) 2009 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. + +#ifndef WEBKIT_GLUE_PLUGINS_WEBPLUGIN_3D_DEVICE_DELEGATE_H_ +#define WEBKIT_GLUE_PLUGINS_WEBPLUGIN_3D_DEVICE_DELEGATE_H_ + +#include "base/basictypes.h" +#include "third_party/npapi/bindings/npapi_extensions.h" + +namespace webkit_glue { + +// Interface for the NPAPI 3D device extension. This class implements "NOP" +// versions of all these functions so it can be used seamlessly by the +// "regular" plugin delegate while being overridden by the "pepper" one. +class WebPlugin3DDeviceDelegate { + public: + virtual NPError Device3DQueryCapability(int32 capability, int32* value); + virtual NPError Device3DQueryConfig(const NPDeviceContext3DConfig* request, + NPDeviceContext3DConfig* obtain); + virtual NPError Device3DInitializeContext( + const NPDeviceContext3DConfig* config, + NPDeviceContext3D* context); + virtual NPError Device3DSetStateContext(NPDeviceContext3D* context, + int32 state, + intptr_t value); + virtual NPError Device3DGetStateContext(NPDeviceContext3D* context, + int32 state, + intptr_t* value); + virtual NPError Device3DFlushContext(NPP id, + NPDeviceContext3D* context, + NPDeviceFlushContextCallbackPtr callback, + void* user_data); + virtual NPError Device3DDestroyContext(NPDeviceContext3D* context); + virtual NPError Device3DCreateBuffer(NPDeviceContext3D* context, + size_t size, + int32* id); + virtual NPError Device3DDestroyBuffer(NPDeviceContext3D* context, + int32 id); + virtual NPError Device3DMapBuffer(NPDeviceContext3D* context, + int32 id, + NPDeviceBuffer* buffer); + virtual NPError Device3DGetNumConfigs(int32* num_configs); + virtual NPError Device3DGetConfigAttribs(int32 config, + int32* attrib_list); + virtual NPError Device3DCreateContext(int32 config, + const int32* attrib_list, + NPDeviceContext3D** context); + virtual NPError Device3DRegisterCallback( + NPP id, + NPDeviceContext* context, + int32 callback_type, + NPDeviceGenericCallbackPtr callback, + void* callback_data); + virtual NPError Device3DSynchronizeContext( + NPP id, + NPDeviceContext3D* context, + NPDeviceSynchronizationMode mode, + const int32* input_attrib_list, + int32* output_attrib_list, + NPDeviceSynchronizeContextCallbackPtr callback, + void* callback_data); + + protected: + WebPlugin3DDeviceDelegate() {} + virtual ~WebPlugin3DDeviceDelegate() {} +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_PLUGINS_WEBPLUGIN_3D_DEVICE_DELEGATE_H_ diff --git a/webkit/glue/plugins/webplugin_accelerated_surface_mac.h b/webkit/glue/plugins/webplugin_accelerated_surface_mac.h new file mode 100644 index 0000000..13980ca --- /dev/null +++ b/webkit/glue/plugins/webplugin_accelerated_surface_mac.h @@ -0,0 +1,44 @@ +// Copyright (c) 2006-2009 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. + +#ifndef WEBKIT_GLUE_WEBPLUGIN_ACCELERATED_SURFACE_MAC_H_ +#define WEBKIT_GLUE_WEBPLUGIN_ACCELERATED_SURFACE_MAC_H_ +#pragma once + +#include "gfx/native_widget_types.h" +#include "gfx/size.h" + +// Avoid having to include OpenGL headers here. +typedef struct _CGLContextObject* CGLContextObj; + +namespace webkit_glue { + +// Interface class for interacting with an accelerated plugin surface, used +// for the Core Animation flavors of plugin drawing on the Mac. +class WebPluginAcceleratedSurface { + public: + virtual ~WebPluginAcceleratedSurface() {} + + // Sets the window handle used throughout the browser to identify this + // surface. + virtual void SetWindowHandle(gfx::PluginWindowHandle window) = 0; + + // Sets the size of the surface. + virtual void SetSize(const gfx::Size& size) = 0; + + // Returns the context used to draw into this surface. + // If initializing the surface failed, this will be NULL. + virtual CGLContextObj context() = 0; + + // Readies the surface for drawing. Must be called before any drawing session. + virtual void StartDrawing() = 0; + + // Ends a drawing session. Changes to the surface may not be reflected until + // this is called. + virtual void EndDrawing() = 0; +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_WEBPLUGIN_ACCELERATED_SURFACE_MAC_H_ diff --git a/webkit/glue/plugins/webplugin_audio_device_delegate.cc b/webkit/glue/plugins/webplugin_audio_device_delegate.cc new file mode 100644 index 0000000..8cc3d62 --- /dev/null +++ b/webkit/glue/plugins/webplugin_audio_device_delegate.cc @@ -0,0 +1,50 @@ +// 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. + +#include "webkit/glue/plugins/webplugin_audio_device_delegate.h" + +namespace webkit_glue { + +NPError WebPluginAudioDeviceDelegate::DeviceAudioQueryCapability( + int32 capability, int32* value) { + return NPERR_GENERIC_ERROR; +} + +NPError WebPluginAudioDeviceDelegate::DeviceAudioQueryConfig( + const NPDeviceContextAudioConfig* request, + NPDeviceContextAudioConfig* obtain) { + return NPERR_GENERIC_ERROR; +} + +NPError WebPluginAudioDeviceDelegate::DeviceAudioInitializeContext( + const NPDeviceContextAudioConfig* config, + NPDeviceContextAudio* context) { + return NPERR_GENERIC_ERROR; +} + +NPError WebPluginAudioDeviceDelegate::DeviceAudioSetStateContext( + NPDeviceContextAudio* context, + int32 state, intptr_t value) { + return NPERR_GENERIC_ERROR; +} + +NPError WebPluginAudioDeviceDelegate::DeviceAudioGetStateContext( + NPDeviceContextAudio* context, + int32 state, intptr_t* value) { + return NPERR_GENERIC_ERROR; +} + +NPError WebPluginAudioDeviceDelegate::DeviceAudioFlushContext( + NPP id, NPDeviceContextAudio* context, + NPDeviceFlushContextCallbackPtr callback, void* user_data) { + return NPERR_GENERIC_ERROR; +} + +NPError WebPluginAudioDeviceDelegate::DeviceAudioDestroyContext( + NPDeviceContextAudio* context) { + return NPERR_GENERIC_ERROR; +} + + +} // namespace webkit_glue diff --git a/webkit/glue/plugins/webplugin_audio_device_delegate.h b/webkit/glue/plugins/webplugin_audio_device_delegate.h new file mode 100644 index 0000000..de85433 --- /dev/null +++ b/webkit/glue/plugins/webplugin_audio_device_delegate.h @@ -0,0 +1,42 @@ +// Copyright (c) 2009 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. + +#ifndef WEBKIT_GLUE_PLUGINS_WEBPLUGIN_AUDIO_DEVICE_DELEGATE_H_ +#define WEBKIT_GLUE_PLUGINS_WEBPLUGIN_AUDIO_DEVICE_DELEGATE_H_ + +#include "base/basictypes.h" +#include "third_party/npapi/bindings/npapi_extensions.h" + +namespace webkit_glue { + +// Interface for the NPAPI audio device extension. This class implements "NOP" +// versions of all these functions so it can be used seamlessly by the +// "regular" plugin delegate while being overridden by the "pepper" one. +class WebPluginAudioDeviceDelegate { + public: + virtual NPError DeviceAudioQueryCapability(int32 capability, int32* value); + virtual NPError DeviceAudioQueryConfig( + const NPDeviceContextAudioConfig* request, + NPDeviceContextAudioConfig* obtain); + virtual NPError DeviceAudioInitializeContext( + const NPDeviceContextAudioConfig* config, + NPDeviceContextAudio* context); + virtual NPError DeviceAudioSetStateContext(NPDeviceContextAudio* context, + int32 state, intptr_t value); + virtual NPError DeviceAudioGetStateContext(NPDeviceContextAudio* context, + int32 state, intptr_t* value); + virtual NPError DeviceAudioFlushContext( + NPP id, NPDeviceContextAudio* context, + NPDeviceFlushContextCallbackPtr callback, void* user_data); + virtual NPError DeviceAudioDestroyContext(NPDeviceContextAudio* context); + + protected: + WebPluginAudioDeviceDelegate() {} + virtual ~WebPluginAudioDeviceDelegate() {} +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_PLUGINS_WEBPLUGIN_AUDIO_DEVICE_DELEGATE_H_ + diff --git a/webkit/glue/plugins/webplugin_delegate.cc b/webkit/glue/plugins/webplugin_delegate.cc new file mode 100644 index 0000000..c3fb53b --- /dev/null +++ b/webkit/glue/plugins/webplugin_delegate.cc @@ -0,0 +1,40 @@ +// 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. + +#include "webkit/glue/plugins/webplugin_delegate.h" + +namespace webkit_glue { + +bool WebPluginDelegate::StartFind(const string16& search_text, + bool case_sensitive, + int identifier) { + return false; +} + +NPWidgetExtensions* WebPluginDelegate::GetWidgetExtensions() { + return NULL; +} + +bool WebPluginDelegate::SetCursor(NPCursorType type) { + return false; +} + +NPFontExtensions* WebPluginDelegate::GetFontExtensions() { + return NULL; +} + +bool WebPluginDelegate::HasSelection() const { + return false; +} + +string16 WebPluginDelegate::GetSelectionAsText() const { + return string16(); +} + +string16 WebPluginDelegate::GetSelectionAsMarkup() const { + return string16(); +} + + +} // namespace webkit_glue diff --git a/webkit/glue/plugins/webplugin_delegate.h b/webkit/glue/plugins/webplugin_delegate.h new file mode 100644 index 0000000..ac7bb5c --- /dev/null +++ b/webkit/glue/plugins/webplugin_delegate.h @@ -0,0 +1,166 @@ +// 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. + +#ifndef WEBKIT_GLUE_WEBPLUGIN_DELEGATE_H_ +#define WEBKIT_GLUE_WEBPLUGIN_DELEGATE_H_ + +#include <string> +#include <vector> + +#include "base/string16.h" +#include "build/build_config.h" +#include "gfx/native_widget_types.h" +#include "third_party/npapi/bindings/npapi.h" +#include "third_party/npapi/bindings/npapi_extensions.h" +#include "third_party/WebKit/WebKit/chromium/public/WebCanvas.h" +#include "webkit/glue/plugins/webplugin_2d_device_delegate.h" +#include "webkit/glue/plugins/webplugin_3d_device_delegate.h" +#include "webkit/glue/plugins/webplugin_audio_device_delegate.h" +#include "webkit/glue/plugins/webplugin_file_delegate.h" +#include "webkit/glue/plugins/webplugin_print_delegate.h" + +class FilePath; +class GURL; +struct NPObject; + +namespace WebKit { +class WebInputEvent; +struct WebCursorInfo; +} + +namespace gfx { +class Rect; +} + +namespace webkit_glue { + +class WebPlugin; +class WebPluginResourceClient; + +// This is the interface that a plugin implementation needs to provide. +class WebPluginDelegate : public WebPlugin2DDeviceDelegate, + public WebPlugin3DDeviceDelegate, + public WebPluginAudioDeviceDelegate, + public WebPluginPrintDelegate, + public WebPluginFileDelegate { + public: + virtual ~WebPluginDelegate() {} + + // Initializes the plugin implementation with the given (UTF8) arguments. + // Note that the lifetime of WebPlugin must be longer than this delegate. + // If this function returns false the plugin isn't started and shouldn't be + // called again. If this method succeeds, then the WebPlugin is valid until + // PluginDestroyed is called. + // The load_manually parameter if true indicates that the plugin data would + // be passed from webkit. if false indicates that the plugin should download + // the data. This also controls whether the plugin is instantiated as a full + // page plugin (NP_FULL) or embedded (NP_EMBED). + virtual bool Initialize(const GURL& url, + const std::vector<std::string>& arg_names, + const std::vector<std::string>& arg_values, + WebPlugin* plugin, + bool load_manually) = 0; + + // Called when the WebPlugin is being destroyed. This is a signal to the + // delegate that it should tear-down the plugin implementation and not call + // methods on the WebPlugin again. + virtual void PluginDestroyed() = 0; + + // Update the geometry of the plugin. This is a request to move the + // plugin, relative to its containing window, to the coords given by + // window_rect. Its contents should be clipped to the coords given + // by clip_rect, which are relative to the origin of the plugin + // window. The clip_rect is in plugin-relative coordinates. + virtual void UpdateGeometry(const gfx::Rect& window_rect, + const gfx::Rect& clip_rect) = 0; + + // Tells the plugin to paint the damaged rect. |canvas| is only used for + // windowless plugins. + virtual void Paint(WebKit::WebCanvas* canvas, const gfx::Rect& rect) = 0; + + // Tells the plugin to print itself. + virtual void Print(gfx::NativeDrawingContext hdc) = 0; + + // Informs the plugin that it has gained or lost focus. This is only called in + // windowless mode. + virtual void SetFocus(bool focused) = 0; + + // For windowless plugins, gives them a user event like mouse/keyboard. + // Returns whether the event was handled. This is only called in windowsless + // mode. See NPAPI NPP_HandleEvent for more information. + virtual bool HandleInputEvent(const WebKit::WebInputEvent& event, + WebKit::WebCursorInfo* cursor) = 0; + + // Gets the NPObject associated with the plugin for scripting. + virtual NPObject* GetPluginScriptableObject() = 0; + + // Receives notification about a resource load that the plugin initiated + // for a frame. + virtual void DidFinishLoadWithReason(const GURL& url, NPReason reason, + int notify_id) = 0; + + // Returns the process id of the process that is running the plugin. + virtual int GetProcessId() = 0; + + // The result, UTF-8 encoded, of the script execution is returned via this + // function. + virtual void SendJavaScriptStream(const GURL& url, + const std::string& result, + bool success, + int notify_id) = 0; + + // Receives notification about data being available. + virtual void DidReceiveManualResponse(const GURL& url, + const std::string& mime_type, + const std::string& headers, + uint32 expected_length, + uint32 last_modified) = 0; + + // Receives the data. + virtual void DidReceiveManualData(const char* buffer, int length) = 0; + + // Indicates end of data load. + virtual void DidFinishManualLoading() = 0; + + // Indicates a failure in data receipt. + virtual void DidManualLoadFail() = 0; + + // Only supported when the plugin is the default plugin. + virtual void InstallMissingPlugin() = 0; + + // Creates a WebPluginResourceClient instance and returns the same. + virtual WebPluginResourceClient* CreateResourceClient( + unsigned long resource_id, + const GURL& url, + int notify_id) = 0; + + // Creates a WebPluginResourceClient instance for an existing stream that is + // has become seekable. + virtual WebPluginResourceClient* CreateSeekableResourceClient( + unsigned long resource_id, int range_request_id) = 0; + + // See WebPluginContainerImpl's description of the interface. + virtual bool StartFind(const string16& search_text, + bool case_sensitive, + int identifier); + virtual void SelectFindResult(bool forward) {} + virtual void StopFind() {} + virtual void NumberOfFindResultsChanged(int total, bool final_result) {} + virtual void SelectedFindResultChanged(int index) {} + virtual NPWidgetExtensions* GetWidgetExtensions(); + virtual bool SetCursor(NPCursorType type); + virtual NPFontExtensions* GetFontExtensions(); + + // Used for zooming of full page plugins. 0 means reset, while -1 means zoom + // out and +1 means zoom in. + virtual void SetZoomFactor(float scale, bool text_only) {} + // Gets the selected text, if any. + virtual bool HasSelection() const; + virtual string16 GetSelectionAsText() const; + virtual string16 GetSelectionAsMarkup() const; +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_WEBPLUGIN_DELEGATE_H_ diff --git a/webkit/glue/plugins/webplugin_delegate_impl.cc b/webkit/glue/plugins/webplugin_delegate_impl.cc new file mode 100644 index 0000000..e3e4f9d --- /dev/null +++ b/webkit/glue/plugins/webplugin_delegate_impl.cc @@ -0,0 +1,304 @@ +// 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. + +#include "webkit/glue/plugins/webplugin_delegate_impl.h" + +#include <string> +#include <vector> + +#include "base/file_util.h" +#include "base/message_loop.h" +#include "base/process_util.h" +#include "base/scoped_ptr.h" +#include "base/string_util.h" +#include "third_party/WebKit/WebKit/chromium/public/WebInputEvent.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" + +using webkit_glue::WebPlugin; +using webkit_glue::WebPluginDelegate; +using webkit_glue::WebPluginResourceClient; +using WebKit::WebCursorInfo; +using WebKit::WebKeyboardEvent; +using WebKit::WebInputEvent; +using WebKit::WebMouseEvent; + +WebPluginDelegateImpl* WebPluginDelegateImpl::Create( + const FilePath& filename, + const std::string& mime_type, + gfx::PluginWindowHandle containing_view) { + scoped_refptr<NPAPI::PluginLib> plugin_lib( + NPAPI::PluginLib::CreatePluginLib(filename)); + if (plugin_lib.get() == NULL) + return NULL; + + NPError err = plugin_lib->NP_Initialize(); + if (err != NPERR_NO_ERROR) + return NULL; + + scoped_refptr<NPAPI::PluginInstance> instance( + plugin_lib->CreateInstance(mime_type)); + return new WebPluginDelegateImpl(containing_view, instance.get()); +} + +void WebPluginDelegateImpl::PluginDestroyed() { + if (handle_event_depth_) { + MessageLoop::current()->DeleteSoon(FROM_HERE, this); + } else { + delete this; + } +} + +bool WebPluginDelegateImpl::Initialize( + const GURL& url, + const std::vector<std::string>& arg_names, + const std::vector<std::string>& arg_values, + WebPlugin* plugin, + bool load_manually) { + plugin_ = plugin; + + instance_->set_web_plugin(plugin_); + if (quirks_ & PLUGIN_QUIRK_DONT_ALLOW_MULTIPLE_INSTANCES) { + NPAPI::PluginLib* plugin_lib = instance()->plugin_lib(); + if (plugin_lib->instance_count() > 1) { + return false; + } + } + + if (quirks_ & PLUGIN_QUIRK_DIE_AFTER_UNLOAD) + webkit_glue::SetForcefullyTerminatePluginProcess(true); + + int argc = 0; + scoped_array<char*> argn(new char*[arg_names.size()]); + scoped_array<char*> argv(new char*[arg_names.size()]); + for (size_t i = 0; i < arg_names.size(); ++i) { + if (quirks_ & PLUGIN_QUIRK_NO_WINDOWLESS && + LowerCaseEqualsASCII(arg_names[i], "windowlessvideo")) { + continue; + } + argn[argc] = const_cast<char*>(arg_names[i].c_str()); + argv[argc] = const_cast<char*>(arg_values[i].c_str()); + argc++; + } + + creation_succeeded_ = instance_->Start( + url, argn.get(), argv.get(), argc, load_manually); + if (!creation_succeeded_) + return false; + + windowless_ = instance_->windowless(); + if (!windowless_) { + if (!WindowedCreatePlugin()) + return false; + } else { + // For windowless plugins we should set the containing window handle + // as the instance window handle. This is what Safari does. Not having + // a valid window handle causes subtle bugs with plugins which retrieve + // the window handle and validate the same. The window handle can be + // retrieved via NPN_GetValue of NPNVnetscapeWindow. + instance_->set_window_handle(parent_); + } + + bool should_load = PlatformInitialize(); + + plugin_url_ = url.spec(); + + return should_load; +} + +void WebPluginDelegateImpl::DestroyInstance() { + if (instance_ && (instance_->npp()->ndata != NULL)) { + // Shutdown all streams before destroying so that + // no streams are left "in progress". Need to do + // this before calling set_web_plugin(NULL) because the + // instance uses the helper to do the download. + instance_->CloseStreams(); + + window_.window = NULL; + if (creation_succeeded_ && + !(quirks_ & PLUGIN_QUIRK_DONT_SET_NULL_WINDOW_HANDLE_ON_DESTROY)) { + instance_->NPP_SetWindow(&window_); + } + + instance_->NPP_Destroy(); + + instance_->set_web_plugin(NULL); + + PlatformDestroyInstance(); + + instance_ = 0; + } +} + +void WebPluginDelegateImpl::UpdateGeometry( + const gfx::Rect& window_rect, + const gfx::Rect& clip_rect) { + + if (first_set_window_call_) { + first_set_window_call_ = false; + // Plugins like media player on Windows have a bug where in they handle the + // first geometry update and ignore the rest resulting in painting issues. + // This quirk basically ignores the first set window call sequence for + // these plugins and has been tested for Windows plugins only. + if (quirks_ & PLUGIN_QUIRK_IGNORE_FIRST_SETWINDOW_CALL) + return; + } + + if (windowless_) { + WindowlessUpdateGeometry(window_rect, clip_rect); + } else { + WindowedUpdateGeometry(window_rect, clip_rect); + } +} + +void WebPluginDelegateImpl::SetFocus(bool focused) { + DCHECK(windowless_); + // This is called when internal WebKit focus (the focused element on the page) + // changes, but plugins need to know about OS-level focus, so we have an extra + // layer of focus tracking. + // + // On Windows, historically browsers did not set focus events to windowless + // plugins when the toplevel window focus changes. Sending such focus events + // breaks full screen mode in Flash because it will come out of full screen + // mode when it loses focus, and its full screen window causes the browser to + // lose focus. + has_webkit_focus_ = focused; +#ifndef OS_WIN + if (containing_view_has_focus_) + SetPluginHasFocus(focused); +#else + SetPluginHasFocus(focused); +#endif +} + +void WebPluginDelegateImpl::SetPluginHasFocus(bool focused) { + if (focused == plugin_has_focus_) + return; + if (PlatformSetPluginHasFocus(focused)) + plugin_has_focus_ = focused; +} + +void WebPluginDelegateImpl::SetContentAreaHasFocus(bool has_focus) { + containing_view_has_focus_ = has_focus; + if (!windowless_) + return; +#ifndef OS_WIN // See SetFocus above. + SetPluginHasFocus(containing_view_has_focus_ && has_webkit_focus_); +#endif +} + +NPObject* WebPluginDelegateImpl::GetPluginScriptableObject() { + return instance_->GetPluginScriptableObject(); +} + +void WebPluginDelegateImpl::DidFinishLoadWithReason(const GURL& url, + NPReason reason, + int notify_id) { + if (quirks_ & PLUGIN_QUIRK_ALWAYS_NOTIFY_SUCCESS && + reason == NPRES_NETWORK_ERR) { + // Flash needs this or otherwise it unloads the launching swf object. + reason = NPRES_DONE; + } + + instance()->DidFinishLoadWithReason(url, reason, notify_id); +} + +int WebPluginDelegateImpl::GetProcessId() { + // We are in process, so the plugin pid is this current process pid. + return base::GetCurrentProcId(); +} + +void WebPluginDelegateImpl::SendJavaScriptStream(const GURL& url, + const std::string& result, + bool success, + int notify_id) { + instance()->SendJavaScriptStream(url, result, success, notify_id); +} + +void WebPluginDelegateImpl::DidReceiveManualResponse( + const GURL& url, const std::string& mime_type, + const std::string& headers, uint32 expected_length, uint32 last_modified) { + if (!windowless_) { + // Calling NPP_WriteReady before NPP_SetWindow causes movies to not load in + // Flash. See http://b/issue?id=892174. + DCHECK(windowed_did_set_window_); + } + + instance()->DidReceiveManualResponse(url, mime_type, headers, + expected_length, last_modified); +} + +void WebPluginDelegateImpl::DidReceiveManualData(const char* buffer, + int length) { + instance()->DidReceiveManualData(buffer, length); +} + +void WebPluginDelegateImpl::DidFinishManualLoading() { + instance()->DidFinishManualLoading(); +} + +void WebPluginDelegateImpl::DidManualLoadFail() { + instance()->DidManualLoadFail(); +} + +FilePath WebPluginDelegateImpl::GetPluginPath() { + return instance()->plugin_lib()->plugin_info().path; +} + +void WebPluginDelegateImpl::WindowedUpdateGeometry( + const gfx::Rect& window_rect, + const gfx::Rect& clip_rect) { + if (WindowedReposition(window_rect, clip_rect) || + !windowed_did_set_window_) { + // Let the plugin know that it has been moved + WindowedSetWindow(); + } +} + +bool WebPluginDelegateImpl::HandleInputEvent(const WebInputEvent& event, + WebCursorInfo* cursor_info) { + DCHECK(windowless_) << "events should only be received in windowless mode"; + + bool pop_user_gesture = false; + if (IsUserGesture(event)) { + pop_user_gesture = true; + instance()->PushPopupsEnabledState(true); + } + + bool handled = PlatformHandleInputEvent(event, cursor_info); + + if (pop_user_gesture) { + instance()->PopPopupsEnabledState(); + } + + return handled; +} + +bool WebPluginDelegateImpl::IsUserGesture(const WebInputEvent& event) { + switch (event.type) { + case WebInputEvent::MouseDown: + case WebInputEvent::MouseUp: + case WebInputEvent::KeyDown: + case WebInputEvent::KeyUp: + return true; + default: + return false; + } + return false; +} + +WebPluginResourceClient* WebPluginDelegateImpl::CreateResourceClient( + unsigned long resource_id, const GURL& url, int notify_id) { + return instance()->CreateStream( + resource_id, url, std::string(), notify_id); +} + +WebPluginResourceClient* WebPluginDelegateImpl::CreateSeekableResourceClient( + unsigned long resource_id, int range_request_id) { + return instance()->GetRangeRequest(range_request_id); +} diff --git a/webkit/glue/plugins/webplugin_delegate_impl.h b/webkit/glue/plugins/webplugin_delegate_impl.h new file mode 100644 index 0000000..4046c95 --- /dev/null +++ b/webkit/glue/plugins/webplugin_delegate_impl.h @@ -0,0 +1,511 @@ +// 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. + +#ifndef WEBKIT_GLUE_PLUGINS_WEBPLUGIN_DELEGATE_IMPL_H_ +#define WEBKIT_GLUE_PLUGINS_WEBPLUGIN_DELEGATE_IMPL_H_ + +#include "build/build_config.h" + +#include <string> +#include <list> + +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" +#include "base/task.h" +#include "base/time.h" +#include "base/timer.h" +#include "gfx/native_widget_types.h" +#include "gfx/rect.h" +#include "third_party/npapi/bindings/npapi.h" +#include "webkit/glue/plugins/webplugin_delegate.h" +#include "webkit/glue/webcursor.h" + +#if defined(USE_X11) +#include "app/x11_util.h" + +typedef struct _GdkDrawable GdkPixmap; +#endif + +class FilePath; + +namespace NPAPI { +class PluginInstance; +} + +namespace WebKit { +class WebMouseEvent; +} + +#if defined(OS_MACOSX) +class ExternalDragTracker; +#ifndef NP_NO_QUICKDRAW +class QuickDrawDrawingManager; +#endif +#ifdef __OBJC__ +@class CALayer; +@class CARenderer; +#else +class CALayer; +class CARenderer; +#endif +namespace webkit_glue { +class WebPluginAcceleratedSurface; +} +#endif + +// An implementation of WebPluginDelegate that runs in the plugin process, +// proxied from the renderer by WebPluginDelegateProxy. +class WebPluginDelegateImpl : public webkit_glue::WebPluginDelegate { + public: + enum PluginQuirks { + PLUGIN_QUIRK_SETWINDOW_TWICE = 1, // Win32 + PLUGIN_QUIRK_THROTTLE_WM_USER_PLUS_ONE = 2, // Win32 + PLUGIN_QUIRK_DONT_CALL_WND_PROC_RECURSIVELY = 4, // Win32 + PLUGIN_QUIRK_DONT_SET_NULL_WINDOW_HANDLE_ON_DESTROY = 8, // Win32 + PLUGIN_QUIRK_DONT_ALLOW_MULTIPLE_INSTANCES = 16, // Win32 + PLUGIN_QUIRK_DIE_AFTER_UNLOAD = 32, // Win32 + PLUGIN_QUIRK_PATCH_SETCURSOR = 64, // Win32 + PLUGIN_QUIRK_BLOCK_NONSTANDARD_GETURL_REQUESTS = 128, // Win32 + PLUGIN_QUIRK_WINDOWLESS_OFFSET_WINDOW_TO_DRAW = 256, // Linux + PLUGIN_QUIRK_WINDOWLESS_INVALIDATE_AFTER_SET_WINDOW = 512, // Linux + PLUGIN_QUIRK_NO_WINDOWLESS = 1024, // Windows + PLUGIN_QUIRK_PATCH_REGENUMKEYEXW = 2048, // Windows + PLUGIN_QUIRK_ALWAYS_NOTIFY_SUCCESS = 4096, // Windows + PLUGIN_QUIRK_ALLOW_FASTER_QUICKDRAW_PATH = 8192, // Mac + PLUGIN_QUIRK_HANDLE_MOUSE_CAPTURE = 16384, // Windows + PLUGIN_QUIRK_WINDOWLESS_NO_RIGHT_CLICK = 32768, // Linux + PLUGIN_QUIRK_IGNORE_FIRST_SETWINDOW_CALL = 65536, // Windows. + }; + + static WebPluginDelegateImpl* Create(const FilePath& filename, + const std::string& mime_type, + gfx::PluginWindowHandle containing_view); + + static bool IsPluginDelegateWindow(gfx::NativeWindow window); + static bool GetPluginNameFromWindow(gfx::NativeWindow window, + std::wstring *plugin_name); + + // Returns true if the window handle passed in is that of the dummy + // activation window for windowless plugins. + static bool IsDummyActivationWindow(gfx::NativeWindow window); + + // WebPluginDelegate implementation + virtual bool Initialize(const GURL& url, + const std::vector<std::string>& arg_names, + const std::vector<std::string>& arg_values, + webkit_glue::WebPlugin* plugin, + bool load_manually); + virtual void PluginDestroyed(); + virtual void UpdateGeometry(const gfx::Rect& window_rect, + const gfx::Rect& clip_rect); + virtual void Paint(WebKit::WebCanvas* canvas, const gfx::Rect& rect); + virtual void Print(gfx::NativeDrawingContext context); + virtual void SetFocus(bool focused); + virtual bool HandleInputEvent(const WebKit::WebInputEvent& event, + WebKit::WebCursorInfo* cursor_info); + virtual NPObject* GetPluginScriptableObject(); + virtual void DidFinishLoadWithReason( + const GURL& url, NPReason reason, int notify_id); + virtual int GetProcessId(); + virtual void SendJavaScriptStream(const GURL& url, + const std::string& result, + bool success, + int notify_id); + virtual void DidReceiveManualResponse(const GURL& url, + const std::string& mime_type, + const std::string& headers, + uint32 expected_length, + uint32 last_modified); + virtual void DidReceiveManualData(const char* buffer, int length); + virtual void DidFinishManualLoading(); + virtual void DidManualLoadFail(); + virtual void InstallMissingPlugin(); + virtual webkit_glue::WebPluginResourceClient* CreateResourceClient( + unsigned long resource_id, const GURL& url, int notify_id); + virtual webkit_glue::WebPluginResourceClient* CreateSeekableResourceClient( + unsigned long resource_id, int range_request_id); + // End of WebPluginDelegate implementation. + + bool IsWindowless() const { return windowless_ ; } + gfx::Rect GetRect() const { return window_rect_; } + gfx::Rect GetClipRect() const { return clip_rect_; } + + // Returns the path for the library implementing this plugin. + FilePath GetPluginPath(); + + // Returns a combination of PluginQuirks. + int GetQuirks() const { return quirks_; } + + // Informs the plugin that the view it is in has gained or lost focus. + void SetContentAreaHasFocus(bool has_focus); + +#if defined(OS_MACOSX) + // Informs the plugin that the geometry has changed, as with UpdateGeometry, + // but also includes the new buffer context for that new geometry. + void UpdateGeometryAndContext(const gfx::Rect& window_rect, + const gfx::Rect& clip_rect, + gfx::NativeDrawingContext context); + // Informs the delegate that the plugin called NPN_Invalidate*. Used as a + // trigger for Core Animation drawing. + void PluginDidInvalidate(); + // Returns the delegate currently processing events. + static WebPluginDelegateImpl* GetActiveDelegate(); + // Informs the plugin that the window it is in has gained or lost focus. + void SetWindowHasFocus(bool has_focus); + // Returns whether or not the window the plugin is in has focus. + bool GetWindowHasFocus() const { return containing_window_has_focus_; } + // Informs the plugin that its tab or window has been hidden or shown. + void SetContainerVisibility(bool is_visible); + // Informs the plugin that its containing window's frame has changed. + // Frames are in screen coordinates. + void WindowFrameChanged(const gfx::Rect& window_frame, + const gfx::Rect& view_frame); + // Informs the plugin that IME composition has been confirmed. + void ImeCompositionConfirmed(const string16& text); + // Informs the delegate that the plugin set a Carbon ThemeCursor. + void SetThemeCursor(ThemeCursor cursor); + // Informs the delegate that the plugin set a Carbon Cursor. + void SetCursor(const Cursor* cursor); + // Informs the delegate that the plugin set a Cocoa NSCursor. + void SetNSCursor(NSCursor* cursor); + +#ifndef NP_NO_CARBON + // Indicates that it's time to send the plugin a null event. + void FireIdleEvent(); +#endif +#endif // OS_MACOSX + + gfx::PluginWindowHandle windowed_handle() const { + return windowed_handle_; + } + +#if defined(OS_MACOSX) + // Allow setting a "fake" window handle to associate this plug-in with + // an IOSurface in the browser. Used for accelerated drawing surfaces. + void set_windowed_handle(gfx::PluginWindowHandle handle); +#endif + +#if defined(USE_X11) + void SetWindowlessShmPixmap(XID shm_pixmap) { + windowless_shm_pixmap_ = shm_pixmap; + } +#endif + + private: + friend class DeleteTask<WebPluginDelegateImpl>; + friend class webkit_glue::WebPluginDelegate; + + WebPluginDelegateImpl(gfx::PluginWindowHandle containing_view, + NPAPI::PluginInstance *instance); + ~WebPluginDelegateImpl(); + + // Called by Initialize() for platform-specific initialization. + // If this returns false, the plugin shouldn't be started--see Initialize(). + bool PlatformInitialize(); + + // Called by DestroyInstance(), used for platform-specific destruction. + void PlatformDestroyInstance(); + + //-------------------------- + // used for windowed plugins + void WindowedUpdateGeometry(const gfx::Rect& window_rect, + const gfx::Rect& clip_rect); + // Create the native window. + // Returns true if the window is created (or already exists). + // Returns false if unable to create the window. + bool WindowedCreatePlugin(); + + // Destroy the native window. + void WindowedDestroyWindow(); + + // Reposition the native window to be in sync with the given geometry. + // Returns true if the native window has moved or been clipped differently. + bool WindowedReposition(const gfx::Rect& window_rect, + const gfx::Rect& clip_rect); + + // Tells the plugin about the current state of the window. + // See NPAPI NPP_SetWindow for more information. + void WindowedSetWindow(); + +#if defined(OS_WIN) + // Registers the window class for our window + ATOM RegisterNativeWindowClass(); + + // Our WndProc functions. + static LRESULT CALLBACK DummyWindowProc( + HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); + static LRESULT CALLBACK NativeWndProc( + HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam); + static LRESULT CALLBACK FlashWindowlessWndProc( + HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam); + + // Used for throttling Flash messages. + static void ClearThrottleQueueForWindow(HWND window); + static void OnThrottleMessage(); + static void ThrottleMessage(WNDPROC proc, HWND hwnd, UINT message, + WPARAM wParam, LPARAM lParam); +#endif + + //---------------------------- + // used for windowless plugins + void WindowlessUpdateGeometry(const gfx::Rect& window_rect, + const gfx::Rect& clip_rect); + void WindowlessPaint(gfx::NativeDrawingContext hdc, const gfx::Rect& rect); + + // Tells the plugin about the current state of the window. + // See NPAPI NPP_SetWindow for more information. + void WindowlessSetWindow(); + + // Informs the plugin that it has gained or lost keyboard focus (on the Mac, + // this just means window first responder status). + void SetPluginHasFocus(bool focused); + + // Handles the platform specific details of setting plugin focus. Returns + // false if the platform cancelled the focus tranfer. + bool PlatformSetPluginHasFocus(bool focused); + + //----------------------------------------- + // used for windowed and windowless plugins + + // Does platform-specific event handling. Arguments and return are identical + // to HandleInputEvent. + bool PlatformHandleInputEvent(const WebKit::WebInputEvent& event, + WebKit::WebCursorInfo* cursor_info); + + NPAPI::PluginInstance* instance() { return instance_.get(); } + + // Closes down and destroys our plugin instance. + void DestroyInstance(); + + + // used for windowed plugins + // Note: on Mac OS X, the only time the windowed handle is non-zero + // is the case of accelerated rendering, which uses a fake window handle to + // identify itself back to the browser. It still performs all of its + // work offscreen. + gfx::PluginWindowHandle windowed_handle_; + gfx::Rect windowed_last_pos_; + + bool windowed_did_set_window_; + + // used by windowed and windowless plugins + bool windowless_; + + webkit_glue::WebPlugin* plugin_; + scoped_refptr<NPAPI::PluginInstance> instance_; + +#if defined(OS_WIN) + // Original wndproc before we subclassed. + WNDPROC plugin_wnd_proc_; + + // Used to throttle WM_USER+1 messages in Flash. + uint32 last_message_; + bool is_calling_wndproc; + + // The current keyboard layout of this process and the main thread ID of the + // browser process. These variables are used for synchronizing the keyboard + // layout of this process with the one of the browser process. + HKL keyboard_layout_; + int parent_thread_id_; +#endif // defined(OS_WIN) + +#if defined(USE_X11) + // The SHM pixmap for a windowless plugin. + XID windowless_shm_pixmap_; + + // The pixmap we're drawing into, for a windowless plugin. + GdkPixmap* pixmap_; + double first_event_time_; + + // On Linux some plugins assume that the GtkSocket container is in the same + // process. So we create a GtkPlug to plug into the browser's container, and + // a GtkSocket to hold the plugin. We then send the GtkPlug to the browser + // process. + GtkWidget* plug_; + GtkWidget* socket_; + + // Ensure pixmap_ exists and is at least width by height pixels. + void EnsurePixmapAtLeastSize(int width, int height); +#endif + + gfx::PluginWindowHandle parent_; + NPWindow window_; + gfx::Rect window_rect_; + gfx::Rect clip_rect_; + int quirks_; + +#if defined(OS_WIN) + // Windowless plugins don't have keyboard focus causing issues with the + // plugin not receiving keyboard events if the plugin enters a modal + // loop like TrackPopupMenuEx or MessageBox, etc. + // This is a basic issue with windows activation and focus arising due to + // the fact that these windows are created by different threads. Activation + // and focus are thread specific states, and if the browser has focus, + // the plugin may not have focus. + // To fix a majority of these activation issues we create a dummy visible + // child window to which we set focus whenever the windowless plugin + // receives a WM_LBUTTONDOWN/WM_RBUTTONDOWN message via NPP_HandleEvent. + + HWND dummy_window_for_activation_; + bool CreateDummyWindowForActivation(); + + // Returns true if the event passed in needs to be tracked for a potential + // modal loop. + static bool ShouldTrackEventForModalLoops(NPEvent* event); + + // The message filter hook procedure, which tracks modal loops entered by + // a plugin in the course of a NPP_HandleEvent call. + static LRESULT CALLBACK HandleEventMessageFilterHook(int code, WPARAM wParam, + LPARAM lParam); + + // TrackPopupMenu interceptor. Parameters are the same as the Win32 function + // TrackPopupMenu. + static BOOL WINAPI TrackPopupMenuPatch(HMENU menu, unsigned int flags, int x, + int y, int reserved, HWND window, + const RECT* rect); + + // SetCursor interceptor for windowless plugins. + static HCURSOR WINAPI SetCursorPatch(HCURSOR cursor); + + // RegEnumKeyExW interceptor. + static LONG WINAPI RegEnumKeyExWPatch( + HKEY key, DWORD index, LPWSTR name, LPDWORD name_size, LPDWORD reserved, + LPWSTR class_name, LPDWORD class_size, PFILETIME last_write_time); + + // The mouse hook proc which handles mouse capture in windowed plugins. + static LRESULT CALLBACK MouseHookProc(int code, WPARAM wParam, + LPARAM lParam); + + // Calls SetCapture/ReleaseCapture based on the message type. + static void HandleCaptureForMessage(HWND window, UINT message); + +#elif defined(OS_MACOSX) + // Sets window_rect_ to |rect| + void SetPluginRect(const gfx::Rect& rect); + // Sets content_area_origin to |origin| + void SetContentAreaOrigin(const gfx::Point& origin); + // Updates everything that depends on the plugin's absolute screen location. + void PluginScreenLocationChanged(); + // Updates anything that depends on plugin visibility. + void PluginVisibilityChanged(); + + // Enables/disables IME. + void SetImeEnabled(bool enabled); + + // Informs the browser about the updated accelerated drawing surface. + void UpdateAcceleratedSurface(); + + // Uses a CARenderer to draw the plug-in's layer in our OpenGL surface. + void DrawLayerInSurface(); + +#ifndef NP_NO_CARBON + // Moves our dummy window to match the current screen location of the plugin. + void UpdateDummyWindowBounds(const gfx::Point& plugin_origin); + +#ifndef NP_NO_QUICKDRAW + // Sets the mode used for QuickDraw plugin drawing. If enabled is true the + // plugin draws into a GWorld that's not connected to a window (the faster + // path), otherwise the plugin draws into our invisible dummy window (which is + // slower, since the call we use to scrape the window contents is much more + // expensive than copying between GWorlds). + void SetQuickDrawFastPathEnabled(bool enabled); +#endif + + // Adjusts the idle event rate for a Carbon plugin based on its current + // visibility. + void UpdateIdleEventRate(); +#endif // !NP_NO_CARBON + + CGContextRef buffer_context_; // Weak ref. + +#ifndef NP_NO_CARBON + NP_CGContext np_cg_context_; +#endif +#ifndef NP_NO_QUICKDRAW + NP_Port qd_port_; + scoped_ptr<QuickDrawDrawingManager> qd_manager_; + base::TimeTicks fast_path_enable_tick_; +#endif + + CALayer* layer_; // Used for CA drawing mode. Weak, retained by plug-in. + webkit_glue::WebPluginAcceleratedSurface* surface_; // Weak ref. + CARenderer* renderer_; // Renders layer_ to surface_. + scoped_ptr<base::RepeatingTimer<WebPluginDelegateImpl> > redraw_timer_; + + // The upper-left corner of the web content area in screen coordinates, + // relative to an upper-left (0,0). + gfx::Point content_area_origin_; + + bool containing_window_has_focus_; + bool initial_window_focus_; + bool container_is_visible_; + bool have_called_set_window_; + + gfx::Rect cached_clip_rect_; + + bool ime_enabled_; + + scoped_ptr<ExternalDragTracker> external_drag_tracker_; +#endif // OS_MACOSX + + // Called by the message filter hook when the plugin enters a modal loop. + void OnModalLoopEntered(); + + // Returns true if the message passed in corresponds to a user gesture. + static bool IsUserGesture(const WebKit::WebInputEvent& event); + + // The url with which the plugin was instantiated. + std::string plugin_url_; + +#if defined(OS_WIN) + // Indicates the end of a user gesture period. + void OnUserGestureEnd(); + + // Handle to the message filter hook + HHOOK handle_event_message_filter_hook_; + + // Event which is set when the plugin enters a modal loop in the course + // of a NPP_HandleEvent call. + HANDLE handle_event_pump_messages_event_; + + // This flag indicates whether we started tracking a user gesture message. + bool user_gesture_message_posted_; + + // Runnable Method Factory used to invoke the OnUserGestureEnd method + // asynchronously. + ScopedRunnableMethodFactory<WebPluginDelegateImpl> user_gesture_msg_factory_; + + // Handle to the mouse hook installed for certain windowed plugins like + // flash. + HHOOK mouse_hook_; +#endif + + // Holds the depth of the HandleEvent callstack. + int handle_event_depth_; + + // Holds the current cursor set by the windowless plugin. + WebCursor current_windowless_cursor_; + + // Set to true initially and indicates if this is the first npp_setwindow + // call received by the plugin. + bool first_set_window_call_; + + // True if the plugin thinks it has keyboard focus + bool plugin_has_focus_; + // True if the plugin element has focus within the web content, regardless of + // whether its containing view currently has focus. + bool has_webkit_focus_; + // True if the containing view currently has focus. + // Initially set to true so that plugin focus still works in environments + // where SetContentAreaHasFocus is never called. See + // https://bugs.webkit.org/show_bug.cgi?id=46013 for details. + bool containing_view_has_focus_; + + // True if NPP_New did not return an error. + bool creation_succeeded_; + + DISALLOW_COPY_AND_ASSIGN(WebPluginDelegateImpl); +}; + +#endif // WEBKIT_GLUE_PLUGINS_WEBPLUGIN_DELEGATE_IMPL_H_ diff --git a/webkit/glue/plugins/webplugin_delegate_impl_gtk.cc b/webkit/glue/plugins/webplugin_delegate_impl_gtk.cc new file mode 100644 index 0000000..609b41e --- /dev/null +++ b/webkit/glue/plugins/webplugin_delegate_impl_gtk.cc @@ -0,0 +1,767 @@ +// 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. + +#include "webkit/glue/plugins/webplugin_delegate_impl.h" + +#include <string> +#include <vector> + +#include <gtk/gtk.h> +#include <gdk/gdkx.h> + +#include "base/basictypes.h" +#include "base/file_util.h" +#include "base/message_loop.h" +#include "base/process_util.h" +#include "base/metrics/stats_counters.h" +#include "base/string_util.h" +#include "gfx/blit.h" +#include "skia/ext/platform_canvas.h" +#include "third_party/WebKit/WebKit/chromium/public/WebCursorInfo.h" +#include "third_party/WebKit/WebKit/chromium/public/WebInputEvent.h" +#include "webkit/glue/plugins/gtk_plugin_container.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/plugins/webplugin.h" +#include "webkit/glue/webkit_glue.h" + +#include "third_party/npapi/bindings/npapi_x11.h" + +using WebKit::WebCursorInfo; +using WebKit::WebKeyboardEvent; +using WebKit::WebInputEvent; +using WebKit::WebMouseEvent; + +WebPluginDelegateImpl::WebPluginDelegateImpl( + gfx::PluginWindowHandle containing_view, + NPAPI::PluginInstance *instance) + : windowed_handle_(0), + windowed_did_set_window_(false), + windowless_(false), + plugin_(NULL), + instance_(instance), + windowless_shm_pixmap_(None), + pixmap_(NULL), + first_event_time_(-1.0), + plug_(NULL), + socket_(NULL), + parent_(containing_view), + quirks_(0), + 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_)); + if (instance_->mime_type() == "application/x-shockwave-flash") { + // Flash is tied to Firefox's whacky behavior with windowless plugins. See + // comments in WindowlessPaint. + // TODO(viettrungluu): PLUGIN_QUIRK_WINDOWLESS_NO_RIGHT_CLICK: Don't allow + // right-clicks in windowless content since Flash 10.1 (initial release, at + // least) hangs in that case. Remove this once Flash is fixed. + quirks_ |= PLUGIN_QUIRK_WINDOWLESS_OFFSET_WINDOW_TO_DRAW + | PLUGIN_QUIRK_WINDOWLESS_INVALIDATE_AFTER_SET_WINDOW + | PLUGIN_QUIRK_WINDOWLESS_NO_RIGHT_CLICK; + } + + // TODO(evanm): I played with this for quite a while but couldn't + // figure out a way to make Flash not crash unless I didn't call + // NPP_SetWindow. + // However, after piman's grand refactor of windowed plugins, maybe + // this is no longer necessary. + quirks_ |= PLUGIN_QUIRK_DONT_SET_NULL_WINDOW_HANDLE_ON_DESTROY; +} + +WebPluginDelegateImpl::~WebPluginDelegateImpl() { + DestroyInstance(); + + if (!windowless_) + WindowedDestroyWindow(); + + if (window_.ws_info) { + // We only ever use ws_info as an NPSetWindowCallbackStruct. + delete static_cast<NPSetWindowCallbackStruct*>(window_.ws_info); + } + + if (pixmap_) { + g_object_unref(pixmap_); + pixmap_ = NULL; + } +} + +bool WebPluginDelegateImpl::PlatformInitialize() { + gfx::PluginWindowHandle handle = + windowless_ ? 0 : gtk_plug_get_id(GTK_PLUG(plug_)); + plugin_->SetWindow(handle); + return true; +} + +void WebPluginDelegateImpl::PlatformDestroyInstance() { + // Nothing to do here. +} + +void WebPluginDelegateImpl::Paint(WebKit::WebCanvas* canvas, + const gfx::Rect& rect) { + if (!windowless_) + return; + cairo_t* context = canvas->beginPlatformPaint(); + WindowlessPaint(context, rect); + canvas->endPlatformPaint(); +} + +void WebPluginDelegateImpl::Print(cairo_t* context) { + NOTIMPLEMENTED(); +} + +void WebPluginDelegateImpl::InstallMissingPlugin() { + NOTIMPLEMENTED(); +} + +bool WebPluginDelegateImpl::WindowedCreatePlugin() { + DCHECK(!windowed_handle_); + DCHECK(!plug_); + + // NPP_GetValue() might write 4 bytes of data to this variable. Don't use a + // single byte bool, use an int instead and make sure it is initialized. + int xembed = 0; + NPError err = instance_->NPP_GetValue(NPPVpluginNeedsXEmbed, &xembed); + if (err != NPERR_NO_ERROR || !xembed) { + NOTIMPLEMENTED() << " windowed plugin but without xembed. " + "See http://code.google.com/p/chromium/issues/detail?id=38229"; + return false; + } + + // Passing 0 as the socket XID creates a plug without plugging it in a socket + // yet, so that it can be latter added with gtk_socket_add_id(). + plug_ = gtk_plug_new(0); + gtk_widget_show(plug_); + socket_ = gtk_socket_new(); + gtk_widget_show(socket_); + gtk_container_add(GTK_CONTAINER(plug_), socket_); + gtk_widget_show_all(plug_); + + // Prevent the plug from being destroyed if the browser kills the container + // window. + g_signal_connect(plug_, "delete-event", G_CALLBACK(gtk_true), NULL); + // Prevent the socket from being destroyed when the plugin removes itself. + g_signal_connect(socket_, "plug_removed", G_CALLBACK(gtk_true), NULL); + + windowed_handle_ = gtk_socket_get_id(GTK_SOCKET(socket_)); + + window_.window = reinterpret_cast<void*>(windowed_handle_); + + if (!window_.ws_info) + window_.ws_info = new NPSetWindowCallbackStruct; + NPSetWindowCallbackStruct* extra = + static_cast<NPSetWindowCallbackStruct*>(window_.ws_info); + extra->display = GDK_DISPLAY(); + extra->visual = DefaultVisual(GDK_DISPLAY(), 0); + extra->depth = DefaultDepth(GDK_DISPLAY(), 0); + extra->colormap = DefaultColormap(GDK_DISPLAY(), 0); + + return true; +} + +void WebPluginDelegateImpl::WindowedDestroyWindow() { + if (plug_) { + plugin_->WillDestroyWindow(gtk_plug_get_id(GTK_PLUG(plug_))); + + gtk_widget_destroy(plug_); + plug_ = NULL; + socket_ = NULL; + windowed_handle_ = 0; + } +} + +bool WebPluginDelegateImpl::WindowedReposition( + const gfx::Rect& window_rect, + const gfx::Rect& clip_rect) { + if (window_rect == window_rect_ && clip_rect == clip_rect_) + return false; + + window_rect_ = window_rect; + clip_rect_ = clip_rect; + + return true; +} + +void WebPluginDelegateImpl::WindowedSetWindow() { + if (!instance_) + return; + + if (!windowed_handle_) { + NOTREACHED(); + return; + } + + // See https://bugzilla.mozilla.org/show_bug.cgi?id=108347 + // If we call NPP_SetWindow with a <= 0 width or height, problems arise in + // Flash (and possibly other plugins). + // TODO(piman): the Mozilla code suggests that for the Java plugin, we should + // still call NPP_SetWindow in that case. We need to verify that. + if (window_rect_.width() <= 0 || window_rect_.height() <= 0) { + return; + } + + instance()->set_window_handle(windowed_handle_); + + DCHECK(!instance()->windowless()); + + window_.clipRect.top = clip_rect_.y(); + window_.clipRect.left = clip_rect_.x(); + window_.clipRect.bottom = clip_rect_.y() + clip_rect_.height(); + window_.clipRect.right = clip_rect_.x() + clip_rect_.width(); + window_.height = window_rect_.height(); + window_.width = window_rect_.width(); + window_.x = window_rect_.x(); + window_.y = window_rect_.y(); + + //window_.window = windowed_handle_; + window_.type = NPWindowTypeWindow; + + // Reset this flag before entering the instance in case of side-effects. + windowed_did_set_window_ = true; + + NPError err = instance()->NPP_SetWindow(&window_); + DCHECK(err == NPERR_NO_ERROR); +} + +void WebPluginDelegateImpl::WindowlessUpdateGeometry( + const gfx::Rect& window_rect, + const gfx::Rect& clip_rect) { + // Only resend to the instance if the geometry has changed. + if (window_rect == window_rect_ && clip_rect == clip_rect_) + return; + + clip_rect_ = clip_rect; + window_rect_ = window_rect; + WindowlessSetWindow(); +} + +void WebPluginDelegateImpl::EnsurePixmapAtLeastSize(int width, int height) { + if (pixmap_) { + gint cur_width, cur_height; + gdk_drawable_get_size(pixmap_, &cur_width, &cur_height); + if (cur_width >= width && cur_height >= height) + return; // We are already the appropriate size. + + // Otherwise, we need to recreate ourselves. + g_object_unref(pixmap_); + pixmap_ = NULL; + } + + // |sys_visual| is owned by gdk; we shouldn't free it. + GdkVisual* sys_visual = gdk_visual_get_system(); + pixmap_ = gdk_pixmap_new(NULL, // use width/height/depth params + std::max(1, width), std::max(1, height), + sys_visual->depth); + GdkColormap* colormap = gdk_colormap_new(gdk_visual_get_system(), + FALSE); + gdk_drawable_set_colormap(GDK_DRAWABLE(pixmap_), colormap); + // The GdkDrawable now owns the GdkColormap. + g_object_unref(colormap); +} + +#ifdef DEBUG_RECTANGLES +namespace { + +// Draw a rectangle on a Cairo context. +// Useful for debugging various rectangles involved in drawing plugins. +void DrawDebugRectangle(cairo_t* cairo, + const gfx::Rect& rect, + float r, float g, float b) { + cairo_set_source_rgba(cairo, r, g, b, 0.5); + cairo_rectangle(cairo, rect.x(), rect.y(), + rect.width(), rect.height()); + cairo_stroke(cairo); +} + +} // namespace +#endif + +void WebPluginDelegateImpl::WindowlessPaint(cairo_t* context, + const gfx::Rect& damage_rect) { + // Compare to: + // http://mxr.mozilla.org/firefox/source/layout/generic/nsObjectFrame.cpp: + // nsPluginInstanceOwner::Renderer::NativeDraw(). + + DCHECK(context); + + // TODO(darin): we should avoid calling NPP_SetWindow here since it may + // cause page layout to be invalidated. + + // The actual dirty region is just the intersection of the plugin window and + // the clip window with the damage region. However, the plugin wants to draw + // relative to the containing window's origin, so our pixmap must be from the + // window's origin down to the bottom-right edge of the dirty region. + // + // Typical case: + // X-----------------------------------+-----------------------------+ + // | | | + // | pixmap +-------------------+ | + // | | damage | window | + // | | | | + // | +---+-------------------+-------------+ | + // | | | | clip | | + // | +---+---+-------------------+----------+ | | + // | | | | | | | | + // | | | | draw | | | | + // | | | | | | | | + // +-------+---+---+-------------------+----------+--+ | + // | | | | | | + // | | +-------------------+ | | + // | | | | + // | | plugin | | + // | +--------------------------------------+ | + // | | + // | | + // +-----------------------------------------------------------------+ + // X = origin + // + // NPAPI doesn't properly define which coordinates each of + // - window.clipRect, window.x and window.y in the SetWindow call + // - x and y in GraphicsExpose HandleEvent call + // are relative to, nor does it define what the pixmap is relative to. + // + // Any sane values for them just don't work with the flash plugin. Firefox + // has some interesting behavior. Experiments showed that: + // - window.clipRect is always in the same space as window.x and window.y + // - in the first SetWindow call, or when scrolling, window.x and window.y are + // the coordinates of the plugin relative to the window. + // - whenever only a part of the plugin is drawn, Firefox issues a SetWindow + // call before each GraphicsExpose event, that sets the drawing origin to + // (0, 0) as if the plugin was scrolled to be partially out of the view. The + // GraphicsExpose event has coordinates relative to the "window" (assuming + // that virtual scroll). The pixmap is also relative to the window. It always + // sets the clip rect to the draw rect. + // + // Attempts to deviate from that makes Flash render at the wrong place in the + // pixmap, or render the wrong pixels. + // + // Flash plugin: + // X-----------------------------------------------------------------+ + // | | + // | +-------------------+ "real" window | + // | | damage | | + // | | | | + // | +---+-------------------+-------------+ | + // | | | | "real" clip | | + // | +---+---O===================#==========#==#===============# + // | | | H draw | | | H + // | | | H = pixmap | | | H + // | | | H = "apparent" clip | | | H + // | + +---#-------------------+----------+--+ H + // | | H | | H + // | | H-------------------+ | H + // | | H | H + // | | H plugin | H + // | +-------#------------------------------+ H + // | H H + // | H "apparent" window H + // +---------------#=================================================# + // X = "real" origin + // O = "apparent" origin + // "real" means as seen by Chrome + // "apparent" means as seen by the plugin. + + gfx::Rect draw_rect = window_rect_.Intersect(damage_rect); + + // clip_rect_ is relative to the plugin + gfx::Rect clip_rect_window = clip_rect_; + clip_rect_window.Offset(window_rect_.x(), window_rect_.y()); + draw_rect = draw_rect.Intersect(clip_rect_window); + + // These offsets represent by how much the view is shifted to accomodate + // Flash (the coordinates of X relative to O in the diagram above). + int offset_x = 0; + int offset_y = 0; + if (quirks_ & PLUGIN_QUIRK_WINDOWLESS_OFFSET_WINDOW_TO_DRAW) { + offset_x = -draw_rect.x(); + offset_y = -draw_rect.y(); + window_.clipRect.top = 0; + window_.clipRect.left = 0; + window_.clipRect.bottom = draw_rect.height(); + window_.clipRect.right = draw_rect.width(); + window_.height = window_rect_.height(); + window_.width = window_rect_.width(); + window_.x = window_rect_.x() - draw_rect.x(); + window_.y = window_rect_.y() - draw_rect.y(); + window_.type = NPWindowTypeDrawable; + DCHECK(window_.ws_info); + NPError err = instance()->NPP_SetWindow(&window_); + DCHECK_EQ(err, NPERR_NO_ERROR); + } + + gfx::Rect pixmap_draw_rect = draw_rect; + pixmap_draw_rect.Offset(offset_x, offset_y); + + gfx::Rect pixmap_rect(0, 0, + pixmap_draw_rect.right(), + pixmap_draw_rect.bottom()); + + // Construct the paint message, targeting the pixmap. + NPEvent np_event = {0}; + XGraphicsExposeEvent &event = np_event.xgraphicsexpose; + event.type = GraphicsExpose; + event.x = pixmap_draw_rect.x(); + event.y = pixmap_draw_rect.y(); + event.width = pixmap_draw_rect.width(); + event.height = pixmap_draw_rect.height(); + event.display = GDK_DISPLAY(); + + if (windowless_shm_pixmap_ != None) { + Pixmap pixmap = None; + GC xgc = NULL; + Display* display = event.display; + gfx::Rect plugin_draw_rect = draw_rect; + + // Make plugin_draw_rect relative to the plugin window. + plugin_draw_rect.Offset(-window_rect_.x(), -window_rect_.y()); + + // In case the drawing area does not start with the plugin window origin, + // we can not let the plugin directly draw over the shared memory pixmap. + if (plugin_draw_rect.x() != pixmap_draw_rect.x() || + plugin_draw_rect.y() != pixmap_draw_rect.y()) { + pixmap = XCreatePixmap(display, windowless_shm_pixmap_, + std::max(1, pixmap_rect.width()), + std::max(1, pixmap_rect.height()), + DefaultDepth(display, 0)); + xgc = XCreateGC(display, windowless_shm_pixmap_, 0, NULL); + // Copy the current image into the pixmap, so the plugin can draw over it. + XCopyArea(display, windowless_shm_pixmap_, pixmap, xgc, + plugin_draw_rect.x(), plugin_draw_rect.y(), + pixmap_draw_rect.width(), pixmap_draw_rect.height(), + pixmap_draw_rect.x(), pixmap_draw_rect.y()); + + event.drawable = pixmap; + } else { + event.drawable = windowless_shm_pixmap_; + } + + // Tell the plugin to paint into the pixmap. + static base::StatsRate plugin_paint("Plugin.Paint"); + base::StatsScope<base::StatsRate> scope(plugin_paint); + NPError err = instance()->NPP_HandleEvent(&np_event); + DCHECK_EQ(err, NPERR_NO_ERROR); + + if (pixmap != None) { + // Copy the rendered image pixmap back into the shm pixmap + // and thus the drawing buffer. + XCopyArea(display, pixmap, windowless_shm_pixmap_, xgc, + pixmap_draw_rect.x(), pixmap_draw_rect.y(), + pixmap_draw_rect.width(), pixmap_draw_rect.height(), + plugin_draw_rect.x(), plugin_draw_rect.y()); + XSync(display, FALSE); + if (xgc) + XFreeGC(display, xgc); + XFreePixmap(display, pixmap); + } else { + XSync(display, FALSE); + } + } else { + EnsurePixmapAtLeastSize(pixmap_rect.width(), pixmap_rect.height()); + + // Copy the current image into the pixmap, so the plugin can draw over + // this background. + cairo_t* cairo = gdk_cairo_create(pixmap_); + BlitContextToContext(cairo, pixmap_draw_rect, context, draw_rect.origin()); + cairo_destroy(cairo); + + event.drawable = GDK_PIXMAP_XID(pixmap_); + + // Tell the plugin to paint into the pixmap. + static base::StatsRate plugin_paint("Plugin.Paint"); + base::StatsScope<base::StatsRate> scope(plugin_paint); + NPError err = instance()->NPP_HandleEvent(&np_event); + DCHECK_EQ(err, NPERR_NO_ERROR); + + cairo_save(context); + // Now copy the rendered image pixmap back into the drawing buffer. + gdk_cairo_set_source_pixmap(context, pixmap_, -offset_x, -offset_y); + cairo_rectangle(context, draw_rect.x(), draw_rect.y(), + draw_rect.width(), draw_rect.height()); + cairo_clip(context); + cairo_paint(context); + +#ifdef DEBUG_RECTANGLES + // Draw some debugging rectangles. + // Pixmap rect = blue. + DrawDebugRectangle(context, pixmap_rect, 0, 0, 1); + // Drawing rect = red. + DrawDebugRectangle(context, draw_rect, 1, 0, 0); +#endif + cairo_restore(context); + } +} + +void WebPluginDelegateImpl::WindowlessSetWindow() { + if (!instance()) + return; + + if (window_rect_.IsEmpty()) // wait for geometry to be set. + return; + + DCHECK(instance()->windowless()); + // Mozilla docs say that this window param is not used for windowless + // plugins; rather, the window is passed during the GraphicsExpose event. + DCHECK(window_.window == 0); + + window_.clipRect.top = clip_rect_.y() + window_rect_.y(); + window_.clipRect.left = clip_rect_.x() + window_rect_.x(); + window_.clipRect.bottom = + clip_rect_.y() + clip_rect_.height() + window_rect_.y(); + window_.clipRect.right = + clip_rect_.x() + clip_rect_.width() + window_rect_.x(); + window_.height = window_rect_.height(); + window_.width = window_rect_.width(); + window_.x = window_rect_.x(); + window_.y = window_rect_.y(); + window_.type = NPWindowTypeDrawable; + + if (!window_.ws_info) + window_.ws_info = new NPSetWindowCallbackStruct; + NPSetWindowCallbackStruct* extra = + static_cast<NPSetWindowCallbackStruct*>(window_.ws_info); + extra->display = GDK_DISPLAY(); + extra->visual = DefaultVisual(GDK_DISPLAY(), 0); + extra->depth = DefaultDepth(GDK_DISPLAY(), 0); + extra->colormap = DefaultColormap(GDK_DISPLAY(), 0); + + NPError err = instance()->NPP_SetWindow(&window_); + DCHECK(err == NPERR_NO_ERROR); + if (quirks_ & PLUGIN_QUIRK_WINDOWLESS_INVALIDATE_AFTER_SET_WINDOW) { + // After a NPP_SetWindow, Flash cancels its timer that generates the + // invalidates until it gets a paint event, but doesn't explicitly call + // NPP_InvalidateRect. + plugin_->InvalidateRect(clip_rect_); + } +} + +bool WebPluginDelegateImpl::PlatformSetPluginHasFocus(bool focused) { + DCHECK(instance()->windowless()); + + NPEvent np_event = {0}; + XFocusChangeEvent &event = np_event.xfocus; + event.type = focused ? FocusIn : FocusOut; + event.display = GDK_DISPLAY(); + // Same values as Firefox. .serial and .window stay 0. + event.mode = -1; + event.detail = NotifyDetailNone; + instance()->NPP_HandleEvent(&np_event); + return true; +} + +// Converts a WebInputEvent::Modifiers bitfield into a +// corresponding X modifier state. +static int GetXModifierState(int modifiers) { + int x_state = 0; + if (modifiers & WebInputEvent::ControlKey) + x_state |= ControlMask; + if (modifiers & WebInputEvent::ShiftKey) + x_state |= ShiftMask; + if (modifiers & WebInputEvent::AltKey) + x_state |= Mod1Mask; + if (modifiers & WebInputEvent::MetaKey) + x_state |= Mod2Mask; + if (modifiers & WebInputEvent::LeftButtonDown) + x_state |= Button1Mask; + if (modifiers & WebInputEvent::MiddleButtonDown) + x_state |= Button2Mask; + if (modifiers & WebInputEvent::RightButtonDown) + x_state |= Button3Mask; + // TODO(piman@google.com): There are other modifiers, e.g. Num Lock, that + // should be set (and Firefox does), but we didn't keep the information in + // the WebKit event. + return x_state; +} + +static bool NPEventFromWebMouseEvent(const WebMouseEvent& event, + Time timestamp, + NPEvent *np_event) { + np_event->xany.display = GDK_DISPLAY(); + // NOTE: Firefox keeps xany.serial and xany.window as 0. + + int modifier_state = GetXModifierState(event.modifiers); + + Window root = GDK_ROOT_WINDOW(); + switch (event.type) { + case WebInputEvent::MouseMove: { + np_event->type = MotionNotify; + XMotionEvent &motion_event = np_event->xmotion; + motion_event.root = root; + motion_event.time = timestamp; + motion_event.x = event.x; + motion_event.y = event.y; + motion_event.x_root = event.globalX; + motion_event.y_root = event.globalY; + motion_event.state = modifier_state; + motion_event.is_hint = NotifyNormal; + motion_event.same_screen = True; + break; + } + case WebInputEvent::MouseLeave: + case WebInputEvent::MouseEnter: { + if (event.type == WebInputEvent::MouseEnter) { + np_event->type = EnterNotify; + } else { + np_event->type = LeaveNotify; + } + XCrossingEvent &crossing_event = np_event->xcrossing; + crossing_event.root = root; + crossing_event.time = timestamp; + crossing_event.x = event.x; + crossing_event.y = event.y; + crossing_event.x_root = event.globalX; + crossing_event.y_root = event.globalY; + crossing_event.mode = -1; // This is what Firefox sets it to. + crossing_event.detail = NotifyDetailNone; + crossing_event.same_screen = True; + // TODO(piman@google.com): set this to the correct value. Firefox does. I + // don't know where to get the information though, we get focus + // notifications, but no unfocus. + crossing_event.focus = 0; + crossing_event.state = modifier_state; + break; + } + case WebInputEvent::MouseUp: + case WebInputEvent::MouseDown: { + if (event.type == WebInputEvent::MouseDown) { + np_event->type = ButtonPress; + } else { + np_event->type = ButtonRelease; + } + XButtonEvent &button_event = np_event->xbutton; + button_event.root = root; + button_event.time = timestamp; + button_event.x = event.x; + button_event.y = event.y; + button_event.x_root = event.globalX; + button_event.y_root = event.globalY; + button_event.state = modifier_state; + switch (event.button) { + case WebMouseEvent::ButtonLeft: + button_event.button = Button1; + break; + case WebMouseEvent::ButtonMiddle: + button_event.button = Button2; + break; + case WebMouseEvent::ButtonRight: + button_event.button = Button3; + break; + default: + NOTREACHED(); + } + button_event.same_screen = True; + break; + } + default: + NOTREACHED(); + return false; + } + return true; +} + +static bool NPEventFromWebKeyboardEvent(const WebKeyboardEvent& event, + Time timestamp, + NPEvent *np_event) { + np_event->xany.display = GDK_DISPLAY(); + // NOTE: Firefox keeps xany.serial and xany.window as 0. + + switch (event.type) { + case WebKeyboardEvent::KeyDown: + np_event->type = KeyPress; + break; + case WebKeyboardEvent::KeyUp: + np_event->type = KeyRelease; + break; + default: + NOTREACHED(); + return false; + } + XKeyEvent &key_event = np_event->xkey; + key_event.send_event = False; + key_event.display = GDK_DISPLAY(); + // NOTE: Firefox keeps xany.serial and xany.window as 0. + // TODO(piman@google.com): is this right for multiple screens ? + key_event.root = DefaultRootWindow(key_event.display); + key_event.time = timestamp; + // NOTE: We don't have the correct information for x/y/x_root/y_root. Firefox + // doesn't have it either, so we pass the same values. + key_event.x = 0; + key_event.y = 0; + key_event.x_root = -1; + key_event.y_root = -1; + key_event.state = GetXModifierState(event.modifiers); + key_event.keycode = event.nativeKeyCode; + key_event.same_screen = True; + return true; +} + +static bool NPEventFromWebInputEvent(const WebInputEvent& event, + Time timestamp, + NPEvent* np_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 NPEventFromWebMouseEvent( + *static_cast<const WebMouseEvent*>(&event), timestamp, np_event); + case WebInputEvent::KeyDown: + case WebInputEvent::KeyUp: + if (event.size < sizeof(WebKeyboardEvent)) { + NOTREACHED(); + return false; + } + return NPEventFromWebKeyboardEvent( + *static_cast<const WebKeyboardEvent*>(&event), timestamp, np_event); + default: + return false; + } +} + +bool WebPluginDelegateImpl::PlatformHandleInputEvent( + const WebInputEvent& event, WebCursorInfo* cursor_info) { + + if (first_event_time_ < 0.0) + first_event_time_ = event.timeStampSeconds; + Time timestamp = static_cast<Time>( + (event.timeStampSeconds - first_event_time_) * 1.0e3); + NPEvent np_event = {0}; + if (!NPEventFromWebInputEvent(event, timestamp, &np_event)) { + return false; + } + // See comment about PLUGIN_QUIRK_WINDOWLESS_NO_RIGHT_CLICK in constructor. + if (windowless_ && + (quirks_ & PLUGIN_QUIRK_WINDOWLESS_NO_RIGHT_CLICK) && + (np_event.type == ButtonPress || np_event.type == ButtonRelease) && + (np_event.xbutton.button == Button3)) { + return false; + } + + bool ret = instance()->NPP_HandleEvent(&np_event) != 0; + + // Flash always returns false, even when the event is handled. + ret = true; + +#if 0 + if (event->event == WM_MOUSEMOVE) { + // Snag a reference to the current cursor ASAP in case the plugin modified + // it. There is a nasty race condition here with the multiprocess browser + // as someone might be setting the cursor in the main process as well. + *cursor = current_windowless_cursor_; + } +#endif + + return ret; +} 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..552484a --- /dev/null +++ b/webkit/glue/plugins/webplugin_delegate_impl_mac.mm @@ -0,0 +1,1145 @@ +// 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/message_loop.h" +#include "base/metrics/stats_counters.h" +#include "base/scoped_ptr.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "base/sys_string_conversions.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/plugins/webplugin_accelerated_surface_mac.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), + containing_window_has_focus_(false), + initial_window_focus_(false), + container_is_visible_(false), + have_called_set_window_(false), + ime_enabled_(false), + 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<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. + // 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<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_ = 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_->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<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); + } + + 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; + } + } + + // 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))); + } + int16_t handle_response = instance()->NPP_HandleEvent(plugin_event); + bool handled = handle_response != kNPEventNotHandled; + + if (handled && event.type == WebInputEvent::KeyDown) { + // Update IME state as requested by the plugin. + SetImeEnabled(handle_response == kNPEventStartIME); + } + + // 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<base::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; + + if (!has_focus) + SetImeEnabled(false); + +#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; + } + } +} + +bool WebPluginDelegateImpl::PlatformSetPluginHasFocus(bool focused) { + if (!have_called_set_window_) + return false; + + if (!focused) + SetImeEnabled(false); + + 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 + // <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(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::ImeCompositionConfirmed(const string16& text) { + if (instance()->event_model() != NPEventModelCocoa) { + DLOG(ERROR) << "IME text receieved in Carbon event model"; + return; + } + + NPCocoaEvent text_event; + memset(&text_event, 0, sizeof(NPCocoaEvent)); + text_event.type = NPCocoaEventTextInput; + text_event.data.text.text = + reinterpret_cast<NPNSString*>(base::SysUTF16ToNSString(text)); + instance()->NPP_HandleEvent(&text_event); +} + +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(); + } + } +} + +void WebPluginDelegateImpl::SetImeEnabled(bool enabled) { + if (instance()->event_model() != NPEventModelCocoa) + return; + if (enabled == ime_enabled_) + return; + ime_enabled_ = enabled; + plugin_->SetImeEnabled(enabled); +} + +#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<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 diff --git a/webkit/glue/plugins/webplugin_delegate_impl_win.cc b/webkit/glue/plugins/webplugin_delegate_impl_win.cc new file mode 100644 index 0000000..e1acba1 --- /dev/null +++ b/webkit/glue/plugins/webplugin_delegate_impl_win.cc @@ -0,0 +1,1410 @@ +// 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. + +#include "webkit/glue/plugins/webplugin_delegate_impl.h" + +#include <map> +#include <string> +#include <vector> + +#include "app/win/iat_patch_function.h" +#include "base/file_util.h" +#include "base/lazy_instance.h" +#include "base/message_loop.h" +#include "base/metrics/stats_counters.h" +#include "base/scoped_ptr.h" +#include "base/string_number_conversions.h" +#include "base/string_split.h" +#include "base/string_util.h" +#include "base/stringprintf.h" +#include "base/win/registry.h" +#include "base/win/windows_version.h" +#include "skia/ext/platform_canvas.h" +#include "third_party/WebKit/WebKit/chromium/public/WebInputEvent.h" +#include "webkit/glue/plugins/default_plugin_shared.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/plugins/webplugin.h" +#include "webkit/glue/webkit_glue.h" + +using WebKit::WebCursorInfo; +using WebKit::WebKeyboardEvent; +using WebKit::WebInputEvent; +using WebKit::WebMouseEvent; + +namespace { + +const wchar_t kWebPluginDelegateProperty[] = L"WebPluginDelegateProperty"; +const wchar_t kPluginNameAtomProperty[] = L"PluginNameAtom"; +const wchar_t kDummyActivationWindowName[] = L"DummyWindowForActivation"; +const wchar_t kPluginFlashThrottle[] = L"FlashThrottle"; + +// The fastest we are willing to process WM_USER+1 events for Flash. +// Flash can easily exceed the limits of our CPU if we don't throttle it. +// The throttle has been chosen by testing various delays and compromising +// on acceptable Flash performance and reasonable CPU consumption. +// +// I'd like to make the throttle delay variable, based on the amount of +// time currently required to paint Flash plugins. There isn't a good +// way to count the time spent in aggregate plugin painting, however, so +// this seems to work well enough. +const int kFlashWMUSERMessageThrottleDelayMs = 5; + +// Flash displays popups in response to user clicks by posting a WM_USER +// message to the plugin window. The handler for this message displays +// the popup. To ensure that the popups allowed state is sent correctly +// to the renderer we reset the popups allowed state in a timer. +const int kWindowedPluginPopupTimerMs = 50; + +// The current instance of the plugin which entered the modal loop. +WebPluginDelegateImpl* g_current_plugin_instance = NULL; + +typedef std::deque<MSG> ThrottleQueue; +base::LazyInstance<ThrottleQueue> g_throttle_queue(base::LINKER_INITIALIZED); +base::LazyInstance<std::map<HWND, WNDPROC> > g_window_handle_proc_map( + base::LINKER_INITIALIZED); + + +// Helper object for patching the TrackPopupMenu API. +base::LazyInstance<app::win::IATPatchFunction> g_iat_patch_track_popup_menu( + base::LINKER_INITIALIZED); + +// Helper object for patching the SetCursor API. +base::LazyInstance<app::win::IATPatchFunction> g_iat_patch_set_cursor( + base::LINKER_INITIALIZED); + +// Helper object for patching the RegEnumKeyExW API. +base::LazyInstance<app::win::IATPatchFunction> g_iat_patch_reg_enum_key_ex_w( + base::LINKER_INITIALIZED); + +// http://crbug.com/16114 +// Enforces providing a valid device context in NPWindow, so that NPP_SetWindow +// is never called with NPNWindoTypeDrawable and NPWindow set to NULL. +// Doing so allows removing NPP_SetWindow call during painting a windowless +// plugin, which otherwise could trigger layout change while painting by +// invoking NPN_Evaluate. Which would cause bad, bad crashes. Bad crashes. +// TODO(dglazkov): If this approach doesn't produce regressions, move class to +// webplugin_delegate_impl.h and implement for other platforms. +class DrawableContextEnforcer { + public: + explicit DrawableContextEnforcer(NPWindow* window) + : window_(window), + disposable_dc_(window && !window->window) { + // If NPWindow is NULL, create a device context with monochrome 1x1 surface + // and stuff it to NPWindow. + if (disposable_dc_) + window_->window = CreateCompatibleDC(NULL); + } + + ~DrawableContextEnforcer() { + if (!disposable_dc_) + return; + + DeleteDC(static_cast<HDC>(window_->window)); + window_->window = NULL; + } + + private: + NPWindow* window_; + bool disposable_dc_; +}; + +// These are from ntddk.h +typedef LONG NTSTATUS; + +#ifndef STATUS_SUCCESS +#define STATUS_SUCCESS ((NTSTATUS)0x00000000L) +#endif + +#ifndef STATUS_BUFFER_TOO_SMALL +#define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L) +#endif + +typedef enum _KEY_INFORMATION_CLASS { + KeyBasicInformation, + KeyNodeInformation, + KeyFullInformation, + KeyNameInformation, + KeyCachedInformation, + KeyVirtualizationInformation +} KEY_INFORMATION_CLASS; + +typedef struct _KEY_NAME_INFORMATION { + ULONG NameLength; + WCHAR Name[1]; +} KEY_NAME_INFORMATION, *PKEY_NAME_INFORMATION; + +typedef DWORD (__stdcall *ZwQueryKeyType)( + HANDLE key_handle, + int key_information_class, + PVOID key_information, + ULONG length, + PULONG result_length); + +// Returns a key's full path. +std::wstring GetKeyPath(HKEY key) { + if (key == NULL) + return L""; + + HMODULE dll = GetModuleHandle(L"ntdll.dll"); + if (dll == NULL) + return L""; + + ZwQueryKeyType func = reinterpret_cast<ZwQueryKeyType>( + ::GetProcAddress(dll, "ZwQueryKey")); + if (func == NULL) + return L""; + + DWORD size = 0; + DWORD result = 0; + result = func(key, KeyNameInformation, 0, 0, &size); + if (result != STATUS_BUFFER_TOO_SMALL) + return L""; + + scoped_array<char> buffer(new char[size]); + if (buffer.get() == NULL) + return L""; + + result = func(key, KeyNameInformation, buffer.get(), size, &size); + if (result != STATUS_SUCCESS) + return L""; + + KEY_NAME_INFORMATION* info = + reinterpret_cast<KEY_NAME_INFORMATION*>(buffer.get()); + return std::wstring(info->Name, info->NameLength / sizeof(wchar_t)); +} + +} // namespace + +bool WebPluginDelegateImpl::IsPluginDelegateWindow(HWND window) { + // We use a buffer that is one char longer than we need to detect cases where + // kNativeWindowClassName is a prefix of the given window's class name. It + // happens that GetClassNameW will just silently truncate the class name to + // fit into the given buffer. + wchar_t class_name[arraysize(kNativeWindowClassName) + 1]; + if (!GetClassNameW(window, class_name, arraysize(class_name))) + return false; + return wcscmp(class_name, kNativeWindowClassName) == 0; +} + +bool WebPluginDelegateImpl::GetPluginNameFromWindow( + HWND window, std::wstring *plugin_name) { + if (NULL == plugin_name) { + return false; + } + if (!IsPluginDelegateWindow(window)) { + return false; + } + ATOM plugin_name_atom = reinterpret_cast<ATOM>( + GetPropW(window, kPluginNameAtomProperty)); + if (plugin_name_atom != 0) { + WCHAR plugin_name_local[MAX_PATH] = {0}; + GlobalGetAtomNameW(plugin_name_atom, + plugin_name_local, + ARRAYSIZE(plugin_name_local)); + *plugin_name = plugin_name_local; + return true; + } + return false; +} + +bool WebPluginDelegateImpl::IsDummyActivationWindow(HWND window) { + if (!IsWindow(window)) + return false; + + wchar_t window_title[MAX_PATH + 1] = {0}; + if (GetWindowText(window, window_title, arraysize(window_title))) { + return (0 == lstrcmpiW(window_title, kDummyActivationWindowName)); + } + return false; +} + +LRESULT CALLBACK WebPluginDelegateImpl::HandleEventMessageFilterHook( + int code, WPARAM wParam, LPARAM lParam) { + if (g_current_plugin_instance) { + g_current_plugin_instance->OnModalLoopEntered(); + } else { + NOTREACHED(); + } + return CallNextHookEx(NULL, code, wParam, lParam); +} + +LRESULT CALLBACK WebPluginDelegateImpl::MouseHookProc( + int code, WPARAM wParam, LPARAM lParam) { + if (code == HC_ACTION) { + MOUSEHOOKSTRUCT* hook_struct = reinterpret_cast<MOUSEHOOKSTRUCT*>(lParam); + if (hook_struct) + HandleCaptureForMessage(hook_struct->hwnd, wParam); + } + + return CallNextHookEx(NULL, code, wParam, lParam); +} + +WebPluginDelegateImpl::WebPluginDelegateImpl( + gfx::PluginWindowHandle containing_view, + NPAPI::PluginInstance *instance) + : parent_(containing_view), + instance_(instance), + quirks_(0), + plugin_(NULL), + windowless_(false), + windowed_handle_(NULL), + windowed_did_set_window_(false), + plugin_wnd_proc_(NULL), + last_message_(0), + is_calling_wndproc(false), + keyboard_layout_(NULL), + parent_thread_id_(0), + dummy_window_for_activation_(NULL), + handle_event_message_filter_hook_(NULL), + handle_event_pump_messages_event_(NULL), + user_gesture_message_posted_(false), +#pragma warning(suppress: 4355) // can use this + user_gesture_msg_factory_(this), + handle_event_depth_(0), + mouse_hook_(NULL), + 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_)); + + const WebPluginInfo& plugin_info = instance_->plugin_lib()->plugin_info(); + std::wstring filename = + StringToLowerASCII(plugin_info.path.BaseName().value()); + + if (instance_->mime_type() == "application/x-shockwave-flash" || + filename == kFlashPlugin) { + // Flash only requests windowless plugins if we return a Mozilla user + // agent. + instance_->set_use_mozilla_user_agent(); + quirks_ |= PLUGIN_QUIRK_THROTTLE_WM_USER_PLUS_ONE; + quirks_ |= PLUGIN_QUIRK_PATCH_SETCURSOR; + quirks_ |= PLUGIN_QUIRK_ALWAYS_NOTIFY_SUCCESS; + quirks_ |= PLUGIN_QUIRK_HANDLE_MOUSE_CAPTURE; + } else if (filename == kAcrobatReaderPlugin) { + // Check for the version number above or equal 9. + std::vector<std::wstring> version; + base::SplitString(plugin_info.version, L'.', &version); + if (version.size() > 0) { + int major; + base::StringToInt(version[0], &major); + if (major >= 9) { + quirks_ |= PLUGIN_QUIRK_DIE_AFTER_UNLOAD; + + // 9.2 needs this. + quirks_ |= PLUGIN_QUIRK_SETWINDOW_TWICE; + } + } + quirks_ |= PLUGIN_QUIRK_BLOCK_NONSTANDARD_GETURL_REQUESTS; + } else if (plugin_info.name.find(L"Windows Media Player") != + std::wstring::npos) { + // Windows Media Player needs two NPP_SetWindow calls. + quirks_ |= PLUGIN_QUIRK_SETWINDOW_TWICE; + + // Windowless mode doesn't work in the WMP NPAPI plugin. + quirks_ |= PLUGIN_QUIRK_NO_WINDOWLESS; + + // The media player plugin sets its size on the first NPP_SetWindow call + // and never updates its size. We should call the underlying NPP_SetWindow + // only when we have the correct size. + quirks_ |= PLUGIN_QUIRK_IGNORE_FIRST_SETWINDOW_CALL; + + if (filename == kOldWMPPlugin) { + // Non-admin users on XP couldn't modify the key to force the new UI. + quirks_ |= PLUGIN_QUIRK_PATCH_REGENUMKEYEXW; + } + } else if (instance_->mime_type() == "audio/x-pn-realaudio-plugin" || + filename == kRealPlayerPlugin) { + quirks_ |= PLUGIN_QUIRK_DONT_CALL_WND_PROC_RECURSIVELY; + } else if (plugin_info.name.find(L"VLC Multimedia Plugin") != + std::wstring::npos || + plugin_info.name.find(L"VLC Multimedia Plug-in") != + std::wstring::npos) { + // VLC hangs on NPP_Destroy if we call NPP_SetWindow with a null window + // handle + quirks_ |= PLUGIN_QUIRK_DONT_SET_NULL_WINDOW_HANDLE_ON_DESTROY; + // VLC 0.8.6d and 0.8.6e crash if multiple instances are created. + quirks_ |= PLUGIN_QUIRK_DONT_ALLOW_MULTIPLE_INSTANCES; + } else if (filename == kSilverlightPlugin) { + // Explanation for this quirk can be found in + // WebPluginDelegateImpl::Initialize. + quirks_ |= PLUGIN_QUIRK_PATCH_SETCURSOR; + } else if (plugin_info.name.find(L"DivX Web Player") != + std::wstring::npos) { + // The divx plugin sets its size on the first NPP_SetWindow call and never + // updates its size. We should call the underlying NPP_SetWindow only when + // we have the correct size. + quirks_ |= PLUGIN_QUIRK_IGNORE_FIRST_SETWINDOW_CALL; + } +} + +WebPluginDelegateImpl::~WebPluginDelegateImpl() { + if (::IsWindow(dummy_window_for_activation_)) { + ::DestroyWindow(dummy_window_for_activation_); + } + + DestroyInstance(); + + if (!windowless_) + WindowedDestroyWindow(); + + if (handle_event_pump_messages_event_) { + CloseHandle(handle_event_pump_messages_event_); + } +} + +bool WebPluginDelegateImpl::PlatformInitialize() { + plugin_->SetWindow(windowed_handle_); + + if (windowless_ && !instance_->plugin_lib()->internal()) { + CreateDummyWindowForActivation(); + handle_event_pump_messages_event_ = CreateEvent(NULL, TRUE, FALSE, NULL); + plugin_->SetWindowlessPumpEvent(handle_event_pump_messages_event_); + } + + // We cannot patch internal plugins as they are not shared libraries. + if (!instance_->plugin_lib()->internal()) { + // Windowless plugins call the WindowFromPoint API and passes the result of + // that to the TrackPopupMenu API call as the owner window. This causes the + // API to fail as the API expects the window handle to live on the same + // thread as the caller. It works in the other browsers as the plugin lives + // on the browser thread. Our workaround is to intercept the TrackPopupMenu + // API and replace the window handle with the dummy activation window. + if (windowless_ && !g_iat_patch_track_popup_menu.Pointer()->is_patched()) { + g_iat_patch_track_popup_menu.Pointer()->Patch( + GetPluginPath().value().c_str(), "user32.dll", "TrackPopupMenu", + WebPluginDelegateImpl::TrackPopupMenuPatch); + } + + // Windowless plugins can set cursors by calling the SetCursor API. This + // works because the thread inputs of the browser UI thread and the plugin + // thread are attached. We intercept the SetCursor API for windowless + // plugins and remember the cursor being set. This is shipped over to the + // browser in the HandleEvent call, which ensures that the cursor does not + // change when a windowless plugin instance changes the cursor + // in a background tab. + if (windowless_ && !g_iat_patch_set_cursor.Pointer()->is_patched() && + (quirks_ & PLUGIN_QUIRK_PATCH_SETCURSOR)) { + g_iat_patch_set_cursor.Pointer()->Patch( + GetPluginPath().value().c_str(), "user32.dll", "SetCursor", + WebPluginDelegateImpl::SetCursorPatch); + } + + // The windowed flash plugin has a bug which occurs when the plugin enters + // fullscreen mode. It basically captures the mouse on WM_LBUTTONDOWN and + // does not release capture correctly causing it to stop receiving + // subsequent mouse events. This problem is also seen in Safari where there + // is code to handle this in the wndproc. However the plugin subclasses the + // window again in WM_LBUTTONDOWN before entering full screen. As a result + // Safari does not receive the WM_LBUTTONUP message. To workaround this + // issue we use a per thread mouse hook. This bug does not occur in Firefox + // and opera. Firefox has code similar to Safari. It could well be a bug in + // the flash plugin, which only occurs in webkit based browsers. + if (quirks_ & PLUGIN_QUIRK_HANDLE_MOUSE_CAPTURE) { + mouse_hook_ = SetWindowsHookEx(WH_MOUSE, MouseHookProc, NULL, + GetCurrentThreadId()); + } + } + + // On XP, WMP will use its old UI unless a registry key under HKLM has the + // name of the current process. We do it in the installer for admin users, + // for the rest patch this function. + if ((quirks_ & PLUGIN_QUIRK_PATCH_REGENUMKEYEXW) && + base::win::GetVersion() == base::win::VERSION_XP && + !base::win::RegKey().Open(HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\MediaPlayer\\ShimInclusionList\\chrome.exe", + KEY_READ) && + !g_iat_patch_reg_enum_key_ex_w.Pointer()->is_patched()) { + g_iat_patch_reg_enum_key_ex_w.Pointer()->Patch( + L"wmpdxm.dll", "advapi32.dll", "RegEnumKeyExW", + WebPluginDelegateImpl::RegEnumKeyExWPatch); + } + + return true; +} + +void WebPluginDelegateImpl::PlatformDestroyInstance() { + if (!instance_->plugin_lib()) + return; + + // Unpatch if this is the last plugin instance. + if (instance_->plugin_lib()->instance_count() != 1) + return; + + if (g_iat_patch_set_cursor.Pointer()->is_patched()) + g_iat_patch_set_cursor.Pointer()->Unpatch(); + + if (g_iat_patch_track_popup_menu.Pointer()->is_patched()) + g_iat_patch_track_popup_menu.Pointer()->Unpatch(); + + if (g_iat_patch_reg_enum_key_ex_w.Pointer()->is_patched()) + g_iat_patch_reg_enum_key_ex_w.Pointer()->Unpatch(); + + if (mouse_hook_) { + UnhookWindowsHookEx(mouse_hook_); + mouse_hook_ = NULL; + } +} + +void WebPluginDelegateImpl::Paint(skia::PlatformCanvas* canvas, + const gfx::Rect& rect) { + if (windowless_) { + HDC hdc = canvas->beginPlatformPaint(); + WindowlessPaint(hdc, rect); + canvas->endPlatformPaint(); + } +} + +void WebPluginDelegateImpl::Print(HDC hdc) { + // 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. +#if 0 + NPPrint npprint; + npprint.mode = NP_EMBED; + npprint.print.embedPrint.platformPrint = reinterpret_cast<void*>(hdc); + npprint.print.embedPrint.window = window_; + instance()->NPP_Print(&npprint); +#endif +} + +void WebPluginDelegateImpl::InstallMissingPlugin() { + NPEvent evt; + evt.event = default_plugin::kInstallMissingPluginMessage; + evt.lParam = 0; + evt.wParam = 0; + instance()->NPP_HandleEvent(&evt); +} + +bool WebPluginDelegateImpl::WindowedCreatePlugin() { + DCHECK(!windowed_handle_); + + RegisterNativeWindowClass(); + + // The window will be sized and shown later. + windowed_handle_ = CreateWindowEx( + WS_EX_LEFT | WS_EX_LTRREADING | WS_EX_RIGHTSCROLLBAR, + kNativeWindowClassName, + 0, + WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, + 0, + 0, + 0, + 0, + parent_, + 0, + GetModuleHandle(NULL), + 0); + if (windowed_handle_ == 0) + return false; + + if (IsWindow(parent_)) { + // This is a tricky workaround for Issue 2673 in chromium "Flash: IME not + // available". To use IMEs in this window, we have to make Windows attach + // IMEs to this window (i.e. load IME DLLs, attach them to this process, + // and add their message hooks to this window). Windows attaches IMEs while + // this process creates a top-level window. On the other hand, to layout + // this window correctly in the given parent window (RenderWidgetHostHWND), + // this window should be a child window of the parent window. + // To satisfy both of the above conditions, this code once creates a + // top-level window and change it to a child window of the parent window. + SetWindowLongPtr(windowed_handle_, GWL_STYLE, + WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS); + SetParent(windowed_handle_, parent_); + } + + BOOL result = SetProp(windowed_handle_, kWebPluginDelegateProperty, this); + DCHECK(result == TRUE) << "SetProp failed, last error = " << GetLastError(); + // Get the name of the plugin, create an atom and set that in a window + // property. Use an atom so that other processes can access the name of + // the plugin that this window is hosting + if (instance_ != NULL) { + NPAPI::PluginLib* plugin_lib = instance()->plugin_lib(); + if (plugin_lib != NULL) { + std::wstring plugin_name = plugin_lib->plugin_info().name; + if (!plugin_name.empty()) { + ATOM plugin_name_atom = GlobalAddAtomW(plugin_name.c_str()); + DCHECK(0 != plugin_name_atom); + result = SetProp(windowed_handle_, + kPluginNameAtomProperty, + reinterpret_cast<HANDLE>(plugin_name_atom)); + DCHECK(result == TRUE) << "SetProp failed, last error = " << + GetLastError(); + } + } + } + + // Calling SetWindowLongPtrA here makes the window proc ASCII, which is + // required by at least the Shockwave Director plug-in. + SetWindowLongPtrA( + windowed_handle_, GWL_WNDPROC, reinterpret_cast<LONG>(DefWindowProcA)); + + return true; +} + +void WebPluginDelegateImpl::WindowedDestroyWindow() { + if (windowed_handle_ != NULL) { + // Unsubclass the window. + WNDPROC current_wnd_proc = reinterpret_cast<WNDPROC>( + GetWindowLongPtr(windowed_handle_, GWLP_WNDPROC)); + if (current_wnd_proc == NativeWndProc) { + SetWindowLongPtr(windowed_handle_, + GWLP_WNDPROC, + reinterpret_cast<LONG>(plugin_wnd_proc_)); + } + + plugin_->WillDestroyWindow(windowed_handle_); + + DestroyWindow(windowed_handle_); + windowed_handle_ = 0; + } +} + +// Erase all messages in the queue destined for a particular window. +// When windows are closing, callers should use this function to clear +// the queue. +// static +void WebPluginDelegateImpl::ClearThrottleQueueForWindow(HWND window) { + ThrottleQueue* throttle_queue = g_throttle_queue.Pointer(); + + ThrottleQueue::iterator it; + for (it = throttle_queue->begin(); it != throttle_queue->end(); ) { + if (it->hwnd == window) { + it = throttle_queue->erase(it); + } else { + it++; + } + } +} + +// Delayed callback for processing throttled messages. +// Throttled messages are aggregated globally across all plugins. +// static +void WebPluginDelegateImpl::OnThrottleMessage() { + // The current algorithm walks the list and processes the first + // message it finds for each plugin. It is important to service + // all active plugins with each pass through the throttle, otherwise + // we see video jankiness. Copy the set to notify before notifying + // since we may re-enter OnThrottleMessage from CallWindowProc! + ThrottleQueue* throttle_queue = g_throttle_queue.Pointer(); + ThrottleQueue notify_queue; + std::set<HWND> processed; + + ThrottleQueue::iterator it = throttle_queue->begin(); + while (it != throttle_queue->end()) { + const MSG& msg = *it; + if (processed.find(msg.hwnd) == processed.end()) { + processed.insert(msg.hwnd); + notify_queue.push_back(msg); + it = throttle_queue->erase(it); + } else { + it++; + } + } + + for (it = notify_queue.begin(); it != notify_queue.end(); ++it) { + const MSG& msg = *it; + WNDPROC proc = reinterpret_cast<WNDPROC>(msg.time); + // It is possible that the window was closed after we queued + // this message. This is a rare event; just verify the window + // is alive. (see also bug 1259488) + if (IsWindow(msg.hwnd)) + CallWindowProc(proc, msg.hwnd, msg.message, msg.wParam, msg.lParam); + } + + if (!throttle_queue->empty()) { + MessageLoop::current()->PostDelayedTask(FROM_HERE, + NewRunnableFunction(&WebPluginDelegateImpl::OnThrottleMessage), + kFlashWMUSERMessageThrottleDelayMs); + } +} + +// Schedule a windows message for delivery later. +// static +void WebPluginDelegateImpl::ThrottleMessage(WNDPROC proc, HWND hwnd, + UINT message, WPARAM wParam, + LPARAM lParam) { + MSG msg; + msg.time = reinterpret_cast<DWORD>(proc); + msg.hwnd = hwnd; + msg.message = message; + msg.wParam = wParam; + msg.lParam = lParam; + + ThrottleQueue* throttle_queue = g_throttle_queue.Pointer(); + + throttle_queue->push_back(msg); + + if (throttle_queue->size() == 1) { + MessageLoop::current()->PostDelayedTask(FROM_HERE, + NewRunnableFunction(&WebPluginDelegateImpl::OnThrottleMessage), + kFlashWMUSERMessageThrottleDelayMs); + } +} + +// We go out of our way to find the hidden windows created by Flash for +// windowless plugins. We throttle the rate at which they deliver messages +// so that they will not consume outrageous amounts of CPU. +// static +LRESULT CALLBACK WebPluginDelegateImpl::FlashWindowlessWndProc(HWND hwnd, + UINT message, WPARAM wparam, LPARAM lparam) { + std::map<HWND, WNDPROC>::iterator index = + g_window_handle_proc_map.Get().find(hwnd); + + WNDPROC old_proc = (*index).second; + DCHECK(old_proc); + + switch (message) { + case WM_NCDESTROY: { + WebPluginDelegateImpl::ClearThrottleQueueForWindow(hwnd); + g_window_handle_proc_map.Get().erase(index); + break; + } + // Flash may flood the message queue with WM_USER+1 message causing 100% CPU + // usage. See https://bugzilla.mozilla.org/show_bug.cgi?id=132759. We + // prevent this by throttling the messages. + case WM_USER + 1: { + WebPluginDelegateImpl::ThrottleMessage(old_proc, hwnd, message, wparam, + lparam); + return TRUE; + } + default: { + break; + } + } + return CallWindowProc(old_proc, hwnd, message, wparam, lparam); +} + +// Callback for enumerating the Flash windows. +BOOL CALLBACK EnumFlashWindows(HWND window, LPARAM arg) { + WNDPROC wnd_proc = reinterpret_cast<WNDPROC>(arg); + TCHAR class_name[1024]; + if (!RealGetWindowClass(window, class_name, + sizeof(class_name)/sizeof(TCHAR))) { + LOG(ERROR) << "RealGetWindowClass failure: " << GetLastError(); + return FALSE; + } + + if (wcscmp(class_name, L"SWFlash_PlaceholderX")) + return TRUE; + + WNDPROC current_wnd_proc = reinterpret_cast<WNDPROC>( + GetWindowLongPtr(window, GWLP_WNDPROC)); + if (current_wnd_proc != wnd_proc) { + WNDPROC old_flash_proc = reinterpret_cast<WNDPROC>(SetWindowLongPtr( + window, GWLP_WNDPROC, + reinterpret_cast<LONG>(wnd_proc))); + DCHECK(old_flash_proc); + g_window_handle_proc_map.Get()[window] = old_flash_proc; + } + + return TRUE; +} + +bool WebPluginDelegateImpl::CreateDummyWindowForActivation() { + DCHECK(!dummy_window_for_activation_); + dummy_window_for_activation_ = CreateWindowEx( + 0, + L"Static", + kDummyActivationWindowName, + WS_CHILD, + 0, + 0, + 0, + 0, + parent_, + 0, + GetModuleHandle(NULL), + 0); + + if (dummy_window_for_activation_ == 0) + return false; + + // Flash creates background windows which use excessive CPU in our + // environment; we wrap these windows and throttle them so that they don't + // get out of hand. + if (!EnumThreadWindows(::GetCurrentThreadId(), EnumFlashWindows, + reinterpret_cast<LPARAM>( + &WebPluginDelegateImpl::FlashWindowlessWndProc))) { + // Log that this happened. Flash will still work; it just means the + // throttle isn't installed (and Flash will use more CPU). + NOTREACHED(); + LOG(ERROR) << "Failed to wrap all windowless Flash windows"; + } + return true; +} + +bool WebPluginDelegateImpl::WindowedReposition( + const gfx::Rect& window_rect, + const gfx::Rect& clip_rect) { + if (!windowed_handle_) { + NOTREACHED(); + return false; + } + + if (window_rect_ == window_rect && clip_rect_ == clip_rect) + return false; + + // We only set the plugin's size here. Its position is moved elsewhere, which + // allows the window moves/scrolling/clipping to be synchronized with the page + // and other windows. + // If the plugin window has no parent, then don't focus it because it isn't + // being displayed anywhere. See: + // http://code.google.com/p/chromium/issues/detail?id=32658 + if (window_rect.size() != window_rect_.size()) { + UINT flags = SWP_NOMOVE | SWP_NOZORDER; + if (!GetParent(windowed_handle_)) + flags |= SWP_NOACTIVATE; + ::SetWindowPos(windowed_handle_, + NULL, + 0, + 0, + window_rect.width(), + window_rect.height(), + flags); + } + + window_rect_ = window_rect; + clip_rect_ = clip_rect; + + // Ensure that the entire window gets repainted. + ::InvalidateRect(windowed_handle_, NULL, FALSE); + + return true; +} + +void WebPluginDelegateImpl::WindowedSetWindow() { + if (!instance_) + return; + + if (!windowed_handle_) { + NOTREACHED(); + return; + } + + instance()->set_window_handle(windowed_handle_); + + DCHECK(!instance()->windowless()); + + window_.clipRect.top = std::max(0, clip_rect_.y()); + window_.clipRect.left = std::max(0, clip_rect_.x()); + window_.clipRect.bottom = std::max(0, clip_rect_.y() + clip_rect_.height()); + window_.clipRect.right = std::max(0, clip_rect_.x() + clip_rect_.width()); + window_.height = window_rect_.height(); + window_.width = window_rect_.width(); + window_.x = 0; + window_.y = 0; + + window_.window = windowed_handle_; + window_.type = NPWindowTypeWindow; + + // Reset this flag before entering the instance in case of side-effects. + windowed_did_set_window_ = true; + + NPError err = instance()->NPP_SetWindow(&window_); + if (quirks_ & PLUGIN_QUIRK_SETWINDOW_TWICE) + instance()->NPP_SetWindow(&window_); + + WNDPROC current_wnd_proc = reinterpret_cast<WNDPROC>( + GetWindowLongPtr(windowed_handle_, GWLP_WNDPROC)); + if (current_wnd_proc != NativeWndProc) { + plugin_wnd_proc_ = reinterpret_cast<WNDPROC>(SetWindowLongPtr( + windowed_handle_, GWLP_WNDPROC, reinterpret_cast<LONG>(NativeWndProc))); + } +} + +ATOM WebPluginDelegateImpl::RegisterNativeWindowClass() { + static bool have_registered_window_class = false; + if (have_registered_window_class == true) + return true; + + have_registered_window_class = true; + + WNDCLASSEX wcex; + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.style = CS_DBLCLKS; + wcex.lpfnWndProc = DummyWindowProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = GetModuleHandle(NULL); + wcex.hIcon = 0; + wcex.hCursor = 0; + // Some plugins like windows media player 11 create child windows parented + // by our plugin window, where the media content is rendered. These plugins + // dont implement WM_ERASEBKGND, which causes painting issues, when the + // window where the media is rendered is moved around. DefWindowProc does + // implement WM_ERASEBKGND correctly if we have a valid background brush. + wcex.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW+1); + wcex.lpszMenuName = 0; + wcex.lpszClassName = kNativeWindowClassName; + wcex.hIconSm = 0; + + return RegisterClassEx(&wcex); +} + +LRESULT CALLBACK WebPluginDelegateImpl::DummyWindowProc( + HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { + // This is another workaround for Issue 2673 in chromium "Flash: IME not + // available". Somehow, the CallWindowProc() function does not dispatch + // window messages when its first parameter is a handle representing the + // DefWindowProc() function. To avoid this problem, this code creates a + // wrapper function which just encapsulates the DefWindowProc() function + // and set it as the window procedure of a windowed plug-in. + return DefWindowProc(hWnd, message, wParam, lParam); +} + +// Returns true if the message passed in corresponds to a user gesture. +static bool IsUserGestureMessage(unsigned int message) { + switch (message) { + case WM_LBUTTONUP: + case WM_RBUTTONUP: + case WM_MBUTTONUP: + case WM_KEYUP: + return true; + + default: + break; + } + + return false; +} + +LRESULT CALLBACK WebPluginDelegateImpl::NativeWndProc( + HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { + WebPluginDelegateImpl* delegate = reinterpret_cast<WebPluginDelegateImpl*>( + GetProp(hwnd, kWebPluginDelegateProperty)); + if (!delegate) { + NOTREACHED(); + return 0; + } + + if (message == delegate->last_message_ && + delegate->GetQuirks() & PLUGIN_QUIRK_DONT_CALL_WND_PROC_RECURSIVELY && + delegate->is_calling_wndproc) { + // Real may go into a state where it recursively dispatches the same event + // when subclassed. See https://bugzilla.mozilla.org/show_bug.cgi?id=192914 + // We only do the recursive check for Real because it's possible and valid + // for a plugin to synchronously dispatch a message to itself such that it + // looks like it's in recursion. + return TRUE; + } + + // Flash may flood the message queue with WM_USER+1 message causing 100% CPU + // usage. See https://bugzilla.mozilla.org/show_bug.cgi?id=132759. We + // prevent this by throttling the messages. + if (message == WM_USER + 1 && + delegate->GetQuirks() & PLUGIN_QUIRK_THROTTLE_WM_USER_PLUS_ONE) { + WebPluginDelegateImpl::ThrottleMessage(delegate->plugin_wnd_proc_, hwnd, + message, wparam, lparam); + return FALSE; + } + + LRESULT result; + uint32 old_message = delegate->last_message_; + delegate->last_message_ = message; + + static UINT custom_msg = RegisterWindowMessage(kPaintMessageName); + if (message == custom_msg) { + // Get the invalid rect which is in screen coordinates and convert to + // window coordinates. + gfx::Rect invalid_rect; + invalid_rect.set_x(wparam >> 16); + invalid_rect.set_y(wparam & 0xFFFF); + invalid_rect.set_width(lparam >> 16); + invalid_rect.set_height(lparam & 0xFFFF); + + RECT window_rect; + GetWindowRect(hwnd, &window_rect); + invalid_rect.Offset(-window_rect.left, -window_rect.top); + + // The plugin window might have non-client area. If we don't pass in + // RDW_FRAME then the children don't receive WM_NCPAINT messages while + // scrolling, which causes painting problems (http://b/issue?id=923945). + uint32 flags = RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_FRAME; + + // If a plugin (like Google Earth or Java) has child windows that are hosted + // in a different process, then RedrawWindow with UPDATENOW will + // synchronously wait for this call to complete. Some messages are pumped + // but not others, which could lead to a deadlock. So avoid reentrancy by + // only synchronously calling RedrawWindow once at a time. + if (old_message != custom_msg) + flags |= RDW_UPDATENOW; + + RedrawWindow(hwnd, &invalid_rect.ToRECT(), NULL, flags); + result = FALSE; + } else { + delegate->is_calling_wndproc = true; + + if (!delegate->user_gesture_message_posted_ && + IsUserGestureMessage(message)) { + delegate->user_gesture_message_posted_ = true; + + delegate->instance()->PushPopupsEnabledState(true); + + MessageLoop::current()->PostDelayedTask(FROM_HERE, + delegate->user_gesture_msg_factory_.NewRunnableMethod( + &WebPluginDelegateImpl::OnUserGestureEnd), + kWindowedPluginPopupTimerMs); + } + + HandleCaptureForMessage(hwnd, message); + + // Maintain a local/global stack for the g_current_plugin_instance variable + // as this may be a nested invocation. + WebPluginDelegateImpl* last_plugin_instance = g_current_plugin_instance; + + g_current_plugin_instance = delegate; + + result = CallWindowProc( + delegate->plugin_wnd_proc_, hwnd, message, wparam, lparam); + + delegate->is_calling_wndproc = false; + g_current_plugin_instance = last_plugin_instance; + + if (message == WM_NCDESTROY) { + RemoveProp(hwnd, kWebPluginDelegateProperty); + ATOM plugin_name_atom = reinterpret_cast<ATOM>( + RemoveProp(hwnd, kPluginNameAtomProperty)); + if (plugin_name_atom != 0) + GlobalDeleteAtom(plugin_name_atom); + ClearThrottleQueueForWindow(hwnd); + } + } + delegate->last_message_ = old_message; + return result; +} + +void WebPluginDelegateImpl::WindowlessUpdateGeometry( + const gfx::Rect& window_rect, + const gfx::Rect& clip_rect) { + bool window_rect_changed = (window_rect_ != window_rect); + // Only resend to the instance if the geometry has changed. + if (!window_rect_changed && clip_rect == clip_rect_) + return; + + clip_rect_ = clip_rect; + window_rect_ = window_rect; + + WindowlessSetWindow(); + + if (window_rect_changed) { + WINDOWPOS win_pos = {0}; + win_pos.x = window_rect_.x(); + win_pos.y = window_rect_.y(); + win_pos.cx = window_rect_.width(); + win_pos.cy = window_rect_.height(); + + NPEvent pos_changed_event; + pos_changed_event.event = WM_WINDOWPOSCHANGED; + pos_changed_event.wParam = 0; + pos_changed_event.lParam = PtrToUlong(&win_pos); + + instance()->NPP_HandleEvent(&pos_changed_event); + } +} + +void WebPluginDelegateImpl::WindowlessPaint(HDC hdc, + const gfx::Rect& damage_rect) { + DCHECK(hdc); + + RECT damage_rect_win; + damage_rect_win.left = damage_rect.x(); // + window_rect_.x(); + damage_rect_win.top = damage_rect.y(); // + window_rect_.y(); + damage_rect_win.right = damage_rect_win.left + damage_rect.width(); + damage_rect_win.bottom = damage_rect_win.top + damage_rect.height(); + + // Save away the old HDC as this could be a nested invocation. + void* old_dc = window_.window; + window_.window = hdc; + + NPEvent paint_event; + paint_event.event = WM_PAINT; + // NOTE: NPAPI is not 64bit safe. It puts pointers into 32bit values. + paint_event.wParam = PtrToUlong(hdc); + paint_event.lParam = PtrToUlong(&damage_rect_win); + static base::StatsRate plugin_paint("Plugin.Paint"); + base::StatsScope<base::StatsRate> scope(plugin_paint); + instance()->NPP_HandleEvent(&paint_event); + window_.window = old_dc; +} + +void WebPluginDelegateImpl::WindowlessSetWindow() { + if (!instance()) + return; + + if (window_rect_.IsEmpty()) // wait for geometry to be set. + return; + + DCHECK(instance()->windowless()); + + window_.clipRect.top = clip_rect_.y(); + window_.clipRect.left = clip_rect_.x(); + window_.clipRect.bottom = clip_rect_.y() + clip_rect_.height(); + window_.clipRect.right = clip_rect_.x() + clip_rect_.width(); + window_.height = window_rect_.height(); + window_.width = window_rect_.width(); + window_.x = window_rect_.x(); + window_.y = window_rect_.y(); + window_.type = NPWindowTypeDrawable; + DrawableContextEnforcer enforcer(&window_); + + NPError err = instance()->NPP_SetWindow(&window_); + DCHECK(err == NPERR_NO_ERROR); +} + +bool WebPluginDelegateImpl::PlatformSetPluginHasFocus(bool focused) { + DCHECK(instance()->windowless()); + + NPEvent focus_event; + focus_event.event = focused ? WM_SETFOCUS : WM_KILLFOCUS; + focus_event.wParam = 0; + focus_event.lParam = 0; + + instance()->NPP_HandleEvent(&focus_event); + return true; +} + +static bool NPEventFromWebMouseEvent(const WebMouseEvent& event, + NPEvent *np_event) { + np_event->lParam = static_cast<uint32>(MAKELPARAM(event.windowX, + event.windowY)); + np_event->wParam = 0; + + if (event.modifiers & WebInputEvent::ControlKey) + np_event->wParam |= MK_CONTROL; + if (event.modifiers & WebInputEvent::ShiftKey) + np_event->wParam |= MK_SHIFT; + if (event.modifiers & WebInputEvent::LeftButtonDown) + np_event->wParam |= MK_LBUTTON; + if (event.modifiers & WebInputEvent::MiddleButtonDown) + np_event->wParam |= MK_MBUTTON; + if (event.modifiers & WebInputEvent::RightButtonDown) + np_event->wParam |= MK_RBUTTON; + + switch (event.type) { + case WebInputEvent::MouseMove: + case WebInputEvent::MouseLeave: + case WebInputEvent::MouseEnter: + np_event->event = WM_MOUSEMOVE; + return true; + case WebInputEvent::MouseDown: + switch (event.button) { + case WebMouseEvent::ButtonLeft: + np_event->event = WM_LBUTTONDOWN; + break; + case WebMouseEvent::ButtonMiddle: + np_event->event = WM_MBUTTONDOWN; + break; + case WebMouseEvent::ButtonRight: + np_event->event = WM_RBUTTONDOWN; + break; + } + return true; + case WebInputEvent::MouseUp: + switch (event.button) { + case WebMouseEvent::ButtonLeft: + np_event->event = WM_LBUTTONUP; + break; + case WebMouseEvent::ButtonMiddle: + np_event->event = WM_MBUTTONUP; + break; + case WebMouseEvent::ButtonRight: + np_event->event = WM_RBUTTONUP; + break; + } + return true; + default: + NOTREACHED(); + return false; + } +} + +static bool NPEventFromWebKeyboardEvent(const WebKeyboardEvent& event, + NPEvent *np_event) { + np_event->wParam = event.windowsKeyCode; + + switch (event.type) { + case WebInputEvent::KeyDown: + np_event->event = WM_KEYDOWN; + np_event->lParam = 0; + return true; + case WebInputEvent::Char: + np_event->event = WM_CHAR; + np_event->lParam = 0; + return true; + case WebInputEvent::KeyUp: + np_event->event = WM_KEYUP; + np_event->lParam = 0x8000; + return true; + default: + NOTREACHED(); + return false; + } +} + +static bool NPEventFromWebInputEvent(const WebInputEvent& event, + NPEvent* np_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 NPEventFromWebMouseEvent( + *static_cast<const WebMouseEvent*>(&event), np_event); + case WebInputEvent::KeyDown: + case WebInputEvent::Char: + case WebInputEvent::KeyUp: + if (event.size < sizeof(WebKeyboardEvent)) { + NOTREACHED(); + return false; + } + return NPEventFromWebKeyboardEvent( + *static_cast<const WebKeyboardEvent*>(&event), np_event); + default: + return false; + } +} + +bool WebPluginDelegateImpl::PlatformHandleInputEvent( + const WebInputEvent& event, WebCursorInfo* cursor_info) { + DCHECK(cursor_info != NULL); + + NPEvent np_event; + if (!NPEventFromWebInputEvent(event, &np_event)) { + return false; + } + + // Synchronize the keyboard layout with the one of the browser process. Flash + // uses the keyboard layout of this window to verify a WM_CHAR message is + // valid. That is, Flash discards a WM_CHAR message unless its character is + // the one translated with ToUnicode(). (Since a plug-in is running on a + // separate process from the browser process, we need to syncronize it + // manually.) + if (np_event.event == WM_CHAR) { + if (!keyboard_layout_) + keyboard_layout_ = GetKeyboardLayout(GetCurrentThreadId()); + if (!parent_thread_id_) + parent_thread_id_ = GetWindowThreadProcessId(parent_, NULL); + HKL parent_layout = GetKeyboardLayout(parent_thread_id_); + if (keyboard_layout_ != parent_layout) { + std::wstring layout_name(base::StringPrintf(L"%08x", parent_layout)); + LoadKeyboardLayout(layout_name.c_str(), KLF_ACTIVATE); + keyboard_layout_ = parent_layout; + } + } + + if (ShouldTrackEventForModalLoops(&np_event)) { + // A windowless plugin can enter a modal loop in a NPP_HandleEvent call. + // For e.g. Flash puts up a context menu when we right click on the + // windowless plugin area. We detect this by setting up a message filter + // hook pror to calling NPP_HandleEvent on the plugin and unhook on + // return from NPP_HandleEvent. If the plugin does enter a modal loop + // in that context we unhook on receiving the first notification in + // the message filter hook. + handle_event_message_filter_hook_ = + SetWindowsHookEx(WH_MSGFILTER, HandleEventMessageFilterHook, NULL, + GetCurrentThreadId()); + } + + bool old_task_reentrancy_state = + MessageLoop::current()->NestableTasksAllowed(); + + + // Maintain a local/global stack for the g_current_plugin_instance variable + // as this may be a nested invocation. + WebPluginDelegateImpl* last_plugin_instance = g_current_plugin_instance; + + g_current_plugin_instance = this; + + handle_event_depth_++; + + bool ret = instance()->NPP_HandleEvent(&np_event) != 0; + + // Flash and SilverLight always return false, even when they swallow the + // event. Flash does this because it passes the event to its window proc, + // which is supposed to return 0 if an event was handled. There are few + // exceptions, such as IME, where it sometimes returns true. + ret = true; + + if (np_event.event == WM_MOUSEMOVE) { + // Snag a reference to the current cursor ASAP in case the plugin modified + // it. There is a nasty race condition here with the multiprocess browser + // as someone might be setting the cursor in the main process as well. + current_windowless_cursor_.GetCursorInfo(cursor_info); + } + + handle_event_depth_--; + + g_current_plugin_instance = last_plugin_instance; + + MessageLoop::current()->SetNestableTasksAllowed(old_task_reentrancy_state); + + // We could have multiple NPP_HandleEvent calls nested together in case + // the plugin enters a modal loop. Reset the pump messages event when + // the outermost NPP_HandleEvent call unwinds. + if (handle_event_depth_ == 0) { + ResetEvent(handle_event_pump_messages_event_); + } + + return ret; +} + + +void WebPluginDelegateImpl::OnModalLoopEntered() { + DCHECK(handle_event_pump_messages_event_ != NULL); + SetEvent(handle_event_pump_messages_event_); + + MessageLoop::current()->SetNestableTasksAllowed(true); + + UnhookWindowsHookEx(handle_event_message_filter_hook_); + handle_event_message_filter_hook_ = NULL; +} + +bool WebPluginDelegateImpl::ShouldTrackEventForModalLoops(NPEvent* event) { + if (event->event == WM_RBUTTONDOWN) + return true; + return false; +} + +void WebPluginDelegateImpl::OnUserGestureEnd() { + user_gesture_message_posted_ = false; + instance()->PopPopupsEnabledState(); +} + +BOOL WINAPI WebPluginDelegateImpl::TrackPopupMenuPatch( + HMENU menu, unsigned int flags, int x, int y, int reserved, + HWND window, const RECT* rect) { + + HWND last_focus_window = NULL; + + if (g_current_plugin_instance) { + unsigned long window_process_id = 0; + unsigned long window_thread_id = + GetWindowThreadProcessId(window, &window_process_id); + // TrackPopupMenu fails if the window passed in belongs to a different + // thread. + if (::GetCurrentThreadId() != window_thread_id) { + window = g_current_plugin_instance->dummy_window_for_activation_; + } + + // To ensure that the plugin receives keyboard events we set focus to the + // dummy window. + // TODO(iyengar) We need a framework in the renderer to identify which + // windowless plugin is under the mouse and to handle this. This would + // also require some changes in RenderWidgetHost to detect this in the + // WM_MOUSEACTIVATE handler and inform the renderer accordingly. + if (g_current_plugin_instance->dummy_window_for_activation_) { + last_focus_window = + ::SetFocus(g_current_plugin_instance->dummy_window_for_activation_); + } + } + + BOOL result = TrackPopupMenu(menu, flags, x, y, reserved, window, rect); + + if (IsWindow(last_focus_window)) { + // The Flash plugin at times sets focus to its hidden top level window + // with class name SWFlash_PlaceholderX. This causes the chrome browser + // window to receive a WM_ACTIVATEAPP message as a top level window from + // another thread is now active. We end up in a state where the chrome + // browser window is not active even though the user clicked on it. + // Our workaround for this is to send over a raw + // WM_LBUTTONDOWN/WM_LBUTTONUP combination to the last focus window, which + // does the trick. + if (g_current_plugin_instance->dummy_window_for_activation_ != + ::GetFocus()) { + INPUT input_info = {0}; + input_info.type = INPUT_MOUSE; + input_info.mi.dwFlags = MOUSEEVENTF_LEFTDOWN; + ::SendInput(1, &input_info, sizeof(INPUT)); + + input_info.type = INPUT_MOUSE; + input_info.mi.dwFlags = MOUSEEVENTF_LEFTUP; + ::SendInput(1, &input_info, sizeof(INPUT)); + } else { + ::SetFocus(last_focus_window); + } + } + + return result; +} + +HCURSOR WINAPI WebPluginDelegateImpl::SetCursorPatch(HCURSOR cursor) { + // The windowless flash plugin periodically calls SetCursor in a wndproc + // instantiated on the plugin thread. This causes annoying cursor flicker + // when the mouse is moved on a foreground tab, with a windowless plugin + // instance in a background tab. We just ignore the call here. + if (!g_current_plugin_instance) { + HCURSOR current_cursor = GetCursor(); + if (current_cursor != cursor) { + ::SetCursor(cursor); + } + return current_cursor; + } + + if (!g_current_plugin_instance->IsWindowless()) { + return ::SetCursor(cursor); + } + + // It is ok to pass NULL here to GetCursor as we are not looking for cursor + // types defined by Webkit. + HCURSOR previous_cursor = + g_current_plugin_instance->current_windowless_cursor_.GetCursor(NULL); + + g_current_plugin_instance->current_windowless_cursor_.InitFromExternalCursor( + cursor); + return previous_cursor; +} + +LONG WINAPI WebPluginDelegateImpl::RegEnumKeyExWPatch( + HKEY key, DWORD index, LPWSTR name, LPDWORD name_size, LPDWORD reserved, + LPWSTR class_name, LPDWORD class_size, PFILETIME last_write_time) { + DWORD orig_size = *name_size; + LONG rv = RegEnumKeyExW(key, index, name, name_size, reserved, class_name, + class_size, last_write_time); + if (rv == ERROR_SUCCESS && + GetKeyPath(key).find(L"Microsoft\\MediaPlayer\\ShimInclusionList") != + std::wstring::npos) { + static const wchar_t kChromeExeName[] = L"chrome.exe"; + wcsncpy_s(name, orig_size, kChromeExeName, arraysize(kChromeExeName)); + *name_size = + std::min(orig_size, static_cast<DWORD>(arraysize(kChromeExeName))); + } + + return rv; +} + +void WebPluginDelegateImpl::HandleCaptureForMessage(HWND window, + UINT message) { + if (!WebPluginDelegateImpl::IsPluginDelegateWindow(window)) + return; + + switch (message) { + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: + ::SetCapture(window); + break; + + case WM_LBUTTONUP: + case WM_MBUTTONUP: + case WM_RBUTTONUP: + ::ReleaseCapture(); + break; + + default: + break; + } +} diff --git a/webkit/glue/plugins/webplugin_file_delegate.cc b/webkit/glue/plugins/webplugin_file_delegate.cc new file mode 100644 index 0000000..68c4c60 --- /dev/null +++ b/webkit/glue/plugins/webplugin_file_delegate.cc @@ -0,0 +1,17 @@ +// 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. + +#include "webkit/glue/plugins/webplugin_file_delegate.h" + +namespace webkit_glue { + +bool WebPluginFileDelegate::ChooseFile(const char* mime_types, + int mode, + NPChooseFileCallback callback, + void* user_data) { + return false; +} + +} // namespace webkit_glue + diff --git a/webkit/glue/plugins/webplugin_file_delegate.h b/webkit/glue/plugins/webplugin_file_delegate.h new file mode 100644 index 0000000..ad2bba0 --- /dev/null +++ b/webkit/glue/plugins/webplugin_file_delegate.h @@ -0,0 +1,33 @@ +// 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. + +#ifndef WEBKIT_GLUE_PLUGINS_WEBPLUGIN_FILE_DELEGATE_H_ +#define WEBKIT_GLUE_PLUGINS_WEBPLUGIN_FILE_DELEGATE_H_ + +#include "base/basictypes.h" +#include "third_party/npapi/bindings/npapi_extensions.h" + +namespace webkit_glue { + +// Interface for the NPAPI file extensions. This class implements "NOP" +// versions of all these functions so it can be used seamlessly by the +// "regular" plugin delegate while being overridden by the "pepper" one. +class WebPluginFileDelegate { + public: + // See NPChooseFilePtr in npapi_extensions.h. Returns true on success, on + // cancel, returns true but *filename will be filled with an empty FilePath + // and *handle will be 0. + virtual bool ChooseFile(const char* mime_types, + int mode, + NPChooseFileCallback callback, + void* user_data); + + protected: + WebPluginFileDelegate() {} + virtual ~WebPluginFileDelegate() {} +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_PLUGINS_WEBPLUGIN_FILE_DELEGATE_H_ diff --git a/webkit/glue/plugins/webplugin_impl.cc b/webkit/glue/plugins/webplugin_impl.cc new file mode 100644 index 0000000..666775b --- /dev/null +++ b/webkit/glue/plugins/webplugin_impl.cc @@ -0,0 +1,1393 @@ +// 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. + +#include "webkit/glue/plugins/webplugin_impl.h" + +#include "base/linked_ptr.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/string_util.h" +#include "base/stringprintf.h" +#include "base/utf_string_conversions.h" +#include "gfx/rect.h" +#include "googleurl/src/gurl.h" +#include "net/base/escape.h" +#include "net/base/net_errors.h" +#include "net/http/http_response_headers.h" +#include "skia/ext/platform_canvas.h" +#include "third_party/WebKit/WebKit/chromium/public/WebConsoleMessage.h" +#include "third_party/WebKit/WebKit/chromium/public/WebCookieJar.h" +#include "third_party/WebKit/WebKit/chromium/public/WebCString.h" +#include "third_party/WebKit/WebKit/chromium/public/WebCursorInfo.h" +#include "third_party/WebKit/WebKit/chromium/public/WebDevToolsAgent.h" +#include "third_party/WebKit/WebKit/chromium/public/WebData.h" +#include "third_party/WebKit/WebKit/chromium/public/WebDocument.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/WebKit/chromium/public/WebHTTPBody.h" +#include "third_party/WebKit/WebKit/chromium/public/WebHTTPHeaderVisitor.h" +#include "third_party/WebKit/WebKit/chromium/public/WebInputEvent.h" +#include "third_party/WebKit/WebKit/chromium/public/WebKit.h" +#include "third_party/WebKit/WebKit/chromium/public/WebKitClient.h" +#include "third_party/WebKit/WebKit/chromium/public/WebPluginContainer.h" +#include "third_party/WebKit/WebKit/chromium/public/WebPluginParams.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURL.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLError.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLLoader.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLLoaderClient.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLResponse.h" +#include "third_party/WebKit/WebKit/chromium/public/WebView.h" +#include "webkit/appcache/web_application_cache_host_impl.h" +#include "webkit/glue/multipart_response_delegate.h" +#include "webkit/glue/plugins/plugin_host.h" +#include "webkit/glue/plugins/plugin_instance.h" +#include "webkit/glue/plugins/webplugin_delegate.h" +#include "webkit/glue/plugins/webplugin_page_delegate.h" + +using appcache::WebApplicationCacheHostImpl; +using WebKit::WebCanvas; +using WebKit::WebConsoleMessage; +using WebKit::WebCookieJar; +using WebKit::WebCString; +using WebKit::WebCursorInfo; +using WebKit::WebData; +using WebKit::WebDataSource; +using WebKit::WebDevToolsAgent; +using WebKit::WebFrame; +using WebKit::WebHTTPBody; +using WebKit::WebHTTPHeaderVisitor; +using WebKit::WebInputEvent; +using WebKit::WebKeyboardEvent; +using WebKit::WebMouseEvent; +using WebKit::WebPluginContainer; +using WebKit::WebPluginParams; +using WebKit::WebRect; +using WebKit::WebString; +using WebKit::WebURL; +using WebKit::WebURLError; +using WebKit::WebURLLoader; +using WebKit::WebURLLoaderClient; +using WebKit::WebURLRequest; +using WebKit::WebURLResponse; +using WebKit::WebVector; +using WebKit::WebView; +using webkit_glue::MultipartResponseDelegate; + +namespace webkit_glue { +namespace { + +// This class handles individual multipart responses. It is instantiated when +// we receive HTTP status code 206 in the HTTP response. This indicates +// that the response could have multiple parts each separated by a boundary +// specified in the response header. +class MultiPartResponseClient : public WebURLLoaderClient { + public: + explicit MultiPartResponseClient(WebPluginResourceClient* resource_client) + : resource_client_(resource_client) { + Clear(); + } + + virtual void willSendRequest( + WebURLLoader*, WebURLRequest&, const WebURLResponse&) {} + virtual void didSendData( + WebURLLoader*, unsigned long long, unsigned long long) {} + + // Called when the multipart parser encounters an embedded multipart + // response. + virtual void didReceiveResponse( + WebURLLoader*, const WebURLResponse& response) { + int instance_size; + if (!MultipartResponseDelegate::ReadContentRanges( + response, + &byte_range_lower_bound_, + &byte_range_upper_bound_, + &instance_size)) { + NOTREACHED(); + return; + } + + resource_response_ = response; + } + + // Receives individual part data from a multipart response. + virtual void didReceiveData( + WebURLLoader*, const char* data, int data_size) { + // TODO(ananta) + // We should defer further loads on multipart resources on the same lines + // as regular resources requested by plugins to prevent reentrancy. + resource_client_->DidReceiveData( + data, data_size, byte_range_lower_bound_); + byte_range_lower_bound_ += data_size; + } + + virtual void didFinishLoading(WebURLLoader*, double finishTime) {} + virtual void didFail(WebURLLoader*, const WebURLError&) {} + + void Clear() { + resource_response_.reset(); + byte_range_lower_bound_ = 0; + byte_range_upper_bound_ = 0; + } + + private: + WebURLResponse resource_response_; + // The lower bound of the byte range. + int byte_range_lower_bound_; + // The upper bound of the byte range. + int byte_range_upper_bound_; + // The handler for the data. + WebPluginResourceClient* resource_client_; +}; + +class HeaderFlattener : public WebHTTPHeaderVisitor { + public: + HeaderFlattener(std::string* buf) : buf_(buf) { + } + + virtual void visitHeader(const WebString& name, const WebString& value) { + // TODO(darin): Should we really exclude headers with an empty value? + if (!name.isEmpty() && !value.isEmpty()) { + buf_->append(name.utf8()); + buf_->append(": "); + buf_->append(value.utf8()); + buf_->append("\n"); + } + } + + private: + std::string* buf_; +}; + +std::string GetAllHeaders(const WebURLResponse& response) { + // TODO(darin): It is possible for httpStatusText to be empty and still have + // an interesting response, so this check seems wrong. + std::string result; + const WebString& status = response.httpStatusText(); + if (status.isEmpty()) + return result; + + // TODO(darin): Shouldn't we also report HTTP version numbers? + result = base::StringPrintf("HTTP %d ", response.httpStatusCode()); + result.append(status.utf8()); + result.append("\n"); + + HeaderFlattener flattener(&result); + response.visitHTTPHeaderFields(&flattener); + + return result; +} + +struct ResponseInfo { + GURL url; + std::string mime_type; + uint32 last_modified; + uint32 expected_length; +}; + +void GetResponseInfo(const WebURLResponse& response, + ResponseInfo* response_info) { + response_info->url = response.url(); + response_info->mime_type = response.mimeType().utf8(); + + // Measured in seconds since 12:00 midnight GMT, January 1, 1970. + response_info->last_modified = + static_cast<uint32>(response.lastModifiedDate()); + + // If the length comes in as -1, then it indicates that it was not + // read off the HTTP headers. We replicate Safari webkit behavior here, + // which is to set it to 0. + response_info->expected_length = + static_cast<uint32>(std::max(response.expectedContentLength(), 0LL)); + + WebString content_encoding = + response.httpHeaderField(WebString::fromUTF8("Content-Encoding")); + if (!content_encoding.isNull() && + !EqualsASCII(content_encoding, "identity")) { + // Don't send the compressed content length to the plugin, which only + // cares about the decoded length. + response_info->expected_length = 0; + } +} + +} // namespace + +// WebKit::WebPlugin ---------------------------------------------------------- + +struct WebPluginImpl::ClientInfo { + unsigned long id; + WebPluginResourceClient* client; + WebKit::WebURLRequest request; + bool pending_failure_notification; + linked_ptr<WebKit::WebURLLoader> loader; + bool notify_redirects; +}; + +bool WebPluginImpl::initialize(WebPluginContainer* container) { + if (!page_delegate_) + return false; + + WebPluginDelegate* plugin_delegate = page_delegate_->CreatePluginDelegate( + file_path_, mime_type_); + if (!plugin_delegate) + return false; + + // Set the container before Initialize because the plugin may + // synchronously call NPN_GetValue to get its container during its + // initialization. + SetContainer(container); + bool ok = plugin_delegate->Initialize( + plugin_url_, arg_names_, arg_values_, this, load_manually_); + if (!ok) { + plugin_delegate->PluginDestroyed(); + return false; + } + + delegate_ = plugin_delegate; + + return true; +} + +void WebPluginImpl::destroy() { + SetContainer(NULL); + MessageLoop::current()->DeleteSoon(FROM_HERE, this); +} + +NPObject* WebPluginImpl::scriptableObject() { + return delegate_->GetPluginScriptableObject(); +} + +void WebPluginImpl::paint(WebCanvas* canvas, const WebRect& paint_rect) { + if (!delegate_ || !container_) + return; + +#if defined(OS_WIN) + // Force a geometry update if needed to allow plugins like media player + // which defer the initial geometry update to work. + container_->reportGeometry(); +#endif // OS_WIN + + // Note that |canvas| is only used when in windowless mode. + delegate_->Paint(canvas, paint_rect); +} + +void WebPluginImpl::updateGeometry( + const WebRect& window_rect, const WebRect& clip_rect, + const WebVector<WebRect>& cutout_rects, bool is_visible) { + WebPluginGeometry new_geometry; + new_geometry.window = window_; + new_geometry.window_rect = window_rect; + new_geometry.clip_rect = clip_rect; + new_geometry.visible = is_visible; + new_geometry.rects_valid = true; + for (size_t i = 0; i < cutout_rects.size(); ++i) + new_geometry.cutout_rects.push_back(cutout_rects[i]); + + // Only send DidMovePlugin if the geometry changed in some way. + if (window_ && + page_delegate_ && + (first_geometry_update_ || !new_geometry.Equals(geometry_))) { + page_delegate_->DidMovePlugin(new_geometry); + } + + // Only UpdateGeometry if either the window or clip rects have changed. + if (first_geometry_update_ || + new_geometry.window_rect != geometry_.window_rect || + new_geometry.clip_rect != geometry_.clip_rect) { + // Notify the plugin that its parameters have changed. + delegate_->UpdateGeometry(new_geometry.window_rect, new_geometry.clip_rect); + } + + // Initiate a download on the plugin url. This should be done for the + // first update geometry sequence. We need to ensure that the plugin + // receives the geometry update before it starts receiving data. + if (first_geometry_update_) { + // An empty url corresponds to an EMBED tag with no src attribute. + if (!load_manually_ && plugin_url_.is_valid()) { + // The Flash plugin hangs for a while if it receives data before + // receiving valid plugin geometry. By valid geometry we mean the + // geometry received by a call to setFrameRect in the Webkit + // layout code path. To workaround this issue we download the + // plugin source url on a timer. + MessageLoop::current()->PostDelayedTask( + FROM_HERE, method_factory_.NewRunnableMethod( + &WebPluginImpl::OnDownloadPluginSrcUrl), 0); + } + } + +#if defined(OS_WIN) + // Don't cache the geometry during the first geometry update. The first + // geometry update sequence is received when Widget::setParent is called. + // For plugins like media player which have a bug where they only honor + // the first geometry update, we have a quirk which ignores the first + // geometry update. To ensure that these plugins work correctly in cases + // where we receive only one geometry update from webkit, we also force + // a geometry update during paint which should go out correctly as the + // initial geometry update was not cached. + if (!first_geometry_update_) + geometry_ = new_geometry; +#else // OS_WIN + geometry_ = new_geometry; +#endif // OS_WIN + first_geometry_update_ = false; +} + +unsigned WebPluginImpl::getBackingTextureId() { + // Regular plugins do not have a backing texture. + return 0; +} + +void WebPluginImpl::updateFocus(bool focused) { + if (accepts_input_events_) + delegate_->SetFocus(focused); +} + +void WebPluginImpl::updateVisibility(bool visible) { + if (!window_ || !page_delegate_) + return; + + WebPluginGeometry move; + move.window = window_; + move.window_rect = gfx::Rect(); + move.clip_rect = gfx::Rect(); + move.rects_valid = false; + move.visible = visible; + + page_delegate_->DidMovePlugin(move); +} + +bool WebPluginImpl::acceptsInputEvents() { + return accepts_input_events_; +} + +bool WebPluginImpl::handleInputEvent( + const WebInputEvent& event, WebCursorInfo& cursor_info) { + // Swallow context menu events in order to suppress the default context menu. + if (event.type == WebInputEvent::ContextMenu) + return true; + + return delegate_->HandleInputEvent(event, &cursor_info); +} + +void WebPluginImpl::didReceiveResponse(const WebURLResponse& response) { + ignore_response_error_ = false; + + ResponseInfo response_info; + GetResponseInfo(response, &response_info); + + delegate_->DidReceiveManualResponse( + response_info.url, + response_info.mime_type, + GetAllHeaders(response), + response_info.expected_length, + response_info.last_modified); +} + +void WebPluginImpl::didReceiveData(const char* data, int data_length) { + delegate_->DidReceiveManualData(data, data_length); +} + +void WebPluginImpl::didFinishLoading() { + delegate_->DidFinishManualLoading(); +} + +void WebPluginImpl::didFailLoading(const WebURLError& error) { + if (!ignore_response_error_) + delegate_->DidManualLoadFail(); +} + +void WebPluginImpl::didFinishLoadingFrameRequest( + const WebURL& url, void* notify_data) { + if (delegate_) { + // We're converting a void* into an arbitrary int id. Though + // these types are the same size on all the platforms we support, + // the compiler may complain as though they are different, so to + // make the casting gods happy go through an intptr_t (the union + // of void* and int) rather than converting straight across. + delegate_->DidFinishLoadWithReason( + url, NPRES_DONE, reinterpret_cast<intptr_t>(notify_data)); + } +} + +void WebPluginImpl::didFailLoadingFrameRequest( + const WebURL& url, void* notify_data, const WebURLError& error) { + if (!delegate_) + return; + + NPReason reason = + error.reason == net::ERR_ABORTED ? NPRES_USER_BREAK : NPRES_NETWORK_ERR; + // See comment in didFinishLoadingFrameRequest about the cast here. + delegate_->DidFinishLoadWithReason( + url, reason, reinterpret_cast<intptr_t>(notify_data)); +} + +bool WebPluginImpl::supportsPaginatedPrint() { + if (!delegate_) + return false; + return delegate_->PrintSupportsPrintExtension(); +} + +int WebPluginImpl::printBegin(const WebRect& printable_area, int printer_dpi) { + if (!delegate_) + return 0; + + if (!supportsPaginatedPrint()) + return 0; + + return delegate_->PrintBegin(printable_area, printer_dpi); +} + +bool WebPluginImpl::printPage(int page_number, WebCanvas* canvas) { + if (!delegate_) + return false; + + return delegate_->PrintPage(page_number, canvas); +} + +void WebPluginImpl::printEnd() { + if (delegate_) + delegate_->PrintEnd(); +} + +bool WebPluginImpl::hasSelection() const { + if (!delegate_) + return false; + + return delegate_->HasSelection(); +} + +WebKit::WebString WebPluginImpl::selectionAsText() const { + if (!delegate_) + return WebString(); + + return delegate_->GetSelectionAsText(); +} + +WebKit::WebString WebPluginImpl::selectionAsMarkup() const { + if (!delegate_) + return WebString(); + + return delegate_->GetSelectionAsMarkup(); +} + +void WebPluginImpl::setZoomFactor(float scale, bool text_only) { + if (delegate_) + delegate_->SetZoomFactor(scale, text_only); +} + +bool WebPluginImpl::startFind(const WebKit::WebString& search_text, + bool case_sensitive, + int identifier) { + if (!delegate_) + return false; + return delegate_->StartFind(search_text, case_sensitive, identifier); +} + +void WebPluginImpl::selectFindResult(bool forward) { + if (delegate_) + delegate_->SelectFindResult(forward); +} + +void WebPluginImpl::stopFind() { + if (delegate_) + delegate_->StopFind(); +} + + +// ----------------------------------------------------------------------------- + +WebPluginImpl::WebPluginImpl( + WebFrame* webframe, + const WebPluginParams& params, + const FilePath& file_path, + const std::string& mime_type, + const base::WeakPtr<WebPluginPageDelegate>& page_delegate) + : windowless_(false), + window_(gfx::kNullPluginWindow), + accepts_input_events_(false), + page_delegate_(page_delegate), + webframe_(webframe), + delegate_(NULL), + container_(NULL), + plugin_url_(params.url), + load_manually_(params.loadManually), + first_geometry_update_(true), + ignore_response_error_(false), + file_path_(file_path), + mime_type_(mime_type), + ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) { + DCHECK_EQ(params.attributeNames.size(), params.attributeValues.size()); + StringToLowerASCII(&mime_type_); + + for (size_t i = 0; i < params.attributeNames.size(); ++i) { + arg_names_.push_back(params.attributeNames[i].utf8()); + arg_values_.push_back(params.attributeValues[i].utf8()); + } +} + +WebPluginImpl::~WebPluginImpl() { +} + +void WebPluginImpl::SetWindow(gfx::PluginWindowHandle window) { +#if defined(OS_MACOSX) + // The only time this is called twice, and the second time with a + // non-zero PluginWindowHandle, is the case when this WebPluginImpl + // is created on behalf of the GPU plugin. This entire code path + // will go away soon, as soon as the GPU plugin becomes the GPU + // process, so it is being separated out for easy deletion. + + // The logic we want here is: if (window) DCHECK(!window_); + DCHECK(!(window_ && window)); + window_ = window; + // Lie to ourselves about being windowless even if we got a fake + // plugin window handle, so we continue to get input events. + windowless_ = true; + accepts_input_events_ = true; + // We do not really need to notify the page delegate that a plugin + // window was created -- so don't. +#else + if (window) { + DCHECK(!windowless_); + window_ = window; + accepts_input_events_ = false; + if (page_delegate_) { + // Tell the view delegate that the plugin window was created, so that it + // can create necessary container widgets. + page_delegate_->CreatedPluginWindow(window); + } + } else { + DCHECK(!window_); // Make sure not called twice. + windowless_ = true; + accepts_input_events_ = true; + } +#endif +} + +void WebPluginImpl::SetAcceptsInputEvents(bool accepts) { + accepts_input_events_ = accepts; +} + +void WebPluginImpl::WillDestroyWindow(gfx::PluginWindowHandle window) { + DCHECK_EQ(window, window_); + window_ = gfx::kNullPluginWindow; + if (page_delegate_) + page_delegate_->WillDestroyPluginWindow(window); +} + +GURL WebPluginImpl::CompleteURL(const char* url) { + if (!webframe_) { + NOTREACHED(); + return GURL(); + } + // TODO(darin): Is conversion from UTF8 correct here? + return webframe_->document().completeURL(WebString::fromUTF8(url)); +} + +void WebPluginImpl::CancelResource(unsigned long id) { + for (size_t i = 0; i < clients_.size(); ++i) { + if (clients_[i].id == id) { + if (clients_[i].loader.get()) { + clients_[i].loader->setDefersLoading(false); + clients_[i].loader->cancel(); + RemoveClient(i); + } + return; + } + } +} + +bool WebPluginImpl::SetPostData(WebURLRequest* request, + const char *buf, + uint32 length) { + std::vector<std::string> names; + std::vector<std::string> values; + std::vector<char> body; + bool rv = NPAPI::PluginHost::SetPostData(buf, length, &names, &values, &body); + + for (size_t i = 0; i < names.size(); ++i) { + request->addHTTPHeaderField(WebString::fromUTF8(names[i]), + WebString::fromUTF8(values[i])); + } + + WebString content_type_header = WebString::fromUTF8("Content-Type"); + const WebString& content_type = + request->httpHeaderField(content_type_header); + if (content_type.isEmpty()) { + request->setHTTPHeaderField( + content_type_header, + WebString::fromUTF8("application/x-www-form-urlencoded")); + } + + WebHTTPBody http_body; + if (body.size()) { + http_body.initialize(); + http_body.appendData(WebData(&body[0], body.size())); + } + request->setHTTPBody(http_body); + + return rv; +} + +WebPluginDelegate* WebPluginImpl::delegate() { + return delegate_; +} + +bool WebPluginImpl::IsValidUrl(const GURL& url, Referrer referrer_flag) { + if (referrer_flag == PLUGIN_SRC && + mime_type_ == "application/x-shockwave-flash" && + url.GetOrigin() != plugin_url_.GetOrigin()) { + // Do url check to make sure that there are no @, ;, \ chars in between url + // scheme and url path. + const char* url_to_check(url.spec().data()); + url_parse::Parsed parsed; + url_parse::ParseStandardURL(url_to_check, strlen(url_to_check), &parsed); + if (parsed.path.begin <= parsed.scheme.end()) + return true; + std::string string_to_search; + string_to_search.assign(url_to_check + parsed.scheme.end(), + parsed.path.begin - parsed.scheme.end()); + if (string_to_search.find("@") != std::string::npos || + string_to_search.find(";") != std::string::npos || + string_to_search.find("\\") != std::string::npos) + return false; + } + + return true; +} + +WebPluginImpl::RoutingStatus WebPluginImpl::RouteToFrame( + const char* url, + bool is_javascript_url, + const char* method, + const char* target, + const char* buf, + unsigned int len, + int notify_id, + Referrer referrer_flag) { + // If there is no target, there is nothing to do + if (!target) + return NOT_ROUTED; + + // This could happen if the WebPluginContainer was already deleted. + if (!webframe_) + return NOT_ROUTED; + + WebString target_str = WebString::fromUTF8(target); + + // Take special action for JavaScript URLs + if (is_javascript_url) { + WebFrame* target_frame = + webframe_->view()->findFrameByName(target_str, webframe_); + // For security reasons, do not allow JavaScript on frames + // other than this frame. + if (target_frame != webframe_) { + // TODO(darin): Localize this message. + const char kMessage[] = + "Ignoring cross-frame javascript URL load requested by plugin."; + webframe_->addMessageToConsole( + WebConsoleMessage(WebConsoleMessage::LevelError, + WebString::fromUTF8(kMessage))); + return ROUTED; + } + + // Route javascript calls back to the plugin. + return NOT_ROUTED; + } + + // If we got this far, we're routing content to a target frame. + // Go fetch the URL. + + GURL complete_url = CompleteURL(url); + // Remove when flash bug is fixed. http://crbug.com/40016. + if (!WebPluginImpl::IsValidUrl(complete_url, referrer_flag)) + return INVALID_URL; + + if (strcmp(method, "GET") != 0) { + // We're only going to route HTTP/HTTPS requests + if (!(complete_url.SchemeIs("http") || complete_url.SchemeIs("https"))) + return INVALID_URL; + } + + WebURLRequest request(complete_url); + SetReferrer(&request, referrer_flag); + + request.setHTTPMethod(WebString::fromUTF8(method)); + request.setFirstPartyForCookies( + webframe_->document().firstPartyForCookies()); + if (len > 0) { + if (!SetPostData(&request, buf, len)) { + // Uhoh - we're in trouble. There isn't a good way + // to recover at this point. Break out. + NOTREACHED(); + return ROUTED; + } + } + + container_->loadFrameRequest( + request, target_str, notify_id != 0, reinterpret_cast<void*>(notify_id)); + return ROUTED; +} + +NPObject* WebPluginImpl::GetWindowScriptNPObject() { + if (!webframe_) { + NOTREACHED(); + return NULL; + } + return webframe_->windowObject(); +} + +NPObject* WebPluginImpl::GetPluginElement() { + return container_->scriptableObjectForElement(); +} + +void WebPluginImpl::SetCookie(const GURL& url, + const GURL& first_party_for_cookies, + const std::string& cookie) { + if (!page_delegate_) + return; + + WebCookieJar* cookie_jar = page_delegate_->GetCookieJar(); + if (!cookie_jar) { + DLOG(WARNING) << "No cookie jar!"; + return; + } + + cookie_jar->setCookie( + url, first_party_for_cookies, WebString::fromUTF8(cookie)); +} + +std::string WebPluginImpl::GetCookies(const GURL& url, + const GURL& first_party_for_cookies) { + if (!page_delegate_) + return std::string(); + + WebCookieJar* cookie_jar = page_delegate_->GetCookieJar(); + if (!cookie_jar) { + DLOG(WARNING) << "No cookie jar!"; + return std::string(); + } + + return UTF16ToUTF8(cookie_jar->cookies(url, first_party_for_cookies)); +} + +void WebPluginImpl::ShowModalHTMLDialog(const GURL& url, int width, int height, + const std::string& json_arguments, + std::string* json_retval) { + if (page_delegate_) { + page_delegate_->ShowModalHTMLDialogForPlugin( + url, gfx::Size(width, height), json_arguments, json_retval); + } +} + +void WebPluginImpl::OnMissingPluginStatus(int status) { + NOTREACHED(); +} + +void WebPluginImpl::URLRedirectResponse(bool allow, int resource_id) { + for (size_t i = 0; i < clients_.size(); ++i) { + if (clients_[i].id == static_cast<unsigned long>(resource_id)) { + if (clients_[i].loader.get()) { + if (allow) { + clients_[i].loader->setDefersLoading(false); + } else { + clients_[i].loader->cancel(); + clients_[i].client->DidFail(); + } + } + break; + } + } +} + +void WebPluginImpl::Invalidate() { + if (container_) + container_->invalidate(); +} + +void WebPluginImpl::InvalidateRect(const gfx::Rect& rect) { + if (container_) + container_->invalidateRect(rect); +} + +void WebPluginImpl::OnDownloadPluginSrcUrl() { + HandleURLRequestInternal( + plugin_url_.spec().c_str(), "GET", NULL, NULL, 0, 0, false, DOCUMENT_URL, + false); +} + +WebPluginResourceClient* WebPluginImpl::GetClientFromLoader( + WebURLLoader* loader) { + ClientInfo* client_info = GetClientInfoFromLoader(loader); + if (client_info) + return client_info->client; + return NULL; +} + +WebPluginImpl::ClientInfo* WebPluginImpl::GetClientInfoFromLoader( + WebURLLoader* loader) { + for (size_t i = 0; i < clients_.size(); ++i) { + if (clients_[i].loader.get() == loader) + return &clients_[i]; + } + + NOTREACHED(); + return 0; +} + +void WebPluginImpl::willSendRequest(WebURLLoader* loader, + WebURLRequest& request, + const WebURLResponse& response) { + WebPluginImpl::ClientInfo* client_info = GetClientInfoFromLoader(loader); + if (client_info) { + if (net::HttpResponseHeaders::IsRedirectResponseCode( + response.httpStatusCode())) { + // If the plugin does not participate in url redirect notifications then + // just block cross origin 307 POST redirects. + if (!client_info->notify_redirects) { + if (response.httpStatusCode() == 307 && + LowerCaseEqualsASCII(request.httpMethod().utf8(), "post")) { + GURL original_request_url(response.url()); + GURL response_url(request.url()); + if (original_request_url.GetOrigin() != response_url.GetOrigin()) { + loader->setDefersLoading(true); + loader->cancel(); + client_info->client->DidFail(); + return; + } + } + } else { + loader->setDefersLoading(true); + } + } + client_info->client->WillSendRequest(request.url(), + response.httpStatusCode()); + } +} + +void WebPluginImpl::didSendData(WebURLLoader* loader, + unsigned long long bytes_sent, + unsigned long long total_bytes_to_be_sent) { +} + +void WebPluginImpl::didReceiveResponse(WebURLLoader* loader, + const WebURLResponse& response) { + static const int kHttpPartialResponseStatusCode = 206; + static const int kHttpResponseSuccessStatusCode = 200; + + WebPluginResourceClient* client = GetClientFromLoader(loader); + if (!client) + return; + + ResponseInfo response_info; + GetResponseInfo(response, &response_info); + + bool request_is_seekable = true; + if (client->IsMultiByteResponseExpected()) { + if (response.httpStatusCode() == kHttpPartialResponseStatusCode) { + HandleHttpMultipartResponse(response, client); + return; + } else if (response.httpStatusCode() == kHttpResponseSuccessStatusCode) { + // If the client issued a byte range request and the server responds with + // HTTP 200 OK, it indicates that the server does not support byte range + // requests. + // We need to emulate Firefox behavior by doing the following:- + // 1. Destroy the plugin instance in the plugin process. Ensure that + // existing resource requests initiated for the plugin instance + // continue to remain valid. + // 2. Create a new plugin instance and notify it about the response + // received here. + if (!ReinitializePluginForResponse(loader)) { + NOTREACHED(); + return; + } + + // The server does not support byte range requests. No point in creating + // seekable streams. + request_is_seekable = false; + + delete client; + client = NULL; + + // Create a new resource client for this request. + for (size_t i = 0; i < clients_.size(); ++i) { + if (clients_[i].loader.get() == loader) { + WebPluginResourceClient* resource_client = + delegate_->CreateResourceClient(clients_[i].id, plugin_url_, 0); + clients_[i].client = resource_client; + client = resource_client; + break; + } + } + + DCHECK(client != NULL); + } + } + + // Calling into a plugin could result in reentrancy if the plugin yields + // control to the OS like entering a modal loop etc. Prevent this by + // stopping further loading until the plugin notifies us that it is ready to + // accept data + loader->setDefersLoading(true); + + client->DidReceiveResponse( + response_info.mime_type, + GetAllHeaders(response), + response_info.expected_length, + response_info.last_modified, + request_is_seekable); + + if (WebDevToolsAgent* devtools_agent = GetDevToolsAgent()) { + ClientInfo* client_info = GetClientInfoFromLoader(loader); + if (client_info) + devtools_agent->didReceiveResponse(client_info->id, response); + } + + // Bug http://b/issue?id=925559. The flash plugin would not handle the HTTP + // error codes in the stream header and as a result, was unaware of the + // fate of the HTTP requests issued via NPN_GetURLNotify. Webkit and FF + // destroy the stream and invoke the NPP_DestroyStream function on the + // plugin if the HTTP request fails. + const GURL& url = response.url(); + if (url.SchemeIs("http") || url.SchemeIs("https")) { + if (response.httpStatusCode() < 100 || response.httpStatusCode() >= 400) { + // The plugin instance could be in the process of deletion here. + // Verify if the WebPluginResourceClient instance still exists before + // use. + ClientInfo* client_info = GetClientInfoFromLoader(loader); + if (client_info) { + client_info->pending_failure_notification = true; + } + } + } +} + +void WebPluginImpl::didReceiveData(WebURLLoader* loader, + const char *buffer, + int length) { + WebPluginResourceClient* client = GetClientFromLoader(loader); + if (!client) + return; + + // ClientInfo can be removed from clients_ vector by next statements. + if (WebDevToolsAgent* devtools_agent = GetDevToolsAgent()) { + ClientInfo* client_info = GetClientInfoFromLoader(loader); + if (client_info) + devtools_agent->didReceiveData(client_info->id, length); + } + MultiPartResponseHandlerMap::iterator index = + multi_part_response_map_.find(client); + if (index != multi_part_response_map_.end()) { + MultipartResponseDelegate* multi_part_handler = (*index).second; + DCHECK(multi_part_handler != NULL); + multi_part_handler->OnReceivedData(buffer, length); + } else { + loader->setDefersLoading(true); + client->DidReceiveData(buffer, length, 0); + } +} + +void WebPluginImpl::didFinishLoading(WebURLLoader* loader, double finishTime) { + ClientInfo* client_info = GetClientInfoFromLoader(loader); + if (client_info && client_info->client) { + MultiPartResponseHandlerMap::iterator index = + multi_part_response_map_.find(client_info->client); + if (index != multi_part_response_map_.end()) { + delete (*index).second; + multi_part_response_map_.erase(index); + if (page_delegate_) + page_delegate_->DidStopLoadingForPlugin(); + } + loader->setDefersLoading(true); + WebPluginResourceClient* resource_client = client_info->client; + // The ClientInfo can get deleted in the call to DidFinishLoading below. + // It is not safe to access this structure after that. + client_info->client = NULL; + resource_client->DidFinishLoading(); + + if (WebDevToolsAgent* devtools_agent = GetDevToolsAgent()) + devtools_agent->didFinishLoading(client_info->id); + } +} + +void WebPluginImpl::didFail(WebURLLoader* loader, + const WebURLError& error) { + ClientInfo* client_info = GetClientInfoFromLoader(loader); + if (client_info && client_info->client) { + loader->setDefersLoading(true); + WebPluginResourceClient* resource_client = client_info->client; + // The ClientInfo can get deleted in the call to DidFail below. + // It is not safe to access this structure after that. + client_info->client = NULL; + resource_client->DidFail(); + + if (WebDevToolsAgent* devtools_agent = GetDevToolsAgent()) + devtools_agent->didFailLoading(client_info->id, error); + } +} + +void WebPluginImpl::RemoveClient(size_t i) { + clients_.erase(clients_.begin() + i); +} + +void WebPluginImpl::RemoveClient(WebURLLoader* loader) { + for (size_t i = 0; i < clients_.size(); ++i) { + if (clients_[i].loader.get() == loader) { + RemoveClient(i); + return; + } + } +} + +void WebPluginImpl::SetContainer(WebPluginContainer* container) { + if (!container) + TearDownPluginInstance(NULL); + container_ = container; +} + +void WebPluginImpl::HandleURLRequest(const char* url, + const char* method, + const char* target, + const char* buf, + unsigned int len, + int notify_id, + bool popups_allowed, + bool notify_redirects) { + // GetURL/PostURL requests initiated explicitly by plugins should specify the + // plugin SRC url as the referrer if it is available. + HandleURLRequestInternal( + url, method, target, buf, len, notify_id, popups_allowed, PLUGIN_SRC, + notify_redirects); +} + +void WebPluginImpl::HandleURLRequestInternal(const char* url, + const char* method, + const char* target, + const char* buf, + unsigned int len, + int notify_id, + bool popups_allowed, + Referrer referrer_flag, + bool notify_redirects) { + // For this request, we either route the output to a frame + // because a target has been specified, or we handle the request + // here, i.e. by executing the script if it is a javascript url + // or by initiating a download on the URL, etc. There is one special + // case in that the request is a javascript url and the target is "_self", + // in which case we route the output to the plugin rather than routing it + // to the plugin's frame. + bool is_javascript_url = StartsWithASCII(url, "javascript:", false); + RoutingStatus routing_status = RouteToFrame( + url, is_javascript_url, method, target, buf, len, notify_id, + referrer_flag); + if (routing_status == ROUTED) + return; + + if (is_javascript_url) { + GURL gurl(url); + WebString result = container_->executeScriptURL(gurl, popups_allowed); + + // delegate_ could be NULL because executeScript caused the container to + // be deleted. + if (delegate_) { + delegate_->SendJavaScriptStream( + gurl, result.utf8(), !result.isNull(), notify_id); + } + + return; + } + + unsigned long resource_id = GetNextResourceId(); + if (!resource_id) + return; + + GURL complete_url = CompleteURL(url); + // Remove when flash bug is fixed. http://crbug.com/40016. + if (!WebPluginImpl::IsValidUrl(complete_url, referrer_flag)) + return; + + WebPluginResourceClient* resource_client = delegate_->CreateResourceClient( + resource_id, complete_url, notify_id); + if (!resource_client) + return; + + // If the RouteToFrame call returned a failure then inform the result + // back to the plugin asynchronously. + if ((routing_status == INVALID_URL) || + (routing_status == GENERAL_FAILURE)) { + resource_client->DidFail(); + return; + } + + // CreateResourceClient() sends a synchronous IPC message so it's possible + // that TearDownPluginInstance() may have been called in the nested + // message loop. If so, don't start the request. + if (!delegate_) + return; + + InitiateHTTPRequest(resource_id, resource_client, complete_url, method, buf, + len, NULL, referrer_flag, notify_redirects); +} + +unsigned long WebPluginImpl::GetNextResourceId() { + if (!webframe_) + return 0; + WebView* view = webframe_->view(); + if (!view) + return 0; + return view->createUniqueIdentifierForRequest(); +} + +bool WebPluginImpl::InitiateHTTPRequest(unsigned long resource_id, + WebPluginResourceClient* client, + const GURL& url, + const char* method, + const char* buf, + int buf_len, + const char* range_info, + Referrer referrer_flag, + bool notify_redirects) { + if (!client) { + NOTREACHED(); + return false; + } + + ClientInfo info; + info.id = resource_id; + info.client = client; + info.request.initialize(); + info.request.setURL(url); + info.request.setFirstPartyForCookies( + webframe_->document().firstPartyForCookies()); + info.request.setRequestorProcessID(delegate_->GetProcessId()); + info.request.setTargetType(WebURLRequest::TargetIsObject); + info.request.setHTTPMethod(WebString::fromUTF8(method)); + info.pending_failure_notification = false; + info.notify_redirects = notify_redirects; + + if (range_info) { + info.request.addHTTPHeaderField(WebString::fromUTF8("Range"), + WebString::fromUTF8(range_info)); + } + + if (strcmp(method, "POST") == 0) { + // Adds headers or form data to a request. This must be called before + // we initiate the actual request. + SetPostData(&info.request, buf, buf_len); + } + + SetReferrer(&info.request, referrer_flag); + + // Sets the routing id to associate the ResourceRequest with the RenderView. + webframe_->dispatchWillSendRequest(info.request); + + // Sets the appcache host id to allow retrieval from the appcache. + if (WebApplicationCacheHostImpl* appcache_host = + WebApplicationCacheHostImpl::FromFrame(webframe_)) { + appcache_host->willStartSubResourceRequest(info.request); + } + + if (WebDevToolsAgent* devtools_agent = GetDevToolsAgent()) { + devtools_agent->identifierForInitialRequest(resource_id, webframe_, + info.request); + devtools_agent->willSendRequest(resource_id, info.request); + } + + info.loader.reset(WebKit::webKitClient()->createURLLoader()); + if (!info.loader.get()) + return false; + info.loader->loadAsynchronously(info.request, this); + + clients_.push_back(info); + return true; +} + +void WebPluginImpl::CancelDocumentLoad() { + if (webframe_) { + ignore_response_error_ = true; + webframe_->stopLoading(); + } +} + +void WebPluginImpl::InitiateHTTPRangeRequest( + const char* url, const char* range_info, int range_request_id) { + unsigned long resource_id = GetNextResourceId(); + if (!resource_id) + return; + + GURL complete_url = CompleteURL(url); + // Remove when flash bug is fixed. http://crbug.com/40016. + if (!WebPluginImpl::IsValidUrl(complete_url, + load_manually_ ? NO_REFERRER : PLUGIN_SRC)) + return; + + WebPluginResourceClient* resource_client = + delegate_->CreateSeekableResourceClient(resource_id, range_request_id); + InitiateHTTPRequest( + resource_id, resource_client, complete_url, "GET", NULL, 0, range_info, + load_manually_ ? NO_REFERRER : PLUGIN_SRC, false); +} + +void WebPluginImpl::SetDeferResourceLoading(unsigned long resource_id, + bool defer) { + std::vector<ClientInfo>::iterator client_index = clients_.begin(); + while (client_index != clients_.end()) { + ClientInfo& client_info = *client_index; + + if (client_info.id == resource_id) { + client_info.loader->setDefersLoading(defer); + + // If we determined that the request had failed via the HTTP headers + // in the response then we send out a failure notification to the + // plugin process, as certain plugins don't handle HTTP failure codes + // correctly. + if (!defer && client_info.client && + client_info.pending_failure_notification) { + // The ClientInfo and the iterator can become invalid due to the call + // to DidFail below. + WebPluginResourceClient* resource_client = client_info.client; + client_info.loader->cancel(); + clients_.erase(client_index++); + resource_client->DidFail(); + + // Report that resource loading finished. + if (WebDevToolsAgent* devtools_agent = GetDevToolsAgent()) + devtools_agent->didFinishLoading(resource_id); + } + break; + } + client_index++; + } +} + +bool WebPluginImpl::IsOffTheRecord() { + return false; +} + +void WebPluginImpl::HandleHttpMultipartResponse( + const WebURLResponse& response, WebPluginResourceClient* client) { + std::string multipart_boundary; + if (!MultipartResponseDelegate::ReadMultipartBoundary( + response, &multipart_boundary)) { + NOTREACHED(); + return; + } + + if (page_delegate_) + page_delegate_->DidStartLoadingForPlugin(); + + MultiPartResponseClient* multi_part_response_client = + new MultiPartResponseClient(client); + + MultipartResponseDelegate* multi_part_response_handler = + new MultipartResponseDelegate(multi_part_response_client, NULL, + response, + multipart_boundary); + multi_part_response_map_[client] = multi_part_response_handler; +} + +bool WebPluginImpl::ReinitializePluginForResponse( + WebURLLoader* loader) { + WebFrame* webframe = webframe_; + if (!webframe) + return false; + + WebView* webview = webframe->view(); + if (!webview) + return false; + + WebPluginContainer* container_widget = container_; + + // Destroy the current plugin instance. + TearDownPluginInstance(loader); + + container_ = container_widget; + webframe_ = webframe; + + WebPluginDelegate* plugin_delegate = page_delegate_->CreatePluginDelegate( + file_path_, mime_type_); + + bool ok = plugin_delegate && plugin_delegate->Initialize( + plugin_url_, arg_names_, arg_values_, this, load_manually_); + + if (!ok) { + container_ = NULL; + // TODO(iyengar) Should we delete the current plugin instance here? + return false; + } + + delegate_ = plugin_delegate; + + // Force a geometry update to occur to ensure that the plugin becomes + // visible. + container_->reportGeometry(); + + // The plugin move sequences accumulated via DidMove are sent to the browser + // whenever the renderer paints. Force a paint here to ensure that changes + // to the plugin window are propagated to the browser. + container_->invalidate(); + return true; +} + +void WebPluginImpl::TearDownPluginInstance( + WebURLLoader* loader_to_ignore) { + // The container maintains a list of JSObjects which are related to this + // plugin. Tell the frame we're gone so that it can invalidate all of + // those sub JSObjects. + if (container_) + container_->clearScriptObjects(); + + if (delegate_) { + // Call PluginDestroyed() first to prevent the plugin from calling us back + // in the middle of tearing down the render tree. + delegate_->PluginDestroyed(); + delegate_ = NULL; + } + + // Cancel any pending requests because otherwise this deleted object will + // be called by the ResourceDispatcher. + std::vector<ClientInfo>::iterator client_index = clients_.begin(); + while (client_index != clients_.end()) { + ClientInfo& client_info = *client_index; + + if (loader_to_ignore == client_info.loader) { + client_index++; + continue; + } + + if (client_info.loader.get()) + client_info.loader->cancel(); + + client_index = clients_.erase(client_index); + } + + // This needs to be called now and not in the destructor since the + // webframe_ might not be valid anymore. + webframe_ = NULL; + method_factory_.RevokeAll(); +} + +void WebPluginImpl::SetReferrer(WebKit::WebURLRequest* request, + Referrer referrer_flag) { + switch (referrer_flag) { + case DOCUMENT_URL: + webframe_->setReferrerForRequest(*request, GURL()); + break; + + case PLUGIN_SRC: + webframe_->setReferrerForRequest(*request, plugin_url_); + break; + + default: + break; + } +} + +WebDevToolsAgent* WebPluginImpl::GetDevToolsAgent() { + if (!webframe_) + return NULL; + WebView* view = webframe_->view(); + if (!view) + return NULL; + return view->devToolsAgent(); +} + +} // namespace webkit_glue diff --git a/webkit/glue/plugins/webplugin_impl.h b/webkit/glue/plugins/webplugin_impl.h new file mode 100644 index 0000000..27a692b --- /dev/null +++ b/webkit/glue/plugins/webplugin_impl.h @@ -0,0 +1,330 @@ +// 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. + +#ifndef WEBKIT_GLUE_WEBPLUGIN_IMPL_H_ +#define WEBKIT_GLUE_WEBPLUGIN_IMPL_H_ + +#include <string> +#include <map> +#include <vector> + +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/task.h" +#include "base/weak_ptr.h" +#include "gfx/native_widget_types.h" +#include "googleurl/src/gurl.h" +#include "third_party/WebKit/WebKit/chromium/public/WebPlugin.h" +#include "third_party/WebKit/WebKit/chromium/public/WebRect.h" +#include "third_party/WebKit/WebKit/chromium/public/WebString.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLLoaderClient.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLRequest.h" +#include "third_party/WebKit/WebKit/chromium/public/WebVector.h" +#include "webkit/glue/plugins/webplugin.h" + +class WebViewDelegate; + +namespace WebKit { +class WebDevToolsAgent; +class WebFrame; +class WebPluginContainer; +class WebURLResponse; +class WebURLLoader; +class WebURLRequest; +} + +namespace webkit_glue { + +class MultipartResponseDelegate; +class WebPluginDelegate; +class WebPluginPageDelegate; + +// This is the WebKit side of the plugin implementation that forwards calls, +// after changing out of WebCore types, to a delegate. The delegate may +// be in a different process. +class WebPluginImpl : public WebPlugin, + public WebKit::WebPlugin, + public WebKit::WebURLLoaderClient { + public: + WebPluginImpl( + WebKit::WebFrame* frame, + const WebKit::WebPluginParams& params, + const FilePath& file_path, + const std::string& mime_type, + const base::WeakPtr<WebPluginPageDelegate>& page_delegate); + virtual ~WebPluginImpl(); + + // Helper function for sorting post data. + static bool SetPostData(WebKit::WebURLRequest* request, + const char* buf, + uint32 length); + + virtual WebPluginDelegate* delegate(); + + private: + // WebKit::WebPlugin methods: + virtual bool initialize( + WebKit::WebPluginContainer* container); + virtual void destroy(); + virtual NPObject* scriptableObject(); + virtual void paint( + WebKit::WebCanvas* canvas, const WebKit::WebRect& paint_rect); + virtual void updateGeometry( + const WebKit::WebRect& frame_rect, const WebKit::WebRect& clip_rect, + const WebKit::WebVector<WebKit::WebRect>& cut_outs, bool is_visible); + virtual unsigned getBackingTextureId(); + virtual void updateFocus(bool focused); + virtual void updateVisibility(bool visible); + virtual bool acceptsInputEvents(); + virtual bool handleInputEvent( + const WebKit::WebInputEvent& event, WebKit::WebCursorInfo& cursor_info); + virtual void didReceiveResponse(const WebKit::WebURLResponse& response); + virtual void didReceiveData(const char* data, int data_length); + virtual void didFinishLoading(); + virtual void didFailLoading(const WebKit::WebURLError& error); + virtual void didFinishLoadingFrameRequest( + const WebKit::WebURL& url, void* notify_data); + virtual void didFailLoadingFrameRequest( + const WebKit::WebURL& url, void* notify_data, + const WebKit::WebURLError& error); + virtual bool supportsPaginatedPrint(); + virtual int printBegin(const WebKit::WebRect& printable_area, + int printer_dpi); + virtual bool printPage(int page_number, WebKit::WebCanvas* canvas); + virtual void printEnd(); + virtual bool hasSelection() const; + virtual WebKit::WebString selectionAsText() const; + virtual WebKit::WebString selectionAsMarkup() const; + virtual void setZoomFactor(float scale, bool text_only); + virtual bool startFind(const WebKit::WebString& search_text, + bool case_sensitive, + int identifier); + virtual void selectFindResult(bool forward); + virtual void stopFind(); + + // WebPlugin implementation: + virtual void SetWindow(gfx::PluginWindowHandle window); + virtual void SetAcceptsInputEvents(bool accepts); + virtual void WillDestroyWindow(gfx::PluginWindowHandle window); +#if defined(OS_WIN) + void SetWindowlessPumpEvent(HANDLE pump_messages_event) { } +#endif + virtual void CancelResource(unsigned long id); + virtual void Invalidate(); + virtual void InvalidateRect(const gfx::Rect& rect); + virtual NPObject* GetWindowScriptNPObject(); + virtual NPObject* GetPluginElement(); + virtual void SetCookie(const GURL& url, + const GURL& first_party_for_cookies, + const std::string& cookie); + virtual std::string GetCookies(const GURL& url, + const GURL& first_party_for_cookies); + virtual void ShowModalHTMLDialog(const GURL& url, int width, int height, + const std::string& json_arguments, + std::string* json_retval); + virtual void OnMissingPluginStatus(int status); + + virtual void URLRedirectResponse(bool allow, int resource_id); + + // Given a (maybe partial) url, completes using the base url. + GURL CompleteURL(const char* url); + + // Executes the script passed in. The notify_needed and notify_data arguments + // are passed in by the plugin process. These indicate whether the plugin + // expects a notification on script execution. We pass them back to the + // plugin as is. This avoids having to track the notification arguments in + // the plugin process. + bool ExecuteScript(const std::string& url, const std::wstring& script, + bool notify_needed, intptr_t notify_data, + bool popups_allowed); + + enum RoutingStatus { + ROUTED, + NOT_ROUTED, + INVALID_URL, + GENERAL_FAILURE + }; + + // Determines the referrer value sent along with outgoing HTTP requests + // issued by plugins. + enum Referrer { + PLUGIN_SRC, + DOCUMENT_URL, + NO_REFERRER + }; + + // Given a download request, check if we need to route the output to a frame. + // Returns ROUTED if the load is done and routed to a frame, NOT_ROUTED or + // corresponding error codes otherwise. + RoutingStatus RouteToFrame(const char* url, + bool is_javascript_url, + const char* method, + const char* target, + const char* buf, + unsigned int len, + int notify_id, + Referrer referrer_flag); + + // Returns the next avaiable resource id. Returns 0 if the operation fails. + // It may fail if the page has already been closed. + unsigned long GetNextResourceId(); + + // Initiates HTTP GET/POST requests. + // Returns true on success. + bool InitiateHTTPRequest(unsigned long resource_id, + WebPluginResourceClient* client, + const GURL& url, + const char* method, + const char* buf, + int len, + const char* range_info, + Referrer referrer_flag, + bool notify_redirects); + + gfx::Rect GetWindowClipRect(const gfx::Rect& rect); + + // Sets the actual Widget for the plugin. + void SetContainer(WebKit::WebPluginContainer* container); + + // Destroys the plugin instance. + // The response_handle_to_ignore parameter if not NULL indicates the + // resource handle to be left valid during plugin shutdown. + void TearDownPluginInstance(WebKit::WebURLLoader* loader_to_ignore); + + // WebURLLoaderClient implementation. We implement this interface in the + // renderer process, and then use the simple WebPluginResourceClient interface + // to relay the callbacks to the plugin. + virtual void willSendRequest(WebKit::WebURLLoader* loader, + WebKit::WebURLRequest& request, + const WebKit::WebURLResponse& response); + virtual void didSendData(WebKit::WebURLLoader* loader, + unsigned long long bytes_sent, + unsigned long long total_bytes_to_be_sent); + virtual void didReceiveResponse(WebKit::WebURLLoader* loader, + const WebKit::WebURLResponse& response); + virtual void didReceiveData(WebKit::WebURLLoader* loader, const char *buffer, + int length); + virtual void didFinishLoading(WebKit::WebURLLoader* loader, + double finishTime); + virtual void didFail(WebKit::WebURLLoader* loader, + const WebKit::WebURLError& error); + + // Helper function to remove the stored information about a resource + // request given its index in m_clients. + void RemoveClient(size_t i); + + // Helper function to remove the stored information about a resource + // request given a handle. + void RemoveClient(WebKit::WebURLLoader* loader); + + virtual void HandleURLRequest(const char* url, + const char *method, + const char* target, + const char* buf, + unsigned int len, + int notify_id, + bool popups_allowed, + bool notify_redirects); + + virtual void CancelDocumentLoad(); + + virtual void InitiateHTTPRangeRequest( + const char* url, const char* range_info, int pending_request_id); + + virtual void SetDeferResourceLoading(unsigned long resource_id, bool defer); + + // Ignore in-process plugins mode for this flag. + virtual bool IsOffTheRecord(); + + // Handles HTTP multipart responses, i.e. responses received with a HTTP + // status code of 206. + void HandleHttpMultipartResponse(const WebKit::WebURLResponse& response, + WebPluginResourceClient* client); + + void HandleURLRequestInternal(const char* url, + const char* method, + const char* target, + const char* buf, + unsigned int len, + int notify_id, + bool popups_allowed, + Referrer referrer_flag, + bool notify_redirects); + + // Tears down the existing plugin instance and creates a new plugin instance + // to handle the response identified by the loader parameter. + bool ReinitializePluginForResponse(WebKit::WebURLLoader* loader); + + // Delayed task for downloading the plugin source URL. + void OnDownloadPluginSrcUrl(); + + struct ClientInfo; + + // Helper functions + WebPluginResourceClient* GetClientFromLoader(WebKit::WebURLLoader* loader); + ClientInfo* GetClientInfoFromLoader(WebKit::WebURLLoader* loader); + + // Helper function to set the referrer on the request passed in. + void SetReferrer(WebKit::WebURLRequest* request, Referrer referrer_flag); + + // Returns DevToolsAgent for the frame or 0. + WebKit::WebDevToolsAgent* GetDevToolsAgent(); + + // Check for invalid chars like @, ;, \ before the first / (in path). + bool IsValidUrl(const GURL& url, Referrer referrer_flag); + + std::vector<ClientInfo> clients_; + + bool windowless_; + gfx::PluginWindowHandle window_; + bool accepts_input_events_; + base::WeakPtr<WebPluginPageDelegate> page_delegate_; + WebKit::WebFrame* webframe_; + + WebPluginDelegate* delegate_; + + // This is just a weak reference. + WebKit::WebPluginContainer* container_; + + typedef std::map<WebPluginResourceClient*, + webkit_glue::MultipartResponseDelegate*> + MultiPartResponseHandlerMap; + // Tracks HTTP multipart response handlers instantiated for + // a WebPluginResourceClient instance. + MultiPartResponseHandlerMap multi_part_response_map_; + + // The plugin source URL. + GURL plugin_url_; + + // Indicates if the download would be initiated by the plugin or us. + bool load_manually_; + + // Indicates if this is the first geometry update received by the plugin. + bool first_geometry_update_; + + // Set to true if the next response error should be ignored. + bool ignore_response_error_; + + // The current plugin geometry and clip rectangle. + WebPluginGeometry geometry_; + + // The location of the plugin on disk. + FilePath file_path_; + + // The mime type of the plugin. + std::string mime_type_; + + // Holds the list of argument names and values passed to the plugin. We keep + // these so that we can re-initialize the plugin if we need to. + std::vector<std::string> arg_names_; + std::vector<std::string> arg_values_; + + ScopedRunnableMethodFactory<WebPluginImpl> method_factory_; + + DISALLOW_COPY_AND_ASSIGN(WebPluginImpl); +}; + +} // namespace webkit_glue + +#endif // #ifndef WEBKIT_GLUE_WEBPLUGIN_IMPL_H_ diff --git a/webkit/glue/plugins/webplugin_impl_unittest.cc b/webkit/glue/plugins/webplugin_impl_unittest.cc new file mode 100644 index 0000000..e70e39a --- /dev/null +++ b/webkit/glue/plugins/webplugin_impl_unittest.cc @@ -0,0 +1,232 @@ +// 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. + +#include "base/string_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/WebKit/WebKit/chromium/public/WebCString.h" +#include "third_party/WebKit/WebKit/chromium/public/WebString.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLRequest.h" +#include "webkit/glue/plugins/webplugin_impl.h" + +using WebKit::WebHTTPBody; +using WebKit::WebString; +using WebKit::WebURLRequest; +using webkit_glue::WebPluginImpl; + +namespace { + +class WebPluginImplTest : public testing::Test { +}; + +} + +static std::string GetHeader(const WebURLRequest& request, const char* name) { + std::string result; + TrimWhitespace( + request.httpHeaderField(WebString::fromUTF8(name)).utf8(), + TRIM_ALL, + &result); + return result; +} + +static std::string GetBodyText(const WebURLRequest& request) { + const WebHTTPBody& body = request.httpBody(); + if (body.isNull()) + return std::string(); + + std::string result; + size_t i = 0; + WebHTTPBody::Element element; + while (body.elementAt(i++, element)) { + if (element.type == WebHTTPBody::Element::TypeData) { + result.append(element.data.data(), element.data.size()); + } else { + NOTREACHED() << "unexpected element type encountered!"; + } + } + return result; +} + +// The Host functions for NPN_PostURL and NPN_PostURLNotify +// need to parse out some HTTP headers. Make sure it works +// with the following tests + +TEST(WebPluginImplTest, PostParserSimple) { + // Test a simple case with headers & data + const char *ex1 = "foo: bar\nContent-length: 10\n\nabcdefghij"; + WebURLRequest request; + request.initialize(); + bool rv = WebPluginImpl::SetPostData(&request, ex1, + static_cast<uint32>(strlen(ex1))); + EXPECT_EQ(true, rv); + EXPECT_EQ("bar", GetHeader(request, "foo")); + EXPECT_EQ(0U, GetHeader(request, "bar").length()); + EXPECT_EQ(0U, GetHeader(request, "Content-length").length()); + EXPECT_EQ("abcdefghij", GetBodyText(request)); +} + +TEST(WebPluginImplTest, PostParserLongHeader) { + // Test a simple case with long headers + const char *ex1 = "foo: 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789\n\nabcdefghij"; + WebURLRequest request; + request.initialize(); + bool rv = WebPluginImpl::SetPostData(&request, ex1, + static_cast<uint32>(strlen(ex1))); + EXPECT_EQ(true, rv); + EXPECT_EQ(100U, GetHeader(request, "foo").length()); +} + +TEST(WebPluginImplTest, PostParserManyHeaders) { + // Test a simple case with long headers + const char *ex1 = "h1:h1\nh2:h2\nh3:h3\nh4:h4\nh5:h5\nh6:h6\nh7:h7\nh8:h8\nh9:h9\nh10:h10\n\nbody"; + WebURLRequest request; + request.initialize(); + bool rv = WebPluginImpl::SetPostData(&request, ex1, + static_cast<uint32>(strlen(ex1))); + EXPECT_EQ(true, rv); + EXPECT_EQ("h1", GetHeader(request, "h1")); + EXPECT_EQ("h2", GetHeader(request, "h2")); + EXPECT_EQ("h3", GetHeader(request, "h3")); + EXPECT_EQ("h4", GetHeader(request, "h4")); + EXPECT_EQ("h5", GetHeader(request, "h5")); + EXPECT_EQ("h6", GetHeader(request, "h6")); + EXPECT_EQ("h7", GetHeader(request, "h7")); + EXPECT_EQ("h8", GetHeader(request, "h8")); + EXPECT_EQ("h9", GetHeader(request, "h9")); + EXPECT_EQ("h10", GetHeader(request, "h10")); + EXPECT_EQ("body", GetBodyText(request)); +} + +TEST(WebPluginImplTest, PostParserDuplicateHeaders) { + // Test a simple case with long headers + // What value gets returned doesn't really matter. It shouldn't error + // out. + const char *ex1 = "h1:h1\nh1:h2\n\nbody"; + WebURLRequest request; + request.initialize(); + bool rv = WebPluginImpl::SetPostData(&request, ex1, + static_cast<uint32>(strlen(ex1))); + EXPECT_EQ(true, rv); +} + +TEST(WebPluginImplTest, PostParserNoHeaders) { + // Test a simple case with no headers but with data + const char *ex1 = "\nabcdefghij"; + WebURLRequest request; + request.initialize(); + bool rv = WebPluginImpl::SetPostData(&request, ex1, + static_cast<uint32>(strlen(ex1))); + EXPECT_EQ(true, rv); + EXPECT_EQ(0U, GetHeader(request, "foo").length()); + EXPECT_EQ(0U, GetHeader(request, "bar").length()); + EXPECT_EQ(0U, GetHeader(request, "Content-length").length()); + EXPECT_EQ("abcdefghij", GetBodyText(request)); +} + +TEST(WebPluginImplTest, PostParserNoBody) { + // Test a simple case with headers and no body + const char *ex1 = "Foo:bar\n\n"; + WebURLRequest request; + request.initialize(); + bool rv = WebPluginImpl::SetPostData(&request, ex1, + static_cast<uint32>(strlen(ex1))); + EXPECT_EQ(true, rv); + EXPECT_EQ("bar", GetHeader(request, "foo")); + EXPECT_EQ(0U, GetHeader(request, "bar").length()); + EXPECT_EQ(0U, GetHeader(request, "Content-length").length()); + EXPECT_EQ(0U, GetBodyText(request).length()); +} + +TEST(WebPluginImplTest, PostParserBodyWithNewLines) { + // Test a simple case with headers and no body + const char *ex1 = "Foo:bar\n\n\n\nabcdefg\n\nabcdefg"; + WebURLRequest request; + request.initialize(); + bool rv = WebPluginImpl::SetPostData(&request, ex1, + static_cast<uint32>(strlen(ex1))); + EXPECT_EQ(true, rv); + EXPECT_EQ(GetBodyText(request), "\n\nabcdefg\n\nabcdefg"); +} + +TEST(WebPluginImplTest, PostParserErrorNoBody) { + // Test with headers and no body + const char *ex1 = "Foo:bar\n"; + WebURLRequest request; + request.initialize(); + bool rv = WebPluginImpl::SetPostData(&request, ex1, + static_cast<uint32>(strlen(ex1))); + EXPECT_EQ(true, rv); +} + +TEST(WebPluginImplTest, PostParserErrorEmpty) { + // Test with an empty string + const char *ex1 = ""; + WebURLRequest request; + request.initialize(); + bool rv = WebPluginImpl::SetPostData(&request, ex1, + static_cast<uint32>(strlen(ex1))); + EXPECT_EQ(true, rv); +} + +TEST(WebPluginImplTest, PostParserEmptyName) { + // Test an error case with an empty header name field + const char *ex1 = "foo:bar\n:blat\n\nbody"; + WebURLRequest request; + request.initialize(); + bool rv = WebPluginImpl::SetPostData(&request, ex1, + static_cast<uint32>(strlen(ex1))); + EXPECT_EQ(true, rv); + EXPECT_EQ("bar", GetHeader(request, "foo")); + EXPECT_EQ("body", GetBodyText(request)); +} + +TEST(WebPluginImplTest, PostParserEmptyValue) { + // Test an error case with an empty value field + const char *ex1 = "foo:bar\nbar:\n\nbody"; + WebURLRequest request; + request.initialize(); + bool rv = WebPluginImpl::SetPostData(&request, ex1, + static_cast<uint32>(strlen(ex1))); + EXPECT_EQ(true, rv); + EXPECT_EQ("bar", GetHeader(request, "foo")); + EXPECT_EQ(0U, GetHeader(request, "bar").length()); + EXPECT_EQ("body", GetBodyText(request)); +} + +TEST(WebPluginImplTest, PostParserCRLF) { + // Test an error case with an empty value field + const char *ex1 = "foo: bar\r\nbar:\r\n\r\nbody\r\n\r\nbody2"; + WebURLRequest request; + request.initialize(); + bool rv = WebPluginImpl::SetPostData(&request, ex1, + static_cast<uint32>(strlen(ex1))); + EXPECT_EQ(true, rv); + EXPECT_EQ("bar", GetHeader(request, "foo")); + EXPECT_EQ(0U, GetHeader(request, "bar").length()); + EXPECT_EQ("body\r\n\r\nbody2", GetBodyText(request)); +} + +TEST(WebPluginImplTest, PostParserBodyWithBinaryData) { + // Test a simple case with headers and binary data. + char ex1[33] = "foo: bar\nContent-length: 10\n\n"; + unsigned int binary_data = 0xFFFFFFF0; + memcpy(ex1 + strlen("foo: bar\nContent-length: 10\n\n"), &binary_data, + sizeof(binary_data)); + + WebURLRequest request; + request.initialize(); + bool rv = WebPluginImpl::SetPostData(&request, ex1, + sizeof(ex1)/sizeof(ex1[0])); + EXPECT_EQ(true, rv); + EXPECT_EQ("bar", GetHeader(request, "foo")); + EXPECT_EQ(0U, GetHeader(request, "bar").length()); + EXPECT_EQ(0U, GetHeader(request, "Content-length").length()); + + std::string body = GetBodyText(request); + + EXPECT_EQ(0xF0, (unsigned char)body[0]); + EXPECT_EQ(0xFF, (unsigned char)body[1]); + EXPECT_EQ(0xFF, (unsigned char)body[2]); + EXPECT_EQ(0xFF, (unsigned char)body[3]); +} diff --git a/webkit/glue/plugins/webplugin_page_delegate.h b/webkit/glue/plugins/webplugin_page_delegate.h new file mode 100644 index 0000000..d915fdd --- /dev/null +++ b/webkit/glue/plugins/webplugin_page_delegate.h @@ -0,0 +1,69 @@ +// Copyright (c) 2009 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. + +#ifndef WEBKIT_GLUE_WEBPLUGIN_PAGE_DELEGATE_ +#define WEBKIT_GLUE_WEBPLUGIN_PAGE_DELEGATE_ + +#include "gfx/native_widget_types.h" + +class FilePath; +class GURL; + +namespace WebKit { +class WebCookieJar; +} + +namespace webkit_glue { + +class WebPluginDelegate; +struct WebPluginGeometry; + +// Used by the WebPlugin to communicate back to the containing page. +class WebPluginPageDelegate { + public: + // This method is called to create a WebPluginDelegate implementation when a + // new plugin is instanced. See webkit_glue::CreateWebPluginDelegateHelper + // for a default WebPluginDelegate implementation. + virtual WebPluginDelegate* CreatePluginDelegate( + const FilePath& file_path, + const std::string& mime_type) = 0; + + // Called when a windowed plugin is created. + // Lets the view delegate create anything it is using to wrap the plugin. + virtual void CreatedPluginWindow( + gfx::PluginWindowHandle handle) = 0; + + // Called when a windowed plugin is closing. + // Lets the view delegate shut down anything it is using to wrap the plugin. + virtual void WillDestroyPluginWindow( + gfx::PluginWindowHandle handle) = 0; + + // Keeps track of the necessary window move for a plugin window that resulted + // from a scroll operation. That way, all plugin windows can be moved at the + // same time as each other and the page. + virtual void DidMovePlugin( + const WebPluginGeometry& move) = 0; + + // Notifies the parent view that a load has begun. + virtual void DidStartLoadingForPlugin() = 0; + + // Notifies the parent view that all loads are finished. + virtual void DidStopLoadingForPlugin() = 0; + + // Asks the browser to show a modal HTML dialog. The dialog is passed the + // given arguments as a JSON string, and returns its result as a JSON string + // through json_retval. + virtual void ShowModalHTMLDialogForPlugin( + const GURL& url, + const gfx::Size& size, + const std::string& json_arguments, + std::string* json_retval) = 0; + + // The WebCookieJar to use for this plugin. + virtual WebKit::WebCookieJar* GetCookieJar() = 0; +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_WEBPLUGIN_PAGE_DELEGATE_H_ diff --git a/webkit/glue/plugins/webplugin_print_delegate.cc b/webkit/glue/plugins/webplugin_print_delegate.cc new file mode 100644 index 0000000..07bf9b8 --- /dev/null +++ b/webkit/glue/plugins/webplugin_print_delegate.cc @@ -0,0 +1,26 @@ +// 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. + +#include "webkit/glue/plugins/webplugin_print_delegate.h" + +namespace webkit_glue { + +bool WebPluginPrintDelegate::PrintSupportsPrintExtension() { + return false; +} + +int WebPluginPrintDelegate::PrintBegin(const gfx::Rect& printable_area, + int printer_dpi) { + return 0; +} + +bool WebPluginPrintDelegate::PrintPage(int page_number, + WebKit::WebCanvas* canvas) { + return false; +} + +void WebPluginPrintDelegate::PrintEnd() { +} + +} // namespace webkit_glue diff --git a/webkit/glue/plugins/webplugin_print_delegate.h b/webkit/glue/plugins/webplugin_print_delegate.h new file mode 100644 index 0000000..24298e8 --- /dev/null +++ b/webkit/glue/plugins/webplugin_print_delegate.h @@ -0,0 +1,43 @@ +// 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. + +#ifndef WEBKIT_GLUE_PLUGINS_WEBPLUGIN_PRINT_DELEGATE_H_ +#define WEBKIT_GLUE_PLUGINS_WEBPLUGIN_PRINT_DELEGATE_H_ + +#include "base/basictypes.h" +#include "third_party/npapi/bindings/npapi_extensions.h" +#include "third_party/WebKit/WebKit/chromium/public/WebCanvas.h" + +namespace gfx { +class Rect; +} + +namespace webkit_glue { + +// Interface for the NPAPI print extension. This class implements "NOP" +// versions of all these functions so it can be used seamlessly by the +// "regular" plugin delegate while being overridden by the "pepper" one. +class WebPluginPrintDelegate { + public: + // If a plugin supports print extensions, then it gets to participate fully + // in the browser's print workflow by specifying the number of pages to be + // printed and providing a print output for specified pages. + virtual bool PrintSupportsPrintExtension(); + + // Note: printable_area is in points (a point is 1/72 of an inch). + virtual int PrintBegin(const gfx::Rect& printable_area, int printer_dpi); + + virtual bool PrintPage(int page_number, WebKit::WebCanvas* canvas); + + virtual void PrintEnd(); + + protected: + WebPluginPrintDelegate() {} + virtual ~WebPluginPrintDelegate() {} +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_PLUGINS_WEBPLUGIN_PRINT_DELEGATE_H_ + diff --git a/webkit/glue/plugins/webplugininfo.cc b/webkit/glue/plugins/webplugininfo.cc new file mode 100644 index 0000000..7d2b4e4 --- /dev/null +++ b/webkit/glue/plugins/webplugininfo.cc @@ -0,0 +1,44 @@ +// 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. + +#include "webkit/glue/plugins/webplugininfo.h" + +WebPluginMimeType::WebPluginMimeType() {} + +WebPluginMimeType::~WebPluginMimeType() {} + +WebPluginInfo::WebPluginInfo() : enabled(false) {} + +WebPluginInfo::WebPluginInfo(const WebPluginInfo& rhs) + : name(rhs.name), + path(rhs.path), + version(rhs.version), + desc(rhs.desc), + mime_types(rhs.mime_types), + enabled(rhs.enabled) { +} + +WebPluginInfo::~WebPluginInfo() {} + +WebPluginInfo& WebPluginInfo::operator=(const WebPluginInfo& rhs) { + name = rhs.name; + path = rhs.path; + version = rhs.version; + desc = rhs.desc; + mime_types = rhs.mime_types; + enabled = rhs.enabled; + return *this; +} + +WebPluginInfo::WebPluginInfo(const string16& fake_name, + const string16& fake_version, + const string16& fake_desc) + : name(fake_name), + path(), + version(fake_version), + desc(fake_desc), + mime_types(), + enabled(true) { +} + diff --git a/webkit/glue/plugins/webplugininfo.h b/webkit/glue/plugins/webplugininfo.h new file mode 100644 index 0000000..34eff3d --- /dev/null +++ b/webkit/glue/plugins/webplugininfo.h @@ -0,0 +1,60 @@ +// 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. + +#ifndef WEBKIT_GLUE_WEBPLUGININFO_H_ +#define WEBKIT_GLUE_WEBPLUGININFO_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/file_path.h" + +// Describes a mime type entry for a plugin. +struct WebPluginMimeType { + WebPluginMimeType(); + ~WebPluginMimeType(); + + // The name of the mime type (e.g., "application/x-shockwave-flash"). + std::string mime_type; + + // A list of all the file extensions for this mime type. + std::vector<std::string> file_extensions; + + // Description of the mime type. + string16 description; +}; + +// Describes an available NPAPI plugin. +struct WebPluginInfo { + WebPluginInfo(); + WebPluginInfo(const WebPluginInfo& rhs); + ~WebPluginInfo(); + WebPluginInfo& operator=(const WebPluginInfo& rhs); + + // Special constructor only used during unit testing: + WebPluginInfo(const string16& fake_name, + const string16& fake_version, + const string16& fake_desc); + + // The name of the plugin (i.e. Flash). + string16 name; + + // The path to the plugin file (DLL/bundle/library). + FilePath path; + + // The version number of the plugin file (may be OS-specific) + string16 version; + + // A description of the plugin that we get from its version info. + string16 desc; + + // A list of all the mime types that this plugin supports. + std::vector<WebPluginMimeType> mime_types; + + // Whether the plugin is enabled. + bool enabled; +}; + +#endif // WEBKIT_GLUE_WEBPLUGININFO_H_ diff --git a/webkit/glue/plugins/webview_plugin.cc b/webkit/glue/plugins/webview_plugin.cc new file mode 100644 index 0000000..f89ccb4 --- /dev/null +++ b/webkit/glue/plugins/webview_plugin.cc @@ -0,0 +1,235 @@ +// 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. + +#include "webkit/glue/plugins/webview_plugin.h" + +#include "base/message_loop.h" +#include "base/metrics/histogram.h" +#include "third_party/WebKit/WebKit/chromium/public/WebCursorInfo.h" +#include "third_party/WebKit/WebKit/chromium/public/WebElement.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/WebKit/chromium/public/WebInputEvent.h" +#include "third_party/WebKit/WebKit/chromium/public/WebPluginContainer.h" +#include "third_party/WebKit/WebKit/chromium/public/WebSize.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURL.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLRequest.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLResponse.h" +#include "third_party/WebKit/WebKit/chromium/public/WebView.h" +#include "webkit/glue/webpreferences.h" + +#if WEBKIT_USING_CG +#include <CoreGraphics/CGContext.h> +#elif WEBKIT_USING_SKIA +#include "skia/ext/platform_canvas.h" +#endif + +using WebKit::WebCanvas; +using WebKit::WebCursorInfo; +using WebKit::WebDragData; +using WebKit::WebDragOperationsMask; +using WebKit::WebFrame; +using WebKit::WebImage; +using WebKit::WebInputEvent; +using WebKit::WebMouseEvent; +using WebKit::WebPlugin; +using WebKit::WebPluginContainer; +using WebKit::WebPoint; +using WebKit::WebRect; +using WebKit::WebSize; +using WebKit::WebURLError; +using WebKit::WebURLRequest; +using WebKit::WebURLResponse; +using WebKit::WebVector; +using WebKit::WebView; + +WebViewPlugin::WebViewPlugin(WebViewPlugin::Delegate* delegate) + : delegate_(delegate), + container_(NULL), + finished_loading_(false) { + web_view_ = WebView::create(this, NULL); + web_view_->initializeMainFrame(this); +} + +// static +WebViewPlugin* WebViewPlugin::Create(WebViewPlugin::Delegate* delegate, + const WebPreferences& preferences, + const std::string& html_data, + const GURL& url) { + WebViewPlugin* plugin = new WebViewPlugin(delegate); + WebView* web_view = plugin->web_view(); + preferences.Apply(web_view); + web_view->mainFrame()->loadHTMLString(html_data, url); + return plugin; +} + +WebViewPlugin::~WebViewPlugin() { + web_view_->close(); +} + +void WebViewPlugin::ReplayReceivedData(WebPlugin* plugin) { + if (!response_.isNull()) { + plugin->didReceiveResponse(response_); + size_t total_bytes = 0; + for (std::list<std::string>::iterator it = data_.begin(); + it != data_.end(); ++it) { + plugin->didReceiveData(it->c_str(), it->length()); + total_bytes += it->length(); + } + UMA_HISTOGRAM_MEMORY_KB("PluginDocument.Memory", (total_bytes / 1024)); + UMA_HISTOGRAM_COUNTS("PluginDocument.NumChunks", data_.size()); + } + if (finished_loading_) { + plugin->didFinishLoading(); + } + if (error_.get()) { + plugin->didFailLoading(*error_); + } +} + +bool WebViewPlugin::initialize(WebPluginContainer* container) { + container_ = container; + if (container_) + old_title_ = container_->element().getAttribute("title"); + return true; +} + +void WebViewPlugin::destroy() { + if (delegate_) { + delegate_->WillDestroyPlugin(); + delegate_ = NULL; + } + if (container_) + container_->element().setAttribute("title", old_title_); + container_ = NULL; + MessageLoop::current()->DeleteSoon(FROM_HERE, this); +} + +NPObject* WebViewPlugin::scriptableObject() { + return NULL; +} + +void WebViewPlugin::paint(WebCanvas* canvas, const WebRect& rect) { + gfx::Rect paintRect(rect_.Intersect(rect)); + if (paintRect.IsEmpty()) + return; + + paintRect.Offset(-rect_.x(), -rect_.y()); + +#if WEBKIT_USING_CG + CGContextRef context = canvas; + CGContextTranslateCTM(context, rect_.x(), rect_.y()); + CGContextSaveGState(context); +#elif WEBKIT_USING_SKIA + skia::PlatformCanvas* platform_canvas = canvas; + platform_canvas->translate(SkIntToScalar(rect_.x()), + SkIntToScalar(rect_.y())); + platform_canvas->save(); +#endif + + web_view_->layout(); + web_view_->paint(canvas, paintRect); + +#if WEBKIT_USING_SKIA + platform_canvas->restore(); +#elif WEBKIT_USING_CG + CGContextRestoreGState(context); +#endif +} + +// Coordinates are relative to the containing window. +void WebViewPlugin::updateGeometry( + const WebRect& frame_rect, const WebRect& clip_rect, + const WebVector<WebRect>& cut_out_rects, bool is_visible) { + if (frame_rect != rect_) { + rect_ = frame_rect; + web_view_->resize(WebSize(frame_rect.width, frame_rect.height)); + } +} + +bool WebViewPlugin::acceptsInputEvents() { + return true; +} + +bool WebViewPlugin::handleInputEvent(const WebInputEvent& event, + WebCursorInfo& cursor) { + if (event.type == WebInputEvent::ContextMenu) { + if (delegate_) { + const WebMouseEvent& mouse_event = + reinterpret_cast<const WebMouseEvent&>(event); + delegate_->ShowContextMenu(mouse_event); + } + return true; + } + current_cursor_ = cursor; + bool handled = web_view_->handleInputEvent(event); + cursor = current_cursor_; + return handled; +} + +void WebViewPlugin::didReceiveResponse(const WebURLResponse& response) { + DCHECK(response_.isNull()); + response_ = response; +} + +void WebViewPlugin::didReceiveData(const char* data, int data_length) { + data_.push_back(std::string(data, data_length)); +} + +void WebViewPlugin::didFinishLoading() { + DCHECK(!finished_loading_); + finished_loading_ = true; +} + +void WebViewPlugin::didFailLoading(const WebURLError& error) { + DCHECK(!error_.get()); + error_.reset(new WebURLError(error)); +} + +bool WebViewPlugin::acceptsLoadDrops() { + return false; +} + +void WebViewPlugin::setToolTipText(const WebKit::WebString& text, + WebKit::WebTextDirection hint) { + if (container_) + container_->element().setAttribute("title", text); +} + +void WebViewPlugin::startDragging(const WebDragData&, + WebDragOperationsMask, + const WebImage&, + const WebPoint&) { + // Immediately stop dragging. + web_view_->dragSourceSystemDragEnded(); +} + +void WebViewPlugin::didInvalidateRect(const WebRect& rect) { + if (container_) + container_->invalidateRect(rect); +} + +void WebViewPlugin::didChangeCursor(const WebCursorInfo& cursor) { + current_cursor_ = cursor; +} + +void WebViewPlugin::didClearWindowObject(WebFrame* frame) { + if (delegate_) + delegate_->BindWebFrame(frame); +} + +bool WebViewPlugin::canHandleRequest(WebFrame* frame, + const WebURLRequest& request) { + return GURL(request.url()).SchemeIs("chrome"); +} + +WebURLError WebViewPlugin::cancelledError(WebFrame* frame, + const WebURLRequest& request) { + // Return an error with a non-zero reason so isNull() on the corresponding + // ResourceError is false. + WebURLError error; + error.domain = "WebViewPlugin"; + error.reason = -1; + error.unreachableURL = request.url(); + return error; +} diff --git a/webkit/glue/plugins/webview_plugin.h b/webkit/glue/plugins/webview_plugin.h new file mode 100644 index 0000000..f3c75c1 --- /dev/null +++ b/webkit/glue/plugins/webview_plugin.h @@ -0,0 +1,142 @@ +// 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. + +#ifndef WEBKIT_GLUE_PLUGINS_WEBVIEW_PLUGIN_H_ +#define WEBKIT_GLUE_PLUGINS_WEBVIEW_PLUGIN_H_ + +#include <list> + +#include "base/scoped_ptr.h" +#include "base/task.h" +#include "third_party/WebKit/WebKit/chromium/public/WebCursorInfo.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrameClient.h" +#include "third_party/WebKit/WebKit/chromium/public/WebPlugin.h" +#include "third_party/WebKit/WebKit/chromium/public/WebString.h" +#include "third_party/WebKit/WebKit/chromium/public/WebTextDirection.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLResponse.h" +#include "third_party/WebKit/WebKit/chromium/public/WebViewClient.h" + +namespace WebKit { +class WebMouseEvent; +} +struct WebPreferences; + +// This class implements the WebPlugin interface by forwarding drawing and +// handling input events to a WebView. +// It can be used as a placeholder for an actual plugin, using HTML for the UI. +// To show HTML data inside the WebViewPlugin, +// call web_view->mainFrame()->loadHTMLString() with the HTML data and a fake +// chrome:// URL as origin. + +class WebViewPlugin: public WebKit::WebPlugin, public WebKit::WebViewClient, + public WebKit::WebFrameClient { + public: + class Delegate { + public: + // Bind |frame| to a Javascript object, enabling the delegate to receive + // callback methods from Javascript inside the WebFrame. + // This method is called from WebFrameClient::didClearWindowObject. + virtual void BindWebFrame(WebKit::WebFrame* frame) = 0; + + // Called before the WebViewPlugin is destroyed. The delegate should delete + // itself here. + virtual void WillDestroyPlugin() = 0; + + // Called upon a context menu event. + virtual void ShowContextMenu(const WebKit::WebMouseEvent&) = 0; + }; + + explicit WebViewPlugin(Delegate* delegate); + + // Convenience method to set up a new WebViewPlugin using |preferences| + // and displaying |html_data|. |url| should be a (fake) chrome:// URL; it is + // only used for navigation and never actually resolved. + static WebViewPlugin* Create(Delegate* delegate, + const WebPreferences& preferences, + const std::string& html_data, + const GURL& url); + + WebKit::WebView* web_view() { return web_view_; } + + WebKit::WebPluginContainer* container() { return container_; } + + // When loading a plug-in document (i.e. a full page plug-in not embedded in + // another page), we save all data that has been received, and replay it with + // this method on the actual plug-in. + void ReplayReceivedData(WebKit::WebPlugin* plugin); + + // WebPlugin methods: + virtual bool initialize(WebKit::WebPluginContainer*); + virtual void destroy(); + + virtual NPObject* scriptableObject(); + + virtual void paint(WebKit::WebCanvas* canvas, const WebKit::WebRect& rect); + + // Coordinates are relative to the containing window. + virtual void updateGeometry( + const WebKit::WebRect& frame_rect, const WebKit::WebRect& clip_rect, + const WebKit::WebVector<WebKit::WebRect>& cut_out_rects, bool is_visible); + + virtual void updateFocus(bool) { } + virtual void updateVisibility(bool) { } + + virtual bool acceptsInputEvents(); + virtual bool handleInputEvent(const WebKit::WebInputEvent& event, + WebKit::WebCursorInfo& cursor_info); + + virtual void didReceiveResponse(const WebKit::WebURLResponse& response); + virtual void didReceiveData(const char* data, int data_length); + virtual void didFinishLoading(); + virtual void didFailLoading(const WebKit::WebURLError& error); + + // Called in response to WebPluginContainer::loadFrameRequest + virtual void didFinishLoadingFrameRequest( + const WebKit::WebURL& url, void* notifyData) { } + virtual void didFailLoadingFrameRequest(const WebKit::WebURL& url, + void* notify_data, + const WebKit::WebURLError& error) { } + + // WebViewClient methods: + virtual bool acceptsLoadDrops(); + + virtual void setToolTipText(const WebKit::WebString&, + WebKit::WebTextDirection); + + virtual void startDragging(const WebKit::WebDragData& drag_data, + WebKit::WebDragOperationsMask mask, + const WebKit::WebImage& image, + const WebKit::WebPoint& point); + + // WebWidgetClient methods: + virtual void didInvalidateRect(const WebKit::WebRect&); + virtual void didChangeCursor(const WebKit::WebCursorInfo& cursor); + + // WebFrameClient methods: + virtual void didClearWindowObject(WebKit::WebFrame* frame); + + virtual bool canHandleRequest(WebKit::WebFrame* frame, + const WebKit::WebURLRequest& request); + + virtual WebKit::WebURLError cancelledError( + WebKit::WebFrame* frame, const WebKit::WebURLRequest& request); + + private: + friend class DeleteTask<WebViewPlugin>; + virtual ~WebViewPlugin(); + + Delegate* delegate_; + WebKit::WebCursorInfo current_cursor_; + WebKit::WebPluginContainer* container_; + WebKit::WebView* web_view_; + gfx::Rect rect_; + + WebKit::WebURLResponse response_; + std::list<std::string> data_; + bool finished_loading_; + scoped_ptr<WebKit::WebURLError> error_; + WebKit::WebString old_title_; +}; + +#endif // WEBKIT_GLUE_PLUGINS_WEBVIEW_PLUGIN_H_ |