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.
5 #include "components/nacl/renderer/nexe_load_manager.h"
7 #include "base/command_line.h"
8 #include "base/containers/scoped_ptr_hash_map.h"
9 #include "base/lazy_instance.h"
10 #include "base/logging.h"
11 #include "base/metrics/histogram.h"
12 #include "base/strings/string_tokenizer.h"
13 #include "base/strings/string_util.h"
14 #include "components/nacl/common/nacl_host_messages.h"
15 #include "components/nacl/common/nacl_types.h"
16 #include "components/nacl/renderer/histogram.h"
17 #include "components/nacl/renderer/manifest_service_channel.h"
18 #include "components/nacl/renderer/platform_info.h"
19 #include "components/nacl/renderer/pnacl_translation_resource_host.h"
20 #include "components/nacl/renderer/progress_event.h"
21 #include "components/nacl/renderer/trusted_plugin_channel.h"
22 #include "content/public/common/content_client.h"
23 #include "content/public/common/content_switches.h"
24 #include "content/public/common/sandbox_init.h"
25 #include "content/public/renderer/pepper_plugin_instance.h"
26 #include "content/public/renderer/render_thread.h"
27 #include "content/public/renderer/render_view.h"
28 #include "content/public/renderer/renderer_ppapi_host.h"
29 #include "ppapi/c/pp_bool.h"
30 #include "ppapi/c/private/pp_file_handle.h"
31 #include "ppapi/shared_impl/ppapi_globals.h"
32 #include "ppapi/shared_impl/ppapi_permissions.h"
33 #include "ppapi/shared_impl/ppapi_preferences.h"
34 #include "ppapi/shared_impl/scoped_pp_var.h"
35 #include "ppapi/shared_impl/var.h"
36 #include "ppapi/shared_impl/var_tracker.h"
37 #include "ppapi/thunk/enter.h"
38 #include "third_party/WebKit/public/web/WebDocument.h"
39 #include "third_party/WebKit/public/web/WebElement.h"
40 #include "third_party/WebKit/public/web/WebPluginContainer.h"
41 #include "third_party/WebKit/public/web/WebView.h"
42 #include "v8/include/v8.h"
48 const char* const kTypeAttribute
= "type";
49 // The "src" attribute of the <embed> tag. The value is expected to be either
50 // a URL or URI pointing to the manifest file (which is expected to contain
51 // JSON matching ISAs with .nexe URLs).
52 const char* const kSrcManifestAttribute
= "src";
53 // The "nacl" attribute of the <embed> tag. We use the value of this attribute
54 // to find the manifest file when NaCl is registered as a plug-in for another
55 // MIME type because the "src" attribute is used to supply us with the resource
56 // of that MIME type that we're supposed to display.
57 const char* const kNaClManifestAttribute
= "nacl";
58 // Define an argument name to enable 'dev' interfaces. To make sure it doesn't
59 // collide with any user-defined HTML attribute, make the first character '@'.
60 const char* const kDevAttribute
= "@dev";
62 const char* const kNaClMIMEType
= "application/x-nacl";
63 const char* const kPNaClMIMEType
= "application/x-pnacl";
65 static int GetRoutingID(PP_Instance instance
) {
66 // Check that we are on the main renderer thread.
67 DCHECK(content::RenderThread::Get());
68 content::RendererPpapiHost
*host
=
69 content::RendererPpapiHost::GetForPPInstance(instance
);
72 return host
->GetRoutingIDForWidget(instance
);
75 std::string
LookupAttribute(const std::map
<std::string
, std::string
>& args
,
76 const std::string
& key
) {
77 std::map
<std::string
, std::string
>::const_iterator it
= args
.find(key
);
83 typedef base::ScopedPtrHashMap
<PP_Instance
, NexeLoadManager
> NexeLoadManagerMap
;
84 base::LazyInstance
<NexeLoadManagerMap
> g_load_manager_map
=
85 LAZY_INSTANCE_INITIALIZER
;
89 NexeLoadManager::NexeLoadManager(
90 PP_Instance pp_instance
)
91 : pp_instance_(pp_instance
),
92 nacl_ready_state_(PP_NACL_READY_STATE_UNSENT
),
93 nexe_error_reported_(false),
97 plugin_instance_(content::PepperPluginInstance::Get(pp_instance
)),
98 crash_info_shmem_handle_(base::SharedMemory::NULLHandle()),
102 HistogramEnumerateOsArch(GetSandboxArch());
103 if (plugin_instance_
) {
105 plugin_instance_
->GetContainer()->element().document().url();
109 NexeLoadManager::~NexeLoadManager() {
110 if (!nexe_error_reported_
) {
111 base::TimeDelta uptime
= base::Time::Now() - ready_time_
;
112 HistogramTimeLarge("NaCl.ModuleUptime.Normal", uptime
.InMilliseconds());
114 if (base::SharedMemory::IsHandleValid(crash_info_shmem_handle_
))
115 base::SharedMemory::CloseHandle(crash_info_shmem_handle_
);
118 void NexeLoadManager::Create(PP_Instance instance
) {
119 scoped_ptr
<NexeLoadManager
> new_load_manager(new NexeLoadManager(instance
));
120 NexeLoadManagerMap
& map
= g_load_manager_map
.Get();
121 DLOG_IF(ERROR
, map
.count(instance
) != 0) << "Instance count should be 0";
122 map
.add(instance
, new_load_manager
.Pass());
125 NexeLoadManager
* NexeLoadManager::Get(PP_Instance instance
) {
126 NexeLoadManagerMap
& map
= g_load_manager_map
.Get();
127 NexeLoadManagerMap::iterator iter
= map
.find(instance
);
128 if (iter
!= map
.end())
133 void NexeLoadManager::Delete(PP_Instance instance
) {
134 NexeLoadManagerMap
& map
= g_load_manager_map
.Get();
135 // The erase may call NexeLoadManager's destructor prior to removing it from
136 // the map. In that case, it is possible for the trusted Plugin to re-enter
137 // the NexeLoadManager (e.g., by calling ReportLoadError). Passing out the
138 // NexeLoadManager to a local scoped_ptr just ensures that its entry is gone
139 // from the map prior to the destructor being invoked.
140 scoped_ptr
<NexeLoadManager
> temp(map
.take(instance
));
144 void NexeLoadManager::NexeFileDidOpen(int32_t pp_error
,
145 const base::File
& file
,
147 int64_t nexe_bytes_read
,
148 const std::string
& url
,
149 base::TimeDelta time_since_open
) {
150 // Check that we are on the main renderer thread.
151 DCHECK(content::RenderThread::Get());
152 VLOG(1) << "Plugin::NexeFileDidOpen (pp_error=" << pp_error
<< ")";
153 HistogramHTTPStatusCode(
154 is_installed_
? "NaCl.HttpStatusCodeClass.Nexe.InstalledApp" :
155 "NaCl.HttpStatusCodeClass.Nexe.NotInstalledApp",
158 if (pp_error
!= PP_OK
|| !file
.IsValid()) {
159 if (pp_error
== PP_ERROR_ABORTED
) {
161 } else if (pp_error
== PP_ERROR_NOACCESS
) {
162 ReportLoadError(PP_NACL_ERROR_NEXE_NOACCESS_URL
,
163 "access to nexe url was denied.");
165 ReportLoadError(PP_NACL_ERROR_NEXE_LOAD_URL
,
166 "could not load nexe url.");
168 } else if (nexe_bytes_read
== -1) {
169 ReportLoadError(PP_NACL_ERROR_NEXE_STAT
, "could not stat nexe file.");
171 // TODO(dmichael): Can we avoid stashing away so much state?
172 nexe_size_
= nexe_bytes_read
;
173 HistogramSizeKB("NaCl.Perf.Size.Nexe",
174 static_cast<int32_t>(nexe_size_
/ 1024));
175 HistogramStartupTimeMedium(
176 "NaCl.Perf.StartupTime.NexeDownload", time_since_open
, nexe_size_
);
178 // Inform JavaScript that we successfully downloaded the nacl module.
179 ProgressEvent
progress_event(PP_NACL_EVENT_PROGRESS
, url
, true, nexe_size_
,
181 DispatchProgressEvent(pp_instance_
, progress_event
);
182 load_start_
= base::Time::Now();
186 void NexeLoadManager::ReportLoadSuccess(const std::string
& url
,
187 uint64_t loaded_bytes
,
188 uint64_t total_bytes
) {
189 ready_time_
= base::Time::Now();
191 base::TimeDelta load_module_time
= ready_time_
- load_start_
;
192 HistogramStartupTimeSmall(
193 "NaCl.Perf.StartupTime.LoadModule", load_module_time
, nexe_size_
);
194 HistogramStartupTimeMedium(
195 "NaCl.Perf.StartupTime.Total", ready_time_
- init_time_
, nexe_size_
);
198 // Check that we are on the main renderer thread.
199 DCHECK(content::RenderThread::Get());
200 set_nacl_ready_state(PP_NACL_READY_STATE_DONE
);
202 // Inform JavaScript that loading was successful and is complete.
203 ProgressEvent
load_event(PP_NACL_EVENT_LOAD
, url
, true, loaded_bytes
,
205 DispatchProgressEvent(pp_instance_
, load_event
);
207 ProgressEvent
loadend_event(PP_NACL_EVENT_LOADEND
, url
, true, loaded_bytes
,
209 DispatchProgressEvent(pp_instance_
, loadend_event
);
212 HistogramEnumerateLoadStatus(PP_NACL_ERROR_LOAD_SUCCESS
, is_installed_
);
215 void NexeLoadManager::ReportLoadError(PP_NaClError error
,
216 const std::string
& error_message
) {
217 ReportLoadError(error
, error_message
, error_message
);
220 void NexeLoadManager::ReportLoadError(PP_NaClError error
,
221 const std::string
& error_message
,
222 const std::string
& console_message
) {
223 // Check that we are on the main renderer thread.
224 DCHECK(content::RenderThread::Get());
226 if (error
== PP_NACL_ERROR_MANIFEST_PROGRAM_MISSING_ARCH
) {
227 // A special case: the manifest may otherwise be valid but is missing
228 // a program/file compatible with the user's sandbox.
229 IPC::Sender
* sender
= content::RenderThread::Get();
231 new NaClHostMsg_MissingArchError(GetRoutingID(pp_instance_
)));
233 set_nacl_ready_state(PP_NACL_READY_STATE_DONE
);
234 nexe_error_reported_
= true;
236 // We must set all properties before calling DispatchEvent so that when an
237 // event handler runs, the properties reflect the current load state.
238 std::string error_string
= std::string("NaCl module load failed: ") +
239 std::string(error_message
);
240 SetLastError(error_string
);
242 // Inform JavaScript that loading encountered an error and is complete.
243 DispatchProgressEvent(pp_instance_
, ProgressEvent(PP_NACL_EVENT_ERROR
));
244 DispatchProgressEvent(pp_instance_
, ProgressEvent(PP_NACL_EVENT_LOADEND
));
246 HistogramEnumerateLoadStatus(error
, is_installed_
);
247 LogToConsole(console_message
);
250 void NexeLoadManager::ReportLoadAbort() {
251 // Check that we are on the main renderer thread.
252 DCHECK(content::RenderThread::Get());
254 // Set the readyState attribute to indicate we need to start over.
255 set_nacl_ready_state(PP_NACL_READY_STATE_DONE
);
256 nexe_error_reported_
= true;
258 // Report an error in lastError and on the JavaScript console.
259 std::string
error_string("NaCl module load failed: user aborted");
260 SetLastError(error_string
);
262 // Inform JavaScript that loading was aborted and is complete.
263 DispatchProgressEvent(pp_instance_
, ProgressEvent(PP_NACL_EVENT_ABORT
));
264 DispatchProgressEvent(pp_instance_
, ProgressEvent(PP_NACL_EVENT_LOADEND
));
266 HistogramEnumerateLoadStatus(PP_NACL_ERROR_LOAD_ABORTED
, is_installed_
);
267 LogToConsole(error_string
);
270 void NexeLoadManager::NexeDidCrash() {
271 VLOG(1) << "Plugin::NexeDidCrash: crash event!";
272 // The NaCl module voluntarily exited. However, this is still a
273 // crash from the point of view of Pepper, since PPAPI plugins are
274 // event handlers and should never exit.
275 VLOG_IF(1, exit_status_
!= -1)
276 << "Plugin::NexeDidCrash: nexe exited with status " << exit_status_
277 << " so this is a \"controlled crash\".";
278 // If the crash occurs during load, we just want to report an error
279 // that fits into our load progress event grammar. If the crash
280 // occurs after loaded/loadend, then we use ReportDeadNexe to send a
282 if (nexe_error_reported_
) {
283 VLOG(1) << "Plugin::NexeDidCrash: error already reported; suppressing";
285 if (nacl_ready_state_
== PP_NACL_READY_STATE_DONE
) {
288 ReportLoadError(PP_NACL_ERROR_START_PROXY_CRASH
,
289 "Nexe crashed during startup");
292 // In all cases, try to grab the crash log. The first error
293 // reported may have come from the start_module RPC reply indicating
294 // a validation error or something similar, which wouldn't grab the
295 // crash log. In the event that this is called twice, the second
296 // invocation will just be a no-op, since the entire crash log will
297 // have been received and we'll just get an EOF indication.
299 base::SharedMemory
shmem(crash_info_shmem_handle_
, true);
300 if (shmem
.Map(kNaClCrashInfoShmemSize
)) {
301 uint32_t crash_log_length
;
302 // We cast the length value to volatile here to prevent the compiler from
303 // reordering instructions in a way that could introduce a TOCTTOU race.
304 crash_log_length
= *(static_cast<volatile uint32_t*>(shmem
.memory()));
305 crash_log_length
= std::min
<uint32_t>(crash_log_length
,
306 kNaClCrashInfoMaxLogSize
);
308 scoped_ptr
<char[]> crash_log_data(new char[kNaClCrashInfoShmemSize
]);
309 memcpy(crash_log_data
.get(),
310 static_cast<char*>(shmem
.memory()) + sizeof(uint32_t),
312 std::string
crash_log(crash_log_data
.get(), crash_log_length
);
313 CopyCrashLogToJsConsole(crash_log
);
317 void NexeLoadManager::set_trusted_plugin_channel(
318 scoped_ptr
<TrustedPluginChannel
> channel
) {
319 trusted_plugin_channel_
= channel
.Pass();
322 void NexeLoadManager::set_manifest_service_channel(
323 scoped_ptr
<ManifestServiceChannel
> channel
) {
324 manifest_service_channel_
= channel
.Pass();
327 PP_NaClReadyState
NexeLoadManager::nacl_ready_state() {
328 return nacl_ready_state_
;
331 void NexeLoadManager::set_nacl_ready_state(PP_NaClReadyState ready_state
) {
332 nacl_ready_state_
= ready_state
;
333 ppapi::ScopedPPVar
ready_state_name(
334 ppapi::ScopedPPVar::PassRef(),
335 ppapi::StringVar::StringToPPVar("readyState"));
336 SetReadOnlyProperty(ready_state_name
.get(), PP_MakeInt32(ready_state
));
339 void NexeLoadManager::SetLastError(const std::string
& error
) {
340 ppapi::ScopedPPVar
error_name_var(
341 ppapi::ScopedPPVar::PassRef(),
342 ppapi::StringVar::StringToPPVar("lastError"));
343 ppapi::ScopedPPVar
error_var(
344 ppapi::ScopedPPVar::PassRef(),
345 ppapi::StringVar::StringToPPVar(error
));
346 SetReadOnlyProperty(error_name_var
.get(), error_var
.get());
349 void NexeLoadManager::SetReadOnlyProperty(PP_Var key
, PP_Var value
) {
350 plugin_instance_
->SetEmbedProperty(key
, value
);
353 void NexeLoadManager::LogToConsole(const std::string
& message
) {
354 ppapi::PpapiGlobals::Get()->LogWithSource(
355 pp_instance_
, PP_LOGLEVEL_LOG
, std::string("NativeClient"), message
);
358 void NexeLoadManager::set_exit_status(int exit_status
) {
359 exit_status_
= exit_status
;
360 ppapi::ScopedPPVar
exit_status_name_var(
361 ppapi::ScopedPPVar::PassRef(),
362 ppapi::StringVar::StringToPPVar("exitStatus"));
363 SetReadOnlyProperty(exit_status_name_var
.get(), PP_MakeInt32(exit_status
));
366 void NexeLoadManager::InitializePlugin(
367 uint32_t argc
, const char* argn
[], const char* argv
[]) {
368 init_time_
= base::Time::Now();
370 for (size_t i
= 0; i
< argc
; ++i
) {
371 std::string
name(argn
[i
]);
372 std::string
value(argv
[i
]);
376 // Store mime_type_ at initialization time since we make it lowercase.
377 mime_type_
= base::StringToLowerASCII(LookupAttribute(args_
, kTypeAttribute
));
380 void NexeLoadManager::ReportStartupOverhead() const {
381 base::TimeDelta overhead
= base::Time::Now() - init_time_
;
382 HistogramStartupTimeMedium(
383 "NaCl.Perf.StartupTime.NaClOverhead", overhead
, nexe_size_
);
386 bool NexeLoadManager::RequestNaClManifest(const std::string
& url
) {
387 if (plugin_base_url_
.is_valid()) {
388 const GURL
& resolved_url
= plugin_base_url_
.Resolve(url
);
389 if (resolved_url
.is_valid()) {
390 manifest_base_url_
= resolved_url
;
391 is_installed_
= manifest_base_url_
.SchemeIs("chrome-extension");
392 HistogramEnumerateManifestIsDataURI(
393 manifest_base_url_
.SchemeIs("data"));
394 set_nacl_ready_state(PP_NACL_READY_STATE_OPENED
);
395 DispatchProgressEvent(pp_instance_
,
396 ProgressEvent(PP_NACL_EVENT_LOADSTART
));
400 ReportLoadError(PP_NACL_ERROR_MANIFEST_RESOLVE_URL
,
401 std::string("could not resolve URL \"") + url
+
402 "\" relative to \"" +
403 plugin_base_url_
.possibly_invalid_spec() + "\".");
407 void NexeLoadManager::ProcessNaClManifest(const std::string
& program_url
) {
408 program_url_
= program_url
;
409 GURL
gurl(program_url
);
410 DCHECK(gurl
.is_valid());
412 is_installed_
= gurl
.SchemeIs("chrome-extension");
413 set_nacl_ready_state(PP_NACL_READY_STATE_LOADING
);
414 DispatchProgressEvent(pp_instance_
, ProgressEvent(PP_NACL_EVENT_PROGRESS
));
417 std::string
NexeLoadManager::GetManifestURLArgument() const {
418 std::string manifest_url
;
420 // If the MIME type is foreign, then this NEXE is being used as a content
421 // type handler rather than directly by an HTML document.
422 bool nexe_is_content_handler
=
423 !mime_type_
.empty() &&
424 mime_type_
!= kNaClMIMEType
&&
425 mime_type_
!= kPNaClMIMEType
;
427 if (nexe_is_content_handler
) {
428 // For content handlers 'src' will be the URL for the content
429 // and 'nacl' will be the URL for the manifest.
430 manifest_url
= LookupAttribute(args_
, kNaClManifestAttribute
);
432 manifest_url
= LookupAttribute(args_
, kSrcManifestAttribute
);
435 if (manifest_url
.empty()) {
436 VLOG(1) << "WARNING: no 'src' property, so no manifest loaded.";
437 if (args_
.find(kNaClManifestAttribute
) != args_
.end())
438 VLOG(1) << "WARNING: 'nacl' property is incorrect. Use 'src'.";
443 bool NexeLoadManager::IsPNaCl() const {
444 return mime_type_
== kPNaClMIMEType
;
447 bool NexeLoadManager::DevInterfacesEnabled() const {
448 // Look for the developer attribute; if it's present, enable 'dev'
450 return args_
.find(kDevAttribute
) != args_
.end();
453 void NexeLoadManager::ReportDeadNexe() {
454 if (nacl_ready_state_
== PP_NACL_READY_STATE_DONE
&& // After loadEnd
455 !nexe_error_reported_
) {
456 // Crashes will be more likely near startup, so use a medium histogram
457 // instead of a large one.
458 base::TimeDelta uptime
= base::Time::Now() - ready_time_
;
459 HistogramTimeMedium("NaCl.ModuleUptime.Crash", uptime
.InMilliseconds());
461 std::string
message("NaCl module crashed");
462 SetLastError(message
);
463 LogToConsole(message
);
465 DispatchProgressEvent(pp_instance_
, ProgressEvent(PP_NACL_EVENT_CRASH
));
466 nexe_error_reported_
= true;
468 // else ReportLoadError() and ReportLoadAbort() will be used by loading code
469 // to provide error handling.
472 void NexeLoadManager::CopyCrashLogToJsConsole(const std::string
& crash_log
) {
473 base::StringTokenizer
t(crash_log
, "\n");
475 LogToConsole(t
.token());