Chromium Code Reviews| 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 #include "native_client/src/trusted/plugin/file_downloader.h" | 5 #include "native_client/src/trusted/plugin/file_downloader.h" |
| 6 | 6 |
| 7 #include <stdio.h> | 7 #include <stdio.h> |
| 8 #include <string.h> | 8 #include <string.h> |
| 9 #include <string> | 9 #include <string> |
| 10 | 10 |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 48 pp::Module::Get()->GetBrowserInterface(PPB_FILEIOTRUSTED_INTERFACE)); | 48 pp::Module::Get()->GetBrowserInterface(PPB_FILEIOTRUSTED_INTERFACE)); |
| 49 url_loader_trusted_interface_ = static_cast<const PPB_URLLoaderTrusted*>( | 49 url_loader_trusted_interface_ = static_cast<const PPB_URLLoaderTrusted*>( |
| 50 pp::Module::Get()->GetBrowserInterface(PPB_URLLOADERTRUSTED_INTERFACE)); | 50 pp::Module::Get()->GetBrowserInterface(PPB_URLLOADERTRUSTED_INTERFACE)); |
| 51 temp_buffer_.resize(kTempBufferSize); | 51 temp_buffer_.resize(kTempBufferSize); |
| 52 } | 52 } |
| 53 | 53 |
| 54 bool FileDownloader::OpenStream( | 54 bool FileDownloader::OpenStream( |
| 55 const nacl::string& url, | 55 const nacl::string& url, |
| 56 const pp::CompletionCallback& callback, | 56 const pp::CompletionCallback& callback, |
| 57 StreamCallbackSource* stream_callback_source) { | 57 StreamCallbackSource* stream_callback_source) { |
| 58 open_and_stream_ = false; | |
|
dmichael (off chromium)
2013/06/14 16:21:43
It looks like you never initialized open_and_strea
jvoung (off chromium)
2013/06/14 20:38:03
It is initialized to true in the constructor (.h f
| |
| 58 data_stream_callback_source_ = stream_callback_source; | 59 data_stream_callback_source_ = stream_callback_source; |
| 59 return Open(url, DOWNLOAD_STREAM, callback, true, NULL); | 60 return Open(url, DOWNLOAD_STREAM, callback, true, NULL); |
| 60 } | 61 } |
| 61 | 62 |
| 62 bool FileDownloader::Open( | 63 bool FileDownloader::Open( |
| 63 const nacl::string& url, | 64 const nacl::string& url, |
| 64 DownloadMode mode, | 65 DownloadMode mode, |
| 65 const pp::CompletionCallback& callback, | 66 const pp::CompletionCallback& callback, |
| 66 bool record_progress, | 67 bool record_progress, |
| 67 PP_URLLoaderTrusted_StatusCallback progress_callback) { | 68 PP_URLLoaderTrusted_StatusCallback progress_callback) { |
| (...skipping 148 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 216 return (now - open_time_) / NACL_MICROS_PER_MILLI; | 217 return (now - open_time_) / NACL_MICROS_PER_MILLI; |
| 217 } | 218 } |
| 218 | 219 |
| 219 bool FileDownloader::InitialResponseIsValid(int32_t pp_error) { | 220 bool FileDownloader::InitialResponseIsValid(int32_t pp_error) { |
| 220 if (pp_error != PP_OK) { // Url loading failed. | 221 if (pp_error != PP_OK) { // Url loading failed. |
| 221 file_open_notify_callback_.Run(pp_error); | 222 file_open_notify_callback_.Run(pp_error); |
| 222 return false; | 223 return false; |
| 223 } | 224 } |
| 224 | 225 |
| 225 // Process the response, validating the headers to confirm successful loading. | 226 // Process the response, validating the headers to confirm successful loading. |
| 226 pp::URLResponseInfo url_response(url_loader_.GetResponseInfo()); | 227 url_response_ = url_loader_.GetResponseInfo(); |
| 227 if (url_response.is_null()) { | 228 if (url_response_.is_null()) { |
| 228 PLUGIN_PRINTF(( | 229 PLUGIN_PRINTF(( |
| 229 "FileDownloader::InitialResponseIsValid (url_response=NULL)\n")); | 230 "FileDownloader::InitialResponseIsValid (url_response_=NULL)\n")); |
|
dmichael (off chromium)
2013/06/14 16:21:43
nit: maybe == instead of = to be clearer
jvoung (off chromium)
2013/06/14 20:38:03
I think most of the PLUGIN_PRINTFs use = instead o
| |
| 230 file_open_notify_callback_.Run(PP_ERROR_FAILED); | 231 file_open_notify_callback_.Run(PP_ERROR_FAILED); |
| 231 return false; | 232 return false; |
| 232 } | 233 } |
| 233 // Note that URLs in the chrome-extension scheme produce different error | 234 |
| 234 // codes than other schemes. This is because chrome-extension URLs are | 235 pp::Var full_url = url_response_.GetURL(); |
| 235 // really a special kind of file scheme, and therefore do not produce HTTP | |
| 236 // status codes. | |
| 237 pp::Var full_url = url_response.GetURL(); | |
| 238 if (!full_url.is_string()) { | 236 if (!full_url.is_string()) { |
| 239 PLUGIN_PRINTF(( | 237 PLUGIN_PRINTF(( |
| 240 "FileDownloader::InitialResponseIsValid (url is not a string)\n")); | 238 "FileDownloader::InitialResponseIsValid (url is not a string)\n")); |
| 241 file_open_notify_callback_.Run(PP_ERROR_FAILED); | 239 file_open_notify_callback_.Run(PP_ERROR_FAILED); |
| 242 return false; | 240 return false; |
| 243 } | 241 } |
| 242 url_ = full_url.AsString(); | |
| 243 | |
| 244 // Note that URLs in the data-URI scheme produce different error | |
| 245 // codes than other schemes. This is because data-URI are really a | |
| 246 // special kind of file scheme, and therefore do not produce HTTP | |
| 247 // status codes. | |
| 244 bool status_ok = false; | 248 bool status_ok = false; |
| 245 status_code_ = url_response.GetStatusCode(); | 249 status_code_ = url_response_.GetStatusCode(); |
| 246 switch (url_scheme_) { | 250 switch (url_scheme_) { |
| 247 case SCHEME_CHROME_EXTENSION: | 251 case SCHEME_CHROME_EXTENSION: |
| 248 PLUGIN_PRINTF(("FileDownloader::InitialResponseIsValid (chrome-extension " | 252 PLUGIN_PRINTF(("FileDownloader::InitialResponseIsValid (chrome-extension " |
| 249 "response status_code=%"NACL_PRId32")\n", status_code_)); | 253 "response status_code=%"NACL_PRId32")\n", status_code_)); |
| 250 status_ok = (status_code_ == kExtensionUrlRequestStatusOk); | 254 status_ok = (status_code_ == kExtensionUrlRequestStatusOk); |
| 251 break; | 255 break; |
| 252 case SCHEME_DATA: | 256 case SCHEME_DATA: |
| 253 PLUGIN_PRINTF(("FileDownloader::InitialResponseIsValid (data URI " | 257 PLUGIN_PRINTF(("FileDownloader::InitialResponseIsValid (data URI " |
| 254 "response status_code=%"NACL_PRId32")\n", status_code_)); | 258 "response status_code=%"NACL_PRId32")\n", status_code_)); |
| 255 status_ok = (status_code_ == kDataUriRequestStatusOk); | 259 status_ok = (status_code_ == kDataUriRequestStatusOk); |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 267 } | 271 } |
| 268 | 272 |
| 269 return true; | 273 return true; |
| 270 } | 274 } |
| 271 | 275 |
| 272 void FileDownloader::URLLoadStartNotify(int32_t pp_error) { | 276 void FileDownloader::URLLoadStartNotify(int32_t pp_error) { |
| 273 PLUGIN_PRINTF(("FileDownloader::URLLoadStartNotify (pp_error=%" | 277 PLUGIN_PRINTF(("FileDownloader::URLLoadStartNotify (pp_error=%" |
| 274 NACL_PRId32")\n", pp_error)); | 278 NACL_PRId32")\n", pp_error)); |
| 275 | 279 |
| 276 if (!InitialResponseIsValid(pp_error)) | 280 if (!InitialResponseIsValid(pp_error)) |
| 281 // InitialResponseIsValid() calls file_open_notify_callback_ on errors. | |
| 277 return; | 282 return; |
|
dmichael (off chromium)
2013/06/14 16:21:43
style nit: use braces since it's >1 line now
jvoung (off chromium)
2013/06/14 20:38:03
Done.
| |
| 283 | |
| 284 if (open_and_stream_) | |
| 285 return FinishStreaming(file_open_notify_callback_); | |
| 286 | |
| 287 file_open_notify_callback_.Run(pp_error); | |
| 288 } | |
| 289 | |
| 290 void FileDownloader::URLBufferStartNotify(int32_t pp_error) { | |
| 291 PLUGIN_PRINTF(("FileDownloader::URLBufferStartNotify (pp_error=%" | |
| 292 NACL_PRId32")\n", pp_error)); | |
| 293 | |
| 294 if (!InitialResponseIsValid(pp_error)) | |
| 295 // InitialResponseIsValid() calls file_open_notify_callback_ on errors. | |
| 296 return; | |
|
dmichael (off chromium)
2013/06/14 16:21:43
style nit: curly braces
jvoung (off chromium)
2013/06/14 20:38:03
Done.
| |
| 297 | |
| 298 if (open_and_stream_) | |
| 299 return FinishStreaming(file_open_notify_callback_); | |
| 300 | |
| 301 file_open_notify_callback_.Run(pp_error); | |
|
dmichael (off chromium)
2013/06/14 16:21:43
It would be nice to clear the callback after you r
jvoung (off chromium)
2013/06/14 20:38:03
Like set them to PP_BlockUntilComplete()?
I thin
dmichael (off chromium)
2013/06/14 21:09:03
Right, that's what I meant:
file_open_notify_callb
jvoung (off chromium)
2013/06/17 18:20:27
Okay, there was a RunAndClearCompletionCallback fu
| |
| 302 } | |
| 303 | |
| 304 void FileDownloader::FinishStreaming( | |
| 305 const pp::CompletionCallback& callback) { | |
| 306 stream_finish_callback_ = callback; | |
| 307 | |
| 278 // Finish streaming the body providing an optional callback. | 308 // Finish streaming the body providing an optional callback. |
| 279 pp::CompletionCallback onload_callback = | 309 if (streaming_to_file()) { |
| 280 callback_factory_.NewOptionalCallback( | 310 pp::CompletionCallback onload_callback = |
| 281 &FileDownloader::URLLoadFinishNotify); | 311 callback_factory_.NewOptionalCallback( |
| 282 pp_error = url_loader_.FinishStreamingToFile(onload_callback); | 312 &FileDownloader::URLLoadFinishNotify); |
| 283 bool async_notify_ok = (pp_error == PP_OK_COMPLETIONPENDING); | 313 int32_t pp_error = url_loader_.FinishStreamingToFile(onload_callback); |
| 284 PLUGIN_PRINTF(("FileDownloader::URLLoadStartNotify (async_notify_ok=%d)\n", | 314 bool async_notify_ok = (pp_error == PP_OK_COMPLETIONPENDING); |
| 285 async_notify_ok)); | 315 PLUGIN_PRINTF(("FileDownloader::FinishStreaming (async_notify_ok=%d)\n", |
| 286 if (!async_notify_ok) { | 316 async_notify_ok)); |
| 287 // Call manually to free allocated memory and report errors. This calls | 317 if (!async_notify_ok) { |
| 288 // |file_open_notify_callback_| with |pp_error| as the parameter. | 318 // Call manually to free allocated memory and report errors. This calls |
| 289 onload_callback.Run(pp_error); | 319 // |stream_finish_callback_| with |pp_error| as the parameter. |
| 320 onload_callback.Run(pp_error); | |
| 321 } | |
| 322 } else { | |
| 323 pp::CompletionCallback onread_callback = | |
| 324 callback_factory_.NewOptionalCallback( | |
| 325 &FileDownloader::URLReadBodyNotify); | |
| 326 int32_t temp_size = static_cast<int32_t>(temp_buffer_.size()); | |
| 327 int32_t pp_error = url_loader_.ReadResponseBody(&temp_buffer_[0], | |
| 328 temp_size, | |
| 329 onread_callback); | |
| 330 bool async_notify_ok = (pp_error == PP_OK_COMPLETIONPENDING); | |
| 331 PLUGIN_PRINTF(( | |
| 332 "FileDownloader::FinishStreaming (async_notify_ok=%d)\n", | |
| 333 async_notify_ok)); | |
| 334 if (!async_notify_ok) { | |
| 335 onread_callback.Run(pp_error); | |
| 336 } | |
| 290 } | 337 } |
| 291 } | 338 } |
| 292 | 339 |
| 293 void FileDownloader::URLLoadFinishNotify(int32_t pp_error) { | 340 void FileDownloader::URLLoadFinishNotify(int32_t pp_error) { |
| 294 PLUGIN_PRINTF(("FileDownloader::URLLoadFinishNotify (pp_error=%" | 341 PLUGIN_PRINTF(("FileDownloader::URLLoadFinishNotify (pp_error=%" |
| 295 NACL_PRId32")\n", pp_error)); | 342 NACL_PRId32")\n", pp_error)); |
| 296 if (pp_error != PP_OK) { // Streaming failed. | 343 if (pp_error != PP_OK) { // Streaming failed. |
| 297 file_open_notify_callback_.Run(pp_error); | 344 stream_finish_callback_.Run(pp_error); |
| 298 return; | 345 return; |
| 299 } | 346 } |
| 300 | 347 |
| 301 pp::URLResponseInfo url_response(url_loader_.GetResponseInfo()); | 348 // Validate response again on load (though it should be the same |
| 302 // Validated on load. | 349 // as it was during InitialResponseIsValid?). |
| 303 CHECK(url_response.GetStatusCode() == NACL_HTTP_STATUS_OK || | 350 url_response_ = url_loader_.GetResponseInfo(); |
| 304 url_response.GetStatusCode() == kExtensionUrlRequestStatusOk); | 351 CHECK(url_response_.GetStatusCode() == NACL_HTTP_STATUS_OK || |
| 352 url_response_.GetStatusCode() == kExtensionUrlRequestStatusOk); | |
| 305 | 353 |
| 306 // Record the full url from the response. | 354 // Record the full url from the response. |
| 307 pp::Var full_url = url_response.GetURL(); | 355 pp::Var full_url = url_response_.GetURL(); |
| 308 PLUGIN_PRINTF(("FileDownloader::URLLoadFinishNotify (full_url=%s)\n", | 356 PLUGIN_PRINTF(("FileDownloader::URLLoadFinishNotify (full_url=%s)\n", |
| 309 full_url.DebugString().c_str())); | 357 full_url.DebugString().c_str())); |
| 310 if (!full_url.is_string()) { | 358 if (!full_url.is_string()) { |
| 311 file_open_notify_callback_.Run(PP_ERROR_FAILED); | 359 stream_finish_callback_.Run(PP_ERROR_FAILED); |
| 312 return; | 360 return; |
| 313 } | 361 } |
| 314 url_ = full_url.AsString(); | 362 url_ = full_url.AsString(); |
| 315 | 363 |
| 316 // The file is now fully downloaded. | 364 // The file is now fully downloaded. |
| 317 pp::FileRef file(url_response.GetBodyAsFileRef()); | 365 pp::FileRef file(url_response_.GetBodyAsFileRef()); |
| 318 if (file.is_null()) { | 366 if (file.is_null()) { |
| 319 PLUGIN_PRINTF(("FileDownloader::URLLoadFinishNotify (file=NULL)\n")); | 367 PLUGIN_PRINTF(("FileDownloader::URLLoadFinishNotify (file=NULL)\n")); |
| 320 file_open_notify_callback_.Run(PP_ERROR_FAILED); | 368 stream_finish_callback_.Run(PP_ERROR_FAILED); |
| 321 return; | 369 return; |
| 322 } | 370 } |
| 323 | 371 |
| 324 // Open the file providing an optional callback. | 372 // Open the file providing an optional callback. |
| 325 pp::CompletionCallback onopen_callback = | 373 pp::CompletionCallback onopen_callback = |
| 326 callback_factory_.NewOptionalCallback(&FileDownloader::FileOpenNotify); | 374 callback_factory_.NewOptionalCallback( |
| 375 &FileDownloader::StreamFinishNotify); | |
| 327 pp_error = file_reader_.Open(file, PP_FILEOPENFLAG_READ, onopen_callback); | 376 pp_error = file_reader_.Open(file, PP_FILEOPENFLAG_READ, onopen_callback); |
| 328 bool async_notify_ok = (pp_error == PP_OK_COMPLETIONPENDING); | 377 bool async_notify_ok = (pp_error == PP_OK_COMPLETIONPENDING); |
| 329 PLUGIN_PRINTF(("FileDownloader::URLLoadFinishNotify (async_notify_ok=%d)\n", | 378 PLUGIN_PRINTF(("FileDownloader::URLLoadFinishNotify (async_notify_ok=%d)\n", |
| 330 async_notify_ok)); | 379 async_notify_ok)); |
| 331 if (!async_notify_ok) { | 380 if (!async_notify_ok) { |
| 332 // Call manually to free allocated memory and report errors. This calls | 381 // Call manually to free allocated memory and report errors. This calls |
| 333 // |file_open_notify_callback_| with |pp_error| as the parameter. | 382 // |stream_finish_callback_| with |pp_error| as the parameter. |
| 334 onopen_callback.Run(pp_error); | 383 onopen_callback.Run(pp_error); |
| 335 } | 384 } |
| 336 } | 385 } |
| 337 | 386 |
| 338 void FileDownloader::URLBufferStartNotify(int32_t pp_error) { | |
| 339 PLUGIN_PRINTF(("FileDownloader::URLBufferStartNotify (pp_error=%" | |
| 340 NACL_PRId32")\n", pp_error)); | |
| 341 | |
| 342 if (!InitialResponseIsValid(pp_error)) | |
| 343 return; | |
| 344 // Finish streaming the body asynchronously providing a callback. | |
| 345 pp::CompletionCallback onread_callback = | |
| 346 callback_factory_.NewOptionalCallback(&FileDownloader::URLReadBodyNotify); | |
| 347 | |
| 348 int32_t temp_size = static_cast<int32_t>(temp_buffer_.size()); | |
| 349 pp_error = url_loader_.ReadResponseBody(&temp_buffer_[0], | |
| 350 temp_size, | |
| 351 onread_callback); | |
| 352 bool async_notify_ok = (pp_error == PP_OK_COMPLETIONPENDING); | |
| 353 PLUGIN_PRINTF(("FileDownloader::URLBufferStartNotify (async_notify_ok=%d)\n", | |
| 354 async_notify_ok)); | |
| 355 if (!async_notify_ok) { | |
| 356 onread_callback.Run(pp_error); | |
| 357 } | |
| 358 } | |
| 359 | |
| 360 void FileDownloader::URLReadBodyNotify(int32_t pp_error) { | 387 void FileDownloader::URLReadBodyNotify(int32_t pp_error) { |
| 361 PLUGIN_PRINTF(("FileDownloader::URLReadBodyNotify (pp_error=%" | 388 PLUGIN_PRINTF(("FileDownloader::URLReadBodyNotify (pp_error=%" |
| 362 NACL_PRId32")\n", pp_error)); | 389 NACL_PRId32")\n", pp_error)); |
| 363 if (pp_error < PP_OK) { | 390 if (pp_error < PP_OK) { |
| 364 file_open_notify_callback_.Run(pp_error); | 391 stream_finish_callback_.Run(pp_error); |
| 365 } else if (pp_error == PP_OK) { | 392 } else if (pp_error == PP_OK) { |
| 366 if (streaming_to_user()) { | 393 if (streaming_to_user()) { |
| 367 data_stream_callback_source_->GetCallback().Run(PP_OK); | 394 data_stream_callback_source_->GetCallback().Run(PP_OK); |
| 368 } | 395 } |
| 369 FileOpenNotify(PP_OK); | 396 StreamFinishNotify(PP_OK); |
| 370 } else { | 397 } else { |
| 371 if (streaming_to_buffer()) { | 398 if (streaming_to_buffer()) { |
| 372 buffer_.insert(buffer_.end(), &temp_buffer_[0], &temp_buffer_[pp_error]); | 399 buffer_.insert(buffer_.end(), &temp_buffer_[0], &temp_buffer_[pp_error]); |
| 373 } else if (streaming_to_user()) { | 400 } else if (streaming_to_user()) { |
| 374 PLUGIN_PRINTF(("Running data_stream_callback, temp_buffer_=%p\n", | 401 PLUGIN_PRINTF(("Running data_stream_callback, temp_buffer_=%p\n", |
| 375 &temp_buffer_[0])); | 402 &temp_buffer_[0])); |
| 376 StreamCallback cb = data_stream_callback_source_->GetCallback(); | 403 StreamCallback cb = data_stream_callback_source_->GetCallback(); |
| 377 *(cb.output()) = &temp_buffer_; | 404 *(cb.output()) = &temp_buffer_; |
| 378 cb.Run(pp_error); | 405 cb.Run(pp_error); |
| 379 } | 406 } |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 391 } | 418 } |
| 392 } | 419 } |
| 393 | 420 |
| 394 bool FileDownloader::GetDownloadProgress( | 421 bool FileDownloader::GetDownloadProgress( |
| 395 int64_t* bytes_received, | 422 int64_t* bytes_received, |
| 396 int64_t* total_bytes_to_be_received) const { | 423 int64_t* total_bytes_to_be_received) const { |
| 397 return url_loader_.GetDownloadProgress(bytes_received, | 424 return url_loader_.GetDownloadProgress(bytes_received, |
| 398 total_bytes_to_be_received); | 425 total_bytes_to_be_received); |
| 399 } | 426 } |
| 400 | 427 |
| 401 void FileDownloader::FileOpenNotify(int32_t pp_error) { | 428 nacl::string FileDownloader::GetResponseHeaders() const { |
| 402 PLUGIN_PRINTF(("FileDownloader::FileOpenNotify (pp_error=%"NACL_PRId32")\n", | 429 pp::Var headers = url_response_.GetHeaders(); |
| 403 pp_error)); | 430 if (!headers.is_string()) { |
| 404 file_open_notify_callback_.Run(pp_error); | 431 PLUGIN_PRINTF(( |
| 432 "FileDownloader::GetResponseHeaders (headers are not a string)\n")); | |
| 433 return nacl::string(); | |
| 434 } | |
| 435 return headers.AsString(); | |
| 436 } | |
| 437 | |
| 438 void FileDownloader::StreamFinishNotify(int32_t pp_error) { | |
| 439 PLUGIN_PRINTF(( | |
| 440 "FileDownloader::StreamFinishNotify (pp_error=%"NACL_PRId32")\n", | |
| 441 pp_error)); | |
| 442 stream_finish_callback_.Run(pp_error); | |
| 405 } | 443 } |
| 406 | 444 |
| 407 bool FileDownloader::streaming_to_file() const { | 445 bool FileDownloader::streaming_to_file() const { |
| 408 return mode_ == DOWNLOAD_TO_FILE; | 446 return mode_ == DOWNLOAD_TO_FILE; |
| 409 } | 447 } |
| 410 | 448 |
| 411 bool FileDownloader::streaming_to_buffer() const { | 449 bool FileDownloader::streaming_to_buffer() const { |
| 412 return mode_ == DOWNLOAD_TO_BUFFER; | 450 return mode_ == DOWNLOAD_TO_BUFFER; |
| 413 } | 451 } |
| 414 | 452 |
| 415 bool FileDownloader::streaming_to_user() const { | 453 bool FileDownloader::streaming_to_user() const { |
| 416 return mode_ == DOWNLOAD_STREAM; | 454 return mode_ == DOWNLOAD_STREAM; |
| 417 } | 455 } |
| 418 | 456 |
| 419 bool FileDownloader::not_streaming() const { | 457 bool FileDownloader::not_streaming() const { |
| 420 return mode_ == DOWNLOAD_NONE; | 458 return mode_ == DOWNLOAD_NONE; |
| 421 } | 459 } |
| 422 | 460 |
| 423 } // namespace plugin | 461 } // namespace plugin |
| OLD | NEW |