// Copyright (c) 2012 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 #include #include #include #include "base/at_exit.h" #include "base/basictypes.h" #include "base/logging.h" #include "base/stringize_macros.h" #include "net/socket/ssl_server_socket.h" #include "remoting/base/plugin_thread_task_runner.h" #include "remoting/host/plugin/constants.h" #include "remoting/host/plugin/host_log_handler.h" #include "remoting/host/plugin/host_plugin_utils.h" #include "remoting/host/plugin/host_script_object.h" #include "third_party/npapi/bindings/npapi.h" #include "third_party/npapi/bindings/npfunctions.h" #include "third_party/npapi/bindings/npruntime.h" // Symbol export is handled with a separate def file on Windows. #if defined (__GNUC__) && __GNUC__ >= 4 #define EXPORT __attribute__((visibility("default"))) #else #define EXPORT #endif #if defined(OS_WIN) // TODO(wez): libvpx expects these 64-bit division functions to be provided // by libgcc.a, which we aren't linked against. These implementations can // be removed once we have native MSVC libvpx builds for Windows. extern "C" { int64_t __cdecl __divdi3(int64_t a, int64_t b) { return a / b; } uint64_t __cdecl __udivdi3(uint64_t a, uint64_t b) { return a / b; } } #endif using remoting::g_npnetscape_funcs; using remoting::HostLogHandler; using remoting::HostNPScriptObject; using remoting::StringFromNPIdentifier; namespace { base::AtExitManager* g_at_exit_manager = NULL; // NPAPI plugin implementation for remoting host. // Documentation for most of the calls in this class can be found here: // https://developer.mozilla.org/en/Gecko_Plugin_API_Reference/Scripting_plugins class HostNPPlugin : public remoting::PluginThreadTaskRunner::Delegate { public: // |mode| is the display mode of plug-in. Values: // NP_EMBED: (1) Instance was created by an EMBED tag and shares the browser // window with other content. // NP_FULL: (2) Instance was created by a separate file and is the primary // content in the window. HostNPPlugin(NPP instance, uint16 mode) : instance_(instance), scriptable_object_(NULL) { plugin_task_runner_ = new remoting::PluginThreadTaskRunner(this); plugin_auto_task_runner_ = new remoting::AutoThreadTaskRunner( plugin_task_runner_, base::Bind(&remoting::PluginThreadTaskRunner::Quit, plugin_task_runner_)); } ~HostNPPlugin() { if (scriptable_object_) { DCHECK_EQ(scriptable_object_->referenceCount, 1UL); g_npnetscape_funcs->releaseobject(scriptable_object_); scriptable_object_ = NULL; } // Process tasks on |plugin_task_runner_| until all references to // |plugin_auto_task_runner_| have been dropped. This requires that the // browser has dropped any script object references - see above. plugin_auto_task_runner_ = NULL; plugin_task_runner_->DetachAndRunShutdownLoop(); } bool Init(int16 argc, char** argn, char** argv, NPSavedData* saved) { #if defined(OS_MACOSX) // Use the modern CoreGraphics and Cocoa models when available, since // QuickDraw and Carbon are deprecated. // The drawing and event models don't change anything for this plugin, since // none of the functions affected by the models actually do anything. // This does however keep the plugin from breaking when Chromium eventually // drops support for QuickDraw and Carbon, and it also keeps the browser // from sending Null Events once a second to support old Carbon based // timers. // Chromium should always be supporting the newer models. // Sanity check to see if Chromium supports the CoreGraphics drawing model. NPBool supports_core_graphics = false; NPError err = g_npnetscape_funcs->getvalue(instance_, NPNVsupportsCoreGraphicsBool, &supports_core_graphics); if (err == NPERR_NO_ERROR && supports_core_graphics) { // Switch to CoreGraphics drawing model. g_npnetscape_funcs->setvalue(instance_, NPPVpluginDrawingModel, reinterpret_cast(NPDrawingModelCoreGraphics)); } else { LOG(ERROR) << "No Core Graphics support"; return false; } // Sanity check to see if Chromium supports the Cocoa event model. NPBool supports_cocoa = false; err = g_npnetscape_funcs->getvalue(instance_, NPNVsupportsCocoaBool, &supports_cocoa); if (err == NPERR_NO_ERROR && supports_cocoa) { // Switch to Cocoa event model. g_npnetscape_funcs->setvalue(instance_, NPPVpluginEventModel, reinterpret_cast(NPEventModelCocoa)); } else { LOG(ERROR) << "No Cocoa Event Model support"; return false; } #endif // OS_MACOSX net::EnableSSLServerSockets(); return true; } bool Save(NPSavedData** saved) { return true; } NPObject* GetScriptableObject() { if (!scriptable_object_) { // Must be static. If it is a temporary, objects created by this // method will fail in weird and wonderful ways later. static NPClass npc_ref_object = { NP_CLASS_STRUCT_VERSION, &Allocate, &Deallocate, &Invalidate, &HasMethod, &Invoke, &InvokeDefault, &HasProperty, &GetProperty, &SetProperty, &RemoveProperty, &Enumerate, NULL }; scriptable_object_ = g_npnetscape_funcs->createobject(instance_, &npc_ref_object); } return scriptable_object_; } // PluginThreadTaskRunner::Delegate implementation. virtual bool RunOnPluginThread( base::TimeDelta delay, void(function)(void*), void* data) OVERRIDE { if (delay == base::TimeDelta()) { g_npnetscape_funcs->pluginthreadasynccall(instance_, function, data); } else { base::AutoLock auto_lock(timers_lock_); uint32_t timer_id = g_npnetscape_funcs->scheduletimer( instance_, delay.InMilliseconds(), false, &NPDelayedTaskSpringboard); DelayedTask task = {function, data}; timers_[timer_id] = task; } return true; } void SetWindow(NPWindow* np_window) { if (scriptable_object_) { ScriptableFromObject(scriptable_object_)->SetWindow(np_window); } } static void NPDelayedTaskSpringboard(NPP npp, uint32_t timer_id) { HostNPPlugin* self = reinterpret_cast(npp->pdata); DelayedTask task; { base::AutoLock auto_lock(self->timers_lock_); std::map::iterator it = self->timers_.find(timer_id); CHECK(it != self->timers_.end()); task = it->second; self->timers_.erase(it); } task.function(task.data); } private: struct ScriptableNPObject : public NPObject { HostNPScriptObject* scriptable_object; }; struct DelayedTask { void (*function)(void*); void* data; }; static HostNPScriptObject* ScriptableFromObject(NPObject* obj) { return reinterpret_cast(obj)->scriptable_object; } static NPObject* Allocate(NPP npp, NPClass* aClass) { VLOG(2) << "static Allocate"; ScriptableNPObject* object = reinterpret_cast( g_npnetscape_funcs->memalloc(sizeof(ScriptableNPObject))); HostNPPlugin* plugin = reinterpret_cast(npp->pdata); object->_class = aClass; object->referenceCount = 1; object->scriptable_object = new HostNPScriptObject(npp, object, plugin->plugin_auto_task_runner_); return object; } static void Deallocate(NPObject* npobj) { VLOG(2) << "static Deallocate"; if (npobj) { Invalidate(npobj); g_npnetscape_funcs->memfree(npobj); } } static void Invalidate(NPObject* npobj) { if (npobj) { ScriptableNPObject* object = reinterpret_cast(npobj); if (object->scriptable_object) { delete object->scriptable_object; object->scriptable_object = NULL; } } } static bool HasMethod(NPObject* obj, NPIdentifier method_name) { VLOG(2) << "static HasMethod"; HostNPScriptObject* scriptable = ScriptableFromObject(obj); if (!scriptable) return false; std::string method_name_string = StringFromNPIdentifier(method_name); if (method_name_string.empty()) return false; return scriptable->HasMethod(method_name_string); } static bool InvokeDefault(NPObject* obj, const NPVariant* args, uint32_t argCount, NPVariant* result) { VLOG(2) << "static InvokeDefault"; HostNPScriptObject* scriptable = ScriptableFromObject(obj); if (!scriptable) return false; return scriptable->InvokeDefault(args, argCount, result); } static bool Invoke(NPObject* obj, NPIdentifier method_name, const NPVariant* args, uint32_t argCount, NPVariant* result) { VLOG(2) << "static Invoke"; HostNPScriptObject* scriptable = ScriptableFromObject(obj); if (!scriptable) return false; std::string method_name_string = StringFromNPIdentifier(method_name); if (method_name_string.empty()) return false; return scriptable->Invoke(method_name_string, args, argCount, result); } static bool HasProperty(NPObject* obj, NPIdentifier property_name) { VLOG(2) << "static HasProperty"; HostNPScriptObject* scriptable = ScriptableFromObject(obj); if (!scriptable) return false; std::string property_name_string = StringFromNPIdentifier(property_name); if (property_name_string.empty()) return false; return scriptable->HasProperty(property_name_string); } static bool GetProperty(NPObject* obj, NPIdentifier property_name, NPVariant* result) { VLOG(2) << "static GetProperty"; HostNPScriptObject* scriptable = ScriptableFromObject(obj); if (!scriptable) return false; std::string property_name_string = StringFromNPIdentifier(property_name); if (property_name_string.empty()) return false; return scriptable->GetProperty(property_name_string, result); } static bool SetProperty(NPObject* obj, NPIdentifier property_name, const NPVariant* value) { VLOG(2) << "static SetProperty"; HostNPScriptObject* scriptable = ScriptableFromObject(obj); if (!scriptable) return false; std::string property_name_string = StringFromNPIdentifier(property_name); if (property_name_string.empty()) return false; return scriptable->SetProperty(property_name_string, value); } static bool RemoveProperty(NPObject* obj, NPIdentifier property_name) { VLOG(2) << "static RemoveProperty"; HostNPScriptObject* scriptable = ScriptableFromObject(obj); if (!scriptable) return false; std::string property_name_string = StringFromNPIdentifier(property_name); if (property_name_string.empty()) return false; return scriptable->RemoveProperty(property_name_string); } static bool Enumerate(NPObject* obj, NPIdentifier** value, uint32_t* count) { VLOG(2) << "static Enumerate"; HostNPScriptObject* scriptable = ScriptableFromObject(obj); if (!scriptable) return false; std::vector values; bool is_good = scriptable->Enumerate(&values); if (is_good) { *count = values.size(); *value = reinterpret_cast( g_npnetscape_funcs->memalloc(sizeof(NPIdentifier) * (*count))); for (uint32_t i = 0; i < *count; ++i) { (*value)[i] = g_npnetscape_funcs->getstringidentifier(values[i].c_str()); } } return is_good; } NPP instance_; NPObject* scriptable_object_; scoped_refptr plugin_task_runner_; scoped_refptr plugin_auto_task_runner_; std::map timers_; base::Lock timers_lock_; }; // Utility functions to map NPAPI Entry Points to C++ Objects. HostNPPlugin* PluginFromInstance(NPP instance) { return reinterpret_cast(instance->pdata); } NPError CreatePlugin(NPMIMEType pluginType, NPP instance, uint16 mode, int16 argc, char** argn, char** argv, NPSavedData* saved) { VLOG(2) << "CreatePlugin"; // Register a global log handler. // The LogMessage registration code is not thread-safe, so we need to perform // this while we're running in a single thread. HostLogHandler::RegisterLogMessageHandler(); HostNPPlugin* plugin = new HostNPPlugin(instance, mode); instance->pdata = plugin; if (!plugin->Init(argc, argn, argv, saved)) { delete plugin; instance->pdata = NULL; return NPERR_INVALID_PLUGIN_ERROR; } else { return NPERR_NO_ERROR; } } NPError DestroyPlugin(NPP instance, NPSavedData** save) { VLOG(2) << "DestroyPlugin"; // Normally, we would unregister the global log handler that we registered // in CreatePlugin. However, the LogHandler registration code is not thread- // safe so we could crash if we update (register or unregister) the // LogHandler while it's being read on another thread. // At this point, all our threads should be shutdown, but it's safer to leave // the handler registered until we're completely destroyed. HostNPPlugin* plugin = PluginFromInstance(instance); if (plugin) { plugin->Save(save); delete plugin; instance->pdata = NULL; return NPERR_NO_ERROR; } else { return NPERR_INVALID_PLUGIN_ERROR; } } NPError GetValue(NPP instance, NPPVariable variable, void* value) { switch(variable) { default: VLOG(2) << "GetValue - default " << variable; return NPERR_GENERIC_ERROR; case NPPVpluginNameString: VLOG(2) << "GetValue - name string"; *reinterpret_cast(value) = HOST_PLUGIN_NAME; break; case NPPVpluginDescriptionString: VLOG(2) << "GetValue - description string"; *reinterpret_cast(value) = HOST_PLUGIN_DESCRIPTION; break; case NPPVpluginNeedsXEmbed: VLOG(2) << "GetValue - NeedsXEmbed"; *(static_cast(value)) = true; break; case NPPVpluginScriptableNPObject: VLOG(2) << "GetValue - scriptable object"; HostNPPlugin* plugin = PluginFromInstance(instance); if (!plugin) return NPERR_INVALID_PLUGIN_ERROR; NPObject* scriptable_object = plugin->GetScriptableObject(); g_npnetscape_funcs->retainobject(scriptable_object); *reinterpret_cast(value) = scriptable_object; break; } return NPERR_NO_ERROR; } NPError HandleEvent(NPP instance, void* ev) { VLOG(2) << "HandleEvent"; return NPERR_NO_ERROR; } NPError SetWindow(NPP instance, NPWindow* pNPWindow) { VLOG(2) << "SetWindow"; HostNPPlugin* plugin = PluginFromInstance(instance); if (plugin) { plugin->SetWindow(pNPWindow); } return NPERR_NO_ERROR; } } // namespace #if defined(OS_WIN) HMODULE g_hModule = NULL; BOOL APIENTRY DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved) { switch (dwReason) { case DLL_PROCESS_ATTACH: g_hModule = hModule; DisableThreadLibraryCalls(hModule); break; case DLL_PROCESS_DETACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: break; } return TRUE; } #endif // The actual required NPAPI Entry points extern "C" { EXPORT NPError API_CALL NP_GetEntryPoints(NPPluginFuncs* nppfuncs) { VLOG(2) << "NP_GetEntryPoints"; nppfuncs->version = (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR; nppfuncs->newp = &CreatePlugin; nppfuncs->destroy = &DestroyPlugin; nppfuncs->getvalue = &GetValue; nppfuncs->event = &HandleEvent; nppfuncs->setwindow = &SetWindow; return NPERR_NO_ERROR; } EXPORT NPError API_CALL NP_Initialize(NPNetscapeFuncs* npnetscape_funcs #if defined(OS_POSIX) && !defined(OS_MACOSX) , NPPluginFuncs* nppfuncs #endif ) { VLOG(2) << "NP_Initialize"; if (g_at_exit_manager) return NPERR_MODULE_LOAD_FAILED_ERROR; if(npnetscape_funcs == NULL) return NPERR_INVALID_FUNCTABLE_ERROR; if(((npnetscape_funcs->version & 0xff00) >> 8) > NP_VERSION_MAJOR) return NPERR_INCOMPATIBLE_VERSION_ERROR; g_at_exit_manager = new base::AtExitManager; g_npnetscape_funcs = npnetscape_funcs; #if defined(OS_POSIX) && !defined(OS_MACOSX) NP_GetEntryPoints(nppfuncs); #endif return NPERR_NO_ERROR; } EXPORT NPError API_CALL NP_Shutdown() { VLOG(2) << "NP_Shutdown"; delete g_at_exit_manager; g_at_exit_manager = NULL; return NPERR_NO_ERROR; } #if defined(OS_POSIX) && !defined(OS_MACOSX) EXPORT const char* API_CALL NP_GetMIMEDescription(void) { VLOG(2) << "NP_GetMIMEDescription"; return HOST_PLUGIN_MIME_TYPE "::"; } EXPORT NPError API_CALL NP_GetValue(void* npp, NPPVariable variable, void* value) { return GetValue((NPP)npp, variable, value); } #endif } // extern "C"