Home | History | Annotate | Download | only in renderer
      1 // Copyright 2014 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 "components/nacl/renderer/nexe_load_manager.h"
      6 
      7 #include "base/command_line.h"
      8 #include "base/logging.h"
      9 #include "base/metrics/histogram.h"
     10 #include "base/strings/string_tokenizer.h"
     11 #include "base/strings/string_util.h"
     12 #include "components/nacl/common/nacl_host_messages.h"
     13 #include "components/nacl/common/nacl_types.h"
     14 #include "components/nacl/renderer/histogram.h"
     15 #include "components/nacl/renderer/manifest_service_channel.h"
     16 #include "components/nacl/renderer/pnacl_translation_resource_host.h"
     17 #include "components/nacl/renderer/progress_event.h"
     18 #include "components/nacl/renderer/sandbox_arch.h"
     19 #include "components/nacl/renderer/trusted_plugin_channel.h"
     20 #include "content/public/common/content_client.h"
     21 #include "content/public/common/content_switches.h"
     22 #include "content/public/common/sandbox_init.h"
     23 #include "content/public/renderer/pepper_plugin_instance.h"
     24 #include "content/public/renderer/render_thread.h"
     25 #include "content/public/renderer/render_view.h"
     26 #include "content/public/renderer/renderer_ppapi_host.h"
     27 #include "ppapi/c/pp_bool.h"
     28 #include "ppapi/c/private/pp_file_handle.h"
     29 #include "ppapi/shared_impl/ppapi_globals.h"
     30 #include "ppapi/shared_impl/ppapi_permissions.h"
     31 #include "ppapi/shared_impl/ppapi_preferences.h"
     32 #include "ppapi/shared_impl/scoped_pp_var.h"
     33 #include "ppapi/shared_impl/var.h"
     34 #include "ppapi/shared_impl/var_tracker.h"
     35 #include "ppapi/thunk/enter.h"
     36 #include "third_party/WebKit/public/web/WebDocument.h"
     37 #include "third_party/WebKit/public/web/WebElement.h"
     38 #include "third_party/WebKit/public/web/WebPluginContainer.h"
     39 #include "third_party/WebKit/public/web/WebView.h"
     40 #include "v8/include/v8.h"
     41 
     42 namespace {
     43 
     44 const char* const kTypeAttribute = "type";
     45 // The "src" attribute of the <embed> tag.  The value is expected to be either
     46 // a URL or URI pointing to the manifest file (which is expected to contain
     47 // JSON matching ISAs with .nexe URLs).
     48 const char* const kSrcManifestAttribute = "src";
     49 // The "nacl" attribute of the <embed> tag.  We use the value of this attribute
     50 // to find the manifest file when NaCl is registered as a plug-in for another
     51 // MIME type because the "src" attribute is used to supply us with the resource
     52 // of that MIME type that we're supposed to display.
     53 const char* const kNaClManifestAttribute = "nacl";
     54 // Define an argument name to enable 'dev' interfaces. To make sure it doesn't
     55 // collide with any user-defined HTML attribute, make the first character '@'.
     56 const char* const kDevAttribute = "@dev";
     57 
     58 const char* const kNaClMIMEType = "application/x-nacl";
     59 const char* const kPNaClMIMEType = "application/x-pnacl";
     60 
     61 static int GetRoutingID(PP_Instance instance) {
     62   // Check that we are on the main renderer thread.
     63   DCHECK(content::RenderThread::Get());
     64   content::RendererPpapiHost *host =
     65       content::RendererPpapiHost::GetForPPInstance(instance);
     66   if (!host)
     67     return 0;
     68   return host->GetRoutingIDForWidget(instance);
     69 }
     70 
     71 std::string LookupAttribute(const std::map<std::string, std::string>& args,
     72                             const std::string& key) {
     73   std::map<std::string, std::string>::const_iterator it = args.find(key);
     74   if (it != args.end())
     75     return it->second;
     76   return std::string();
     77 }
     78 
     79 }  // namespace
     80 
     81 namespace nacl {
     82 
     83 NexeLoadManager::NexeLoadManager(
     84     PP_Instance pp_instance)
     85     : pp_instance_(pp_instance),
     86       nacl_ready_state_(PP_NACL_READY_STATE_UNSENT),
     87       nexe_error_reported_(false),
     88       is_installed_(false),
     89       exit_status_(-1),
     90       nexe_size_(0),
     91       plugin_instance_(content::PepperPluginInstance::Get(pp_instance)),
     92       weak_factory_(this) {
     93   SetLastError("");
     94   HistogramEnumerateOsArch(GetSandboxArch());
     95   if (plugin_instance_) {
     96     plugin_base_url_ =
     97         plugin_instance_->GetContainer()->element().document().url();
     98   }
     99 }
    100 
    101 NexeLoadManager::~NexeLoadManager() {
    102   if (!nexe_error_reported_) {
    103     base::TimeDelta uptime = base::Time::Now() - ready_time_;
    104     HistogramTimeLarge("NaCl.ModuleUptime.Normal", uptime.InMilliseconds());
    105   }
    106 }
    107 
    108 void NexeLoadManager::NexeFileDidOpen(int32_t pp_error,
    109                                       const base::File& file,
    110                                       int32_t http_status,
    111                                       int64_t nexe_bytes_read,
    112                                       const std::string& url,
    113                                       base::TimeDelta time_since_open) {
    114   // Check that we are on the main renderer thread.
    115   DCHECK(content::RenderThread::Get());
    116   VLOG(1) << "Plugin::NexeFileDidOpen (pp_error=" << pp_error << ")";
    117   HistogramHTTPStatusCode(
    118       is_installed_ ? "NaCl.HttpStatusCodeClass.Nexe.InstalledApp" :
    119                       "NaCl.HttpStatusCodeClass.Nexe.NotInstalledApp",
    120       http_status);
    121 
    122   if (pp_error != PP_OK || !file.IsValid()) {
    123     if (pp_error == PP_ERROR_ABORTED) {
    124       ReportLoadAbort();
    125     } else if (pp_error == PP_ERROR_NOACCESS) {
    126       ReportLoadError(PP_NACL_ERROR_NEXE_NOACCESS_URL,
    127                       "access to nexe url was denied.");
    128     } else {
    129       ReportLoadError(PP_NACL_ERROR_NEXE_LOAD_URL,
    130                       "could not load nexe url.");
    131     }
    132   } else if (nexe_bytes_read == -1) {
    133     ReportLoadError(PP_NACL_ERROR_NEXE_STAT, "could not stat nexe file.");
    134   } else {
    135     // TODO(dmichael): Can we avoid stashing away so much state?
    136     nexe_size_ = nexe_bytes_read;
    137     HistogramSizeKB("NaCl.Perf.Size.Nexe",
    138                     static_cast<int32_t>(nexe_size_ / 1024));
    139     HistogramStartupTimeMedium(
    140         "NaCl.Perf.StartupTime.NexeDownload", time_since_open, nexe_size_);
    141 
    142     // Inform JavaScript that we successfully downloaded the nacl module.
    143     ProgressEvent progress_event(PP_NACL_EVENT_PROGRESS, url, true, nexe_size_,
    144                                  nexe_size_);
    145     DispatchProgressEvent(pp_instance_, progress_event);
    146     load_start_ = base::Time::Now();
    147   }
    148 }
    149 
    150 void NexeLoadManager::ReportLoadSuccess(const std::string& url,
    151                                         uint64_t loaded_bytes,
    152                                         uint64_t total_bytes) {
    153   ready_time_ = base::Time::Now();
    154   if (!IsPNaCl()) {
    155     base::TimeDelta load_module_time = ready_time_ - load_start_;
    156     HistogramStartupTimeSmall(
    157         "NaCl.Perf.StartupTime.LoadModule", load_module_time, nexe_size_);
    158     HistogramStartupTimeMedium(
    159         "NaCl.Perf.StartupTime.Total", ready_time_ - init_time_, nexe_size_);
    160   }
    161 
    162   // Check that we are on the main renderer thread.
    163   DCHECK(content::RenderThread::Get());
    164   set_nacl_ready_state(PP_NACL_READY_STATE_DONE);
    165 
    166   // Inform JavaScript that loading was successful and is complete.
    167   ProgressEvent load_event(PP_NACL_EVENT_LOAD, url, true, loaded_bytes,
    168                            total_bytes);
    169   DispatchProgressEvent(pp_instance_, load_event);
    170 
    171   ProgressEvent loadend_event(PP_NACL_EVENT_LOADEND, url, true, loaded_bytes,
    172                               total_bytes);
    173   DispatchProgressEvent(pp_instance_, loadend_event);
    174 
    175   // UMA
    176   HistogramEnumerateLoadStatus(PP_NACL_ERROR_LOAD_SUCCESS, is_installed_);
    177 }
    178 
    179 void NexeLoadManager::ReportLoadError(PP_NaClError error,
    180                                       const std::string& error_message) {
    181   ReportLoadError(error, error_message, error_message);
    182 }
    183 
    184 void NexeLoadManager::ReportLoadError(PP_NaClError error,
    185                                       const std::string& error_message,
    186                                       const std::string& console_message) {
    187   // Check that we are on the main renderer thread.
    188   DCHECK(content::RenderThread::Get());
    189 
    190   if (error == PP_NACL_ERROR_MANIFEST_PROGRAM_MISSING_ARCH) {
    191     // A special case: the manifest may otherwise be valid but is missing
    192     // a program/file compatible with the user's sandbox.
    193     IPC::Sender* sender = content::RenderThread::Get();
    194     sender->Send(
    195         new NaClHostMsg_MissingArchError(GetRoutingID(pp_instance_)));
    196   }
    197   set_nacl_ready_state(PP_NACL_READY_STATE_DONE);
    198   nexe_error_reported_ = true;
    199 
    200   // We must set all properties before calling DispatchEvent so that when an
    201   // event handler runs, the properties reflect the current load state.
    202   std::string error_string = std::string("NaCl module load failed: ") +
    203       std::string(error_message);
    204   SetLastError(error_string);
    205 
    206   // Inform JavaScript that loading encountered an error and is complete.
    207   DispatchProgressEvent(pp_instance_, ProgressEvent(PP_NACL_EVENT_ERROR));
    208   DispatchProgressEvent(pp_instance_, ProgressEvent(PP_NACL_EVENT_LOADEND));
    209 
    210   HistogramEnumerateLoadStatus(error, is_installed_);
    211   LogToConsole(console_message);
    212 }
    213 
    214 void NexeLoadManager::ReportLoadAbort() {
    215   // Check that we are on the main renderer thread.
    216   DCHECK(content::RenderThread::Get());
    217 
    218   // Set the readyState attribute to indicate we need to start over.
    219   set_nacl_ready_state(PP_NACL_READY_STATE_DONE);
    220   nexe_error_reported_ = true;
    221 
    222   // Report an error in lastError and on the JavaScript console.
    223   std::string error_string("NaCl module load failed: user aborted");
    224   SetLastError(error_string);
    225 
    226   // Inform JavaScript that loading was aborted and is complete.
    227   DispatchProgressEvent(pp_instance_, ProgressEvent(PP_NACL_EVENT_ABORT));
    228   DispatchProgressEvent(pp_instance_, ProgressEvent(PP_NACL_EVENT_LOADEND));
    229 
    230   HistogramEnumerateLoadStatus(PP_NACL_ERROR_LOAD_ABORTED, is_installed_);
    231   LogToConsole(error_string);
    232 }
    233 
    234 void NexeLoadManager::NexeDidCrash(const char* crash_log) {
    235   VLOG(1) << "Plugin::NexeDidCrash: crash event!";
    236     // The NaCl module voluntarily exited.  However, this is still a
    237     // crash from the point of view of Pepper, since PPAPI plugins are
    238     // event handlers and should never exit.
    239   VLOG_IF(1, exit_status_ != -1)
    240       << "Plugin::NexeDidCrash: nexe exited with status " << exit_status_
    241       << " so this is a \"controlled crash\".";
    242   // If the crash occurs during load, we just want to report an error
    243   // that fits into our load progress event grammar.  If the crash
    244   // occurs after loaded/loadend, then we use ReportDeadNexe to send a
    245   // "crash" event.
    246   if (nexe_error_reported_) {
    247     VLOG(1) << "Plugin::NexeDidCrash: error already reported; suppressing";
    248   } else {
    249     if (nacl_ready_state_ == PP_NACL_READY_STATE_DONE) {
    250       ReportDeadNexe();
    251     } else {
    252       ReportLoadError(PP_NACL_ERROR_START_PROXY_CRASH,
    253                       "Nexe crashed during startup");
    254     }
    255   }
    256   // In all cases, try to grab the crash log.  The first error
    257   // reported may have come from the start_module RPC reply indicating
    258   // a validation error or something similar, which wouldn't grab the
    259   // crash log.  In the event that this is called twice, the second
    260   // invocation will just be a no-op, since the entire crash log will
    261   // have been received and we'll just get an EOF indication.
    262   CopyCrashLogToJsConsole(crash_log);
    263 }
    264 
    265 void NexeLoadManager::set_trusted_plugin_channel(
    266     scoped_ptr<TrustedPluginChannel> channel) {
    267   trusted_plugin_channel_ = channel.Pass();
    268 }
    269 
    270 void NexeLoadManager::set_manifest_service_channel(
    271     scoped_ptr<ManifestServiceChannel> channel) {
    272   manifest_service_channel_ = channel.Pass();
    273 }
    274 
    275 PP_NaClReadyState NexeLoadManager::nacl_ready_state() {
    276   return nacl_ready_state_;
    277 }
    278 
    279 void NexeLoadManager::set_nacl_ready_state(PP_NaClReadyState ready_state) {
    280   nacl_ready_state_ = ready_state;
    281   ppapi::ScopedPPVar ready_state_name(
    282       ppapi::ScopedPPVar::PassRef(),
    283       ppapi::StringVar::StringToPPVar("readyState"));
    284   SetReadOnlyProperty(ready_state_name.get(), PP_MakeInt32(ready_state));
    285 }
    286 
    287 void NexeLoadManager::SetLastError(const std::string& error) {
    288   ppapi::ScopedPPVar error_name_var(
    289       ppapi::ScopedPPVar::PassRef(),
    290       ppapi::StringVar::StringToPPVar("lastError"));
    291   ppapi::ScopedPPVar error_var(
    292       ppapi::ScopedPPVar::PassRef(),
    293       ppapi::StringVar::StringToPPVar(error));
    294   SetReadOnlyProperty(error_name_var.get(), error_var.get());
    295 }
    296 
    297 void NexeLoadManager::SetReadOnlyProperty(PP_Var key, PP_Var value) {
    298   plugin_instance_->SetEmbedProperty(key, value);
    299 }
    300 
    301 void NexeLoadManager::LogToConsole(const std::string& message) {
    302   ppapi::PpapiGlobals::Get()->LogWithSource(
    303       pp_instance_, PP_LOGLEVEL_LOG, std::string("NativeClient"), message);
    304 }
    305 
    306 void NexeLoadManager::set_exit_status(int exit_status) {
    307   exit_status_ = exit_status;
    308   ppapi::ScopedPPVar exit_status_name_var(
    309       ppapi::ScopedPPVar::PassRef(),
    310       ppapi::StringVar::StringToPPVar("exitStatus"));
    311   SetReadOnlyProperty(exit_status_name_var.get(), PP_MakeInt32(exit_status));
    312 }
    313 
    314 void NexeLoadManager::InitializePlugin(
    315     uint32_t argc, const char* argn[], const char* argv[]) {
    316   init_time_ = base::Time::Now();
    317 
    318   for (size_t i = 0; i < argc; ++i) {
    319     std::string name(argn[i]);
    320     std::string value(argv[i]);
    321     args_[name] = value;
    322   }
    323 
    324   // Store mime_type_ at initialization time since we make it lowercase.
    325   mime_type_ = StringToLowerASCII(LookupAttribute(args_, kTypeAttribute));
    326 }
    327 
    328 void NexeLoadManager::ReportStartupOverhead() const {
    329   base::TimeDelta overhead = base::Time::Now() - init_time_;
    330   HistogramStartupTimeMedium(
    331       "NaCl.Perf.StartupTime.NaClOverhead", overhead, nexe_size_);
    332 }
    333 
    334 bool NexeLoadManager::RequestNaClManifest(const std::string& url) {
    335   if (plugin_base_url_.is_valid()) {
    336     const GURL& resolved_url = plugin_base_url_.Resolve(url);
    337     if (resolved_url.is_valid()) {
    338       manifest_base_url_ = resolved_url;
    339       is_installed_ = manifest_base_url_.SchemeIs("chrome-extension");
    340       HistogramEnumerateManifestIsDataURI(
    341           manifest_base_url_.SchemeIs("data"));
    342       set_nacl_ready_state(PP_NACL_READY_STATE_OPENED);
    343       DispatchProgressEvent(pp_instance_,
    344                             ProgressEvent(PP_NACL_EVENT_LOADSTART));
    345       return true;
    346     }
    347   }
    348   ReportLoadError(PP_NACL_ERROR_MANIFEST_RESOLVE_URL,
    349                   std::string("could not resolve URL \"") + url +
    350                   "\" relative to \"" +
    351                   plugin_base_url_.possibly_invalid_spec() + "\".");
    352   return false;
    353 }
    354 
    355 void NexeLoadManager::ProcessNaClManifest(const std::string& program_url) {
    356   GURL gurl(program_url);
    357   DCHECK(gurl.is_valid());
    358   if (gurl.is_valid())
    359     is_installed_ = gurl.SchemeIs("chrome-extension");
    360   set_nacl_ready_state(PP_NACL_READY_STATE_LOADING);
    361   DispatchProgressEvent(pp_instance_, ProgressEvent(PP_NACL_EVENT_PROGRESS));
    362 }
    363 
    364 std::string NexeLoadManager::GetManifestURLArgument() const {
    365   std::string manifest_url;
    366 
    367   // If the MIME type is foreign, then this NEXE is being used as a content
    368   // type handler rather than directly by an HTML document.
    369   bool nexe_is_content_handler =
    370       !mime_type_.empty() &&
    371       mime_type_ != kNaClMIMEType &&
    372       mime_type_ != kPNaClMIMEType;
    373 
    374   if (nexe_is_content_handler) {
    375     // For content handlers 'src' will be the URL for the content
    376     // and 'nacl' will be the URL for the manifest.
    377     manifest_url = LookupAttribute(args_, kNaClManifestAttribute);
    378   } else {
    379     manifest_url = LookupAttribute(args_, kSrcManifestAttribute);
    380   }
    381 
    382   if (manifest_url.empty()) {
    383     VLOG(1) << "WARNING: no 'src' property, so no manifest loaded.";
    384     if (args_.find(kNaClManifestAttribute) != args_.end())
    385       VLOG(1) << "WARNING: 'nacl' property is incorrect. Use 'src'.";
    386   }
    387   return manifest_url;
    388 }
    389 
    390 bool NexeLoadManager::IsPNaCl() const {
    391   return mime_type_ == kPNaClMIMEType;
    392 }
    393 
    394 bool NexeLoadManager::DevInterfacesEnabled() const {
    395   // Look for the developer attribute; if it's present, enable 'dev'
    396   // interfaces.
    397   return args_.find(kDevAttribute) != args_.end();
    398 }
    399 
    400 void NexeLoadManager::ReportDeadNexe() {
    401   if (nacl_ready_state_ == PP_NACL_READY_STATE_DONE &&  // After loadEnd
    402       !nexe_error_reported_) {
    403     // Crashes will be more likely near startup, so use a medium histogram
    404     // instead of a large one.
    405     base::TimeDelta uptime = base::Time::Now() - ready_time_;
    406     HistogramTimeMedium("NaCl.ModuleUptime.Crash", uptime.InMilliseconds());
    407 
    408     std::string message("NaCl module crashed");
    409     SetLastError(message);
    410     LogToConsole(message);
    411 
    412     DispatchProgressEvent(pp_instance_, ProgressEvent(PP_NACL_EVENT_CRASH));
    413     nexe_error_reported_ = true;
    414   }
    415   // else ReportLoadError() and ReportLoadAbort() will be used by loading code
    416   // to provide error handling.
    417 }
    418 
    419 void NexeLoadManager::CopyCrashLogToJsConsole(const std::string& crash_log) {
    420   base::StringTokenizer t(crash_log, "\n");
    421   while (t.GetNext())
    422     LogToConsole(t.token());
    423 }
    424 
    425 }  // namespace nacl
    426