// 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", "installExtension", "loadExtension", "enableExtensionAutomation" }; ChromeFrameNPAPI::PluginMethod ChromeFrameNPAPI::plugin_methods_[] = { &ChromeFrameNPAPI::postMessage, &ChromeFrameNPAPI::postPrivateMessage, &ChromeFrameNPAPI::installExtension, &ChromeFrameNPAPI::loadExtension, &ChromeFrameNPAPI::enableExtensionAutomation, }; 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 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 privileged mode is enabled, this argument will be taken as a // comma-separated list of API function calls to automate. static const char kPluginChromeFunctionsAutomatedAttribute[] = "chrome_functions_automated"; // If chrome network stack is to be used static const char kPluginUseChromeNetwork[] = "usechromenetwork"; // 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], kPluginChromeFunctionsAutomatedAttribute)) { functions_enabled_.clear(); // SplitString writes one empty entry for blank strings, so we need this // to allow specifying zero automation of API functions. if (argv[i][0] != '\0') SplitString(argv[i], ',', &functions_enabled_); } 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); } // Setup Url fetcher. url_fetcher_.set_NPPInstance(instance_); url_fetcher_.set_frame_busting(!is_privileged_); automation_client_->SetUrlFetcher(&url_fetcher_); // TODO(joshia): Initialize navigation here and send proxy config as // part of LaunchSettings /* if (!src_.empty()) automation_client_->InitiateNavigation(src_, is_privileged_); 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(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(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_); } url_fetcher_.UrlNotify(url, reason, notify_data); } 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: case NEW_POPUP: 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::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(plugin_object); } void ChromeFrameNPAPI::DeallocateObject(NPObject* header) { ChromeFrameNPObject* plugin_object = reinterpret_cast(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(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(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(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 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_, GetDocumentUrl(), is_privileged_)) { 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 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 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, GetDocumentUrl(), is_privileged_)) { // 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) { // TODO(tommi) See if we can factor these checks out somehow. 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; } bool ChromeFrameNPAPI::installExtension(NPObject* npobject, const NPVariant* args, uint32_t arg_count, NPVariant* result) { if (arg_count > 2 || !NPVARIANT_IS_STRING(args[0]) || (arg_count == 2 && !NPVARIANT_IS_OBJECT(args[1]))) { NOTREACHED(); return false; } if (!is_privileged_) { DLOG(WARNING) << "installExtension invoked in non-privileged mode"; return false; } if (!automation_client_.get()) { DLOG(WARNING) << "installExtension invoked with no automaton client"; NOTREACHED(); return false; } const NPString& crx_path_str = args[0].value.stringValue; std::string crx_path_a(crx_path_str.UTF8Characters, crx_path_str.UTF8Length); FilePath::StringType crx_path_u(UTF8ToWide(crx_path_a)); FilePath crx_path(crx_path_u); NPObject* retained_function = npapi::RetainObject(args[1].value.objectValue); automation_client_->InstallExtension(crx_path, retained_function); // The response to this command will be returned in the OnExtensionInstalled // delegate callback function. return true; } void ChromeFrameNPAPI::OnExtensionInstalled( const FilePath& path, void* user_data, AutomationMsg_ExtensionResponseValues res) { ScopedNpVariant result; NPVariant param; INT32_TO_NPVARIANT(res, param); NPObject* func = reinterpret_cast(user_data); InvokeDefault(func, param, &result); npapi::ReleaseObject(func); } bool ChromeFrameNPAPI::loadExtension(NPObject* npobject, const NPVariant* args, uint32_t arg_count, NPVariant* result) { if (arg_count > 2 || !NPVARIANT_IS_STRING(args[0]) || (arg_count == 2 && !NPVARIANT_IS_OBJECT(args[1]))) { NOTREACHED(); return false; } if (!is_privileged_) { DLOG(WARNING) << "loadExtension invoked in non-privileged mode"; return false; } if (!automation_client_.get()) { DLOG(WARNING) << "loadExtension invoked with no automaton client"; NOTREACHED(); return false; } const NPString& path_str = args[0].value.stringValue; std::string path_a(path_str.UTF8Characters, path_str.UTF8Length); FilePath::StringType path_u(UTF8ToWide(path_a)); FilePath path(path_u); NPObject* retained_function = npapi::RetainObject(args[1].value.objectValue); automation_client_->LoadExpandedExtension(path, retained_function); // The response to this command will be returned in the OnExtensionInstalled // delegate callback function. return true; } bool ChromeFrameNPAPI::enableExtensionAutomation(NPObject* npobject, const NPVariant* args, uint32_t arg_count, NPVariant* result) { if (arg_count > 1 || (arg_count == 1 && !NPVARIANT_IS_STRING(args[0]))) { NOTREACHED(); return false; } if (!is_privileged_) { DLOG(WARNING) << "enableExtensionAutomation invoked in non-privileged mode"; return false; } if (!automation_client_.get()) { DLOG(WARNING) << "enableExtensionAutomation invoked with no automaton client"; NOTREACHED(); return false; } if (!automation_client_->tab()) { DLOG(WARNING) << "enableExtensionAutomation invoked with no hosted tab"; NOTREACHED(); return false; } // Empty by default e.g. if no arguments passed. std::vector functions; if (arg_count == 1) { const NPString& functions_str = args[0].value.stringValue; std::string functions_a(functions_str.UTF8Characters, functions_str.UTF8Length); // SplitString writes one empty entry for blank strings, so we need this // to allow specifying zero automation of API functions. if (functions_a[0] != '\0') SplitString(functions_a, ',', &functions); } automation_client_->tab()->SetEnableExtensionAutomation(functions); // This function returns no result. 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 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(&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; } bool ChromeFrameNPAPI::HandleContextMenuCommand(UINT cmd, const IPC::ContextMenuParams& params) { if (cmd == IDC_ABOUT_CHROME_FRAME) { // TODO: implement "About Chrome Frame" } return false; } NPError ChromeFrameNPAPI::NewStream(NPMIMEType type, NPStream* stream, NPBool seekable, uint16* stream_type) { return url_fetcher_.NewStream(type, stream, seekable, stream_type); } int32 ChromeFrameNPAPI::WriteReady(NPStream* stream) { return url_fetcher_.WriteReady(stream); } int32 ChromeFrameNPAPI::Write(NPStream* stream, int32 offset, int32 len, void* buffer) { return url_fetcher_.Write(stream, offset, len, buffer); } NPError ChromeFrameNPAPI::DestroyStream(NPStream* stream, NPReason reason) { return url_fetcher_.DestroyStream(stream, reason); }