OLD | NEW |
---|---|
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #ifdef _MSC_VER | 5 #ifdef _MSC_VER |
6 // Do not warn about use of std::copy with raw pointers. | 6 // Do not warn about use of std::copy with raw pointers. |
7 #pragma warning(disable : 4996) | 7 #pragma warning(disable : 4996) |
8 #endif | 8 #endif |
9 | 9 |
10 #include "ppapi/native_client/src/trusted/plugin/plugin.h" | 10 #include "ppapi/native_client/src/trusted/plugin/plugin.h" |
(...skipping 17 matching lines...) Expand all Loading... | |
28 #include "native_client/src/trusted/nonnacl_util/sel_ldr_launcher.h" | 28 #include "native_client/src/trusted/nonnacl_util/sel_ldr_launcher.h" |
29 #include "native_client/src/trusted/service_runtime/nacl_error_code.h" | 29 #include "native_client/src/trusted/service_runtime/nacl_error_code.h" |
30 | 30 |
31 #include "ppapi/c/pp_errors.h" | 31 #include "ppapi/c/pp_errors.h" |
32 #include "ppapi/c/ppb_var.h" | 32 #include "ppapi/c/ppb_var.h" |
33 #include "ppapi/c/private/ppb_nacl_private.h" | 33 #include "ppapi/c/private/ppb_nacl_private.h" |
34 #include "ppapi/cpp/dev/url_util_dev.h" | 34 #include "ppapi/cpp/dev/url_util_dev.h" |
35 #include "ppapi/cpp/module.h" | 35 #include "ppapi/cpp/module.h" |
36 | 36 |
37 #include "ppapi/native_client/src/trusted/plugin/file_utils.h" | 37 #include "ppapi/native_client/src/trusted/plugin/file_utils.h" |
38 #include "ppapi/native_client/src/trusted/plugin/json_manifest.h" | |
39 #include "ppapi/native_client/src/trusted/plugin/nacl_entry_points.h" | 38 #include "ppapi/native_client/src/trusted/plugin/nacl_entry_points.h" |
40 #include "ppapi/native_client/src/trusted/plugin/nacl_subprocess.h" | 39 #include "ppapi/native_client/src/trusted/plugin/nacl_subprocess.h" |
41 #include "ppapi/native_client/src/trusted/plugin/plugin_error.h" | 40 #include "ppapi/native_client/src/trusted/plugin/plugin_error.h" |
42 #include "ppapi/native_client/src/trusted/plugin/service_runtime.h" | 41 #include "ppapi/native_client/src/trusted/plugin/service_runtime.h" |
43 #include "ppapi/native_client/src/trusted/plugin/utility.h" | 42 #include "ppapi/native_client/src/trusted/plugin/utility.h" |
44 | 43 |
45 namespace plugin { | 44 namespace plugin { |
46 | 45 |
47 namespace { | 46 namespace { |
48 | 47 |
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
130 // error. | 129 // error. |
131 // Note: installed files may have "0" for a status code. | 130 // Note: installed files may have "0" for a status code. |
132 if (status < 0 || status >= 600) | 131 if (status < 0 || status >= 600) |
133 sample = 6; | 132 sample = 6; |
134 HistogramEnumerate(name, sample, 7, 6); | 133 HistogramEnumerate(name, sample, 7, 6); |
135 } | 134 } |
136 | 135 |
137 bool Plugin::LoadNaClModuleFromBackgroundThread( | 136 bool Plugin::LoadNaClModuleFromBackgroundThread( |
138 nacl::DescWrapper* wrapper, | 137 nacl::DescWrapper* wrapper, |
139 NaClSubprocess* subprocess, | 138 NaClSubprocess* subprocess, |
140 const Manifest* manifest, | 139 int32_t manifest_id, |
141 const SelLdrStartParams& params) { | 140 const SelLdrStartParams& params) { |
142 CHECK(!pp::Module::Get()->core()->IsMainThread()); | 141 CHECK(!pp::Module::Get()->core()->IsMainThread()); |
143 ServiceRuntime* service_runtime = | 142 ServiceRuntime* service_runtime = |
144 new ServiceRuntime(this, manifest, false, uses_nonsfi_mode_, | 143 new ServiceRuntime(this, manifest_id, false, uses_nonsfi_mode_, |
145 pp::BlockUntilComplete(), pp::BlockUntilComplete()); | 144 pp::BlockUntilComplete(), pp::BlockUntilComplete()); |
146 subprocess->set_service_runtime(service_runtime); | 145 subprocess->set_service_runtime(service_runtime); |
147 PLUGIN_PRINTF(("Plugin::LoadNaClModuleFromBackgroundThread " | 146 PLUGIN_PRINTF(("Plugin::LoadNaClModuleFromBackgroundThread " |
148 "(service_runtime=%p)\n", | 147 "(service_runtime=%p)\n", |
149 static_cast<void*>(service_runtime))); | 148 static_cast<void*>(service_runtime))); |
150 | 149 |
151 // Now start the SelLdr instance. This must be created on the main thread. | 150 // Now start the SelLdr instance. This must be created on the main thread. |
152 bool service_runtime_started = false; | 151 bool service_runtime_started = false; |
153 pp::CompletionCallback sel_ldr_callback = | 152 pp::CompletionCallback sel_ldr_callback = |
154 callback_factory_.NewCallback(&Plugin::SignalStartSelLdrDone, | 153 callback_factory_.NewCallback(&Plugin::SignalStartSelLdrDone, |
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
222 SelLdrStartParams params(manifest_base_url_str, | 221 SelLdrStartParams params(manifest_base_url_str, |
223 true /* uses_irt */, | 222 true /* uses_irt */, |
224 true /* uses_ppapi */, | 223 true /* uses_ppapi */, |
225 uses_nonsfi_mode, | 224 uses_nonsfi_mode, |
226 enable_dev_interfaces, | 225 enable_dev_interfaces, |
227 enable_dyncode_syscalls, | 226 enable_dyncode_syscalls, |
228 enable_exception_handling, | 227 enable_exception_handling, |
229 enable_crash_throttling); | 228 enable_crash_throttling); |
230 ErrorInfo error_info; | 229 ErrorInfo error_info; |
231 ServiceRuntime* service_runtime = | 230 ServiceRuntime* service_runtime = |
232 new ServiceRuntime(this, manifest_.get(), true, uses_nonsfi_mode, | 231 new ServiceRuntime(this, manifest_id_, true, uses_nonsfi_mode, |
233 init_done_cb, crash_cb); | 232 init_done_cb, crash_cb); |
234 main_subprocess_.set_service_runtime(service_runtime); | 233 main_subprocess_.set_service_runtime(service_runtime); |
235 PLUGIN_PRINTF(("Plugin::LoadNaClModule (service_runtime=%p)\n", | 234 PLUGIN_PRINTF(("Plugin::LoadNaClModule (service_runtime=%p)\n", |
236 static_cast<void*>(service_runtime))); | 235 static_cast<void*>(service_runtime))); |
237 if (NULL == service_runtime) { | 236 if (NULL == service_runtime) { |
238 error_info.SetReport( | 237 error_info.SetReport( |
239 PP_NACL_ERROR_SEL_LDR_INIT, | 238 PP_NACL_ERROR_SEL_LDR_INIT, |
240 "sel_ldr init failure " + main_subprocess_.description()); | 239 "sel_ldr init failure " + main_subprocess_.description()); |
241 ReportLoadError(error_info); | 240 ReportLoadError(error_info); |
242 return; | 241 return; |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
287 bool result = PP_ToBool(nacl_interface_->StartPpapiProxy(pp_instance())); | 286 bool result = PP_ToBool(nacl_interface_->StartPpapiProxy(pp_instance())); |
288 if (result) { | 287 if (result) { |
289 PLUGIN_PRINTF(("Plugin::LoadNaClModule (%s)\n", | 288 PLUGIN_PRINTF(("Plugin::LoadNaClModule (%s)\n", |
290 main_subprocess_.detailed_description().c_str())); | 289 main_subprocess_.detailed_description().c_str())); |
291 } | 290 } |
292 return result; | 291 return result; |
293 } | 292 } |
294 | 293 |
295 NaClSubprocess* Plugin::LoadHelperNaClModule(const nacl::string& helper_url, | 294 NaClSubprocess* Plugin::LoadHelperNaClModule(const nacl::string& helper_url, |
296 nacl::DescWrapper* wrapper, | 295 nacl::DescWrapper* wrapper, |
297 const Manifest* manifest, | 296 int32_t manifest_id, |
298 ErrorInfo* error_info) { | 297 ErrorInfo* error_info) { |
299 nacl::scoped_ptr<NaClSubprocess> nacl_subprocess( | 298 nacl::scoped_ptr<NaClSubprocess> nacl_subprocess( |
300 new NaClSubprocess("helper module", NULL, NULL)); | 299 new NaClSubprocess("helper module", NULL, NULL)); |
301 if (NULL == nacl_subprocess.get()) { | 300 if (NULL == nacl_subprocess.get()) { |
302 error_info->SetReport(PP_NACL_ERROR_SEL_LDR_INIT, | 301 error_info->SetReport(PP_NACL_ERROR_SEL_LDR_INIT, |
303 "unable to allocate helper subprocess."); | 302 "unable to allocate helper subprocess."); |
304 return NULL; | 303 return NULL; |
305 } | 304 } |
306 | 305 |
307 // Do not report UMA stats for translator-related nexes. | 306 // Do not report UMA stats for translator-related nexes. |
308 // TODO(sehr): define new UMA stats for translator related nexe events. | 307 // TODO(sehr): define new UMA stats for translator related nexe events. |
309 // NOTE: The PNaCl translator nexes are not built to use the IRT. This is | 308 // NOTE: The PNaCl translator nexes are not built to use the IRT. This is |
310 // done to save on address space and swap space. | 309 // done to save on address space and swap space. |
311 // TODO(jvoung): See if we still need the uses_ppapi variable, now that | 310 // TODO(jvoung): See if we still need the uses_ppapi variable, now that |
312 // LaunchSelLdr always happens on the main thread. | 311 // LaunchSelLdr always happens on the main thread. |
313 bool enable_dev_interfaces = | 312 bool enable_dev_interfaces = |
314 nacl_interface_->DevInterfacesEnabled(pp_instance()); | 313 nacl_interface_->DevInterfacesEnabled(pp_instance()); |
315 SelLdrStartParams params(helper_url, | 314 SelLdrStartParams params(helper_url, |
316 false /* uses_irt */, | 315 false /* uses_irt */, |
317 false /* uses_ppapi */, | 316 false /* uses_ppapi */, |
318 false /* uses_nonsfi_mode */, | 317 false /* uses_nonsfi_mode */, |
319 enable_dev_interfaces, | 318 enable_dev_interfaces, |
320 false /* enable_dyncode_syscalls */, | 319 false /* enable_dyncode_syscalls */, |
321 false /* enable_exception_handling */, | 320 false /* enable_exception_handling */, |
322 true /* enable_crash_throttling */); | 321 true /* enable_crash_throttling */); |
323 if (!LoadNaClModuleFromBackgroundThread(wrapper, nacl_subprocess.get(), | 322 if (!LoadNaClModuleFromBackgroundThread(wrapper, nacl_subprocess.get(), |
324 manifest, params)) { | 323 manifest_id, params)) { |
325 return NULL; | 324 return NULL; |
326 } | 325 } |
327 // We need not wait for the init_done callback. We can block | 326 // We need not wait for the init_done callback. We can block |
328 // here in StartSrpcServices, since helper NaCl modules | 327 // here in StartSrpcServices, since helper NaCl modules |
329 // are spawned from a private thread. | 328 // are spawned from a private thread. |
330 // | 329 // |
331 // TODO(bsy): if helper module crashes, we should abort. | 330 // TODO(bsy): if helper module crashes, we should abort. |
332 // crash_cb is not used here, so we are relying on crashes | 331 // crash_cb is not used here, so we are relying on crashes |
333 // being detected in StartSrpcServices or later. | 332 // being detected in StartSrpcServices or later. |
334 // | 333 // |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
366 | 365 |
367 Plugin::Plugin(PP_Instance pp_instance) | 366 Plugin::Plugin(PP_Instance pp_instance) |
368 : pp::Instance(pp_instance), | 367 : pp::Instance(pp_instance), |
369 main_subprocess_("main subprocess", NULL, NULL), | 368 main_subprocess_("main subprocess", NULL, NULL), |
370 uses_nonsfi_mode_(false), | 369 uses_nonsfi_mode_(false), |
371 wrapper_factory_(NULL), | 370 wrapper_factory_(NULL), |
372 time_of_last_progress_event_(0), | 371 time_of_last_progress_event_(0), |
373 manifest_open_time_(-1), | 372 manifest_open_time_(-1), |
374 nexe_open_time_(-1), | 373 nexe_open_time_(-1), |
375 nacl_interface_(NULL), | 374 nacl_interface_(NULL), |
376 uma_interface_(this) { | 375 uma_interface_(this) { |
bbudge
2014/05/01 23:32:33
Need an initializer for manifest_id_.
teravest
2014/05/02 14:23:15
Done.
| |
377 PLUGIN_PRINTF(("Plugin::Plugin (this=%p, pp_instance=%" | 376 PLUGIN_PRINTF(("Plugin::Plugin (this=%p, pp_instance=%" |
378 NACL_PRId32 ")\n", static_cast<void*>(this), pp_instance)); | 377 NACL_PRId32 ")\n", static_cast<void*>(this), pp_instance)); |
379 callback_factory_.Initialize(this); | 378 callback_factory_.Initialize(this); |
380 nexe_downloader_.Initialize(this); | 379 nexe_downloader_.Initialize(this); |
381 nacl_interface_ = GetNaClInterface(); | 380 nacl_interface_ = GetNaClInterface(); |
382 CHECK(nacl_interface_ != NULL); | 381 CHECK(nacl_interface_ != NULL); |
383 | 382 |
384 // Notify PPB_NaCl_Private that the instance is created before altering any | 383 // Notify PPB_NaCl_Private that the instance is created before altering any |
385 // state that it tracks. | 384 // state that it tracks. |
386 nacl_interface_->InstanceCreated(pp_instance); | 385 nacl_interface_->InstanceCreated(pp_instance); |
(...skipping 248 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
635 | 634 |
636 ProcessNaClManifest(json_buffer); | 635 ProcessNaClManifest(json_buffer); |
637 } | 636 } |
638 | 637 |
639 void Plugin::ProcessNaClManifest(const nacl::string& manifest_json) { | 638 void Plugin::ProcessNaClManifest(const nacl::string& manifest_json) { |
640 HistogramSizeKB("NaCl.Perf.Size.Manifest", | 639 HistogramSizeKB("NaCl.Perf.Size.Manifest", |
641 static_cast<int32_t>(manifest_json.length() / 1024)); | 640 static_cast<int32_t>(manifest_json.length() / 1024)); |
642 if (!SetManifestObject(manifest_json)) | 641 if (!SetManifestObject(manifest_json)) |
643 return; | 642 return; |
644 | 643 |
645 nacl::string program_url; | 644 PP_Var pp_program_url; |
646 PP_PNaClOptions pnacl_options = {PP_FALSE, PP_FALSE, 2}; | 645 PP_PNaClOptions pnacl_options = {PP_FALSE, PP_FALSE, 2}; |
647 bool uses_nonsfi_mode; | 646 PP_Bool uses_nonsfi_mode; |
648 ErrorInfo error_info; | 647 if (nacl_interface_->GetManifestProgramURL(pp_instance(), |
649 if (manifest_->GetProgramURL( | 648 manifest_id_, &pp_program_url, &pnacl_options, &uses_nonsfi_mode)) { |
650 &program_url, &pnacl_options, &uses_nonsfi_mode, &error_info)) { | 649 std::string program_url = pp::Var(pp::PASS_REF, pp_program_url).AsString(); |
651 // TODO(teravest): Make ProcessNaClManifest take responsibility for more of | 650 // TODO(teravest): Make ProcessNaClManifest take responsibility for more of |
652 // this function. | 651 // this function. |
653 nacl_interface_->ProcessNaClManifest(pp_instance(), program_url.c_str()); | 652 nacl_interface_->ProcessNaClManifest(pp_instance(), program_url.c_str()); |
654 uses_nonsfi_mode_ = uses_nonsfi_mode; | 653 uses_nonsfi_mode_ = PP_ToBool(uses_nonsfi_mode); |
655 if (pnacl_options.translate) { | 654 if (pnacl_options.translate) { |
656 pp::CompletionCallback translate_callback = | 655 pp::CompletionCallback translate_callback = |
657 callback_factory_.NewCallback(&Plugin::BitcodeDidTranslate); | 656 callback_factory_.NewCallback(&Plugin::BitcodeDidTranslate); |
658 // Will always call the callback on success or failure. | |
659 pnacl_coordinator_.reset( | 657 pnacl_coordinator_.reset( |
660 PnaclCoordinator::BitcodeToNative(this, | 658 PnaclCoordinator::BitcodeToNative(this, |
661 program_url, | 659 program_url, |
662 pnacl_options, | 660 pnacl_options, |
663 translate_callback)); | 661 translate_callback)); |
664 return; | 662 return; |
665 } else { | 663 } else { |
666 nexe_open_time_ = NaClGetTimeOfDayMicroseconds(); | 664 nexe_open_time_ = NaClGetTimeOfDayMicroseconds(); |
667 // Try the fast path first. This will only block if the file is installed. | 665 // Try the fast path first. This will only block if the file is installed. |
668 if (OpenURLFast(program_url, &nexe_downloader_)) { | 666 if (OpenURLFast(program_url, &nexe_downloader_)) { |
669 NexeFileDidOpen(PP_OK); | 667 NexeFileDidOpen(PP_OK); |
670 } else { | 668 } else { |
671 pp::CompletionCallback open_callback = | 669 pp::CompletionCallback open_callback = |
672 callback_factory_.NewCallback(&Plugin::NexeFileDidOpen); | 670 callback_factory_.NewCallback(&Plugin::NexeFileDidOpen); |
673 // Will always call the callback on success or failure. | 671 // Will always call the callback on success or failure. |
674 CHECK( | 672 CHECK( |
675 nexe_downloader_.Open(program_url, | 673 nexe_downloader_.Open(program_url, |
676 DOWNLOAD_TO_FILE, | 674 DOWNLOAD_TO_FILE, |
677 open_callback, | 675 open_callback, |
678 true, | 676 true, |
679 &UpdateDownloadProgress)); | 677 &UpdateDownloadProgress)); |
680 } | 678 } |
681 return; | 679 return; |
682 } | 680 } |
683 } | 681 } |
684 // Failed to select the program and/or the translator. | |
685 ReportLoadError(error_info); | |
686 } | 682 } |
687 | 683 |
688 void Plugin::RequestNaClManifest(const nacl::string& url) { | 684 void Plugin::RequestNaClManifest(const nacl::string& url) { |
689 PLUGIN_PRINTF(("Plugin::RequestNaClManifest (url='%s')\n", url.c_str())); | 685 PLUGIN_PRINTF(("Plugin::RequestNaClManifest (url='%s')\n", url.c_str())); |
690 PP_Bool is_data_uri; | 686 PP_Bool is_data_uri; |
691 ErrorInfo error_info; | 687 ErrorInfo error_info; |
692 if (!nacl_interface_->RequestNaClManifest(pp_instance(), url.c_str(), | 688 if (!nacl_interface_->RequestNaClManifest(pp_instance(), url.c_str(), |
693 &is_data_uri)) | 689 &is_data_uri)) |
694 return; | 690 return; |
695 pp::Var nmf_resolved_url = | 691 pp::Var nmf_resolved_url = |
(...skipping 24 matching lines...) Expand all Loading... | |
720 open_callback, | 716 open_callback, |
721 false, | 717 false, |
722 NULL)); | 718 NULL)); |
723 } | 719 } |
724 } | 720 } |
725 | 721 |
726 | 722 |
727 bool Plugin::SetManifestObject(const nacl::string& manifest_json) { | 723 bool Plugin::SetManifestObject(const nacl::string& manifest_json) { |
728 PLUGIN_PRINTF(("Plugin::SetManifestObject(): manifest_json='%s'.\n", | 724 PLUGIN_PRINTF(("Plugin::SetManifestObject(): manifest_json='%s'.\n", |
729 manifest_json.c_str())); | 725 manifest_json.c_str())); |
730 ErrorInfo error_info; | |
731 | |
732 // Determine whether lookups should use portable (i.e., pnacl versions) | 726 // Determine whether lookups should use portable (i.e., pnacl versions) |
733 // rather than platform-specific files. | 727 // rather than platform-specific files. |
734 bool is_pnacl = nacl_interface_->IsPNaCl(pp_instance()); | 728 bool is_pnacl = nacl_interface_->IsPNaCl(pp_instance()); |
735 bool nonsfi_mode_enabled = | |
736 PP_ToBool(nacl_interface_->IsNonSFIModeEnabled()); | |
737 pp::Var manifest_base_url = | 729 pp::Var manifest_base_url = |
738 pp::Var(pp::PASS_REF, nacl_interface_->GetManifestBaseURL(pp_instance())); | 730 pp::Var(pp::PASS_REF, nacl_interface_->GetManifestBaseURL(pp_instance())); |
739 std::string manifest_base_url_str = manifest_base_url.AsString(); | 731 std::string manifest_base_url_str = manifest_base_url.AsString(); |
740 bool pnacl_debug = GetNaClInterface()->NaClDebugEnabledForURL( | |
741 manifest_base_url_str.c_str()); | |
742 const char* sandbox_isa = nacl_interface_->GetSandboxArch(); | 732 const char* sandbox_isa = nacl_interface_->GetSandboxArch(); |
743 nacl::scoped_ptr<JsonManifest> json_manifest( | 733 |
744 new JsonManifest(pp::URLUtil_Dev::Get(), | 734 int32_t manifest_id = nacl_interface_->CreateJsonManifest( |
745 manifest_base_url_str, | 735 pp_instance(), |
746 (is_pnacl ? kPortableArch : sandbox_isa), | 736 manifest_base_url_str.c_str(), |
747 nonsfi_mode_enabled, | 737 is_pnacl ? kPortableArch : sandbox_isa, |
748 pnacl_debug)); | 738 manifest_json.c_str()); |
749 if (!json_manifest->Init(manifest_json, &error_info)) { | 739 if (manifest_id == -1) |
750 ReportLoadError(error_info); | |
751 return false; | 740 return false; |
752 } | 741 manifest_id_ = manifest_id; |
753 manifest_.reset(json_manifest.release()); | |
754 return true; | 742 return true; |
755 } | 743 } |
756 | 744 |
757 void Plugin::UrlDidOpenForStreamAsFile( | 745 void Plugin::UrlDidOpenForStreamAsFile( |
758 int32_t pp_error, | 746 int32_t pp_error, |
759 FileDownloader* url_downloader, | 747 FileDownloader* url_downloader, |
760 pp::CompletionCallback callback) { | 748 pp::CompletionCallback callback) { |
761 PLUGIN_PRINTF(("Plugin::UrlDidOpen (pp_error=%" NACL_PRId32 | 749 PLUGIN_PRINTF(("Plugin::UrlDidOpen (pp_error=%" NACL_PRId32 |
762 ", url_downloader=%p)\n", pp_error, | 750 ", url_downloader=%p)\n", pp_error, |
763 static_cast<void*>(url_downloader))); | 751 static_cast<void*>(url_downloader))); |
(...skipping 198 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
962 | 950 |
963 void Plugin::SetExitStatusOnMainThread(int32_t pp_error, | 951 void Plugin::SetExitStatusOnMainThread(int32_t pp_error, |
964 int exit_status) { | 952 int exit_status) { |
965 DCHECK(pp::Module::Get()->core()->IsMainThread()); | 953 DCHECK(pp::Module::Get()->core()->IsMainThread()); |
966 DCHECK(nacl_interface_); | 954 DCHECK(nacl_interface_); |
967 nacl_interface_->SetExitStatus(pp_instance(), exit_status); | 955 nacl_interface_->SetExitStatus(pp_instance(), exit_status); |
968 } | 956 } |
969 | 957 |
970 | 958 |
971 } // namespace plugin | 959 } // namespace plugin |
OLD | NEW |