Home | History | Annotate | Download | only in plugin
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "remoting/host/plugin/host_script_object.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/json/json_reader.h"
      9 #include "base/json/json_writer.h"
     10 #include "base/message_loop/message_loop.h"
     11 #include "base/message_loop/message_loop_proxy.h"
     12 #include "base/strings/string_util.h"
     13 #include "base/strings/stringprintf.h"
     14 #include "base/strings/sys_string_conversions.h"
     15 #include "base/strings/utf_string_conversions.h"
     16 #include "base/threading/platform_thread.h"
     17 #include "base/values.h"
     18 #include "net/base/net_util.h"
     19 #include "remoting/base/auth_token_util.h"
     20 #include "remoting/base/auto_thread.h"
     21 #include "remoting/base/resources.h"
     22 #include "remoting/base/rsa_key_pair.h"
     23 #include "remoting/host/chromoting_host.h"
     24 #include "remoting/host/chromoting_host_context.h"
     25 #include "remoting/host/host_config.h"
     26 #include "remoting/host/host_event_logger.h"
     27 #include "remoting/host/host_secret.h"
     28 #include "remoting/host/host_status_observer.h"
     29 #include "remoting/host/it2me_desktop_environment.h"
     30 #include "remoting/host/pairing_registry_delegate.h"
     31 #include "remoting/host/pin_hash.h"
     32 #include "remoting/host/plugin/host_log_handler.h"
     33 #include "remoting/host/policy_hack/policy_watcher.h"
     34 #include "remoting/host/register_support_host_request.h"
     35 #include "remoting/host/service_urls.h"
     36 #include "remoting/host/session_manager_factory.h"
     37 #include "remoting/jingle_glue/network_settings.h"
     38 #include "remoting/jingle_glue/xmpp_signal_strategy.h"
     39 #include "remoting/protocol/it2me_host_authenticator_factory.h"
     40 #include "third_party/npapi/bindings/npruntime.h"
     41 
     42 namespace remoting {
     43 
     44 namespace {
     45 
     46 // This is used for tagging system event logs.
     47 const char kApplicationName[] = "chromoting";
     48 
     49 const char* kAttrNameAccessCode = "accessCode";
     50 const char* kAttrNameAccessCodeLifetime = "accessCodeLifetime";
     51 const char* kAttrNameClient = "client";
     52 const char* kAttrNameDaemonState = "daemonState";
     53 const char* kAttrNameState = "state";
     54 const char* kAttrNameLogDebugInfo = "logDebugInfo";
     55 const char* kAttrNameOnNatTraversalPolicyChanged =
     56     "onNatTraversalPolicyChanged";
     57 const char* kAttrNameOnStateChanged = "onStateChanged";
     58 const char* kAttrNameXmppServerAddress = "xmppServerAddress";
     59 const char* kAttrNameXmppServerUseTls = "xmppServerUseTls";
     60 const char* kAttrNameDirectoryBotJid = "directoryBotJid";
     61 const char* kAttrNameSupportedFeatures = "supportedFeatures";
     62 const char* kFuncNameConnect = "connect";
     63 const char* kFuncNameDisconnect = "disconnect";
     64 const char* kFuncNameLocalize = "localize";
     65 const char* kFuncNameClearPairedClients = "clearPairedClients";
     66 const char* kFuncNameDeletePairedClient = "deletePairedClient";
     67 const char* kFuncNameGetHostName = "getHostName";
     68 const char* kFuncNameGetPinHash = "getPinHash";
     69 const char* kFuncNameGenerateKeyPair = "generateKeyPair";
     70 const char* kFuncNameUpdateDaemonConfig = "updateDaemonConfig";
     71 const char* kFuncNameGetDaemonConfig = "getDaemonConfig";
     72 const char* kFuncNameGetDaemonVersion = "getDaemonVersion";
     73 const char* kFuncNameGetPairedClients = "getPairedClients";
     74 const char* kFuncNameGetUsageStatsConsent = "getUsageStatsConsent";
     75 const char* kFuncNameStartDaemon = "startDaemon";
     76 const char* kFuncNameStopDaemon = "stopDaemon";
     77 
     78 // States.
     79 const char* kAttrNameDisconnected = "DISCONNECTED";
     80 const char* kAttrNameStarting = "STARTING";
     81 const char* kAttrNameRequestedAccessCode = "REQUESTED_ACCESS_CODE";
     82 const char* kAttrNameReceivedAccessCode = "RECEIVED_ACCESS_CODE";
     83 const char* kAttrNameConnected = "CONNECTED";
     84 const char* kAttrNameDisconnecting = "DISCONNECTING";
     85 const char* kAttrNameError = "ERROR";
     86 const char* kAttrNameInvalidDomainError = "INVALID_DOMAIN_ERROR";
     87 
     88 const int kMaxLoginAttempts = 5;
     89 
     90 // Space separated list of features supported in addition to the base protocol.
     91 const char* kSupportedFeatures = "pairingRegistry";
     92 
     93 }  // namespace
     94 
     95 // Internal implementation of the plugin's It2Me host function.
     96 class HostNPScriptObject::It2MeImpl
     97     : public base::RefCountedThreadSafe<It2MeImpl>,
     98       public HostStatusObserver {
     99  public:
    100   It2MeImpl(
    101       scoped_ptr<ChromotingHostContext> context,
    102       scoped_refptr<base::SingleThreadTaskRunner> plugin_task_runner,
    103       base::WeakPtr<HostNPScriptObject> script_object,
    104       const XmppSignalStrategy::XmppServerConfig& xmpp_server_config,
    105       const std::string& directory_bot_jid);
    106 
    107   // Methods called by the script object, from the plugin thread.
    108 
    109   // Creates It2Me host structures and starts the host.
    110   void Connect(const std::string& uid,
    111                const std::string& auth_token,
    112                const std::string& auth_service);
    113 
    114   // Disconnects the host, ready for tear-down.
    115   // Also called internally, from the network thread.
    116   void Disconnect();
    117 
    118   // Request a NAT policy notification.
    119   void RequestNatPolicy();
    120 
    121   // remoting::HostStatusObserver implementation.
    122   virtual void OnAccessDenied(const std::string& jid) OVERRIDE;
    123   virtual void OnClientAuthenticated(const std::string& jid) OVERRIDE;
    124   virtual void OnClientDisconnected(const std::string& jid) OVERRIDE;
    125 
    126  private:
    127   friend class base::RefCountedThreadSafe<It2MeImpl>;
    128 
    129   virtual ~It2MeImpl();
    130 
    131   // Updates state of the host. Can be called only on the network thread.
    132   void SetState(State state);
    133 
    134   // Returns true if the host is connected.
    135   bool IsConnected() const;
    136 
    137   // Called by Connect() to check for policies and start connection process.
    138   void ReadPolicyAndConnect(const std::string& uid,
    139                             const std::string& auth_token,
    140                             const std::string& auth_service);
    141 
    142   // Called by ReadPolicyAndConnect once policies have been read.
    143   void FinishConnect(const std::string& uid,
    144                      const std::string& auth_token,
    145                      const std::string& auth_service);
    146 
    147   // Called when the support host registration completes.
    148   void OnReceivedSupportID(bool success,
    149                            const std::string& support_id,
    150                            const base::TimeDelta& lifetime);
    151 
    152   // Shuts down |host_| on the network thread and posts ShutdownOnUiThread()
    153   // to shut down UI thread resources.
    154   void ShutdownOnNetworkThread();
    155 
    156   // Shuts down |desktop_environment_factory_| and |policy_watcher_| on
    157   // the UI thread.
    158   void ShutdownOnUiThread();
    159 
    160   // Called when initial policies are read, and when they change.
    161   void OnPolicyUpdate(scoped_ptr<base::DictionaryValue> policies);
    162 
    163   // Handlers for NAT traversal and host domain policies.
    164   void UpdateNatPolicy(bool nat_traversal_enabled);
    165   void UpdateHostDomainPolicy(const std::string& host_domain);
    166 
    167   // Caller supplied fields.
    168   scoped_ptr<ChromotingHostContext> host_context_;
    169   scoped_refptr<base::SingleThreadTaskRunner> plugin_task_runner_;
    170   base::WeakPtr<HostNPScriptObject> script_object_;
    171   XmppSignalStrategy::XmppServerConfig xmpp_server_config_;
    172   std::string directory_bot_jid_;
    173 
    174   State state_;
    175 
    176   scoped_refptr<RsaKeyPair> host_key_pair_;
    177   scoped_ptr<SignalStrategy> signal_strategy_;
    178   scoped_ptr<RegisterSupportHostRequest> register_request_;
    179   scoped_ptr<LogToServer> log_to_server_;
    180   scoped_ptr<DesktopEnvironmentFactory> desktop_environment_factory_;
    181   scoped_ptr<HostEventLogger> host_event_logger_;
    182 
    183   scoped_ptr<ChromotingHost> host_;
    184   int failed_login_attempts_;
    185 
    186   scoped_ptr<policy_hack::PolicyWatcher> policy_watcher_;
    187 
    188   // Host the current nat traversal policy setting.
    189   bool nat_traversal_enabled_;
    190 
    191   // The host domain policy setting.
    192   std::string required_host_domain_;
    193 
    194   // Indicates whether or not a policy has ever been read. This is to ensure
    195   // that on startup, we do not accidentally start a connection before we have
    196   // queried our policy restrictions.
    197   bool policy_received_;
    198 
    199   // On startup, it is possible to have Connect() called before the policy read
    200   // is completed.  Rather than just failing, we thunk the connection call so
    201   // it can be executed after at least one successful policy read. This
    202   // variable contains the thunk if it is necessary.
    203   base::Closure pending_connect_;
    204 
    205   DISALLOW_COPY_AND_ASSIGN(It2MeImpl);
    206 };
    207 
    208 HostNPScriptObject::It2MeImpl::It2MeImpl(
    209     scoped_ptr<ChromotingHostContext> host_context,
    210     scoped_refptr<base::SingleThreadTaskRunner> plugin_task_runner,
    211     base::WeakPtr<HostNPScriptObject> script_object,
    212     const XmppSignalStrategy::XmppServerConfig& xmpp_server_config,
    213     const std::string& directory_bot_jid)
    214   : host_context_(host_context.Pass()),
    215     plugin_task_runner_(plugin_task_runner),
    216     script_object_(script_object),
    217     xmpp_server_config_(xmpp_server_config),
    218     directory_bot_jid_(directory_bot_jid),
    219     state_(kDisconnected),
    220     failed_login_attempts_(0),
    221     nat_traversal_enabled_(false),
    222     policy_received_(false) {
    223   DCHECK(plugin_task_runner_->BelongsToCurrentThread());
    224 }
    225 
    226 void HostNPScriptObject::It2MeImpl::Connect(
    227     const std::string& uid,
    228     const std::string& auth_token,
    229     const std::string& auth_service) {
    230   if (!host_context_->ui_task_runner()->BelongsToCurrentThread()) {
    231     DCHECK(plugin_task_runner_->BelongsToCurrentThread());
    232     host_context_->ui_task_runner()->PostTask(
    233         FROM_HERE,
    234         base::Bind(&It2MeImpl::Connect, this, uid, auth_token, auth_service));
    235     return;
    236   }
    237 
    238   desktop_environment_factory_.reset(new It2MeDesktopEnvironmentFactory(
    239       host_context_->network_task_runner(),
    240       host_context_->input_task_runner(),
    241       host_context_->ui_task_runner()));
    242 
    243   // Start monitoring configured policies.
    244   policy_watcher_.reset(
    245       policy_hack::PolicyWatcher::Create(host_context_->network_task_runner()));
    246   policy_watcher_->StartWatching(
    247       base::Bind(&It2MeImpl::OnPolicyUpdate, this));
    248 
    249   // Switch to the network thread to start the actual connection.
    250   host_context_->network_task_runner()->PostTask(
    251       FROM_HERE, base::Bind(
    252           &It2MeImpl::ReadPolicyAndConnect, this,
    253           uid, auth_token, auth_service));
    254 }
    255 
    256 void HostNPScriptObject::It2MeImpl::Disconnect() {
    257   if (!host_context_->network_task_runner()->BelongsToCurrentThread()) {
    258     DCHECK(plugin_task_runner_->BelongsToCurrentThread());
    259     host_context_->network_task_runner()->PostTask(
    260         FROM_HERE, base::Bind(&It2MeImpl::Disconnect, this));
    261     return;
    262   }
    263 
    264   switch (state_) {
    265     case kDisconnected:
    266       ShutdownOnNetworkThread();
    267       return;
    268 
    269     case kStarting:
    270       SetState(kDisconnecting);
    271       SetState(kDisconnected);
    272       ShutdownOnNetworkThread();
    273       return;
    274 
    275     case kDisconnecting:
    276       return;
    277 
    278     default:
    279       SetState(kDisconnecting);
    280 
    281       if (!host_) {
    282         SetState(kDisconnected);
    283         ShutdownOnNetworkThread();
    284         return;
    285       }
    286 
    287       // Deleting the host destroys SignalStrategy synchronously, but
    288       // SignalStrategy::Listener handlers are not allowed to destroy
    289       // SignalStrategy, so post task to destroy the host later.
    290       host_context_->network_task_runner()->PostTask(
    291           FROM_HERE, base::Bind(&It2MeImpl::ShutdownOnNetworkThread, this));
    292       return;
    293   }
    294 }
    295 
    296 void HostNPScriptObject::It2MeImpl::RequestNatPolicy() {
    297   if (!host_context_->network_task_runner()->BelongsToCurrentThread()) {
    298     DCHECK(plugin_task_runner_->BelongsToCurrentThread());
    299     host_context_->network_task_runner()->PostTask(
    300         FROM_HERE, base::Bind(&It2MeImpl::RequestNatPolicy, this));
    301     return;
    302   }
    303 
    304   if (policy_received_)
    305     UpdateNatPolicy(nat_traversal_enabled_);
    306 }
    307 
    308 void HostNPScriptObject::It2MeImpl::ReadPolicyAndConnect(
    309     const std::string& uid,
    310     const std::string& auth_token,
    311     const std::string& auth_service) {
    312   DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread());
    313 
    314   SetState(kStarting);
    315 
    316   // Only proceed to FinishConnect() if at least one policy update has been
    317   // received.
    318   if (policy_received_) {
    319     FinishConnect(uid, auth_token, auth_service);
    320   } else {
    321     // Otherwise, create the policy watcher, and thunk the connect.
    322     pending_connect_ =
    323         base::Bind(&It2MeImpl::FinishConnect, this,
    324                    uid, auth_token, auth_service);
    325   }
    326 }
    327 
    328 void HostNPScriptObject::It2MeImpl::FinishConnect(
    329     const std::string& uid,
    330     const std::string& auth_token,
    331     const std::string& auth_service) {
    332   DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread());
    333 
    334   if (state_ != kStarting) {
    335     // Host has been stopped while we were fetching policy.
    336     return;
    337   }
    338 
    339   // Check the host domain policy.
    340   if (!required_host_domain_.empty() &&
    341       !EndsWith(uid, std::string("@") + required_host_domain_, false)) {
    342     SetState(kInvalidDomainError);
    343     return;
    344   }
    345 
    346   // Generate a key pair for the Host to use.
    347   // TODO(wez): Move this to the worker thread.
    348   host_key_pair_ = RsaKeyPair::Generate();
    349 
    350   // Create XMPP connection.
    351   scoped_ptr<SignalStrategy> signal_strategy(
    352       new XmppSignalStrategy(host_context_->url_request_context_getter(),
    353                              uid, auth_token, auth_service,
    354                              xmpp_server_config_));
    355 
    356   // Request registration of the host for support.
    357   scoped_ptr<RegisterSupportHostRequest> register_request(
    358       new RegisterSupportHostRequest(
    359           signal_strategy.get(), host_key_pair_, directory_bot_jid_,
    360           base::Bind(&It2MeImpl::OnReceivedSupportID,
    361                      base::Unretained(this))));
    362 
    363   // Beyond this point nothing can fail, so save the config and request.
    364   signal_strategy_ = signal_strategy.Pass();
    365   register_request_ = register_request.Pass();
    366 
    367   // If NAT traversal is off then limit port range to allow firewall pin-holing.
    368   LOG(INFO) << "NAT state: " << nat_traversal_enabled_;
    369   NetworkSettings network_settings(
    370      nat_traversal_enabled_ ?
    371      NetworkSettings::NAT_TRAVERSAL_ENABLED :
    372      NetworkSettings::NAT_TRAVERSAL_DISABLED);
    373   if (!nat_traversal_enabled_) {
    374     network_settings.min_port = NetworkSettings::kDefaultMinPort;
    375     network_settings.max_port = NetworkSettings::kDefaultMaxPort;
    376   }
    377 
    378   // Create the host.
    379   host_.reset(new ChromotingHost(
    380       signal_strategy_.get(),
    381       desktop_environment_factory_.get(),
    382       CreateHostSessionManager(network_settings,
    383                                host_context_->url_request_context_getter()),
    384       host_context_->audio_task_runner(),
    385       host_context_->input_task_runner(),
    386       host_context_->video_capture_task_runner(),
    387       host_context_->video_encode_task_runner(),
    388       host_context_->network_task_runner(),
    389       host_context_->ui_task_runner()));
    390   host_->AddStatusObserver(this);
    391   log_to_server_.reset(
    392       new LogToServer(host_->AsWeakPtr(), ServerLogEntry::IT2ME,
    393                       signal_strategy_.get(), directory_bot_jid_));
    394 
    395   // Disable audio by default.
    396   // TODO(sergeyu): Add UI to enable it.
    397   scoped_ptr<protocol::CandidateSessionConfig> protocol_config =
    398       protocol::CandidateSessionConfig::CreateDefault();
    399   protocol::CandidateSessionConfig::DisableAudioChannel(protocol_config.get());
    400   host_->set_protocol_config(protocol_config.Pass());
    401 
    402   // Create event logger.
    403   host_event_logger_ =
    404       HostEventLogger::Create(host_->AsWeakPtr(), kApplicationName);
    405 
    406   // Connect signaling and start the host.
    407   signal_strategy_->Connect();
    408   host_->Start(uid);
    409 
    410   SetState(kRequestedAccessCode);
    411   return;
    412 }
    413 
    414 void HostNPScriptObject::It2MeImpl::ShutdownOnNetworkThread() {
    415   DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread());
    416   DCHECK(state_ == kDisconnecting || state_ == kDisconnected);
    417 
    418   if (state_ == kDisconnecting) {
    419     host_event_logger_.reset();
    420     host_->RemoveStatusObserver(this);
    421     host_.reset();
    422 
    423     register_request_.reset();
    424     log_to_server_.reset();
    425     signal_strategy_.reset();
    426     SetState(kDisconnected);
    427   }
    428 
    429   host_context_->ui_task_runner()->PostTask(
    430       FROM_HERE, base::Bind(&It2MeImpl::ShutdownOnUiThread, this));
    431 }
    432 
    433 void HostNPScriptObject::It2MeImpl::ShutdownOnUiThread() {
    434   DCHECK(host_context_->ui_task_runner()->BelongsToCurrentThread());
    435 
    436   // Destroy the DesktopEnvironmentFactory, to free thread references.
    437   desktop_environment_factory_.reset();
    438 
    439   // Stop listening for policy updates.
    440   if (policy_watcher_.get()) {
    441     base::WaitableEvent policy_watcher_stopped_(true, false);
    442     policy_watcher_->StopWatching(&policy_watcher_stopped_);
    443     policy_watcher_stopped_.Wait();
    444     policy_watcher_.reset();
    445   }
    446 }
    447 
    448 void HostNPScriptObject::It2MeImpl::OnAccessDenied(const std::string& jid) {
    449   DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread());
    450 
    451   ++failed_login_attempts_;
    452   if (failed_login_attempts_ == kMaxLoginAttempts) {
    453     Disconnect();
    454   }
    455 }
    456 
    457 void HostNPScriptObject::It2MeImpl::OnClientAuthenticated(
    458     const std::string& jid) {
    459   DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread());
    460 
    461   if (state_ == kDisconnecting) {
    462     // Ignore the new connection if we are disconnecting.
    463     return;
    464   }
    465   if (state_ == kConnected) {
    466     // If we already connected another client then one of the connections may be
    467     // an attacker, so both are suspect and we have to reject the second
    468     // connection and shutdown the host.
    469     host_->RejectAuthenticatingClient();
    470     Disconnect();
    471     return;
    472   }
    473 
    474   std::string client_username = jid;
    475   size_t pos = client_username.find('/');
    476   if (pos != std::string::npos)
    477     client_username.replace(pos, std::string::npos, "");
    478 
    479   LOG(INFO) << "Client " << client_username << " connected.";
    480 
    481   // Pass the client user name to the script object before changing state.
    482   plugin_task_runner_->PostTask(
    483       FROM_HERE, base::Bind(&HostNPScriptObject::StoreClientUsername,
    484                             script_object_, client_username));
    485 
    486   SetState(kConnected);
    487 }
    488 
    489 void HostNPScriptObject::It2MeImpl::OnClientDisconnected(
    490     const std::string& jid) {
    491   DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread());
    492 
    493   // Pass the client user name to the script object before changing state.
    494   plugin_task_runner_->PostTask(
    495       FROM_HERE, base::Bind(&HostNPScriptObject::StoreClientUsername,
    496                             script_object_, std::string()));
    497 
    498   Disconnect();
    499 }
    500 
    501 void HostNPScriptObject::It2MeImpl::OnPolicyUpdate(
    502     scoped_ptr<base::DictionaryValue> policies) {
    503   DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread());
    504 
    505   bool nat_policy;
    506   if (policies->GetBoolean(policy_hack::PolicyWatcher::kNatPolicyName,
    507                            &nat_policy)) {
    508     UpdateNatPolicy(nat_policy);
    509   }
    510   std::string host_domain;
    511   if (policies->GetString(policy_hack::PolicyWatcher::kHostDomainPolicyName,
    512                           &host_domain)) {
    513     UpdateHostDomainPolicy(host_domain);
    514   }
    515 
    516   policy_received_ = true;
    517 
    518   if (!pending_connect_.is_null()) {
    519     pending_connect_.Run();
    520     pending_connect_.Reset();
    521   }
    522 }
    523 
    524 void HostNPScriptObject::It2MeImpl::UpdateNatPolicy(
    525     bool nat_traversal_enabled) {
    526   DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread());
    527 
    528   VLOG(2) << "UpdateNatPolicy: " << nat_traversal_enabled;
    529 
    530   // When transitioning from enabled to disabled, force disconnect any
    531   // existing session.
    532   if (nat_traversal_enabled_ && !nat_traversal_enabled && IsConnected()) {
    533     Disconnect();
    534   }
    535 
    536   nat_traversal_enabled_ = nat_traversal_enabled;
    537 
    538   // Notify the web-app of the policy setting.
    539   plugin_task_runner_->PostTask(
    540       FROM_HERE, base::Bind(&HostNPScriptObject::NotifyNatPolicyChanged,
    541                             script_object_, nat_traversal_enabled_));
    542 }
    543 
    544 void HostNPScriptObject::It2MeImpl::UpdateHostDomainPolicy(
    545     const std::string& host_domain) {
    546   DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread());
    547 
    548   VLOG(2) << "UpdateHostDomainPolicy: " << host_domain;
    549 
    550   // When setting a host domain policy, force disconnect any existing session.
    551   if (!host_domain.empty() && IsConnected()) {
    552     Disconnect();
    553   }
    554 
    555   required_host_domain_ = host_domain;
    556 }
    557 
    558 HostNPScriptObject::It2MeImpl::~It2MeImpl() {
    559   // Check that resources that need to be torn down on the UI thread are gone.
    560   DCHECK(!desktop_environment_factory_.get());
    561   DCHECK(!policy_watcher_.get());
    562 }
    563 
    564 void HostNPScriptObject::It2MeImpl::SetState(State state) {
    565   DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread());
    566 
    567   switch (state_) {
    568     case kDisconnected:
    569       DCHECK(state == kStarting ||
    570              state == kError) << state;
    571       break;
    572     case kStarting:
    573       DCHECK(state == kRequestedAccessCode ||
    574              state == kDisconnecting ||
    575              state == kError ||
    576              state == kInvalidDomainError) << state;
    577       break;
    578     case kRequestedAccessCode:
    579       DCHECK(state == kReceivedAccessCode ||
    580              state == kDisconnecting ||
    581              state == kError) << state;
    582       break;
    583     case kReceivedAccessCode:
    584       DCHECK(state == kConnected ||
    585              state == kDisconnecting ||
    586              state == kError) << state;
    587       break;
    588     case kConnected:
    589       DCHECK(state == kDisconnecting ||
    590              state == kDisconnected ||
    591              state == kError) << state;
    592       break;
    593     case kDisconnecting:
    594       DCHECK(state == kDisconnected) << state;
    595       break;
    596     case kError:
    597       DCHECK(state == kDisconnecting) << state;
    598       break;
    599     case kInvalidDomainError:
    600       DCHECK(state == kDisconnecting) << state;
    601       break;
    602   };
    603 
    604   state_ = state;
    605 
    606   // Post a state-change notification to the web-app.
    607   plugin_task_runner_->PostTask(
    608       FROM_HERE, base::Bind(&HostNPScriptObject::NotifyStateChanged,
    609                             script_object_, state));
    610 }
    611 
    612 bool HostNPScriptObject::It2MeImpl::IsConnected() const {
    613   return state_ == kRequestedAccessCode || state_ == kReceivedAccessCode ||
    614       state_ == kConnected;
    615 }
    616 
    617 void HostNPScriptObject::It2MeImpl::OnReceivedSupportID(
    618     bool success,
    619     const std::string& support_id,
    620     const base::TimeDelta& lifetime) {
    621   DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread());
    622 
    623   if (!success) {
    624     SetState(kError);
    625     Disconnect();
    626     return;
    627   }
    628 
    629   std::string host_secret = GenerateSupportHostSecret();
    630   std::string access_code = support_id + host_secret;
    631 
    632   std::string local_certificate = host_key_pair_->GenerateCertificate();
    633   if (local_certificate.empty()) {
    634     LOG(ERROR) << "Failed to generate host certificate.";
    635     SetState(kError);
    636     Disconnect();
    637     return;
    638   }
    639 
    640   scoped_ptr<protocol::AuthenticatorFactory> factory(
    641       new protocol::It2MeHostAuthenticatorFactory(
    642           local_certificate, host_key_pair_, access_code));
    643   host_->SetAuthenticatorFactory(factory.Pass());
    644 
    645   // Pass the Access Code to the script object before changing state.
    646   plugin_task_runner_->PostTask(
    647       FROM_HERE, base::Bind(&HostNPScriptObject::StoreAccessCode,
    648                             script_object_, access_code, lifetime));
    649 
    650   SetState(kReceivedAccessCode);
    651 }
    652 
    653 HostNPScriptObject::HostNPScriptObject(
    654     NPP plugin,
    655     NPObject* parent,
    656     scoped_refptr<AutoThreadTaskRunner> plugin_task_runner)
    657     : plugin_(plugin),
    658       parent_(parent),
    659       plugin_task_runner_(plugin_task_runner),
    660       am_currently_logging_(false),
    661       state_(kDisconnected),
    662       daemon_controller_(DaemonController::Create()),
    663       weak_factory_(this),
    664       weak_ptr_(weak_factory_.GetWeakPtr()) {
    665   DCHECK(plugin_task_runner_->BelongsToCurrentThread());
    666 
    667   // Set the thread task runner for the plugin thread so that timers and other
    668   // code using |base::ThreadTaskRunnerHandle| could be used on the plugin
    669   // thread.
    670   //
    671   // If component build is used, Chrome and the plugin may end up sharing base
    672   // binary. This means that the instance of |base::ThreadTaskRunnerHandle|
    673   // created by Chrome for the current thread is shared as well. This routinely
    674   // happens in the development setting so the below check for
    675   // |!base::ThreadTaskRunnerHandle::IsSet()| is a hack/workaround allowing this
    676   // configuration to work. It lets the plugin to access Chrome's message loop
    677   // directly via |base::ThreadTaskRunnerHandle|. This is safe as long as both
    678   // Chrome and the plugin are built from the same version of the sources.
    679   if (!base::ThreadTaskRunnerHandle::IsSet()) {
    680     plugin_task_runner_handle_.reset(
    681         new base::ThreadTaskRunnerHandle(plugin_task_runner_));
    682   }
    683 
    684   ServiceUrls* service_urls = ServiceUrls::GetInstance();
    685   bool xmpp_server_valid = net::ParseHostAndPort(
    686       service_urls->xmpp_server_address(),
    687       &xmpp_server_config_.host, &xmpp_server_config_.port);
    688   // For the plugin, this is always the default address, which must be valid.
    689   DCHECK(xmpp_server_valid);
    690   xmpp_server_config_.use_tls = service_urls->xmpp_server_use_tls();
    691   directory_bot_jid_ = service_urls->directory_bot_jid();
    692 
    693   // Create worker thread for encryption key generation and loading the paired
    694   // clients.
    695   worker_thread_ = AutoThread::Create("ChromotingWorkerThread",
    696                                       plugin_task_runner_);
    697 
    698   pairing_registry_ = CreatePairingRegistry(worker_thread_);
    699 }
    700 
    701 HostNPScriptObject::~HostNPScriptObject() {
    702   DCHECK(plugin_task_runner_->BelongsToCurrentThread());
    703 
    704   HostLogHandler::UnregisterLoggingScriptObject(this);
    705 
    706   // Stop the It2Me host if the caller forgot to.
    707   if (it2me_impl_.get()) {
    708     it2me_impl_->Disconnect();
    709     it2me_impl_ = NULL;
    710   }
    711 }
    712 
    713 bool HostNPScriptObject::HasMethod(const std::string& method_name) {
    714   VLOG(2) << "HasMethod " << method_name;
    715   DCHECK(plugin_task_runner_->BelongsToCurrentThread());
    716   return (method_name == kFuncNameConnect ||
    717           method_name == kFuncNameDisconnect ||
    718           method_name == kFuncNameLocalize ||
    719           method_name == kFuncNameClearPairedClients ||
    720           method_name == kFuncNameDeletePairedClient ||
    721           method_name == kFuncNameGetHostName ||
    722           method_name == kFuncNameGetPinHash ||
    723           method_name == kFuncNameGenerateKeyPair ||
    724           method_name == kFuncNameUpdateDaemonConfig ||
    725           method_name == kFuncNameGetDaemonConfig ||
    726           method_name == kFuncNameGetDaemonVersion ||
    727           method_name == kFuncNameGetPairedClients ||
    728           method_name == kFuncNameGetUsageStatsConsent ||
    729           method_name == kFuncNameStartDaemon ||
    730           method_name == kFuncNameStopDaemon);
    731 }
    732 
    733 bool HostNPScriptObject::InvokeDefault(const NPVariant* args,
    734                                        uint32_t arg_count,
    735                                        NPVariant* result) {
    736   VLOG(2) << "InvokeDefault";
    737   DCHECK(plugin_task_runner_->BelongsToCurrentThread());
    738   SetException("exception during default invocation");
    739   return false;
    740 }
    741 
    742 bool HostNPScriptObject::Invoke(const std::string& method_name,
    743                                 const NPVariant* args,
    744                                 uint32_t arg_count,
    745                                 NPVariant* result) {
    746   VLOG(2) << "Invoke " << method_name;
    747   DCHECK(plugin_task_runner_->BelongsToCurrentThread());
    748   if (method_name == kFuncNameConnect) {
    749     return Connect(args, arg_count, result);
    750   } else if (method_name == kFuncNameDisconnect) {
    751     return Disconnect(args, arg_count, result);
    752   } else if (method_name == kFuncNameLocalize) {
    753     return Localize(args, arg_count, result);
    754   } else if (method_name == kFuncNameClearPairedClients) {
    755     return ClearPairedClients(args, arg_count, result);
    756   } else if (method_name == kFuncNameDeletePairedClient) {
    757     return DeletePairedClient(args, arg_count, result);
    758   } else if (method_name == kFuncNameGetHostName) {
    759     return GetHostName(args, arg_count, result);
    760   } else if (method_name == kFuncNameGetPinHash) {
    761     return GetPinHash(args, arg_count, result);
    762   } else if (method_name == kFuncNameGenerateKeyPair) {
    763     return GenerateKeyPair(args, arg_count, result);
    764   } else if (method_name == kFuncNameUpdateDaemonConfig) {
    765     return UpdateDaemonConfig(args, arg_count, result);
    766   } else if (method_name == kFuncNameGetDaemonConfig) {
    767     return GetDaemonConfig(args, arg_count, result);
    768   } else if (method_name == kFuncNameGetDaemonVersion) {
    769     return GetDaemonVersion(args, arg_count, result);
    770   } else if (method_name == kFuncNameGetPairedClients) {
    771     return GetPairedClients(args, arg_count, result);
    772   } else if (method_name == kFuncNameGetUsageStatsConsent) {
    773     return GetUsageStatsConsent(args, arg_count, result);
    774   } else if (method_name == kFuncNameStartDaemon) {
    775     return StartDaemon(args, arg_count, result);
    776   } else if (method_name == kFuncNameStopDaemon) {
    777     return StopDaemon(args, arg_count, result);
    778   } else {
    779     SetException("Invoke: unknown method " + method_name);
    780     return false;
    781   }
    782 }
    783 
    784 bool HostNPScriptObject::HasProperty(const std::string& property_name) {
    785   VLOG(2) << "HasProperty " << property_name;
    786   DCHECK(plugin_task_runner_->BelongsToCurrentThread());
    787   return (property_name == kAttrNameAccessCode ||
    788           property_name == kAttrNameAccessCodeLifetime ||
    789           property_name == kAttrNameClient ||
    790           property_name == kAttrNameDaemonState ||
    791           property_name == kAttrNameState ||
    792           property_name == kAttrNameLogDebugInfo ||
    793           property_name == kAttrNameOnNatTraversalPolicyChanged ||
    794           property_name == kAttrNameOnStateChanged ||
    795           property_name == kAttrNameDisconnected ||
    796           property_name == kAttrNameStarting ||
    797           property_name == kAttrNameRequestedAccessCode ||
    798           property_name == kAttrNameReceivedAccessCode ||
    799           property_name == kAttrNameConnected ||
    800           property_name == kAttrNameDisconnecting ||
    801           property_name == kAttrNameError ||
    802           property_name == kAttrNameXmppServerAddress ||
    803           property_name == kAttrNameXmppServerUseTls ||
    804           property_name == kAttrNameDirectoryBotJid ||
    805           property_name == kAttrNameSupportedFeatures);
    806 }
    807 
    808 bool HostNPScriptObject::GetProperty(const std::string& property_name,
    809                                      NPVariant* result) {
    810   VLOG(2) << "GetProperty " << property_name;
    811   DCHECK(plugin_task_runner_->BelongsToCurrentThread());
    812   if (!result) {
    813     SetException("GetProperty: NULL result");
    814     return false;
    815   }
    816 
    817   if (property_name == kAttrNameOnNatTraversalPolicyChanged) {
    818     OBJECT_TO_NPVARIANT(on_nat_traversal_policy_changed_func_.get(), *result);
    819     return true;
    820   } else if (property_name == kAttrNameOnStateChanged) {
    821     OBJECT_TO_NPVARIANT(on_state_changed_func_.get(), *result);
    822     return true;
    823   } else if (property_name == kAttrNameLogDebugInfo) {
    824     OBJECT_TO_NPVARIANT(log_debug_info_func_.get(), *result);
    825     return true;
    826   } else if (property_name == kAttrNameState) {
    827     INT32_TO_NPVARIANT(state_, *result);
    828     return true;
    829   } else if (property_name == kAttrNameAccessCode) {
    830     *result = NPVariantFromString(access_code_);
    831     return true;
    832   } else if (property_name == kAttrNameAccessCodeLifetime) {
    833     INT32_TO_NPVARIANT(access_code_lifetime_.InSeconds(), *result);
    834     return true;
    835   } else if (property_name == kAttrNameClient) {
    836     *result = NPVariantFromString(client_username_);
    837     return true;
    838   } else if (property_name == kAttrNameDaemonState) {
    839     INT32_TO_NPVARIANT(daemon_controller_->GetState(), *result);
    840     return true;
    841   } else if (property_name == kAttrNameDisconnected) {
    842     INT32_TO_NPVARIANT(kDisconnected, *result);
    843     return true;
    844   } else if (property_name == kAttrNameStarting) {
    845     INT32_TO_NPVARIANT(kStarting, *result);
    846     return true;
    847   } else if (property_name == kAttrNameRequestedAccessCode) {
    848     INT32_TO_NPVARIANT(kRequestedAccessCode, *result);
    849     return true;
    850   } else if (property_name == kAttrNameReceivedAccessCode) {
    851     INT32_TO_NPVARIANT(kReceivedAccessCode, *result);
    852     return true;
    853   } else if (property_name == kAttrNameConnected) {
    854     INT32_TO_NPVARIANT(kConnected, *result);
    855     return true;
    856   } else if (property_name == kAttrNameDisconnecting) {
    857     INT32_TO_NPVARIANT(kDisconnecting, *result);
    858     return true;
    859   } else if (property_name == kAttrNameError) {
    860     INT32_TO_NPVARIANT(kError, *result);
    861     return true;
    862   } else if (property_name == kAttrNameInvalidDomainError) {
    863     INT32_TO_NPVARIANT(kInvalidDomainError, *result);
    864     return true;
    865   } else if (property_name == kAttrNameXmppServerAddress) {
    866     *result = NPVariantFromString(base::StringPrintf(
    867         "%s:%u", xmpp_server_config_.host.c_str(), xmpp_server_config_.port));
    868     return true;
    869   } else if (property_name == kAttrNameXmppServerUseTls) {
    870     BOOLEAN_TO_NPVARIANT(xmpp_server_config_.use_tls, *result);
    871     return true;
    872   } else if (property_name == kAttrNameDirectoryBotJid) {
    873     *result = NPVariantFromString(directory_bot_jid_);
    874     return true;
    875   } else if (property_name == kAttrNameSupportedFeatures) {
    876     *result = NPVariantFromString(kSupportedFeatures);
    877     return true;
    878   } else {
    879     SetException("GetProperty: unsupported property " + property_name);
    880     return false;
    881   }
    882 }
    883 
    884 bool HostNPScriptObject::SetProperty(const std::string& property_name,
    885                                      const NPVariant* value) {
    886   VLOG(2) <<  "SetProperty " << property_name;
    887   DCHECK(plugin_task_runner_->BelongsToCurrentThread());
    888 
    889   if (property_name == kAttrNameOnNatTraversalPolicyChanged) {
    890     if (NPVARIANT_IS_OBJECT(*value)) {
    891       on_nat_traversal_policy_changed_func_ = NPVARIANT_TO_OBJECT(*value);
    892       if (it2me_impl_.get()) {
    893         // Ask the It2Me implementation to notify the web-app of the policy.
    894         it2me_impl_->RequestNatPolicy();
    895       }
    896       return true;
    897     } else {
    898       SetException("SetProperty: unexpected type for property " +
    899                    property_name);
    900     }
    901     return false;
    902   }
    903 
    904   if (property_name == kAttrNameOnStateChanged) {
    905     if (NPVARIANT_IS_OBJECT(*value)) {
    906       on_state_changed_func_ = NPVARIANT_TO_OBJECT(*value);
    907       return true;
    908     } else {
    909       SetException("SetProperty: unexpected type for property " +
    910                    property_name);
    911     }
    912     return false;
    913   }
    914 
    915   if (property_name == kAttrNameLogDebugInfo) {
    916     if (NPVARIANT_IS_OBJECT(*value)) {
    917       log_debug_info_func_ = NPVARIANT_TO_OBJECT(*value);
    918       HostLogHandler::RegisterLoggingScriptObject(this);
    919       return true;
    920     } else {
    921       SetException("SetProperty: unexpected type for property " +
    922                    property_name);
    923     }
    924     return false;
    925   }
    926 
    927 #if !defined(NDEBUG)
    928   if (property_name == kAttrNameXmppServerAddress) {
    929     if (NPVARIANT_IS_STRING(*value)) {
    930       std::string address = StringFromNPVariant(*value);
    931       bool xmpp_server_valid = net::ParseHostAndPort(
    932           address, &xmpp_server_config_.host, &xmpp_server_config_.port);
    933       if (xmpp_server_valid) {
    934         return true;
    935       } else {
    936         SetException("SetProperty: invalid value for property " +
    937                      property_name);
    938       }
    939     } else {
    940       SetException("SetProperty: unexpected type for property " +
    941                    property_name);
    942     }
    943     return false;
    944   }
    945 
    946   if (property_name == kAttrNameXmppServerUseTls) {
    947     if (NPVARIANT_IS_BOOLEAN(*value)) {
    948       xmpp_server_config_.use_tls = NPVARIANT_TO_BOOLEAN(*value);
    949       return true;
    950     } else {
    951       SetException("SetProperty: unexpected type for property " +
    952                    property_name);
    953     }
    954     return false;
    955   }
    956 
    957   if (property_name == kAttrNameDirectoryBotJid) {
    958     if (NPVARIANT_IS_STRING(*value)) {
    959       directory_bot_jid_ = StringFromNPVariant(*value);
    960       return true;
    961     } else {
    962       SetException("SetProperty: unexpected type for property " +
    963                    property_name);
    964     }
    965     return false;
    966   }
    967 #endif  // !defined(NDEBUG)
    968 
    969   return false;
    970 }
    971 
    972 bool HostNPScriptObject::RemoveProperty(const std::string& property_name) {
    973   VLOG(2) << "RemoveProperty " << property_name;
    974   DCHECK(plugin_task_runner_->BelongsToCurrentThread());
    975   return false;
    976 }
    977 
    978 bool HostNPScriptObject::Enumerate(std::vector<std::string>* values) {
    979   VLOG(2) << "Enumerate";
    980   DCHECK(plugin_task_runner_->BelongsToCurrentThread());
    981   const char* entries[] = {
    982     kAttrNameAccessCode,
    983     kAttrNameState,
    984     kAttrNameLogDebugInfo,
    985     kAttrNameOnStateChanged,
    986     kAttrNameDisconnected,
    987     kAttrNameStarting,
    988     kAttrNameRequestedAccessCode,
    989     kAttrNameReceivedAccessCode,
    990     kAttrNameConnected,
    991     kAttrNameDisconnecting,
    992     kAttrNameError,
    993     kAttrNameXmppServerAddress,
    994     kAttrNameXmppServerUseTls,
    995     kAttrNameDirectoryBotJid,
    996     kFuncNameConnect,
    997     kFuncNameDisconnect,
    998     kFuncNameLocalize,
    999     kFuncNameClearPairedClients,
   1000     kFuncNameDeletePairedClient,
   1001     kFuncNameGetHostName,
   1002     kFuncNameGetPinHash,
   1003     kFuncNameGenerateKeyPair,
   1004     kFuncNameUpdateDaemonConfig,
   1005     kFuncNameGetDaemonConfig,
   1006     kFuncNameGetDaemonVersion,
   1007     kFuncNameGetPairedClients,
   1008     kFuncNameGetUsageStatsConsent,
   1009     kFuncNameStartDaemon,
   1010     kFuncNameStopDaemon
   1011   };
   1012   for (size_t i = 0; i < arraysize(entries); ++i) {
   1013     values->push_back(entries[i]);
   1014   }
   1015   return true;
   1016 }
   1017 
   1018 // string uid, string auth_token
   1019 bool HostNPScriptObject::Connect(const NPVariant* args,
   1020                                  uint32_t arg_count,
   1021                                  NPVariant* result) {
   1022   DCHECK(plugin_task_runner_->BelongsToCurrentThread());
   1023 
   1024   LOG(INFO) << "Connecting...";
   1025 
   1026   if (arg_count != 2) {
   1027     SetException("connect: bad number of arguments");
   1028     return false;
   1029   }
   1030 
   1031   if (it2me_impl_.get()) {
   1032     SetException("connect: can be called only when disconnected");
   1033     return false;
   1034   }
   1035 
   1036   std::string uid = StringFromNPVariant(args[0]);
   1037   if (uid.empty()) {
   1038     SetException("connect: bad uid argument");
   1039     return false;
   1040   }
   1041 
   1042   std::string auth_service_with_token = StringFromNPVariant(args[1]);
   1043   std::string auth_token;
   1044   std::string auth_service;
   1045   ParseAuthTokenWithService(auth_service_with_token, &auth_token,
   1046                             &auth_service);
   1047   if (auth_token.empty()) {
   1048     SetException("connect: auth_service_with_token argument has empty token");
   1049     return false;
   1050   }
   1051 
   1052   // Create threads for the Chromoting host & desktop environment to use.
   1053   scoped_ptr<ChromotingHostContext> host_context =
   1054     ChromotingHostContext::Create(plugin_task_runner_);
   1055   if (!host_context) {
   1056     SetException("connect: failed to start threads");
   1057     return false;
   1058   }
   1059 
   1060   // Create the It2Me host implementation and start connecting.
   1061   it2me_impl_ = new It2MeImpl(
   1062       host_context.Pass(), plugin_task_runner_, weak_ptr_,
   1063       xmpp_server_config_, directory_bot_jid_);
   1064   it2me_impl_->Connect(uid, auth_token, auth_service);
   1065 
   1066   return true;
   1067 }
   1068 
   1069 bool HostNPScriptObject::Disconnect(const NPVariant* args,
   1070                                     uint32_t arg_count,
   1071                                     NPVariant* result) {
   1072   DCHECK(plugin_task_runner_->BelongsToCurrentThread());
   1073   if (arg_count != 0) {
   1074     SetException("disconnect: bad number of arguments");
   1075     return false;
   1076   }
   1077 
   1078   if (it2me_impl_.get()) {
   1079     it2me_impl_->Disconnect();
   1080     it2me_impl_ = NULL;
   1081   }
   1082 
   1083   return true;
   1084 }
   1085 
   1086 bool HostNPScriptObject::Localize(const NPVariant* args,
   1087                                   uint32_t arg_count,
   1088                                   NPVariant* result) {
   1089   DCHECK(plugin_task_runner_->BelongsToCurrentThread());
   1090   if (arg_count != 1) {
   1091     SetException("localize: bad number of arguments");
   1092     return false;
   1093   }
   1094 
   1095   if (NPVARIANT_IS_OBJECT(args[0])) {
   1096     ScopedRefNPObject localize_func(NPVARIANT_TO_OBJECT(args[0]));
   1097     LocalizeStrings(localize_func.get());
   1098     return true;
   1099   } else {
   1100     SetException("localize: unexpected type for argument 1");
   1101     return false;
   1102   }
   1103 }
   1104 
   1105 bool HostNPScriptObject::ClearPairedClients(const NPVariant* args,
   1106                                             uint32_t arg_count,
   1107                                             NPVariant* result) {
   1108   if (arg_count != 1) {
   1109     SetException("clearPairedClients: bad number of arguments");
   1110     return false;
   1111   }
   1112 
   1113   if (!NPVARIANT_IS_OBJECT(args[0])) {
   1114     SetException("clearPairedClients: invalid callback parameter");
   1115     return false;
   1116   }
   1117 
   1118   ScopedRefNPObject callback_obj(ObjectFromNPVariant(args[0]));
   1119   if (pairing_registry_) {
   1120     pairing_registry_->ClearAllPairings(
   1121         base::Bind(&HostNPScriptObject::InvokeBooleanCallback, weak_ptr_,
   1122                    callback_obj));
   1123   } else {
   1124     InvokeBooleanCallback(callback_obj, false);
   1125   }
   1126 
   1127   return true;
   1128 }
   1129 
   1130 bool HostNPScriptObject::DeletePairedClient(const NPVariant* args,
   1131                                             uint32_t arg_count,
   1132                                             NPVariant* result) {
   1133   if (arg_count != 2) {
   1134     SetException("deletePairedClient: bad number of arguments");
   1135     return false;
   1136   }
   1137 
   1138   if (!NPVARIANT_IS_STRING(args[0])) {
   1139     SetException("deletePairedClient: bad clientId parameter");
   1140     return false;
   1141   }
   1142 
   1143   if (!NPVARIANT_IS_OBJECT(args[1])) {
   1144     SetException("deletePairedClient: invalid callback parameter");
   1145     return false;
   1146   }
   1147 
   1148   std::string client_id = StringFromNPVariant(args[0]);
   1149   ScopedRefNPObject callback_obj(ObjectFromNPVariant(args[1]));
   1150   if (pairing_registry_) {
   1151     pairing_registry_->DeletePairing(
   1152         client_id,
   1153         base::Bind(&HostNPScriptObject::InvokeBooleanCallback,
   1154                    weak_ptr_, callback_obj));
   1155   } else {
   1156     InvokeBooleanCallback(callback_obj, false);
   1157   }
   1158 
   1159   return true;
   1160 }
   1161 
   1162 bool HostNPScriptObject::GetHostName(const NPVariant* args,
   1163                                      uint32_t arg_count,
   1164                                      NPVariant* result) {
   1165   if (arg_count != 1) {
   1166     SetException("getHostName: bad number of arguments");
   1167     return false;
   1168   }
   1169 
   1170   ScopedRefNPObject callback_obj(ObjectFromNPVariant(args[0]));
   1171   if (!callback_obj.get()) {
   1172     SetException("getHostName: invalid callback parameter");
   1173     return false;
   1174   }
   1175 
   1176   NPVariant host_name_val = NPVariantFromString(net::GetHostName());
   1177   InvokeAndIgnoreResult(callback_obj.get(), &host_name_val, 1);
   1178   g_npnetscape_funcs->releasevariantvalue(&host_name_val);
   1179 
   1180   return true;
   1181 }
   1182 
   1183 bool HostNPScriptObject::GetPinHash(const NPVariant* args,
   1184                                     uint32_t arg_count,
   1185                                     NPVariant* result) {
   1186   if (arg_count != 3) {
   1187     SetException("getPinHash: bad number of arguments");
   1188     return false;
   1189   }
   1190 
   1191   std::string host_id = StringFromNPVariant(args[0]);
   1192   if (host_id.empty()) {
   1193     SetException("getPinHash: bad hostId parameter");
   1194     return false;
   1195   }
   1196 
   1197   if (!NPVARIANT_IS_STRING(args[1])) {
   1198     SetException("getPinHash: bad pin parameter");
   1199     return false;
   1200   }
   1201   std::string pin = StringFromNPVariant(args[1]);
   1202 
   1203   ScopedRefNPObject callback_obj(ObjectFromNPVariant(args[2]));
   1204   if (!callback_obj.get()) {
   1205     SetException("getPinHash: invalid callback parameter");
   1206     return false;
   1207   }
   1208 
   1209   NPVariant pin_hash_val = NPVariantFromString(
   1210       remoting::MakeHostPinHash(host_id, pin));
   1211   InvokeAndIgnoreResult(callback_obj.get(), &pin_hash_val, 1);
   1212   g_npnetscape_funcs->releasevariantvalue(&pin_hash_val);
   1213 
   1214   return true;
   1215 }
   1216 
   1217 bool HostNPScriptObject::GenerateKeyPair(const NPVariant* args,
   1218                                          uint32_t arg_count,
   1219                                          NPVariant* result) {
   1220   if (arg_count != 1) {
   1221     SetException("generateKeyPair: bad number of arguments");
   1222     return false;
   1223   }
   1224 
   1225   ScopedRefNPObject callback_obj(ObjectFromNPVariant(args[0]));
   1226   if (!callback_obj.get()) {
   1227     SetException("generateKeyPair: invalid callback parameter");
   1228     return false;
   1229   }
   1230 
   1231   // TODO(wez): HostNPScriptObject needn't be touched on worker
   1232   // thread, so make DoGenerateKeyPair static and pass it a callback
   1233   // to run (crbug.com/156257).
   1234   worker_thread_->PostTask(
   1235       FROM_HERE, base::Bind(&HostNPScriptObject::DoGenerateKeyPair,
   1236                             base::Unretained(this), callback_obj));
   1237   return true;
   1238 }
   1239 
   1240 bool HostNPScriptObject::UpdateDaemonConfig(const NPVariant* args,
   1241                                             uint32_t arg_count,
   1242                                             NPVariant* result) {
   1243   if (arg_count != 2) {
   1244     SetException("updateDaemonConfig: bad number of arguments");
   1245     return false;
   1246   }
   1247 
   1248   std::string config_str = StringFromNPVariant(args[0]);
   1249   scoped_ptr<base::Value> config(
   1250       base::JSONReader::Read(config_str, base::JSON_ALLOW_TRAILING_COMMAS));
   1251   if (config_str.empty() || !config.get() ||
   1252       !config->IsType(base::Value::TYPE_DICTIONARY)) {
   1253     SetException("updateDaemonConfig: bad config parameter");
   1254     return false;
   1255   }
   1256   scoped_ptr<base::DictionaryValue> config_dict(
   1257       reinterpret_cast<base::DictionaryValue*>(config.release()));
   1258 
   1259   ScopedRefNPObject callback_obj(ObjectFromNPVariant(args[1]));
   1260   if (!callback_obj.get()) {
   1261     SetException("updateDaemonConfig: invalid callback parameter");
   1262     return false;
   1263   }
   1264 
   1265   if (config_dict->HasKey(kHostIdConfigPath) ||
   1266       config_dict->HasKey(kXmppLoginConfigPath)) {
   1267     SetException("updateDaemonConfig: trying to update immutable config "
   1268                  "parameters");
   1269     return false;
   1270   }
   1271 
   1272   // TODO(wez): Pass a static method here, that will post the result
   1273   // back to us on the right thread (crbug.com/156257).
   1274   daemon_controller_->UpdateConfig(
   1275       config_dict.Pass(),
   1276       base::Bind(&HostNPScriptObject::InvokeAsyncResultCallback,
   1277                  base::Unretained(this), callback_obj));
   1278   return true;
   1279 }
   1280 
   1281 bool HostNPScriptObject::GetDaemonConfig(const NPVariant* args,
   1282                                          uint32_t arg_count,
   1283                                          NPVariant* result) {
   1284   if (arg_count != 1) {
   1285     SetException("getDaemonConfig: bad number of arguments");
   1286     return false;
   1287   }
   1288 
   1289   ScopedRefNPObject callback_obj(ObjectFromNPVariant(args[0]));
   1290   if (!callback_obj.get()) {
   1291     SetException("getDaemonConfig: invalid callback parameter");
   1292     return false;
   1293   }
   1294 
   1295   // TODO(wez): Pass a static method here, that will post the result
   1296   // back to us on the right thread (crbug.com/156257).
   1297   daemon_controller_->GetConfig(
   1298       base::Bind(&HostNPScriptObject::InvokeGetDaemonConfigCallback,
   1299                  base::Unretained(this), callback_obj));
   1300 
   1301   return true;
   1302 }
   1303 
   1304 bool HostNPScriptObject::GetDaemonVersion(const NPVariant* args,
   1305                                           uint32_t arg_count,
   1306                                           NPVariant* result) {
   1307   if (arg_count != 1) {
   1308     SetException("getDaemonVersion: bad number of arguments");
   1309     return false;
   1310   }
   1311 
   1312   ScopedRefNPObject callback_obj(ObjectFromNPVariant(args[0]));
   1313   if (!callback_obj.get()) {
   1314     SetException("getDaemonVersion: invalid callback parameter");
   1315     return false;
   1316   }
   1317 
   1318   // TODO(wez): Pass a static method here, that will post the result
   1319   // back to us on the right thread (crbug.com/156257).
   1320   daemon_controller_->GetVersion(
   1321       base::Bind(&HostNPScriptObject::InvokeGetDaemonVersionCallback,
   1322                  base::Unretained(this), callback_obj));
   1323 
   1324   return true;
   1325 }
   1326 
   1327 bool HostNPScriptObject::GetPairedClients(const NPVariant* args,
   1328                                           uint32_t arg_count,
   1329                                           NPVariant* result) {
   1330   if (arg_count != 1) {
   1331     SetException("getPairedClients: bad number of arguments");
   1332     return false;
   1333   }
   1334 
   1335   ScopedRefNPObject callback_obj(ObjectFromNPVariant(args[0]));
   1336   if (!callback_obj.get()) {
   1337     SetException("getPairedClients: invalid callback parameter");
   1338     return false;
   1339   }
   1340 
   1341   if (pairing_registry_) {
   1342     pairing_registry_->GetAllPairings(
   1343         base::Bind(&HostNPScriptObject::InvokeGetPairedClientsCallback,
   1344                    weak_ptr_, callback_obj));
   1345   } else {
   1346     scoped_ptr<base::ListValue> no_paired_clients(new base::ListValue);
   1347     InvokeGetPairedClientsCallback(callback_obj, no_paired_clients.Pass());
   1348   }
   1349   return true;
   1350 }
   1351 
   1352 bool HostNPScriptObject::GetUsageStatsConsent(const NPVariant* args,
   1353                                               uint32_t arg_count,
   1354                                               NPVariant* result) {
   1355   if (arg_count != 1) {
   1356     SetException("getUsageStatsConsent: bad number of arguments");
   1357     return false;
   1358   }
   1359 
   1360   ScopedRefNPObject callback_obj(ObjectFromNPVariant(args[0]));
   1361   if (!callback_obj.get()) {
   1362     SetException("getUsageStatsConsent: invalid callback parameter");
   1363     return false;
   1364   }
   1365 
   1366   // TODO(wez): Pass a static method here, that will post the result
   1367   // back to us on the right thread (crbug.com/156257).
   1368   daemon_controller_->GetUsageStatsConsent(
   1369       base::Bind(&HostNPScriptObject::InvokeGetUsageStatsConsentCallback,
   1370                  base::Unretained(this), callback_obj));
   1371   return true;
   1372 }
   1373 
   1374 bool HostNPScriptObject::StartDaemon(const NPVariant* args,
   1375                                      uint32_t arg_count,
   1376                                      NPVariant* result) {
   1377   DCHECK(plugin_task_runner_->BelongsToCurrentThread());
   1378 
   1379   if (arg_count != 3) {
   1380     SetException("startDaemon: bad number of arguments");
   1381     return false;
   1382   }
   1383 
   1384   std::string config_str = StringFromNPVariant(args[0]);
   1385   scoped_ptr<base::Value> config(
   1386       base::JSONReader::Read(config_str, base::JSON_ALLOW_TRAILING_COMMAS));
   1387   if (config_str.empty() || !config.get() ||
   1388       !config->IsType(base::Value::TYPE_DICTIONARY)) {
   1389     SetException("startDaemon: bad config parameter");
   1390     return false;
   1391   }
   1392   scoped_ptr<base::DictionaryValue> config_dict(
   1393       reinterpret_cast<base::DictionaryValue*>(config.release()));
   1394 
   1395   if (!NPVARIANT_IS_BOOLEAN(args[1])) {
   1396     SetException("startDaemon: invalid consent parameter");
   1397     return false;
   1398   }
   1399 
   1400   ScopedRefNPObject callback_obj(ObjectFromNPVariant(args[2]));
   1401   if (!callback_obj.get()) {
   1402     SetException("startDaemon: invalid callback parameter");
   1403     return false;
   1404   }
   1405 
   1406   // TODO(wez): Pass a static method here, that will post the result
   1407   // back to us on the right thread (crbug.com/156257).
   1408   daemon_controller_->SetConfigAndStart(
   1409       config_dict.Pass(),
   1410       NPVARIANT_TO_BOOLEAN(args[1]),
   1411       base::Bind(&HostNPScriptObject::InvokeAsyncResultCallback,
   1412                  base::Unretained(this), callback_obj));
   1413   return true;
   1414 }
   1415 
   1416 bool HostNPScriptObject::StopDaemon(const NPVariant* args,
   1417                                     uint32_t arg_count,
   1418                                     NPVariant* result) {
   1419   DCHECK(plugin_task_runner_->BelongsToCurrentThread());
   1420 
   1421   if (arg_count != 1) {
   1422     SetException("stopDaemon: bad number of arguments");
   1423     return false;
   1424   }
   1425 
   1426   ScopedRefNPObject callback_obj(ObjectFromNPVariant(args[0]));
   1427   if (!callback_obj.get()) {
   1428     SetException("stopDaemon: invalid callback parameter");
   1429     return false;
   1430   }
   1431 
   1432   // TODO(wez): Pass a static method here, that will post the result
   1433   // back to us on the right thread (crbug.com/156257).
   1434   daemon_controller_->Stop(
   1435       base::Bind(&HostNPScriptObject::InvokeAsyncResultCallback,
   1436                  base::Unretained(this), callback_obj));
   1437   return true;
   1438 }
   1439 
   1440 void HostNPScriptObject::NotifyStateChanged(State state) {
   1441   DCHECK(plugin_task_runner_->BelongsToCurrentThread());
   1442 
   1443   state_ = state;
   1444 
   1445   if (on_state_changed_func_.get()) {
   1446     NPVariant state_var;
   1447     INT32_TO_NPVARIANT(state, state_var);
   1448     InvokeAndIgnoreResult(on_state_changed_func_.get(), &state_var, 1);
   1449   }
   1450 }
   1451 
   1452 void HostNPScriptObject::NotifyNatPolicyChanged(bool nat_traversal_enabled) {
   1453   DCHECK(plugin_task_runner_->BelongsToCurrentThread());
   1454 
   1455   if (on_nat_traversal_policy_changed_func_.get()) {
   1456     NPVariant policy;
   1457     BOOLEAN_TO_NPVARIANT(nat_traversal_enabled, policy);
   1458     InvokeAndIgnoreResult(on_nat_traversal_policy_changed_func_.get(),
   1459                           &policy, 1);
   1460   }
   1461 }
   1462 
   1463 // Stores the Access Code for the web-app to query.
   1464 void HostNPScriptObject::StoreAccessCode(const std::string& access_code,
   1465                                          base::TimeDelta access_code_lifetime) {
   1466   DCHECK(plugin_task_runner_->BelongsToCurrentThread());
   1467 
   1468   access_code_ = access_code;
   1469   access_code_lifetime_ = access_code_lifetime;
   1470 }
   1471 
   1472 // Stores the client user's name for the web-app to query.
   1473 void HostNPScriptObject::StoreClientUsername(
   1474     const std::string& client_username) {
   1475   DCHECK(plugin_task_runner_->BelongsToCurrentThread());
   1476 
   1477   client_username_ = client_username;
   1478 }
   1479 
   1480 void HostNPScriptObject::PostLogDebugInfo(const std::string& message) {
   1481   if (plugin_task_runner_->BelongsToCurrentThread()) {
   1482     // Make sure we're not currently processing a log message.
   1483     // We only need to check this if we're on the plugin thread.
   1484     if (am_currently_logging_)
   1485       return;
   1486   }
   1487 
   1488   // Always post (even if we're already on the correct thread) so that debug
   1489   // log messages are shown in the correct order.
   1490   plugin_task_runner_->PostTask(
   1491       FROM_HERE, base::Bind(&HostNPScriptObject::LogDebugInfo,
   1492                             weak_ptr_, message));
   1493 }
   1494 
   1495 void HostNPScriptObject::SetWindow(NPWindow* np_window) {
   1496   DCHECK(plugin_task_runner_->BelongsToCurrentThread());
   1497 
   1498   daemon_controller_->SetWindow(np_window->window);
   1499 }
   1500 
   1501 void HostNPScriptObject::LocalizeStrings(NPObject* localize_func) {
   1502   DCHECK(plugin_task_runner_->BelongsToCurrentThread());
   1503 
   1504   // Reload resources for the current locale. The default UI locale is used on
   1505   // Windows.
   1506 #if !defined(OS_WIN)
   1507   string16 ui_locale;
   1508   LocalizeString(localize_func, "@@ui_locale", &ui_locale);
   1509   remoting::LoadResources(UTF16ToUTF8(ui_locale));
   1510 #endif  // !defined(OS_WIN)
   1511 }
   1512 
   1513 bool HostNPScriptObject::LocalizeString(NPObject* localize_func,
   1514                                         const char* tag, string16* result) {
   1515   return LocalizeStringWithSubstitution(localize_func, tag, NULL, result);
   1516 }
   1517 
   1518 bool HostNPScriptObject::LocalizeStringWithSubstitution(
   1519     NPObject* localize_func,
   1520     const char* tag,
   1521     const char* substitution,
   1522     string16* result) {
   1523   int argc = substitution ? 2 : 1;
   1524   scoped_ptr<NPVariant[]> args(new NPVariant[argc]);
   1525   STRINGZ_TO_NPVARIANT(tag, args[0]);
   1526   if (substitution) {
   1527     STRINGZ_TO_NPVARIANT(substitution, args[1]);
   1528   }
   1529   NPVariant np_result;
   1530   bool is_good = g_npnetscape_funcs->invokeDefault(
   1531       plugin_, localize_func, args.get(), argc, &np_result);
   1532   if (!is_good) {
   1533     LOG(ERROR) << "Localization failed for " << tag;
   1534     return false;
   1535   }
   1536   std::string translation = StringFromNPVariant(np_result);
   1537   g_npnetscape_funcs->releasevariantvalue(&np_result);
   1538   if (translation.empty()) {
   1539     LOG(ERROR) << "Missing translation for " << tag;
   1540     return false;
   1541   }
   1542   *result = UTF8ToUTF16(translation);
   1543   return true;
   1544 }
   1545 
   1546 void HostNPScriptObject::DoGenerateKeyPair(const ScopedRefNPObject& callback) {
   1547   scoped_refptr<RsaKeyPair> key_pair = RsaKeyPair::Generate();
   1548   InvokeGenerateKeyPairCallback(callback, key_pair->ToString(),
   1549                                 key_pair->GetPublicKey());
   1550 }
   1551 
   1552 void HostNPScriptObject::InvokeGenerateKeyPairCallback(
   1553     const ScopedRefNPObject& callback,
   1554     const std::string& private_key,
   1555     const std::string& public_key) {
   1556   if (!plugin_task_runner_->BelongsToCurrentThread()) {
   1557     plugin_task_runner_->PostTask(
   1558         FROM_HERE, base::Bind(
   1559             &HostNPScriptObject::InvokeGenerateKeyPairCallback,
   1560             weak_ptr_, callback, private_key, public_key));
   1561     return;
   1562   }
   1563 
   1564   NPVariant params[2];
   1565   params[0] = NPVariantFromString(private_key);
   1566   params[1] = NPVariantFromString(public_key);
   1567   InvokeAndIgnoreResult(callback.get(), params, arraysize(params));
   1568   g_npnetscape_funcs->releasevariantvalue(&(params[0]));
   1569   g_npnetscape_funcs->releasevariantvalue(&(params[1]));
   1570 }
   1571 
   1572 void HostNPScriptObject::InvokeAsyncResultCallback(
   1573     const ScopedRefNPObject& callback,
   1574     DaemonController::AsyncResult result) {
   1575   if (!plugin_task_runner_->BelongsToCurrentThread()) {
   1576     plugin_task_runner_->PostTask(
   1577         FROM_HERE, base::Bind(
   1578             &HostNPScriptObject::InvokeAsyncResultCallback,
   1579             weak_ptr_, callback, result));
   1580     return;
   1581   }
   1582 
   1583   NPVariant result_var;
   1584   INT32_TO_NPVARIANT(static_cast<int32>(result), result_var);
   1585   InvokeAndIgnoreResult(callback.get(), &result_var, 1);
   1586   g_npnetscape_funcs->releasevariantvalue(&result_var);
   1587 }
   1588 
   1589 void HostNPScriptObject::InvokeBooleanCallback(
   1590     const ScopedRefNPObject& callback, bool result) {
   1591   if (!plugin_task_runner_->BelongsToCurrentThread()) {
   1592     plugin_task_runner_->PostTask(
   1593         FROM_HERE, base::Bind(
   1594             &HostNPScriptObject::InvokeBooleanCallback,
   1595             weak_ptr_, callback, result));
   1596     return;
   1597   }
   1598 
   1599   NPVariant result_var;
   1600   BOOLEAN_TO_NPVARIANT(result, result_var);
   1601   InvokeAndIgnoreResult(callback.get(), &result_var, 1);
   1602   g_npnetscape_funcs->releasevariantvalue(&result_var);
   1603 }
   1604 
   1605 void HostNPScriptObject::InvokeGetDaemonConfigCallback(
   1606     const ScopedRefNPObject& callback,
   1607     scoped_ptr<base::DictionaryValue> config) {
   1608   if (!plugin_task_runner_->BelongsToCurrentThread()) {
   1609     plugin_task_runner_->PostTask(
   1610         FROM_HERE, base::Bind(
   1611             &HostNPScriptObject::InvokeGetDaemonConfigCallback,
   1612             weak_ptr_, callback, base::Passed(&config)));
   1613     return;
   1614   }
   1615 
   1616   // There is no easy way to create a dictionary from an NPAPI plugin
   1617   // so we have to serialize the dictionary to pass it to JavaScript.
   1618   std::string config_str;
   1619   if (config.get())
   1620     base::JSONWriter::Write(config.get(), &config_str);
   1621 
   1622   NPVariant config_val = NPVariantFromString(config_str);
   1623   InvokeAndIgnoreResult(callback.get(), &config_val, 1);
   1624   g_npnetscape_funcs->releasevariantvalue(&config_val);
   1625 }
   1626 
   1627 void HostNPScriptObject::InvokeGetDaemonVersionCallback(
   1628     const ScopedRefNPObject& callback, const std::string& version) {
   1629   if (!plugin_task_runner_->BelongsToCurrentThread()) {
   1630     plugin_task_runner_->PostTask(
   1631         FROM_HERE, base::Bind(
   1632             &HostNPScriptObject::InvokeGetDaemonVersionCallback,
   1633             weak_ptr_, callback, version));
   1634     return;
   1635   }
   1636 
   1637   NPVariant version_val = NPVariantFromString(version);
   1638   InvokeAndIgnoreResult(callback.get(), &version_val, 1);
   1639   g_npnetscape_funcs->releasevariantvalue(&version_val);
   1640 }
   1641 
   1642 void HostNPScriptObject::InvokeGetPairedClientsCallback(
   1643     const ScopedRefNPObject& callback,
   1644     scoped_ptr<base::ListValue> paired_clients) {
   1645   if (!plugin_task_runner_->BelongsToCurrentThread()) {
   1646     plugin_task_runner_->PostTask(
   1647         FROM_HERE, base::Bind(
   1648             &HostNPScriptObject::InvokeGetPairedClientsCallback,
   1649             weak_ptr_, callback, base::Passed(&paired_clients)));
   1650     return;
   1651   }
   1652 
   1653   std::string paired_clients_json;
   1654   base::JSONWriter::Write(paired_clients.get(), &paired_clients_json);
   1655 
   1656   NPVariant paired_clients_val = NPVariantFromString(paired_clients_json);
   1657   InvokeAndIgnoreResult(callback.get(), &paired_clients_val, 1);
   1658   g_npnetscape_funcs->releasevariantvalue(&paired_clients_val);
   1659 }
   1660 
   1661 void HostNPScriptObject::InvokeGetUsageStatsConsentCallback(
   1662     const ScopedRefNPObject& callback,
   1663     bool supported,
   1664     bool allowed,
   1665     bool set_by_policy) {
   1666   if (!plugin_task_runner_->BelongsToCurrentThread()) {
   1667     plugin_task_runner_->PostTask(
   1668         FROM_HERE, base::Bind(
   1669             &HostNPScriptObject::InvokeGetUsageStatsConsentCallback,
   1670             weak_ptr_, callback, supported, allowed,
   1671             set_by_policy));
   1672     return;
   1673   }
   1674 
   1675   NPVariant params[3];
   1676   BOOLEAN_TO_NPVARIANT(supported, params[0]);
   1677   BOOLEAN_TO_NPVARIANT(allowed, params[1]);
   1678   BOOLEAN_TO_NPVARIANT(set_by_policy, params[2]);
   1679   InvokeAndIgnoreResult(callback.get(), params, arraysize(params));
   1680   g_npnetscape_funcs->releasevariantvalue(&(params[0]));
   1681   g_npnetscape_funcs->releasevariantvalue(&(params[1]));
   1682   g_npnetscape_funcs->releasevariantvalue(&(params[2]));
   1683 }
   1684 
   1685 void HostNPScriptObject::LogDebugInfo(const std::string& message) {
   1686   DCHECK(plugin_task_runner_->BelongsToCurrentThread());
   1687 
   1688   if (log_debug_info_func_.get()) {
   1689     am_currently_logging_ = true;
   1690     NPVariant log_message;
   1691     STRINGZ_TO_NPVARIANT(message.c_str(), log_message);
   1692     bool is_good = InvokeAndIgnoreResult(log_debug_info_func_.get(),
   1693                                          &log_message, 1);
   1694     if (!is_good) {
   1695       LOG(ERROR) << "ERROR - LogDebugInfo failed\n";
   1696     }
   1697     am_currently_logging_ = false;
   1698   }
   1699 }
   1700 
   1701 bool HostNPScriptObject::InvokeAndIgnoreResult(NPObject* func,
   1702                                                const NPVariant* args,
   1703                                                uint32_t arg_count) {
   1704   DCHECK(plugin_task_runner_->BelongsToCurrentThread());
   1705 
   1706   NPVariant np_result;
   1707   bool is_good = g_npnetscape_funcs->invokeDefault(plugin_, func, args,
   1708                                                    arg_count, &np_result);
   1709   if (is_good)
   1710       g_npnetscape_funcs->releasevariantvalue(&np_result);
   1711 
   1712   return is_good;
   1713 }
   1714 
   1715 void HostNPScriptObject::SetException(const std::string& exception_string) {
   1716   DCHECK(plugin_task_runner_->BelongsToCurrentThread());
   1717 
   1718   g_npnetscape_funcs->setexception(parent_, exception_string.c_str());
   1719   LOG(INFO) << exception_string;
   1720 }
   1721 
   1722 }  // namespace remoting
   1723