cc: Added inline to Tile::IsReadyToDraw
[chromium-blink-merge.git] / ppapi / native_client / src / trusted / plugin / json_manifest.cc
blob0928c05f2cedd99974329ac32bb6203b15e41126
1 /*
2 * Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 * Use of this source code is governed by a BSD-style license that can be
4 * found in the LICENSE file.
5 */
7 #include <algorithm>
9 #include "ppapi/native_client/src/trusted/plugin/json_manifest.h"
11 #include <stdlib.h>
13 #include "native_client/src/include/nacl_base.h"
14 #include "native_client/src/include/nacl_macros.h"
15 #include "native_client/src/include/nacl_string.h"
16 #include "native_client/src/include/portability.h"
17 #include "native_client/src/shared/platform/nacl_check.h"
18 #include "ppapi/cpp/dev/url_util_dev.h"
19 #include "ppapi/cpp/var.h"
20 #include "ppapi/native_client/src/trusted/plugin/plugin_error.h"
21 #include "ppapi/native_client/src/trusted/plugin/pnacl_options.h"
22 #include "ppapi/native_client/src/trusted/plugin/utility.h"
23 #include "third_party/jsoncpp/source/include/json/reader.h"
25 namespace plugin {
27 namespace {
28 // Top-level section name keys
29 const char* const kProgramKey = "program";
30 const char* const kInterpreterKey = "interpreter";
31 const char* const kFilesKey = "files";
33 // ISA Dictionary keys
34 const char* const kX8632Key = "x86-32";
35 const char* const kX8664Key = "x86-64";
36 const char* const kArmKey = "arm";
37 const char* const kPortableKey = "portable";
39 // Url Resolution keys
40 const char* const kPnaclTranslateKey = "pnacl-translate";
41 const char* const kUrlKey = "url";
43 // PNaCl keys
44 const char* const kOptLevelKey = "optlevel";
45 // DEPRECATED! TODO(jvoung): remove the error message after launch.
46 const char* const kOptLevelKeyDeprecated = "-O";
48 // Sample NaCl manifest file:
49 // {
50 // "program": {
51 // "x86-32": {"url": "myprogram_x86-32.nexe"},
52 // "x86-64": {"url": "myprogram_x86-64.nexe"},
53 // "arm": {"url": "myprogram_arm.nexe"}
54 // },
55 // "interpreter": {
56 // "x86-32": {"url": "interpreter_x86-32.nexe"},
57 // "x86-64": {"url": "interpreter_x86-64.nexe"},
58 // "arm": {"url": "interpreter_arm.nexe"}
59 // },
60 // "files": {
61 // "foo.txt": {
62 // "portable": {"url": "foo.txt"}
63 // },
64 // "bar.txt": {
65 // "x86-32": {"url": "x86-32/bar.txt"},
66 // "portable": {"url": "bar.txt"}
67 // },
68 // "libfoo.so": {
69 // "x86-64" : { "url": "..." }
70 // }
71 // }
72 // }
74 // Sample PNaCl manifest file:
75 // {
76 // "program": {
77 // "portable": {
78 // "pnacl-translate": {
79 // "url": "myprogram.pexe",
80 // "optlevel": 0
81 // }
82 // }
83 // },
84 // "files": {
85 // "foo.txt": {
86 // "portable": {"url": "foo.txt"}
87 // },
88 // "bar.txt": {
89 // "portable": {"url": "bar.txt"}
90 // }
91 // }
92 // }
94 // Looks up |property_name| in the vector |valid_names| with length
95 // |valid_name_count|. Returns true if |property_name| is found.
96 bool FindMatchingProperty(const nacl::string& property_name,
97 const char** valid_names,
98 size_t valid_name_count) {
99 for (size_t i = 0; i < valid_name_count; ++i) {
100 if (property_name == valid_names[i]) {
101 return true;
104 return false;
107 // Return true if this is a valid dictionary. Having only keys present in
108 // |valid_keys| and having at least the keys in |required_keys|.
109 // Error messages will be placed in |error_string|, given that the dictionary
110 // was the property value of |container_key|.
111 // E.g., "container_key" : dictionary
112 bool IsValidDictionary(const Json::Value& dictionary,
113 const nacl::string& container_key,
114 const nacl::string& parent_key,
115 const char** valid_keys,
116 size_t valid_key_count,
117 const char** required_keys,
118 size_t required_key_count,
119 nacl::string* error_string) {
120 if (!dictionary.isObject()) {
121 nacl::stringstream error_stream;
122 error_stream << parent_key << " property '" << container_key
123 << "' is non-dictionary value '"
124 << dictionary.toStyledString() << "'.";
125 *error_string = error_stream.str();
126 return false;
128 // Check for unknown dictionary members.
129 Json::Value::Members members = dictionary.getMemberNames();
130 for (size_t i = 0; i < members.size(); ++i) {
131 nacl::string property_name = members[i];
132 if (!FindMatchingProperty(property_name,
133 valid_keys,
134 valid_key_count)) {
135 // For forward compatibility, we do not prohibit other keys being in
136 // the dictionary.
137 PLUGIN_PRINTF(("WARNING: '%s' property '%s' has unknown key '%s'.\n",
138 parent_key.c_str(),
139 container_key.c_str(), property_name.c_str()));
142 // Check for required members.
143 for (size_t i = 0; i < required_key_count; ++i) {
144 if (!dictionary.isMember(required_keys[i])) {
145 nacl::stringstream error_stream;
146 error_stream << parent_key << " property '" << container_key
147 << "' does not have required key: '"
148 << required_keys[i] << "'.";
149 *error_string = error_stream.str();
150 return false;
153 return true;
156 // Validate a "url" dictionary assuming it was resolved from container_key.
157 // E.g., "container_key" : { "url": "foo.txt" }
158 bool IsValidUrlSpec(const Json::Value& url_spec,
159 const nacl::string& container_key,
160 const nacl::string& parent_key,
161 const nacl::string& sandbox_isa,
162 nacl::string* error_string) {
163 static const char* kManifestUrlSpecRequired[] = {
164 kUrlKey
166 const char** urlSpecPlusOptional;
167 size_t urlSpecPlusOptionalLength;
168 if (sandbox_isa == kPortableKey) {
169 static const char* kPnaclUrlSpecPlusOptional[] = {
170 kUrlKey,
171 kOptLevelKey,
173 urlSpecPlusOptional = kPnaclUrlSpecPlusOptional;
174 urlSpecPlusOptionalLength = NACL_ARRAY_SIZE(kPnaclUrlSpecPlusOptional);
175 } else {
176 // URL specifications must not contain "pnacl-translate" keys.
177 // This prohibits NaCl clients from invoking PNaCl.
178 if (url_spec.isMember(kPnaclTranslateKey)) {
179 nacl::stringstream error_stream;
180 error_stream << "PNaCl-like NMF with application/x-nacl mimetype instead "
181 << "of x-pnacl mimetype (has " << kPnaclTranslateKey << ").";
182 *error_string = error_stream.str();
183 return false;
185 urlSpecPlusOptional = kManifestUrlSpecRequired;
186 urlSpecPlusOptionalLength = NACL_ARRAY_SIZE(kManifestUrlSpecRequired);
188 if (!IsValidDictionary(url_spec, container_key, parent_key,
189 urlSpecPlusOptional,
190 urlSpecPlusOptionalLength,
191 kManifestUrlSpecRequired,
192 NACL_ARRAY_SIZE(kManifestUrlSpecRequired),
193 error_string)) {
194 return false;
196 // Verify the correct types of the fields if they exist.
197 Json::Value url = url_spec[kUrlKey];
198 if (!url.isString()) {
199 nacl::stringstream error_stream;
200 error_stream << parent_key << " property '" << container_key <<
201 "' has non-string value '" << url.toStyledString() <<
202 "' for key '" << kUrlKey << "'.";
203 *error_string = error_stream.str();
204 return false;
206 Json::Value opt_level = url_spec[kOptLevelKey];
207 if (!opt_level.empty() && !opt_level.isNumeric()) {
208 nacl::stringstream error_stream;
209 error_stream << parent_key << " property '" << container_key <<
210 "' has non-numeric value '" << opt_level.toStyledString() <<
211 "' for key '" << kOptLevelKey << "'.";
212 *error_string = error_stream.str();
213 return false;
215 if (url_spec.isMember(kOptLevelKeyDeprecated)) {
216 nacl::stringstream error_stream;
217 error_stream << parent_key << " property '" << container_key <<
218 "' has deprecated key '" << kOptLevelKeyDeprecated <<
219 "' please use '" << kOptLevelKey << "' instead.";
220 *error_string = error_stream.str();
221 return false;
223 return true;
226 // Validate a "pnacl-translate" dictionary, assuming it was resolved from
227 // container_key. E.g., "container_key" : { "pnacl_translate" : URLSpec }
228 bool IsValidPnaclTranslateSpec(const Json::Value& pnacl_spec,
229 const nacl::string& container_key,
230 const nacl::string& parent_key,
231 const nacl::string& sandbox_isa,
232 nacl::string* error_string) {
233 static const char* kManifestPnaclSpecProperties[] = {
234 kPnaclTranslateKey
236 if (!IsValidDictionary(pnacl_spec, container_key, parent_key,
237 kManifestPnaclSpecProperties,
238 NACL_ARRAY_SIZE(kManifestPnaclSpecProperties),
239 kManifestPnaclSpecProperties,
240 NACL_ARRAY_SIZE(kManifestPnaclSpecProperties),
241 error_string)) {
242 return false;
244 Json::Value url_spec = pnacl_spec[kPnaclTranslateKey];
245 if (!IsValidUrlSpec(url_spec, kPnaclTranslateKey,
246 container_key, sandbox_isa, error_string)) {
247 return false;
249 return true;
252 // Validates that |dictionary| is a valid ISA dictionary. An ISA dictionary
253 // is validated to have keys from within the set of recognized ISAs. Unknown
254 // ISAs are allowed, but ignored and warnings are produced. It is also validated
255 // that it must have an entry to match the ISA specified in |sandbox_isa| or
256 // have a fallback 'portable' entry if there is no match. Returns true if
257 // |dictionary| is an ISA to URL map. Sets |error_info| to something
258 // descriptive if it fails.
259 bool IsValidISADictionary(const Json::Value& dictionary,
260 const nacl::string& parent_key,
261 const nacl::string& sandbox_isa,
262 bool must_find_matching_entry,
263 ErrorInfo* error_info) {
264 if (error_info == NULL) return false;
266 // An ISA to URL dictionary has to be an object.
267 if (!dictionary.isObject()) {
268 error_info->SetReport(ERROR_MANIFEST_SCHEMA_VALIDATE,
269 nacl::string("manifest: ") + parent_key +
270 " property is not an ISA to URL dictionary");
271 return false;
273 // Build the set of reserved ISA dictionary keys.
274 const char** isaProperties;
275 size_t isaPropertiesLength;
276 if (sandbox_isa == kPortableKey) {
277 // The known values for PNaCl ISA dictionaries in the manifest.
278 static const char* kPnaclManifestISAProperties[] = {
279 kPortableKey
281 isaProperties = kPnaclManifestISAProperties;
282 isaPropertiesLength = NACL_ARRAY_SIZE(kPnaclManifestISAProperties);
283 } else {
284 // The known values for NaCl ISA dictionaries in the manifest.
285 static const char* kNaClManifestISAProperties[] = {
286 kX8632Key,
287 kX8664Key,
288 kArmKey,
289 // "portable" is here to allow checking that, if present, it can
290 // only refer to an URL, such as for a data file, and not to
291 // "pnacl-translate", which would cause the creation of a nexe.
292 kPortableKey
294 isaProperties = kNaClManifestISAProperties;
295 isaPropertiesLength = NACL_ARRAY_SIZE(kNaClManifestISAProperties);
297 // Check that entries in the dictionary are structurally correct.
298 Json::Value::Members members = dictionary.getMemberNames();
299 for (size_t i = 0; i < members.size(); ++i) {
300 nacl::string property_name = members[i];
301 Json::Value property_value = dictionary[property_name];
302 nacl::string error_string;
303 if (FindMatchingProperty(property_name,
304 isaProperties,
305 isaPropertiesLength)) {
306 // For NaCl, arch entries can only be
307 // "arch/portable" : URLSpec
308 // For PNaCl arch in "program" dictionary entries can only be
309 // "portable" : { "pnacl-translate": URLSpec }
310 // For PNaCl arch elsewhere, dictionary entries can only be
311 // "portable" : URLSpec
312 if ((sandbox_isa != kPortableKey &&
313 !IsValidUrlSpec(property_value, property_name, parent_key,
314 sandbox_isa, &error_string)) ||
315 (sandbox_isa == kPortableKey &&
316 parent_key == kProgramKey &&
317 !IsValidPnaclTranslateSpec(property_value, property_name, parent_key,
318 sandbox_isa, &error_string)) ||
319 (sandbox_isa == kPortableKey &&
320 parent_key != kProgramKey &&
321 !IsValidUrlSpec(property_value, property_name, parent_key,
322 sandbox_isa, &error_string))) {
323 error_info->SetReport(ERROR_MANIFEST_SCHEMA_VALIDATE,
324 nacl::string("manifest: ") + error_string);
325 return false;
327 } else {
328 // For forward compatibility, we do not prohibit other keys being in
329 // the dictionary, as they may be architectures supported in later
330 // versions. However, the value of these entries must be an URLSpec.
331 PLUGIN_PRINTF(("IsValidISADictionary: unrecognized key '%s'.\n",
332 property_name.c_str()));
333 if (!IsValidUrlSpec(property_value, property_name, parent_key,
334 sandbox_isa, &error_string)) {
335 error_info->SetReport(ERROR_MANIFEST_SCHEMA_VALIDATE,
336 nacl::string("manifest: ") + error_string);
337 return false;
342 if (sandbox_isa == kPortableKey) {
343 bool has_portable = dictionary.isMember(kPortableKey);
345 if (!has_portable) {
346 error_info->SetReport(
347 ERROR_MANIFEST_PROGRAM_MISSING_ARCH,
348 nacl::string("manifest: no version of ") + parent_key +
349 " given for portable.");
350 return false;
352 } else if (must_find_matching_entry) {
353 // TODO(elijahtaylor) add ISA resolver here if we expand ISAs to include
354 // micro-architectures that can resolve to multiple valid sandboxes.
355 bool has_isa = dictionary.isMember(sandbox_isa);
356 bool has_portable = dictionary.isMember(kPortableKey);
358 if (!has_isa && !has_portable) {
359 error_info->SetReport(
360 ERROR_MANIFEST_PROGRAM_MISSING_ARCH,
361 nacl::string("manifest: no version of ") + parent_key +
362 " given for current arch and no portable version found.");
363 return false;
367 return true;
370 void GrabUrlAndPnaclOptions(const Json::Value& url_spec,
371 nacl::string* url,
372 PnaclOptions* pnacl_options) {
373 *url = url_spec[kUrlKey].asString();
374 if (url_spec.isMember(kOptLevelKey)) {
375 int32_t opt_raw = url_spec[kOptLevelKey].asInt();
376 // set_opt_level will normalize the values.
377 pnacl_options->set_opt_level(opt_raw);
381 bool GetURLFromISADictionary(const Json::Value& dictionary,
382 const nacl::string& parent_key,
383 const nacl::string& sandbox_isa,
384 nacl::string* url,
385 PnaclOptions* pnacl_options,
386 ErrorInfo* error_info) {
387 if (url == NULL || pnacl_options == NULL || error_info == NULL)
388 return false;
390 // When the application actually requests a resolved URL, we must have
391 // a matching entry (sandbox_isa or portable) for NaCl.
392 if (!IsValidISADictionary(dictionary, parent_key, sandbox_isa, true,
393 error_info)) {
394 error_info->SetReport(ERROR_MANIFEST_RESOLVE_URL,
395 "architecture " + sandbox_isa +
396 " is not found for file " + parent_key);
397 return false;
400 *url = "";
402 // The call to IsValidISADictionary() above guarantees that either
403 // sandbox_isa or kPortableKey is present in the dictionary.
404 bool has_portable = dictionary.isMember(kPortableKey);
405 bool has_isa = dictionary.isMember(sandbox_isa);
406 nacl::string chosen_isa;
407 if ((sandbox_isa == kPortableKey) || (has_portable && !has_isa)) {
408 chosen_isa = kPortableKey;
409 } else {
410 chosen_isa = sandbox_isa;
412 const Json::Value& isa_spec = dictionary[chosen_isa];
413 // Check if this requires a pnacl-translate, otherwise just grab the URL.
414 // We may have pnacl-translate for isa-specific bitcode for CPU tuning.
415 if (isa_spec.isMember(kPnaclTranslateKey)) {
416 // PNaCl
417 GrabUrlAndPnaclOptions(isa_spec[kPnaclTranslateKey], url, pnacl_options);
418 pnacl_options->set_translate(true);
419 } else {
420 // NaCl
421 *url = isa_spec[kUrlKey].asString();
422 pnacl_options->set_translate(false);
425 return true;
428 bool GetKeyUrl(const Json::Value& dictionary,
429 const nacl::string& key,
430 const nacl::string& sandbox_isa,
431 const Manifest* manifest,
432 nacl::string* full_url,
433 PnaclOptions* pnacl_options,
434 ErrorInfo* error_info) {
435 CHECK(full_url != NULL && error_info != NULL);
436 if (!dictionary.isMember(key)) {
437 error_info->SetReport(ERROR_MANIFEST_RESOLVE_URL,
438 "file key not found in manifest");
439 return false;
441 const Json::Value& isa_dict = dictionary[key];
442 nacl::string relative_url;
443 if (!GetURLFromISADictionary(isa_dict, key, sandbox_isa, &relative_url,
444 pnacl_options, error_info)) {
445 return false;
447 return manifest->ResolveURL(relative_url, full_url, error_info);
450 } // namespace
452 bool JsonManifest::Init(const nacl::string& manifest_json,
453 ErrorInfo* error_info) {
454 if (error_info == NULL) {
455 return false;
457 Json::Reader reader;
458 if (!reader.parse(manifest_json, dictionary_)) {
459 std::string json_error = reader.getFormatedErrorMessages();
460 error_info->SetReport(ERROR_MANIFEST_PARSING,
461 "manifest JSON parsing failed: " + json_error);
462 return false;
464 // Parse has ensured the string was valid JSON. Check that it matches the
465 // manifest schema.
466 return MatchesSchema(error_info);
469 bool JsonManifest::MatchesSchema(ErrorInfo* error_info) {
470 pp::Var exception;
471 if (error_info == NULL) {
472 return false;
474 if (!dictionary_.isObject()) {
475 error_info->SetReport(
476 ERROR_MANIFEST_SCHEMA_VALIDATE,
477 "manifest: is not a json dictionary.");
478 return false;
480 Json::Value::Members members = dictionary_.getMemberNames();
481 for (size_t i = 0; i < members.size(); ++i) {
482 // The top level dictionary entries valid in the manifest file.
483 static const char* kManifestTopLevelProperties[] = { kProgramKey,
484 kInterpreterKey,
485 kFilesKey };
486 nacl::string property_name = members[i];
487 if (!FindMatchingProperty(property_name,
488 kManifestTopLevelProperties,
489 NACL_ARRAY_SIZE(kManifestTopLevelProperties))) {
490 PLUGIN_PRINTF(("JsonManifest::MatchesSchema: WARNING: unknown top-level "
491 "section '%s' in manifest.\n", property_name.c_str()));
495 // A manifest file must have a program section.
496 if (!dictionary_.isMember(kProgramKey)) {
497 error_info->SetReport(
498 ERROR_MANIFEST_SCHEMA_VALIDATE,
499 nacl::string("manifest: missing '") + kProgramKey + "' section.");
500 return false;
503 // Validate the program section.
504 // There must be a matching (portable or sandbox_isa_) entry for program for
505 // NaCl.
506 if (!IsValidISADictionary(dictionary_[kProgramKey],
507 kProgramKey,
508 sandbox_isa_,
509 true,
510 error_info)) {
511 return false;
514 // Validate the interpreter section (if given).
515 // There must be a matching (portable or sandbox_isa_) entry for interpreter
516 // for NaCl.
517 if (dictionary_.isMember(kInterpreterKey)) {
518 if (!IsValidISADictionary(dictionary_[kInterpreterKey],
519 kInterpreterKey,
520 sandbox_isa_,
521 true,
522 error_info)) {
523 return false;
527 // Validate the file dictionary (if given).
528 // The "files" key does not require a matching (portable or sandbox_isa_)
529 // entry at schema validation time for NaCl. This allows manifests to specify
530 // resources that are only loaded for a particular sandbox_isa.
531 if (dictionary_.isMember(kFilesKey)) {
532 const Json::Value& files = dictionary_[kFilesKey];
533 if (!files.isObject()) {
534 error_info->SetReport(
535 ERROR_MANIFEST_SCHEMA_VALIDATE,
536 nacl::string("manifest: '") + kFilesKey + "' is not a dictionary.");
538 Json::Value::Members members = files.getMemberNames();
539 for (size_t i = 0; i < members.size(); ++i) {
540 nacl::string file_name = members[i];
541 if (!IsValidISADictionary(files[file_name],
542 file_name,
543 sandbox_isa_,
544 false,
545 error_info)) {
546 return false;
551 return true;
554 bool JsonManifest::ResolveURL(const nacl::string& relative_url,
555 nacl::string* full_url,
556 ErrorInfo* error_info) const {
557 // The contents of the manifest are resolved relative to the manifest URL.
558 CHECK(url_util_ != NULL);
559 pp::Var resolved_url =
560 url_util_->ResolveRelativeToURL(pp::Var(manifest_base_url_),
561 relative_url);
562 if (!resolved_url.is_string()) {
563 error_info->SetReport(
564 ERROR_MANIFEST_RESOLVE_URL,
565 "could not resolve url '" + relative_url +
566 "' relative to manifest base url '" + manifest_base_url_.c_str() +
567 "'.");
568 return false;
570 *full_url = resolved_url.AsString();
571 return true;
574 bool JsonManifest::GetProgramURL(nacl::string* full_url,
575 PnaclOptions* pnacl_options,
576 ErrorInfo* error_info) const {
577 if (full_url == NULL || pnacl_options == NULL || error_info == NULL)
578 return false;
580 Json::Value program = dictionary_[kProgramKey];
582 nacl::string nexe_url;
583 nacl::string error_string;
585 if (!GetURLFromISADictionary(program,
586 kProgramKey,
587 sandbox_isa_,
588 &nexe_url,
589 pnacl_options,
590 error_info)) {
591 return false;
594 return ResolveURL(nexe_url, full_url, error_info);
597 bool JsonManifest::GetFileKeys(std::set<nacl::string>* keys) const {
598 if (!dictionary_.isMember(kFilesKey)) {
599 // trivial success: no keys when there is no "files" section.
600 return true;
602 const Json::Value& files = dictionary_[kFilesKey];
603 CHECK(files.isObject());
604 Json::Value::Members members = files.getMemberNames();
605 for (size_t i = 0; i < members.size(); ++i) {
606 keys->insert(members[i]);
608 return true;
611 bool JsonManifest::ResolveKey(const nacl::string& key,
612 nacl::string* full_url,
613 PnaclOptions* pnacl_options,
614 ErrorInfo* error_info) const {
615 NaClLog(3, "JsonManifest::ResolveKey(%s)\n", key.c_str());
616 // key must be one of kProgramKey or kFileKey '/' file-section-key
618 if (full_url == NULL || pnacl_options == NULL || error_info == NULL)
619 return false;
621 if (key == kProgramKey) {
622 return GetKeyUrl(dictionary_, key, sandbox_isa_, this, full_url,
623 pnacl_options, error_info);
625 nacl::string::const_iterator p = find(key.begin(), key.end(), '/');
626 if (p == key.end()) {
627 error_info->SetReport(ERROR_MANIFEST_RESOLVE_URL,
628 nacl::string("ResolveKey: invalid key, no slash: ")
629 + key);
630 return false;
633 // generalize to permit other sections?
634 nacl::string prefix(key.begin(), p);
635 if (prefix != kFilesKey) {
636 error_info->SetReport(ERROR_MANIFEST_RESOLVE_URL,
637 nacl::string("ResolveKey: invalid key: not \"files\""
638 " prefix: ") + key);
639 return false;
642 nacl::string rest(p + 1, key.end());
644 const Json::Value& files = dictionary_[kFilesKey];
645 if (!files.isObject()) {
646 error_info->SetReport(
647 ERROR_MANIFEST_RESOLVE_URL,
648 nacl::string("ResolveKey: no \"files\" dictionary"));
649 return false;
651 if (!files.isMember(rest)) {
652 error_info->SetReport(
653 ERROR_MANIFEST_RESOLVE_URL,
654 nacl::string("ResolveKey: no such \"files\" entry: ") + key);
655 return false;
657 return GetKeyUrl(files, rest, sandbox_isa_, this, full_url, pnacl_options,
658 error_info);
661 } // namespace plugin