text: // 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);
  
    }
  
    // 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<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(), !is_privileged_)) {
      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, 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_,
                                                  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, &param, 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"), &param, 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<NPObject*>(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<std::string> 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<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,
      const IPC::ContextMenuParams& params) {
    if (cmd == IDC_ABOUT_CHROME_FRAME) {
      // TODO: implement "About Chrome Frame"
    }
    return false;
  }
  