diff options
Diffstat (limited to 'chrome_frame/chrome_frame_npapi.cc')
-rw-r--r-- | chrome_frame/chrome_frame_npapi.cc | 1462 |
1 files changed, 1462 insertions, 0 deletions
diff --git a/chrome_frame/chrome_frame_npapi.cc b/chrome_frame/chrome_frame_npapi.cc new file mode 100644 index 0000000..0c58cff --- /dev/null +++ b/chrome_frame/chrome_frame_npapi.cc @@ -0,0 +1,1462 @@ +// 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 "chrome_frame/chrome_frame_npapi.h" + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/string_util.h" +#include "base/win_util.h" +#include "chrome/test/automation/tab_proxy.h" +#include "chrome_frame/ff_privilege_check.h" +#include "chrome_frame/utils.h" + +MessageLoop* ChromeFrameNPAPI::message_loop_ = NULL; +int ChromeFrameNPAPI::instance_count_ = 0; + +static const char* kNpEventNames[] = { + "focus", + "blur", +}; + +NPClass ChromeFrameNPAPI::plugin_class_ = { + NP_CLASS_STRUCT_VERSION, + ChromeFrameNPAPI::AllocateObject, + ChromeFrameNPAPI::DeallocateObject, + ChromeFrameNPAPI::Invalidate, + ChromeFrameNPAPI::HasMethod, + ChromeFrameNPAPI::Invoke, + NULL, // invokeDefault + ChromeFrameNPAPI::HasProperty, + ChromeFrameNPAPI::GetProperty, + ChromeFrameNPAPI::SetProperty, + NULL, // remove property + NULL, // enumeration + NULL, // construct +}; + +NPIdentifier + ChromeFrameNPAPI::plugin_property_identifiers_[PLUGIN_PROPERTY_COUNT] + = {0}; + +const NPUTF8* ChromeFrameNPAPI::plugin_property_identifier_names_[] = { + "version", + "src", + "onload", + "onloaderror", + "onmessage", + "readystate", + "onprivatemessage", + "usechromenetwork", +}; + +const NPUTF8* ChromeFrameNPAPI::plugin_method_identifier_names_[] = { + "postMessage", + "postPrivateMessage", +}; + +ChromeFrameNPAPI::PluginMethod ChromeFrameNPAPI::plugin_methods_[] = { + &ChromeFrameNPAPI::postMessage, + &ChromeFrameNPAPI::postPrivateMessage, +}; + +NPIdentifier + ChromeFrameNPAPI::plugin_method_identifiers_[arraysize(plugin_methods_)] + = {0}; + + +void ChromeFrameNPAPI::CompileAsserts() { + NOTREACHED(); // This function should never be invoked. + + COMPILE_ASSERT(arraysize(plugin_method_identifier_names_) == + arraysize(plugin_methods_), + you_must_add_both_plugin_method_and_name); + + COMPILE_ASSERT(arraysize(plugin_property_identifier_names_) == + arraysize(plugin_property_identifiers_), + you_must_add_both_plugin_property_and_name); +} + +static const int kMaxBytesForPluginConsumption = 0x7FFFFFFF; + +static const char kPluginSrcAttribute[] = "src"; +static const char kPluginForceFullPageAttribute[] = "force_full_page"; +static const char kPluginOnloadAttribute[] = "onload"; +static const char kPluginOnErrorAttribute[] = "onloaderror"; +static const char kPluginOnMessageAttribute[] = "onmessage"; +static const char kPluginOnPrivateMessageAttribute[] = "onprivatemessage"; +// These properties can only be set in arguments at control instantiation. +// When the privileged_mode property is provided and set to true, the control +// will probe for whether its hosting document has the system principal, in +// which case privileged mode will be enabled. +static const char kPluginPrivilegedModeAttribute[] = "privileged_mode"; +// If privileged mode is enabled, the string value of this argument will +// be appended to the chrome.exe command line. +static const char kPluginChromeExtraArguments[] = "chrome_extra_arguments"; +// If privileged mode is enabled, the string value of this argument will +// be used as the profile name for our chrome.exe instance. +static const char kPluginChromeProfileName[] = "chrome_profile_name"; +// If chrome network stack is to be used +static const char kPluginUseChromeNetwork[] = "usechromenetwork"; + + +NPError NPP_New(NPMIMEType plugin_type, NPP instance, uint16 mode, int16 argc, + char* argn[], char* argv[], NPSavedData* saved) { + if (instance == NULL) + return NPERR_INVALID_INSTANCE_ERROR; + + ChromeFrameNPAPI::ChromeFrameNPObject* chrome_frame_npapi_obj = + reinterpret_cast<ChromeFrameNPAPI::ChromeFrameNPObject*>( + npapi::CreateObject(instance, ChromeFrameNPAPI::PluginClass())); + DCHECK(chrome_frame_npapi_obj != NULL); + + ChromeFrameNPAPI* plugin_instance = + chrome_frame_npapi_obj->chrome_frame_plugin_instance; + DCHECK(plugin_instance != NULL); + + // Note that we MUST set instance->pdata BEFORE calling Initialize. This is + // because Initialize can call back into the NPAPI host which will need the + // pdata field to be set. + chrome_frame_npapi_obj->chrome_frame_plugin_instance = + plugin_instance; + instance->pdata = chrome_frame_npapi_obj; + + bool init = plugin_instance->Initialize(plugin_type, instance, + mode, argc, argn, argv); + DCHECK(init); + + return NPERR_NO_ERROR; +} + +NPError NPP_Destroy(NPP instance, NPSavedData** save) { + // Takes ownership and releases the object at the end of scope. + ScopedNpObject<ChromeFrameNPAPI::ChromeFrameNPObject> chrome_frame_npapi_obj( + reinterpret_cast<ChromeFrameNPAPI::ChromeFrameNPObject*>( + instance->pdata)); + + if (chrome_frame_npapi_obj.get()) { + ChromeFrameNPAPI* plugin_instance = + ChromeFrameNPAPI::ChromeFrameInstanceFromPluginInstance(instance); + + plugin_instance->Uninitialize(); + instance->pdata = NULL; + } + + return NPERR_NO_ERROR; +} + +NPError NPP_SetWindow(NPP instance, NPWindow* window_info) { + if (window_info == NULL) { + NOTREACHED(); + return NPERR_GENERIC_ERROR; + } + + ChromeFrameNPAPI* plugin_instance = + ChromeFrameNPAPI::ChromeFrameInstanceFromPluginInstance(instance); + + if (plugin_instance == NULL) { + return NPERR_INVALID_INSTANCE_ERROR; + } + + plugin_instance->SetWindow(window_info); + return NPERR_NO_ERROR; +} + +NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream, + NPBool seekable, uint16* stream_type) { + NPAPIUrlRequest* url_request = ChromeFrameNPAPI::ValidateRequest( + instance, stream->notifyData); + if (url_request) { + if (!url_request->OnStreamCreated(type, stream)) + return NPERR_GENERIC_ERROR; + } + + // We need to return the requested stream mode if we are returning a success + // code. If we don't do this it causes Opera to blow up. + *stream_type = NP_NORMAL; + return NPERR_NO_ERROR; +} + +NPError NPP_DestroyStream(NPP instance, NPStream* stream, NPReason reason) { + NPAPIUrlRequest* url_request = ChromeFrameNPAPI::ValidateRequest( + instance, stream->notifyData); + if (url_request) { + url_request->OnStreamDestroyed(reason); + } + + return NPERR_NO_ERROR; +} + +NPError NPP_GetValue(NPP instance, NPPVariable variable, void* value) { + if (variable == NPPVpluginScriptableNPObject) { + void** plugin = reinterpret_cast<void**>(value); + ChromeFrameNPAPI::ChromeFrameNPObject* chrome_frame_npapi_obj = + reinterpret_cast<ChromeFrameNPAPI::ChromeFrameNPObject*>( + instance->pdata); + // Return value is expected to be retained + npapi::RetainObject(reinterpret_cast<NPObject*>(chrome_frame_npapi_obj)); + *plugin = chrome_frame_npapi_obj; + return NPERR_NO_ERROR; + } + return NPERR_GENERIC_ERROR; +} + +NPError NPP_SetValue(NPP instance, NPNVariable variable, void* value) { + return NPERR_GENERIC_ERROR; +} + +int32 NPP_WriteReady(NPP instance, NPStream* stream) { + NPAPIUrlRequest* url_request = ChromeFrameNPAPI::ValidateRequest( + instance, stream->notifyData); + if (url_request) { + return url_request->OnWriteReady(); + } + + return kMaxBytesForPluginConsumption; +} + +int32 NPP_Write(NPP instance, NPStream* stream, int32 offset, int32 len, + void* buffer) { + NPAPIUrlRequest* url_request = ChromeFrameNPAPI::ValidateRequest( + instance, stream->notifyData); + if (url_request) { + return url_request->OnWrite(buffer, len); + } + + return len; +} + +void NPP_URLNotify(NPP instance, const char* url, NPReason reason, + void* notifyData) { + ChromeFrameNPAPI* plugin_instance = + ChromeFrameNPAPI::ChromeFrameInstanceFromPluginInstance(instance); + if (plugin_instance) { + plugin_instance->UrlNotify(url, reason, notifyData); + } +} + +void NPP_Print(NPP instance, NPPrint* print_info) { + ChromeFrameNPAPI* plugin_instance = + ChromeFrameNPAPI::ChromeFrameInstanceFromPluginInstance(instance); + + if (plugin_instance == NULL) { + NOTREACHED(); + return; + } + + plugin_instance->Print(print_info); +} + +// ChromeFrameNPAPI member defines. + +// TODO(tommi): remove ignore_setfocus_ since that's not how focus is +// handled anymore. + +ChromeFrameNPAPI::ChromeFrameNPAPI() + : instance_(NULL), + mode_(NP_EMBED), + force_full_page_plugin_(false), + ready_state_(READYSTATE_LOADING), + enabled_popups_(false) { +} + +ChromeFrameNPAPI::~ChromeFrameNPAPI() { + if (IsWindow()) { + if (!UnsubclassWindow()) { + // TODO(tommi): Figure out why this can sometimes happen in the + // WidgetModeFF_Resize unittest. + DLOG(ERROR) << "Couldn't unsubclass safely!"; + UnsubclassWindow(TRUE); + } + } + m_hWnd = NULL; + + instance_count_--; + if (instance_count_ <= 0) { + delete message_loop_; + message_loop_ = NULL; + } + + Uninitialize(); +} + +std::string GetLocation(NPP instance, NPObject* window) { + if (!window) { + // Can fail if the browser is closing (seen in Opera). + return ""; + } + + std::string result; + ScopedNpVariant href; + ScopedNpVariant location; + + bool ok = npapi::GetProperty(instance, window, + npapi::GetStringIdentifier("location"), &location); + DCHECK(ok); + DCHECK(location.type == NPVariantType_Object); + + if (ok) { + ok = npapi::GetProperty(instance, + location.value.objectValue, + npapi::GetStringIdentifier("href"), + &href); + DCHECK(ok); + DCHECK(href.type == NPVariantType_String); + if (ok) { + result.assign(href.value.stringValue.UTF8Characters, + href.value.stringValue.UTF8Length); + } + } + + return result; +} + +std::string ChromeFrameNPAPI::GetLocation() { + // Note that GetWindowObject() will cache the window object here. + return ::GetLocation(instance_, GetWindowObject()); +} + +bool ChromeFrameNPAPI::Initialize(NPMIMEType mime_type, NPP instance, + uint16 mode, int16 argc, char* argn[], + char* argv[]) { + if (!Base::Initialize()) + return false; + + instance_ = instance; + mime_type_ = mime_type; + mode_ = mode; + document_url_ = GetLocation(); + + if (instance_count_ == 0) { + DCHECK(message_loop_ == NULL); + message_loop_ = new MessageLoop(); + } + + instance_count_++; + + // Create our prefs service wrapper here. + DCHECK(!pref_service_.get()); + pref_service_ = CreatePrefService(); + if (!pref_service_.get()) { + NOTREACHED() << "new NpProxyService"; + return false; + } + + // Temporary variables for privileged only parameters + const char* onprivatemessage_arg = NULL; + const char* chrome_extra_arguments_arg = NULL; + const char* chrome_profile_name_arg = NULL; + bool chrome_network_arg_set = false; + bool chrome_network_arg = false; + bool wants_privileged = false; + + for (int i = 0; i < argc; ++i) { + if (LowerCaseEqualsASCII(argn[i], kPluginSrcAttribute)) { + src_ = ResolveURL(GetDocumentUrl(), argv[i]); + } else if (LowerCaseEqualsASCII(argn[i], kPluginForceFullPageAttribute)) { + force_full_page_plugin_ = atoi(argv[i]) ? true : false; + } else if (LowerCaseEqualsASCII(argn[i], kPluginOnErrorAttribute)) { + onerror_handler_ = JavascriptToNPObject(argv[i]); + } else if (LowerCaseEqualsASCII(argn[i], kPluginOnMessageAttribute)) { + onmessage_handler_ = JavascriptToNPObject(argv[i]); + } else if (LowerCaseEqualsASCII(argn[i], + kPluginPrivilegedModeAttribute)) { + // Test for the FireFox privileged mode if the user requests it + // in initialization parameters. + wants_privileged = atoi(argv[i]) ? true : false; + } else if (LowerCaseEqualsASCII(argn[i], + kPluginOnPrivateMessageAttribute)) { + onprivatemessage_arg = argv[i]; + } else if (LowerCaseEqualsASCII(argn[i], kPluginChromeExtraArguments)) { + chrome_extra_arguments_arg = argv[i]; + } else if (LowerCaseEqualsASCII(argn[i], kPluginChromeProfileName)) { + chrome_profile_name_arg = argv[i]; + } else if (LowerCaseEqualsASCII(argn[i], kPluginUseChromeNetwork)) { + chrome_network_arg_set = true; + chrome_network_arg = atoi(argv[i]) ? true : false; + } + } + + // Is the privileged mode requested? + if (wants_privileged) { + is_privileged_ = IsFireFoxPrivilegedInvocation(instance); + if (!is_privileged_) { + DLOG(WARNING) << "Privileged mode requested in non-privileged context"; + } + } + + std::wstring extra_arguments; + std::wstring profile_name(GetHostProcessName(false)); + if (is_privileged_) { + // Process any privileged mode-only arguments we were handed. + if (onprivatemessage_arg) + onprivatemessage_handler_ = JavascriptToNPObject(onprivatemessage_arg); + + if (chrome_extra_arguments_arg) + extra_arguments = UTF8ToWide(chrome_extra_arguments_arg); + + if (chrome_profile_name_arg) + profile_name = UTF8ToWide(chrome_profile_name_arg); + + if (chrome_network_arg_set) + automation_client_->set_use_chrome_network(chrome_network_arg); + + } + + // TODO(joshia): Initialize navigation here and send proxy config as + // part of LaunchSettings + /* + if (!src_.empty()) + automation_client_->InitiateNavigation(src_); + + std::string proxy_settings; + bool has_prefs = pref_service_->Initialize(instance_, + automation_client_.get()); + if (has_prefs && pref_service_->GetProxyValueJSONString(&proxy_settings)) { + automation_client_->SetProxySettings(proxy_settings); + } + */ + + // We can't call SubscribeToFocusEvents here since + // when Initialize gets called, Opera is in a state where + // it can't handle calls back and the thread will hang. + // Instead, we call SubscribeToFocusEvents when we initialize + // our plugin window. + + // TODO(stoyan): Ask host for specific interface whether to honor + // host's in-private mode. + return InitializeAutomation(profile_name, extra_arguments, + GetBrowserIncognitoMode()); +} + +void ChromeFrameNPAPI::Uninitialize() { + // Don't call SetReadyState as it will end up calling FireEvent. + // We are in the context of NPP_DESTROY. + ready_state_ = READYSTATE_UNINITIALIZED; + + UnsubscribeFromFocusEvents(); + + if (pref_service_) { + pref_service_->UnInitialize(); + pref_service_ = NULL; + } + + window_object_.Free(); + onerror_handler_.Free(); + onmessage_handler_.Free(); + onprivatemessage_handler_.Free(); + + Base::Uninitialize(); +} + +void ChromeFrameNPAPI::OnFinalMessage(HWND window) { + // The automation server should be gone by now. + Uninitialize(); +} + +void ChromeFrameNPAPI::SubscribeToFocusEvents() { + DCHECK(focus_listener_.get() == NULL); + + focus_listener_ = new DomEventListener(this); + if (!focus_listener_->Subscribe(instance_, kNpEventNames, + arraysize(kNpEventNames))) { + focus_listener_ = NULL; + focus_listener_ = new NPObjectEventListener(this); + if (!focus_listener_->Subscribe(instance_, kNpEventNames, + arraysize(kNpEventNames))) { + DLOG(ERROR) << "Failed to subscribe to focus events"; + focus_listener_ = NULL; + } + } +} + +void ChromeFrameNPAPI::UnsubscribeFromFocusEvents() { + if (!focus_listener_.get()) + return; + + bool ret = focus_listener_->Unsubscribe(instance_, kNpEventNames, + arraysize(kNpEventNames)); + DLOG_IF(WARNING, !ret) << "focus_listener_->Unsubscribe failed"; + focus_listener_ = NULL; +} + +bool ChromeFrameNPAPI::SetWindow(NPWindow* window_info) { + if (!window_info || !automation_client_.get()) { + NOTREACHED(); + return false; + } + + HWND window = reinterpret_cast<HWND>(window_info->window); + if (!::IsWindow(window)) { + // No window created yet. Ignore this call. + return false; + } + + if (IsWindow()) { + // We've already subclassed, make sure that SetWindow doesn't get called + // with an HWND other than the one we subclassed during our lifetime. + DCHECK(window == m_hWnd); + return true; + } + + automation_client_->SetParentWindow(window); + + SubscribeToFocusEvents(); + + if (force_full_page_plugin_) { + // By default full page mode is only enabled when the plugin is loaded off + // a separate file, i.e. it is the primary content in the window. Even if + // we specify the width/height attributes for the plugin as 100% each, FF + // instantiates the plugin passing in a width/height of 100px each. To + // workaround this we resize the plugin window passed in by FF to the size + // of its parent. + HWND plugin_parent_window = ::GetParent(window); + RECT plugin_parent_rect = {0}; + ::GetClientRect(plugin_parent_window, &plugin_parent_rect); + ::SetWindowPos(window, NULL, plugin_parent_rect.left, + plugin_parent_rect.top, + plugin_parent_rect.right - plugin_parent_rect.left, + plugin_parent_rect.bottom - plugin_parent_rect.top, 0); + } + + // Subclass the browser's plugin window here. + if (SubclassWindow(window)) { + DWORD new_style_flags = WS_CLIPCHILDREN; + ModifyStyle(0, new_style_flags, 0); + + if (ready_state_ < READYSTATE_INTERACTIVE) { + SetReadyState(READYSTATE_INTERACTIVE); + } + } + + return true; +} + +void ChromeFrameNPAPI::Print(NPPrint* print_info) { + if (!print_info) { + NOTREACHED(); + return; + } + + // We dont support full tab mode yet. + if (print_info->mode != NP_EMBED) { + NOTREACHED(); + return; + } + + NPWindow window = print_info->print.embedPrint.window; + + RECT print_bounds = {0}; + print_bounds.left = window.x; + print_bounds.top = window.y; + print_bounds.right = window.x + window.width; + print_bounds.bottom = window.x + window.height; + + automation_client_->Print( + reinterpret_cast<HDC>(print_info->print.embedPrint.platformPrint), + print_bounds); +} + +void ChromeFrameNPAPI::UrlNotify(const char* url, NPReason reason, + void* notify_data) { + if (enabled_popups_) { + // We have opened the URL so tell the browser to restore popup settings + enabled_popups_ = false; + npapi::PopPopupsEnabledState(instance_); + } + + // It is now safe to release the additional reference on the request + NPAPIUrlRequest* request = RequestFromNotifyData(notify_data); + if (request) { + request->Stop(); + request->Release(); + } +} + +void ChromeFrameNPAPI::OnAcceleratorPressed(int tab_handle, + const MSG& accel_message) { + DLOG(INFO) << __FUNCTION__ << " msg:" + << StringPrintf("0x%04X", accel_message.message) << " key:" + << accel_message.wParam; + + // The host browser does call TranslateMessage on messages like WM_KEYDOWN + // WM_KEYUP, etc, which will result in messages like WM_CHAR, WM_SYSCHAR, etc + // being posted to the message queue. We don't post these messages here to + // avoid these messages from getting handled twice. + if (accel_message.message != WM_CHAR && + accel_message.message != WM_DEADCHAR && + accel_message.message != WM_SYSCHAR && + accel_message.message != WM_SYSDEADCHAR) { + // A very primitive way to handle keystrokes. + // TODO(tommi): When we've implemented a way for chrome to + // know when keystrokes are handled (deterministically) on that side, + // then this function should get called and not otherwise. + ::PostMessage(::GetParent(m_hWnd), accel_message.message, + accel_message.wParam, accel_message.lParam); + } + + if (automation_client_.get()) { + TabProxy* tab = automation_client_->tab(); + if (tab) { + tab->ProcessUnhandledAccelerator(accel_message); + } + } +} + +void ChromeFrameNPAPI::OnTabbedOut(int tab_handle, bool reverse) { + DLOG(INFO) << __FUNCTION__; + + ignore_setfocus_ = true; + HWND parent = ::GetParent(m_hWnd); + ::SetFocus(parent); + + INPUT input = {0}; + input.type = INPUT_KEYBOARD; + input.ki.wVk = VK_TAB; + SendInput(1, &input, sizeof(input)); + input.ki.dwFlags = KEYEVENTF_KEYUP; + SendInput(1, &input, sizeof(input)); + + ignore_setfocus_ = false; +} + +void ChromeFrameNPAPI::OnOpenURL(int tab_handle, + const GURL& url, int open_disposition) { + std::string target; + switch (open_disposition) { + case NEW_FOREGROUND_TAB: + target = "_blank"; + break; + case NEW_BACKGROUND_TAB: + target = "_blank"; + break; + case NEW_WINDOW: + target = "_new"; + break; + default: + break; + } + + // Tell the browser to temporarily allow popups + enabled_popups_ = true; + npapi::PushPopupsEnabledState(instance_, TRUE); + npapi::GetURLNotify(instance_, url.spec().c_str(), target.c_str(), NULL); +} + +void ChromeFrameNPAPI::OnRequestStart(int tab_handle, int request_id, + const IPC::AutomationURLRequest& request) { + scoped_refptr<NPAPIUrlRequest> new_request(new NPAPIUrlRequest(instance_)); + DCHECK(new_request); + if (new_request->Initialize(automation_client_.get(), tab_handle, + request_id, request.url, request.method, + request.referrer, request.extra_request_headers, + request.upload_data.get(), true)) { + if (new_request->Start()) { + // Keep additional reference on request for NPSTREAM + // This will be released in NPP_UrlNotify + new_request->AddRef(); + } + } +} + +void ChromeFrameNPAPI::OnRequestRead(int tab_handle, int request_id, + int bytes_to_read) { + automation_client_->ReadRequest(request_id, bytes_to_read); +} + +void ChromeFrameNPAPI::OnRequestEnd(int tab_handle, int request_id, + const URLRequestStatus& status) { + automation_client_->RemoveRequest(request_id, status.status(), true); +} + +void ChromeFrameNPAPI::OnSetCookieAsync(int tab_handle, const GURL& url, + const std::string& cookie) { + // Use the newer NPAPI way if available + if (npapi::VersionMinor() >= NPVERS_HAS_URL_AND_AUTH_INFO) { + npapi::SetValueForURL(instance_, NPNURLVCookie, url.spec().c_str(), + cookie.c_str(), cookie.length()); + } else if (url == GURL(document_url_)) { + std::string script = "javascript:document.cookie="; + script.append(cookie); + script.append(1, ';'); + ExecuteScript(script, NULL); + } else { + // Third party cookie, use nsICookieService to set the cookie. + NOTREACHED(); + } +} + +bool ChromeFrameNPAPI::HasMethod(NPObject* obj, NPIdentifier name) { + for (int i = 0; i < arraysize(plugin_methods_); ++i) { + if (name == plugin_method_identifiers_[i]) + return true; + } + + return false; +} + +bool ChromeFrameNPAPI::Invoke(NPObject* header, NPIdentifier name, + const NPVariant* args, uint32_t arg_count, + NPVariant* result) { + ChromeFrameNPAPI* plugin_instance = ChromeFrameInstanceFromNPObject(header); + if (!plugin_instance && (plugin_instance->automation_client_.get())) + return false; + + bool success = false; + for (int i = 0; i < arraysize(plugin_methods_); ++i) { + if (name == plugin_method_identifiers_[i]) { + PluginMethod method = plugin_methods_[i]; + success = (plugin_instance->*method)(header, args, arg_count, result); + break; + } + } + + return success; +} + +void ChromeFrameNPAPI::InitializeIdentifiers() { + npapi::GetStringIdentifiers(plugin_method_identifier_names_, + arraysize(plugin_methods_), + plugin_method_identifiers_); + + npapi::GetStringIdentifiers(plugin_property_identifier_names_, + PLUGIN_PROPERTY_COUNT, + plugin_property_identifiers_); +} + +NPObject* ChromeFrameNPAPI::AllocateObject(NPP instance, NPClass* class_name) { + static bool identifiers_initialized = false; + + ChromeFrameNPObject* plugin_object = new ChromeFrameNPObject(); + DCHECK(plugin_object != NULL); + + plugin_object->chrome_frame_plugin_instance = new ChromeFrameNPAPI(); + DCHECK(plugin_object->chrome_frame_plugin_instance != NULL); + + plugin_object->npp = NULL; + + COMPILE_ASSERT(arraysize(plugin_method_identifiers_) == + arraysize(plugin_method_identifier_names_), + method_count_mismatch); + + COMPILE_ASSERT(arraysize(plugin_method_identifiers_) == + arraysize(plugin_methods_), + method_count_mismatch); + + if (!identifiers_initialized) { + InitializeIdentifiers(); + identifiers_initialized = true; + } + + return reinterpret_cast<NPObject*>(plugin_object); +} + +void ChromeFrameNPAPI::DeallocateObject(NPObject* header) { + ChromeFrameNPObject* plugin_object = + reinterpret_cast<ChromeFrameNPObject*>(header); + DCHECK(plugin_object != NULL); + + if (plugin_object) { + delete plugin_object->chrome_frame_plugin_instance; + delete plugin_object; + } +} + +void ChromeFrameNPAPI::Invalidate(NPObject* header) { + DCHECK(header); + ChromeFrameNPObject* plugin_object = + reinterpret_cast<ChromeFrameNPObject*>(header); + if (plugin_object) { + DCHECK(plugin_object->chrome_frame_plugin_instance); + plugin_object->chrome_frame_plugin_instance->Uninitialize(); + } +} + +ChromeFrameNPAPI* ChromeFrameNPAPI::ChromeFrameInstanceFromPluginInstance( + NPP instance) { + if ((instance == NULL) || (instance->pdata == NULL)) { + NOTREACHED(); + return NULL; + } + + return ChromeFrameInstanceFromNPObject(instance->pdata); +} + +ChromeFrameNPAPI* ChromeFrameNPAPI::ChromeFrameInstanceFromNPObject( + void* object) { + ChromeFrameNPObject* plugin_object = + reinterpret_cast<ChromeFrameNPObject*>(object); + if (!plugin_object) { + NOTREACHED(); + return NULL; + } + + DCHECK(plugin_object->chrome_frame_plugin_instance); + return plugin_object->chrome_frame_plugin_instance; +} + +bool ChromeFrameNPAPI::HasProperty(NPObject* obj, NPIdentifier name) { + for (int i = 0; i < PLUGIN_PROPERTY_COUNT; ++i) { + if (name == plugin_property_identifiers_[i]) + return true; + } + return false; +} + +bool ChromeFrameNPAPI::GetProperty(NPIdentifier name, + NPVariant* variant) { + if (name == plugin_property_identifiers_[PLUGIN_PROPERTY_ONERROR]) { + if (onerror_handler_) { + variant->type = NPVariantType_Object; + variant->value.objectValue = onerror_handler_.Copy(); + return true; + } + } else if (name == plugin_property_identifiers_[PLUGIN_PROPERTY_ONMESSAGE]) { + if (onmessage_handler_) { + variant->type = NPVariantType_Object; + variant->value.objectValue = onmessage_handler_.Copy(); + return true; + } + } else if (name == + plugin_property_identifiers_[PLUGIN_PROPERTY_ONPRIVATEMESSAGE]) { + if (!is_privileged_) { + DLOG(WARNING) << "Attempt to read onprivatemessage property while not " + "privileged"; + } else { + if (onprivatemessage_handler_) { + variant->type = NPVariantType_Object; + variant->value.objectValue = + onprivatemessage_handler_.Copy(); + return true; + } + } + } else if (name == plugin_property_identifiers_[PLUGIN_PROPERTY_SRC]) { + AllocateStringVariant(src_, variant); + return true; + } else if (name == plugin_property_identifiers_[PLUGIN_PROPERTY_VERSION]) { + const std::wstring version = + automation_client_->GetVersion(); + AllocateStringVariant(WideToUTF8(version), variant); + return true; + } else if (name == plugin_property_identifiers_[PLUGIN_PROPERTY_READYSTATE]) { + INT32_TO_NPVARIANT(ready_state_, *variant); + return true; + } else if (name == + plugin_property_identifiers_[PLUGIN_PROPERTY_USECHROMENETWORK]) { + BOOLEAN_TO_NPVARIANT(automation_client_->use_chrome_network(), *variant); + return true; + } + + return false; +} + +bool ChromeFrameNPAPI::GetProperty(NPObject* object, NPIdentifier name, + NPVariant* variant) { + if (!object || !variant) { + NOTREACHED(); + return false; + } + + ChromeFrameNPAPI* plugin_instance = ChromeFrameInstanceFromNPObject(object); + if (!plugin_instance) { + NOTREACHED(); + return false; + } + + return plugin_instance->GetProperty(name, variant); +} + +bool ChromeFrameNPAPI::SetProperty(NPIdentifier name, + const NPVariant* variant) { + if (NPVARIANT_IS_OBJECT(*variant)) { + if (name == plugin_property_identifiers_[PLUGIN_PROPERTY_ONERROR]) { + onerror_handler_.Free(); + onerror_handler_ = variant->value.objectValue; + return true; + } else if ( + name == plugin_property_identifiers_[PLUGIN_PROPERTY_ONMESSAGE]) { + onmessage_handler_.Free(); + onmessage_handler_ = variant->value.objectValue; + return true; + } else if (name == + plugin_property_identifiers_[PLUGIN_PROPERTY_ONPRIVATEMESSAGE]) { + if (!is_privileged_) { + DLOG(WARNING) << "Attempt to set onprivatemessage while not privileged"; + } else { + onprivatemessage_handler_.Free(); + onprivatemessage_handler_ = variant->value.objectValue; + return true; + } + } + } else if (NPVARIANT_IS_STRING(*variant) || NPVARIANT_IS_NULL(*variant)) { + if (name == plugin_property_identifiers_[PLUGIN_PROPERTY_SRC]) { + return NavigateToURL(variant, 1, NULL); + } + } else if (NPVARIANT_IS_BOOLEAN(*variant)) { + if (name == + plugin_property_identifiers_[PLUGIN_PROPERTY_USECHROMENETWORK]) { + automation_client_->set_use_chrome_network( + NPVARIANT_TO_BOOLEAN(*variant)); + } + } + + return false; +} + +bool ChromeFrameNPAPI::SetProperty(NPObject* object, NPIdentifier name, + const NPVariant* variant) { + if (!object || !variant) { + DLOG(ERROR) << "Cannot set property: " << npapi::StringFromIdentifier(name); + return false; + } + + ChromeFrameNPAPI* plugin_instance = ChromeFrameInstanceFromNPObject(object); + if (!plugin_instance) { + NOTREACHED(); + return false; + } + + return plugin_instance->SetProperty(name, variant); +} + +void ChromeFrameNPAPI::OnFocus() { + DLOG(INFO) << __FUNCTION__; + PostMessage(WM_SETFOCUS, 0, 0); +} + +void ChromeFrameNPAPI::OnEvent(const char* event_name) { + DCHECK(event_name); + DLOG(INFO) << event_name; + + if (lstrcmpiA(event_name, "focus") == 0) { + OnFocus(); + } else if (lstrcmpiA(event_name, "blur") == 0) { + OnBlur(); + } else { + NOTREACHED() << event_name; + } +} + +LRESULT CALLBACK ChromeFrameNPAPI::DropKillFocusHook(int code, WPARAM wparam, + LPARAM lparam) { + LRESULT ret = 0; + CWPSTRUCT* wp = reinterpret_cast<CWPSTRUCT*>(lparam); + if ((code < 0) || (wp->message != WM_KILLFOCUS)) + ret = ::CallNextHookEx(NULL, code, wparam, lparam); + + return ret; +} + +LRESULT ChromeFrameNPAPI::OnSetFocus(UINT message, WPARAM wparam, + LPARAM lparam, BOOL& handled) { // NO_LINT + // Opera has a WH_CALLWNDPROC hook that handles WM_KILLFOCUS and + // prevents us from setting the focus to the tab. + // To work around that, we set a temporary hook here that does nothing + // (not even call other hooks) when it sees WM_KILLFOCUS. + HHOOK hook = NULL; + hook = ::SetWindowsHookEx(WH_CALLWNDPROC, DropKillFocusHook, NULL, + ::GetCurrentThreadId()); + // Since we chain message maps, make sure we are not calling base class + // twice for WM_SETFOCUS. + BOOL handled_by_base = TRUE; + LRESULT ret = Base::OnSetFocus(message, wparam, lparam, handled_by_base); + if (hook) + ::UnhookWindowsHookEx(hook); + + return ret; +} + +void ChromeFrameNPAPI::OnBlur() { + DLOG(INFO) << __FUNCTION__; +} + +void ChromeFrameNPAPI::OnLoad(int, const GURL& gurl) { + DLOG(INFO) << "Firing onload"; + FireEvent("load", gurl.spec()); +} + +void ChromeFrameNPAPI::OnLoadFailed(int error_code, const std::string& url) { + FireEvent("loaderror", url); + + ScopedNpVariant result; + InvokeDefault(onerror_handler_, url, &result); +} + +void ChromeFrameNPAPI::OnMessageFromChromeFrame(int tab_handle, + const std::string& message, + const std::string& origin, + const std::string& target) { + bool private_message = false; + if (target.compare("*") != 0) { + if (is_privileged_) { + private_message = true; + } else { + if (!HaveSameOrigin(target, document_url_)) { + DLOG(WARNING) << "Dropping posted message since target doesn't match " + "the current document's origin. target=" << target; + return; + } + } + } + + // Create a MessageEvent object that contains the message and origin + // as well as supporting other MessageEvent (see the HTML5 spec) properties. + // Then call the onmessage handler. + ScopedNpObject<NPObject> event; + bool ok = CreateMessageEvent(false, true, message, origin, event.Receive()); + if (ok) { + // Don't call FireEvent here (or we'll have an event wrapped by an event). + DispatchEvent(event); + + ScopedNpVariant result; + NPVariant params[2]; + OBJECT_TO_NPVARIANT(event, params[0]); + bool invoke = false; + if (private_message) { + DCHECK(is_privileged_); + STRINGN_TO_NPVARIANT(target.c_str(), target.length(), params[1]); + invoke = InvokeDefault(onprivatemessage_handler_, + arraysize(params), + params, + &result); + } else { + invoke = InvokeDefault(onmessage_handler_, params[0], &result); + } + DLOG_IF(WARNING, !invoke) << "InvokeDefault failed"; + } else { + NOTREACHED() << "CreateMessageEvent"; + } +} + +void ChromeFrameNPAPI::OnAutomationServerReady() { + Base::OnAutomationServerReady(); + + std::string proxy_settings; + bool has_prefs = pref_service_->Initialize(instance_, + automation_client_.get()); + if (has_prefs && pref_service_->GetProxyValueJSONString(&proxy_settings)) { + automation_client_->SetProxySettings(proxy_settings); + } + + if (!src_.empty()) { + if (!automation_client_->InitiateNavigation(src_)) { + DLOG(ERROR) << "Failed to navigate to: " << src_; + src_.clear(); + } + } + + SetReadyState(READYSTATE_COMPLETE); +} + +void ChromeFrameNPAPI::OnAutomationServerLaunchFailed( + AutomationLaunchResult reason, const std::string& server_version) { + SetReadyState(READYSTATE_UNINITIALIZED); + + if (reason == AUTOMATION_VERSION_MISMATCH) { + DisplayVersionMismatchWarning(m_hWnd, server_version); + } +} + +bool ChromeFrameNPAPI::InvokeDefault(NPObject* object, + unsigned param_count, + const NPVariant* params, + NPVariant* result) { + if (!object) + return false; + + bool ret = npapi::InvokeDefault(instance_, object, params, param_count, + result); + // InvokeDefault can return false in FF even though we do see the call + // go through. It's not clear to me what the circumstances are, so + // we log it as a warning while tracking it down. + DLOG_IF(WARNING, !ret) << "npapi::InvokeDefault failed"; + return ret; +} + +bool ChromeFrameNPAPI::InvokeDefault(NPObject* object, const std::string& param, + NPVariant* result) { + NPVariant arg; + STRINGN_TO_NPVARIANT(param.c_str(), param.length(), arg); + return InvokeDefault(object, arg, result); +} + +bool ChromeFrameNPAPI::InvokeDefault(NPObject* object, const NPVariant& param, + NPVariant* result) { + return InvokeDefault(object, 1, ¶m, result); +} + +bool ChromeFrameNPAPI::CreateEvent(const std::string& type, bool bubbles, + bool cancelable, NPObject** basic_event) { + DCHECK(basic_event); + NPObject* window = GetWindowObject(); + if (!window) { + // Can fail if the browser is closing (seen in Opera). + return false; + } + + const char* identifier_names[] = { + "document", + "createEvent", + "initEvent", + }; + + NPIdentifier identifiers[arraysize(identifier_names)]; + npapi::GetStringIdentifiers(identifier_names, arraysize(identifier_names), + identifiers); + + // Fetch the document object from the window. + ScopedNpVariant document; + bool ok = npapi::GetProperty(instance_, window, identifiers[0], &document); + if (!ok) { + // This could happen if the page is being unloaded. + DLOG(WARNING) << "Failed to fetch the document object"; + return false; + } + + bool success = false; + if (ok && NPVARIANT_IS_OBJECT(document)) { + // Call document.createEvent("Event") to create a basic event object. + NPVariant event_type; + STRINGN_TO_NPVARIANT("Event", sizeof("Event") - 1, event_type); + ScopedNpVariant result; + success = npapi::Invoke(instance_, NPVARIANT_TO_OBJECT(document), + identifiers[1], &event_type, 1, &result); + if (!NPVARIANT_IS_OBJECT(result)) { + DLOG(WARNING) << "Failed to invoke createEvent"; + success = false; + } else { + NPVariant init_args[3]; + STRINGN_TO_NPVARIANT(type.c_str(), type.length(), init_args[0]); + BOOLEAN_TO_NPVARIANT(bubbles, init_args[1]); + BOOLEAN_TO_NPVARIANT(cancelable, init_args[2]); + + // Now initialize the event object by calling + // event.initEvent(type, bubbles, cancelable); + ScopedNpVariant init_results; + ok = npapi::Invoke(instance_, NPVARIANT_TO_OBJECT(result), identifiers[2], + init_args, arraysize(init_args), &init_results); + if (ok) { + success = true; + // Finally, pass the ownership to the caller. + *basic_event = NPVARIANT_TO_OBJECT(result); + VOID_TO_NPVARIANT(result); // Prevent the object from being released. + } else { + DLOG(ERROR) << "initEvent failed"; + success = false; + } + } + } + + return success; +} + +bool ChromeFrameNPAPI::CreateMessageEvent(bool bubbles, bool cancelable, + const std::string& data, + const std::string& origin, + NPObject** message_event) { + DCHECK(message_event); + ScopedNpObject<NPObject> event; + bool ok = CreateEvent("message", false, true, event.Receive()); + if (ok) { + typedef enum { + DATA, + ORIGIN, + LAST_EVENT_ID, + SOURCE, + MESSAGE_PORT, + IDENTIFIER_COUNT, // Must be last. + } StringIdentifiers; + + static NPIdentifier identifiers[IDENTIFIER_COUNT] = {0}; + if (!identifiers[0]) { + const NPUTF8* identifier_names[] = { + "data", + "origin", + "lastEventId", + "source", + "messagePort", + }; + COMPILE_ASSERT(arraysize(identifier_names) == arraysize(identifiers), + mismatched_array_size); + npapi::GetStringIdentifiers(identifier_names, IDENTIFIER_COUNT, + identifiers); + } + + NPVariant arg; + STRINGN_TO_NPVARIANT(data.c_str(), data.length(), arg); + npapi::SetProperty(instance_, event, identifiers[DATA], &arg); + STRINGN_TO_NPVARIANT(origin.c_str(), origin.length(), arg); + npapi::SetProperty(instance_, event, identifiers[ORIGIN], &arg); + STRINGN_TO_NPVARIANT("", 0, arg); + npapi::SetProperty(instance_, event, identifiers[LAST_EVENT_ID], &arg); + NULL_TO_NPVARIANT(arg); + npapi::SetProperty(instance_, event, identifiers[SOURCE], &arg); + npapi::SetProperty(instance_, event, identifiers[MESSAGE_PORT], &arg); + *message_event = event.Detach(); + } + + return ok; +} + + +void ChromeFrameNPAPI::DispatchEvent(NPObject* event) { + DCHECK(event != NULL); + + ScopedNpObject<NPObject> embed; + npapi::GetValue(instance_, NPNVPluginElementNPObject, &embed); + if (embed != NULL) { + NPVariant param; + OBJECT_TO_NPVARIANT(event, param); + ScopedNpVariant result; + bool invoke = npapi::Invoke(instance_, embed, + npapi::GetStringIdentifier("dispatchEvent"), ¶m, 1, &result); + DLOG_IF(WARNING, !invoke) << "dispatchEvent failed"; + } else { + NOTREACHED() << "NPNVPluginElementNPObject"; + } +} + +bool ChromeFrameNPAPI::ExecuteScript(const std::string& script, + NPVariant* result) { + NPObject* window = GetWindowObject(); + if (!window) { + NOTREACHED(); + return false; + } + + NPString script_for_execution; + script_for_execution.UTF8Characters = script.c_str(); + script_for_execution.UTF8Length = script.length(); + + return npapi::Evaluate(instance_, window, &script_for_execution, result); +} + +NPObject* ChromeFrameNPAPI::JavascriptToNPObject(const std::string& script) { + // Convert the passed in script to an invocable NPObject + // To achieve this we save away the function in a dummy window property + // which is then read to get the script object representing the function. + + std::string script_code = + "javascript:window.__cf_get_function_object ="; + + // If we are able to look up the name in the javascript namespace, then it + // means that the caller passed in a function name. Convert the function + // name to a NPObject we can invoke on. + if (IsValidJavascriptFunction(script)) { + script_code += script; + } else { + script_code += "new Function(\""; + script_code += script; + script_code += "\");"; + } + + NPVariant result; + if (!ExecuteScript(script_code, &result)) { + NOTREACHED(); + return NULL; + } + + DCHECK(result.type == NPVariantType_Object); + DCHECK(result.value.objectValue != NULL); + return result.value.objectValue; +} + +bool ChromeFrameNPAPI::IsValidJavascriptFunction(const std::string& script) { + std::string script_code = "javascript:window['"; + script_code += script; + script_code += "'];"; + + ScopedNpVariant result; + if (!ExecuteScript(script_code, &result)) { + NOTREACHED(); + return NULL; + } + + return result.type == NPVariantType_Object; +} + +bool ChromeFrameNPAPI::NavigateToURL(const NPVariant* args, uint32_t arg_count, + NPVariant* result) { + // Note that 'result' might be NULL. + if (arg_count != 1 || !(NPVARIANT_IS_STRING(args[0]) || + NPVARIANT_IS_NULL(args[0]))) { + NOTREACHED(); + return false; + } + + if (ready_state_ == READYSTATE_UNINITIALIZED) { + // Error(L"Chrome Frame failed to initialize."); + // TODO(tommi): call NPN_SetException + DLOG(WARNING) << "NavigateToURL called after failed initialization"; + return false; + } + + std::string url("about:blank"); + + if (!NPVARIANT_IS_NULL(args[0])) { + const NPString& str = args[0].value.stringValue; + if (str.UTF8Length) { + url.assign(std::string(str.UTF8Characters, str.UTF8Length)); + } + } + DLOG(WARNING) << __FUNCTION__ << " " << url; + std::string full_url = ResolveURL(GetDocumentUrl(), url); + if (full_url.empty()) + return false; + + src_ = full_url; + // Navigate only if we completed initialization i.e. proxy is set etc. + if (ready_state_ == READYSTATE_COMPLETE) { + if (!automation_client_->InitiateNavigation(full_url)) { + // TODO(tommi): call NPN_SetException. + src_.clear(); + return false; + } + } + return true; +} + +bool ChromeFrameNPAPI::postMessage(NPObject* npobject, const NPVariant* args, + uint32_t arg_count, NPVariant* result) { + if (arg_count < 1 || arg_count > 2 || !NPVARIANT_IS_STRING(args[0])) { + NOTREACHED(); + return false; + } + + const NPString& str = args[0].value.stringValue; + std::string message(str.UTF8Characters, str.UTF8Length); + std::string target; + if (arg_count == 2 && NPVARIANT_IS_STRING(args[1])) { + const NPString& str = args[1].value.stringValue; + target.assign(str.UTF8Characters, str.UTF8Length); + if (target.compare("*") != 0) { + GURL resolved(target); + if (!resolved.is_valid()) { + npapi::SetException(npobject, + "Unable to parse the specified target URL."); + return false; + } + target = resolved.spec(); + } + } else { + target = "*"; + } + + GURL url(GURL(document_url_).GetOrigin()); + std::string origin(url.is_empty() ? "null" : url.spec()); + + automation_client_->ForwardMessageFromExternalHost(message, origin, target); + + return true; +} + +bool ChromeFrameNPAPI::postPrivateMessage(NPObject* npobject, + const NPVariant* args, + uint32_t arg_count, + NPVariant* result) { + if (!is_privileged_) { + DLOG(WARNING) << "postPrivateMessage invoked in non-privileged mode"; + return false; + } + + if (arg_count != 3 || !NPVARIANT_IS_STRING(args[0]) || + !NPVARIANT_IS_STRING(args[1]) || !NPVARIANT_IS_STRING(args[2])) { + NOTREACHED(); + return false; + } + + const NPString& message_str = args[0].value.stringValue; + const NPString& origin_str = args[1].value.stringValue; + const NPString& target_str = args[2].value.stringValue; + std::string message(message_str.UTF8Characters, message_str.UTF8Length); + std::string origin(origin_str.UTF8Characters, origin_str.UTF8Length); + std::string target(target_str.UTF8Characters, target_str.UTF8Length); + + automation_client_->ForwardMessageFromExternalHost(message, origin, target); + + return true; +} + +void ChromeFrameNPAPI::FireEvent(const std::string& event_type, + const std::string& data) { + NPVariant arg; + STRINGN_TO_NPVARIANT(data.c_str(), data.length(), arg); + FireEvent(event_type, arg); +} + +void ChromeFrameNPAPI::FireEvent(const std::string& event_type, + const NPVariant& data) { + // Check that we're not bundling an event inside an event. + // Right now we're only expecting simple types for the data argument. + DCHECK(NPVARIANT_IS_OBJECT(data) == false); + + ScopedNpObject<NPObject> ev; + CreateEvent(event_type, false, false, ev.Receive()); + if (ev) { + // Add the 'data' member to the event. + bool set = npapi::SetProperty(instance_, ev, + npapi::GetStringIdentifier("data"), const_cast<NPVariant*>(&data)); + DCHECK(set); + DispatchEvent(ev); + } +} + +NpProxyService* ChromeFrameNPAPI::CreatePrefService() { + return new NpProxyService; +} + +NPObject* ChromeFrameNPAPI::GetWindowObject() const { + if (!window_object_.get()) { + NPError ret = npapi::GetValue(instance_, NPNVWindowNPObject, + window_object_.Receive()); + DLOG_IF(ERROR, ret != NPERR_NO_ERROR) << "NPNVWindowNPObject failed"; + } + return window_object_; +} + +bool ChromeFrameNPAPI::GetBrowserIncognitoMode() { + bool incognito_mode = false; + + // Check disabled for Opera due to bug: http://b/issue?id=1815494 + if (GetBrowserType() != BROWSER_OPERA) { + // Check whether host browser is in private mode; + NPBool private_mode = FALSE; + NPError err = npapi::GetValue(instance_, + NPNVprivateModeBool, + &private_mode); + if (err == NPERR_NO_ERROR && private_mode) { + incognito_mode = true; + } + } else { + DLOG(WARNING) << "Not checking for private mode in Opera"; + } + + return incognito_mode; +} + +NPAPIUrlRequest* ChromeFrameNPAPI::ValidateRequest( + NPP instance, void* notify_data) { + ChromeFrameNPAPI* plugin_instance = + ChromeFrameNPAPI::ChromeFrameInstanceFromPluginInstance(instance); + if (plugin_instance) { + return plugin_instance->RequestFromNotifyData(notify_data); + } + + return NULL; +} + +NPAPIUrlRequest* ChromeFrameNPAPI::RequestFromNotifyData( + void* notify_data) const { + NPAPIUrlRequest* request = reinterpret_cast<NPAPIUrlRequest*>(notify_data); + DCHECK(request ? automation_client_->IsValidRequest(request) : 1); + return request; +} + +bool ChromeFrameNPAPI::HandleContextMenuCommand(UINT cmd) { + if (cmd == IDC_ABOUT_CHROME_FRAME) { + // TODO: implement "About Chrome Frame" + } + return false; +} |