// Copyright (c) 2011 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_split.h" #include "base/string_util.h" #include "base/stringprintf.h" #include "base/utf_string_conversions.h" #include "chrome/test/automation/tab_proxy.h" #include "chrome_frame/np_utils.h" #include "chrome_frame/utils.h" MessageLoop* ChromeFrameNPAPI::message_loop_ = NULL; int ChromeFrameNPAPI::instance_count_ = 0; 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", "usechromenetwork", "onclose", }; const NPUTF8* ChromeFrameNPAPI::plugin_method_identifier_names_[] = { "postMessage", }; ChromeFrameNPAPI::PluginMethod ChromeFrameNPAPI::plugin_methods_[] = { &ChromeFrameNPAPI::postMessage, }; 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"; static const char kPluginOnCloseAttribute[] = "onclose"; // 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), navigate_after_initialization_(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 ChromeFrameNPAPI::GetLocation() { // Note that GetWindowObject() will cache the window object here. return np_utils::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_++; 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], kPluginOnCloseAttribute)) { onclose_handler_ = JavascriptToNPObject(argv[i]); } } std::wstring profile_name(GetHostProcessName(false)); std::wstring extra_arguments; static const wchar_t kHandleTopLevelRequests[] = L"HandleTopLevelRequests"; bool top_level_requests = GetConfigBool(true, kHandleTopLevelRequests); automation_client_->set_handle_top_level_requests(top_level_requests); automation_client_->set_route_all_top_level_navigations(true); // 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(), true, GURL(src_), GURL(), true); } void ChromeFrameNPAPI::Uninitialize() { if (ready_state_ != READYSTATE_UNINITIALIZED) SetReadyState(READYSTATE_UNINITIALIZED); window_object_.Free(); onerror_handler_.Free(); onmessage_handler_.Free(); onprivatemessage_handler_.Free(); onclose_handler_.Free(); Base::Uninitialize(); } void ChromeFrameNPAPI::OnFinalMessage(HWND window) { // The automation server should be gone by now. Uninitialize(); } 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); 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(const MSG& accel_message) { DVLOG(1) << __FUNCTION__ << " msg:" << base::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(bool reverse) { DVLOG(1) << __FUNCTION__; ignore_setfocus_ = true; // Previously we set the focus to our parent window before sending the // keyboard event but the browser architecture has changed, so we release // our focus first by calling .blur() and then tabbing to the // next element. ScopedNpObject object; npapi::GetValue(instance_, NPNVPluginElementNPObject, object.Receive()); if (object.get()) { ScopedNpVariant result; bool invoke = npapi::Invoke(instance_, object, npapi::GetStringIdentifier("blur"), NULL, 0, &result); DLOG_IF(WARNING, !invoke) << "blur failed"; } else { DLOG(WARNING) << "Failed to get the plugin element"; } 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(const GURL& url, const GURL& referrer, 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); } bool ChromeFrameNPAPI::HasMethod(NPObject* obj, NPIdentifier name) { for (int i = 0; i < arraysize(plugin_methods_); ++i) { if (name == plugin_method_identifiers_[i]) return true; } DLOG(INFO) << "Do not have method: " << npapi::StringFromIdentifier(name); 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_ONCLOSE]) { if (onclose_handler_) { variant->type = NPVariantType_Object; variant->value.objectValue = onclose_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_ONCLOSE]) { onclose_handler_.Free(); onclose_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); } 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::OnLoad(const GURL& gurl) { DVLOG(1) << "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(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 { DLOG(WARNING) << "CreateMessageEvent failed, probably exiting"; } } void ChromeFrameNPAPI::OnAutomationServerReady() { Base::OnAutomationServerReady(); if (navigate_after_initialization_ && !src_.empty()) { navigate_after_initialization_ = false; if (!automation_client_->InitiateNavigation(src_, GetDocumentUrl(), this)) { 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); // In IE, we don't display warnings for privileged CF instances because // there are 2 CFs created for each tab (so we decide on the CEEE side // whether to show a warning). In FF however, there is only one privileged // CF instance per Firefox window, so OK to show the warning there without // any further logic. if (reason == AUTOMATION_VERSION_MISMATCH) { UMA_HISTOGRAM_COUNTS("ChromeFrame.VersionMismatchDisplayed", 1); DisplayVersionMismatchWarning(m_hWnd, server_version); } } void ChromeFrameNPAPI::OnCloseTab() { std::string arg; FireEvent("close", arg); ScopedNpVariant result; InvokeDefault(onclose_handler_, arg, &result); } 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 { DLOG(WARNING) << "ChromeFrameNPAPI::DispatchEvent failed, probably exiting"; } } 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)); } } GURL document_url(GetDocumentUrl()); if (document_url.SchemeIsSecure()) { GURL source_url(url); if (!source_url.SchemeIsSecure()) { DLOG(WARNING) << __FUNCTION__ << " Prevnting navigation to HTTP url" " since the containing document is HTTPS. URL: " << source_url << " Document URL: " << document_url; return false; } } std::string full_url = ResolveURL(GetDocumentUrl(), url); 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(), this)) { // TODO(tommi): call NPN_SetException. src_.clear(); return false; } } else { navigate_after_initialization_ = true; } 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; } 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); } } NPObject* ChromeFrameNPAPI::GetWindowObject() const { if (!window_object_.get() && instance_) { 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://code.google.com/p/chromium/issues/detail?id=24287 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::PreProcessContextMenu(HMENU menu) { // TODO: Remove this overridden method once HandleContextMenuCommand // implements "About Chrome Frame" handling. if (!is_privileged()) { // Call base class (adds 'About' item). return ChromeFramePlugin::PreProcessContextMenu(menu); } return true; } bool ChromeFrameNPAPI::HandleContextMenuCommand( UINT cmd, const MiniContextMenuParams& 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); } void ChromeFrameNPAPI::URLRedirectNotify(const char* url, int status, void* notify_data) { DVLOG(1) << __FUNCTION__ << "Received redirect notification for url:" << url; // Inform chrome about the redirect and disallow the current redirect // attempt. url_fetcher_.UrlRedirectNotify(url, status, notify_data); npapi::URLRedirectResponse(instance_, notify_data, false); }