Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 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 "extensions/browser/api/runtime/runtime_api.h" | 5 #include "extensions/browser/api/runtime/runtime_api.h" |
| 6 | 6 |
| 7 #include <memory> | 7 #include <memory> |
| 8 #include <utility> | 8 #include <utility> |
| 9 | 9 |
| 10 #include "base/lazy_instance.h" | 10 #include "base/lazy_instance.h" |
| 11 #include "base/location.h" | 11 #include "base/location.h" |
| 12 #include "base/logging.h" | 12 #include "base/logging.h" |
| 13 #include "base/memory/ptr_util.h" | 13 #include "base/memory/ptr_util.h" |
| 14 #include "base/metrics/histogram.h" | 14 #include "base/metrics/histogram.h" |
| 15 #include "base/single_thread_task_runner.h" | 15 #include "base/single_thread_task_runner.h" |
| 16 #include "base/strings/string_number_conversions.h" | |
| 16 #include "base/threading/thread_task_runner_handle.h" | 17 #include "base/threading/thread_task_runner_handle.h" |
| 17 #include "base/values.h" | 18 #include "base/values.h" |
| 18 #include "base/version.h" | 19 #include "base/version.h" |
| 20 #include "components/prefs/pref_registry_simple.h" | |
| 21 #include "components/prefs/pref_service.h" | |
| 19 #include "content/public/browser/browser_context.h" | 22 #include "content/public/browser/browser_context.h" |
| 20 #include "content/public/browser/child_process_security_policy.h" | 23 #include "content/public/browser/child_process_security_policy.h" |
| 21 #include "content/public/browser/notification_service.h" | 24 #include "content/public/browser/notification_service.h" |
| 22 #include "content/public/browser/render_frame_host.h" | 25 #include "content/public/browser/render_frame_host.h" |
| 23 #include "content/public/browser/render_process_host.h" | 26 #include "content/public/browser/render_process_host.h" |
| 24 #include "extensions/browser/api/runtime/runtime_api_delegate.h" | 27 #include "extensions/browser/api/runtime/runtime_api_delegate.h" |
| 25 #include "extensions/browser/event_router.h" | 28 #include "extensions/browser/event_router.h" |
| 26 #include "extensions/browser/extension_host.h" | 29 #include "extensions/browser/extension_host.h" |
| 27 #include "extensions/browser/extension_prefs.h" | 30 #include "extensions/browser/extension_prefs.h" |
| 28 #include "extensions/browser/extension_registry.h" | 31 #include "extensions/browser/extension_registry.h" |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 73 "pending_on_installed_event_dispatch_info"; | 76 "pending_on_installed_event_dispatch_info"; |
| 74 | 77 |
| 75 // Previously installed version number. | 78 // Previously installed version number. |
| 76 const char kPrefPreviousVersion[] = "previous_version"; | 79 const char kPrefPreviousVersion[] = "previous_version"; |
| 77 | 80 |
| 78 // The name of the directory to be returned by getPackageDirectoryEntry. This | 81 // The name of the directory to be returned by getPackageDirectoryEntry. This |
| 79 // particular value does not matter to user code, but is chosen for consistency | 82 // particular value does not matter to user code, but is chosen for consistency |
| 80 // with the equivalent Pepper API. | 83 // with the equivalent Pepper API. |
| 81 const char kPackageDirectoryPath[] = "crxfs"; | 84 const char kPackageDirectoryPath[] = "crxfs"; |
| 82 | 85 |
| 86 // Preference key for storing the last successful restart due to a call to | |
| 87 // chrome.runtime.restartAfterDelay(). | |
| 88 constexpr char kPrefLastRestartAfterDelayTime[] = | |
| 89 "last_restart_after_delay_time"; | |
| 90 // Preference key for storing whether the most recent restart was due to a | |
| 91 // successful call to chrome.runtime.restartAfterDelay(). | |
| 92 constexpr char kPrefLastRestartWasDuetoDelayedRestartApi[] = | |
| 93 "last_restart_was_due_to_delayed_restart_api"; | |
| 94 | |
| 95 // Error and status messages strings for the restartAfterDelay() API. | |
| 96 constexpr char kErrorInvalidArgument[] = "Invalid argument: *."; | |
| 97 constexpr char kErrorOnlyKioskModeAllowed[] = | |
| 98 "API available only for ChromeOS kiosk mode."; | |
| 99 constexpr char kErrorOnlyFirstExtensionAllowed[] = | |
| 100 "Not the first extension to call this API."; | |
| 101 constexpr char kErrorInvalidStatus[] = "Invalid restart request status."; | |
| 102 constexpr char kErrorRequestedTooSoon[] = | |
| 103 "Restart was requested too soon. It was throttled instead."; | |
| 104 | |
| 105 constexpr int kMinDurationBetweenSuccessiveRestartsHours = 3; | |
| 106 | |
| 107 // This is used for unit tests, so that we can test the restartAfterDelay | |
| 108 // API without a kiosk app. | |
| 109 bool allow_non_kiosk_apps_restart_api_for_test = false; | |
| 110 | |
| 83 void DispatchOnStartupEventImpl(BrowserContext* browser_context, | 111 void DispatchOnStartupEventImpl(BrowserContext* browser_context, |
| 84 const std::string& extension_id, | 112 const std::string& extension_id, |
| 85 bool first_call, | 113 bool first_call, |
| 86 ExtensionHost* host) { | 114 ExtensionHost* host) { |
| 87 // A NULL host from the LazyBackgroundTaskQueue means the page failed to | 115 // A NULL host from the LazyBackgroundTaskQueue means the page failed to |
| 88 // load. Give up. | 116 // load. Give up. |
| 89 if (!host && !first_call) | 117 if (!host && !first_call) |
| 90 return; | 118 return; |
| 91 | 119 |
| 92 // Don't send onStartup events to incognito browser contexts. | 120 // Don't send onStartup events to incognito browser contexts. |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 144 /////////////////////////////////////////////////////////////////////////////// | 172 /////////////////////////////////////////////////////////////////////////////// |
| 145 | 173 |
| 146 static base::LazyInstance<BrowserContextKeyedAPIFactory<RuntimeAPI> > | 174 static base::LazyInstance<BrowserContextKeyedAPIFactory<RuntimeAPI> > |
| 147 g_factory = LAZY_INSTANCE_INITIALIZER; | 175 g_factory = LAZY_INSTANCE_INITIALIZER; |
| 148 | 176 |
| 149 // static | 177 // static |
| 150 BrowserContextKeyedAPIFactory<RuntimeAPI>* RuntimeAPI::GetFactoryInstance() { | 178 BrowserContextKeyedAPIFactory<RuntimeAPI>* RuntimeAPI::GetFactoryInstance() { |
| 151 return g_factory.Pointer(); | 179 return g_factory.Pointer(); |
| 152 } | 180 } |
| 153 | 181 |
| 182 // static | |
| 183 void RuntimeAPI::RegisterPrefs(PrefRegistrySimple* registry) { | |
| 184 registry->RegisterBooleanPref(kPrefLastRestartWasDuetoDelayedRestartApi, | |
|
Devlin
2016/06/14 16:27:21
nit: DueTo, not Dueto
afakhry
2016/06/14 18:00:04
Done.
| |
| 185 false); | |
| 186 registry->RegisterDoublePref(kPrefLastRestartAfterDelayTime, 0.0); | |
| 187 } | |
| 188 | |
| 154 template <> | 189 template <> |
| 155 void BrowserContextKeyedAPIFactory<RuntimeAPI>::DeclareFactoryDependencies() { | 190 void BrowserContextKeyedAPIFactory<RuntimeAPI>::DeclareFactoryDependencies() { |
| 156 DependsOn(ProcessManagerFactory::GetInstance()); | 191 DependsOn(ProcessManagerFactory::GetInstance()); |
| 157 } | 192 } |
| 158 | 193 |
| 159 RuntimeAPI::RuntimeAPI(content::BrowserContext* context) | 194 RuntimeAPI::RuntimeAPI(content::BrowserContext* context) |
| 160 : browser_context_(context), | 195 : browser_context_(context), |
| 196 extension_registry_observer_(this), | |
| 197 process_manager_observer_(this), | |
| 198 minimum_duration_between_restarts_(base::TimeDelta::FromHours( | |
| 199 kMinDurationBetweenSuccessiveRestartsHours)), | |
| 161 dispatch_chrome_updated_event_(false), | 200 dispatch_chrome_updated_event_(false), |
| 162 extension_registry_observer_(this), | 201 did_read_delayed_restart_preferences_(false), |
| 163 process_manager_observer_(this) { | 202 was_last_restart_due_to_delayed_restart_api_(false), |
| 203 weak_ptr_factory_(this) { | |
| 164 // RuntimeAPI is redirected in incognito, so |browser_context_| is never | 204 // RuntimeAPI is redirected in incognito, so |browser_context_| is never |
| 165 // incognito. | 205 // incognito. |
| 166 DCHECK(!browser_context_->IsOffTheRecord()); | 206 DCHECK(!browser_context_->IsOffTheRecord()); |
| 167 | 207 |
| 168 registrar_.Add(this, | 208 registrar_.Add(this, |
| 169 extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED, | 209 extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED, |
| 170 content::Source<BrowserContext>(context)); | 210 content::Source<BrowserContext>(context)); |
| 171 extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_)); | 211 extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_)); |
| 172 process_manager_observer_.Add(ProcessManager::Get(browser_context_)); | 212 process_manager_observer_.Add(ProcessManager::Get(browser_context_)); |
| 173 | 213 |
| (...skipping 138 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 312 | 352 |
| 313 void RuntimeAPI::OpenURL(const GURL& update_url) { | 353 void RuntimeAPI::OpenURL(const GURL& update_url) { |
| 314 delegate_->OpenURL(update_url); | 354 delegate_->OpenURL(update_url); |
| 315 } | 355 } |
| 316 | 356 |
| 317 bool RuntimeAPI::GetPlatformInfo(runtime::PlatformInfo* info) { | 357 bool RuntimeAPI::GetPlatformInfo(runtime::PlatformInfo* info) { |
| 318 return delegate_->GetPlatformInfo(info); | 358 return delegate_->GetPlatformInfo(info); |
| 319 } | 359 } |
| 320 | 360 |
| 321 bool RuntimeAPI::RestartDevice(std::string* error_message) { | 361 bool RuntimeAPI::RestartDevice(std::string* error_message) { |
| 362 if (was_last_restart_due_to_delayed_restart_api_ && | |
| 363 (ExtensionsBrowserClient::Get()->IsRunningInForcedAppMode() || | |
| 364 allow_non_kiosk_apps_restart_api_for_test)) { | |
| 365 // We don't allow an app by calling chrome.runtime.restart() to clear the | |
| 366 // throttle enforced on it when calling chrome.runtime.restartAfterDelay(), | |
| 367 // i.e. the app can't unthrottle itself. | |
| 368 // When running in forced kiosk app mode, we assume the following restart | |
| 369 // request will succeed. | |
| 370 PrefService* pref_service = | |
| 371 ExtensionsBrowserClient::Get()->GetPrefServiceForContext( | |
| 372 browser_context_); | |
| 373 DCHECK(pref_service); | |
| 374 pref_service->SetBoolean(kPrefLastRestartWasDuetoDelayedRestartApi, true); | |
| 375 } | |
| 322 return delegate_->RestartDevice(error_message); | 376 return delegate_->RestartDevice(error_message); |
| 377 ; | |
|
Devlin
2016/06/14 16:27:21
delete
afakhry
2016/06/14 18:00:04
Not sure where that came from. Done!
| |
| 378 } | |
| 379 | |
| 380 RuntimeAPI::RestartOnWatchdogStatus RuntimeAPI::RestartDeviceAfterDelay( | |
| 381 const std::string& extension_id, | |
| 382 int seconds_from_now) { | |
| 383 // To achieve as much accuracy as possible, record the time of the call as | |
| 384 // |now| here. | |
| 385 const base::Time now = base::Time::NowFromSystemTime(); | |
| 386 | |
| 387 if (schedule_restart_first_extension_id_.empty()) { | |
| 388 schedule_restart_first_extension_id_ = extension_id; | |
| 389 } else if (extension_id != schedule_restart_first_extension_id_) { | |
| 390 // We only allow the first extension to call this API to call it repeatedly. | |
| 391 // Any other extension will fail. | |
| 392 return RestartOnWatchdogStatus::FAILED_NOT_FIRST_EXTENSION; | |
| 393 } | |
| 394 | |
| 395 MaybeCancelRunningDelayedRestartTimer(); | |
| 396 | |
| 397 if (seconds_from_now == -1) { | |
| 398 // We already stopped the running timer (if any). | |
| 399 return RestartOnWatchdogStatus::SUCCESS_RESTART_CANCELED; | |
| 400 } | |
| 401 | |
| 402 if (!did_read_delayed_restart_preferences_) { | |
| 403 // Try to read any previous successful restart attempt time resulting from | |
| 404 // this API. | |
| 405 PrefService* pref_service = | |
| 406 ExtensionsBrowserClient::Get()->GetPrefServiceForContext( | |
| 407 browser_context_); | |
| 408 DCHECK(pref_service); | |
| 409 | |
| 410 was_last_restart_due_to_delayed_restart_api_ = | |
| 411 pref_service->GetBoolean(kPrefLastRestartWasDuetoDelayedRestartApi); | |
| 412 if (was_last_restart_due_to_delayed_restart_api_) { | |
| 413 // We clear this bit if the previous restart was due to this API, so that | |
| 414 // we don't throttle restart requests coming after other restarts or | |
| 415 // shutdowns not caused by the runtime API. | |
| 416 pref_service->SetBoolean(kPrefLastRestartWasDuetoDelayedRestartApi, | |
| 417 false); | |
| 418 } | |
| 419 | |
| 420 last_delayed_restart_time_ = base::Time::FromDoubleT( | |
| 421 pref_service->GetDouble(kPrefLastRestartAfterDelayTime)); | |
| 422 | |
| 423 if (!allow_non_kiosk_apps_restart_api_for_test) { | |
| 424 // Don't read every time unless in tests. | |
| 425 did_read_delayed_restart_preferences_ = true; | |
| 426 } | |
| 427 } | |
| 428 | |
| 429 return ScheduleDelayedRestart(now, seconds_from_now); | |
| 323 } | 430 } |
| 324 | 431 |
| 325 bool RuntimeAPI::OpenOptionsPage(const Extension* extension) { | 432 bool RuntimeAPI::OpenOptionsPage(const Extension* extension) { |
| 326 return delegate_->OpenOptionsPage(extension); | 433 return delegate_->OpenOptionsPage(extension); |
| 327 } | 434 } |
| 328 | 435 |
| 436 void RuntimeAPI::MaybeCancelRunningDelayedRestartTimer() { | |
| 437 if (restart_after_delay_timer_.IsRunning()) | |
| 438 restart_after_delay_timer_.Stop(); | |
| 439 } | |
| 440 | |
| 441 RuntimeAPI::RestartOnWatchdogStatus RuntimeAPI::ScheduleDelayedRestart( | |
| 442 const base::Time& now, | |
| 443 int seconds_from_now) { | |
| 444 base::TimeDelta delay_till_restart = | |
| 445 base::TimeDelta::FromSeconds(seconds_from_now); | |
| 446 | |
| 447 // Throttle restart requests that are received too soon successively, only if | |
| 448 // the previous restart was due to this API. | |
| 449 bool was_throttled = false; | |
| 450 if (was_last_restart_due_to_delayed_restart_api_) { | |
| 451 base::Time future_restart_time = now + delay_till_restart; | |
| 452 base::TimeDelta future_time_since_last_restart = | |
|
Devlin
2016/06/14 16:27:21
nit: "future_time_since_last_restart" sounds a lit
afakhry
2016/06/14 18:00:05
Done.
| |
| 453 future_restart_time > last_delayed_restart_time_ | |
|
Devlin
2016/06/14 16:27:21
When would this be false (exempting cases of e.g.
afakhry
2016/06/14 18:00:04
Could be corruption, or the device wall clock has
| |
| 454 ? future_restart_time - last_delayed_restart_time_ | |
| 455 : base::TimeDelta(); | |
| 456 if (future_time_since_last_restart < minimum_duration_between_restarts_) { | |
| 457 // Schedule the restart after |minimum_duration_between_restarts_| has | |
| 458 // passed. | |
| 459 delay_till_restart = minimum_duration_between_restarts_ - | |
| 460 (now - last_delayed_restart_time_); | |
| 461 was_throttled = true; | |
| 462 } | |
| 463 } | |
| 464 | |
| 465 restart_after_delay_timer_.Start( | |
| 466 FROM_HERE, delay_till_restart, | |
| 467 base::Bind(&RuntimeAPI::OnDelayedRestartTimerTimeout, | |
| 468 weak_ptr_factory_.GetWeakPtr())); | |
| 469 | |
| 470 return was_throttled ? RestartOnWatchdogStatus::FAILED_THROTTLED | |
| 471 : RestartOnWatchdogStatus::SUCCESS_RESTART_SCHEDULED; | |
| 472 } | |
| 473 | |
| 474 void RuntimeAPI::OnDelayedRestartTimerTimeout() { | |
| 475 // We can persist "now" as the last successful restart time, assuming that the | |
| 476 // following restart request will succeed, since it can only fail if requested | |
| 477 // by non kiosk apps, and we prevent that from the beginning (unless in | |
| 478 // unit tests). | |
| 479 // This assumption is important, since once restart is requested, we might not | |
| 480 // have enough time to persist the data to disk. | |
| 481 double now = base::Time::NowFromSystemTime().ToDoubleT(); | |
| 482 PrefService* pref_service = | |
| 483 ExtensionsBrowserClient::Get()->GetPrefServiceForContext( | |
| 484 browser_context_); | |
| 485 DCHECK(pref_service); | |
| 486 pref_service->SetDouble(kPrefLastRestartAfterDelayTime, now); | |
| 487 pref_service->SetBoolean(kPrefLastRestartWasDuetoDelayedRestartApi, true); | |
| 488 | |
| 489 std::string error_message; | |
| 490 const bool success = delegate_->RestartDevice(&error_message); | |
| 491 | |
| 492 if (!success && !allow_non_kiosk_apps_restart_api_for_test) { | |
|
Devlin
2016/06/14 16:27:21
DCHECK?
afakhry
2016/06/14 18:00:04
Done.
| |
| 493 // This is breaking our above assumption and should never be reached. | |
| 494 NOTREACHED(); | |
| 495 } | |
| 496 } | |
| 497 | |
| 498 void RuntimeAPI::AllowNonKioskAppsInRestartAfterDelayForTesting() { | |
| 499 allow_non_kiosk_apps_restart_api_for_test = true; | |
| 500 } | |
| 501 | |
| 329 /////////////////////////////////////////////////////////////////////////////// | 502 /////////////////////////////////////////////////////////////////////////////// |
| 330 | 503 |
| 331 // static | 504 // static |
| 332 void RuntimeEventRouter::DispatchOnStartupEvent( | 505 void RuntimeEventRouter::DispatchOnStartupEvent( |
| 333 content::BrowserContext* context, | 506 content::BrowserContext* context, |
| 334 const std::string& extension_id) { | 507 const std::string& extension_id) { |
| 335 DispatchOnStartupEventImpl(context, extension_id, true, NULL); | 508 DispatchOnStartupEventImpl(context, extension_id, true, NULL); |
| 336 } | 509 } |
| 337 | 510 |
| 338 // static | 511 // static |
| (...skipping 207 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 546 std::string message; | 719 std::string message; |
| 547 bool result = | 720 bool result = |
| 548 RuntimeAPI::GetFactoryInstance()->Get(browser_context())->RestartDevice( | 721 RuntimeAPI::GetFactoryInstance()->Get(browser_context())->RestartDevice( |
| 549 &message); | 722 &message); |
| 550 if (!result) { | 723 if (!result) { |
| 551 return RespondNow(Error(message)); | 724 return RespondNow(Error(message)); |
| 552 } | 725 } |
| 553 return RespondNow(NoArguments()); | 726 return RespondNow(NoArguments()); |
| 554 } | 727 } |
| 555 | 728 |
| 729 ExtensionFunction::ResponseAction RuntimeRestartAfterDelayFunction::Run() { | |
| 730 if (!allow_non_kiosk_apps_restart_api_for_test && | |
| 731 !ExtensionsBrowserClient::Get()->IsRunningInForcedAppMode()) { | |
| 732 return RespondNow(Error(kErrorOnlyKioskModeAllowed)); | |
| 733 } | |
| 734 | |
| 735 std::unique_ptr<api::runtime::RestartAfterDelay::Params> params( | |
| 736 api::runtime::RestartAfterDelay::Params::Create(*args_)); | |
| 737 EXTENSION_FUNCTION_VALIDATE(params.get()); | |
| 738 int seconds = params->seconds; | |
| 739 | |
| 740 if (seconds < -1) | |
|
Devlin
2016/06/14 16:27:21
maybe also catch 0?
afakhry
2016/06/14 18:00:04
Done.
| |
| 741 return RespondNow(Error(kErrorInvalidArgument, base::IntToString(seconds))); | |
| 742 | |
| 743 RuntimeAPI::RestartOnWatchdogStatus request_status = | |
| 744 RuntimeAPI::GetFactoryInstance() | |
| 745 ->Get(browser_context()) | |
| 746 ->RestartDeviceAfterDelay(extension()->id(), seconds); | |
| 747 | |
| 748 switch (request_status) { | |
| 749 case RuntimeAPI::RestartOnWatchdogStatus::FAILED_NOT_FIRST_EXTENSION: | |
| 750 return RespondNow(Error(kErrorOnlyFirstExtensionAllowed)); | |
| 751 | |
| 752 case RuntimeAPI::RestartOnWatchdogStatus::FAILED_THROTTLED: | |
| 753 return RespondNow(Error(kErrorRequestedTooSoon)); | |
| 754 | |
| 755 case RuntimeAPI::RestartOnWatchdogStatus::SUCCESS_RESTART_CANCELED: | |
| 756 case RuntimeAPI::RestartOnWatchdogStatus::SUCCESS_RESTART_SCHEDULED: | |
| 757 return RespondNow(NoArguments()); | |
| 758 } | |
| 759 | |
| 760 NOTREACHED(); | |
| 761 return RespondNow(Error(kErrorInvalidStatus)); | |
| 762 } | |
| 763 | |
| 556 ExtensionFunction::ResponseAction RuntimeGetPlatformInfoFunction::Run() { | 764 ExtensionFunction::ResponseAction RuntimeGetPlatformInfoFunction::Run() { |
| 557 runtime::PlatformInfo info; | 765 runtime::PlatformInfo info; |
| 558 if (!RuntimeAPI::GetFactoryInstance() | 766 if (!RuntimeAPI::GetFactoryInstance() |
| 559 ->Get(browser_context()) | 767 ->Get(browser_context()) |
| 560 ->GetPlatformInfo(&info)) { | 768 ->GetPlatformInfo(&info)) { |
| 561 return RespondNow(Error(kPlatformInfoUnavailable)); | 769 return RespondNow(Error(kPlatformInfoUnavailable)); |
| 562 } | 770 } |
| 563 return RespondNow( | 771 return RespondNow( |
| 564 ArgumentList(runtime::GetPlatformInfo::Results::Create(info))); | 772 ArgumentList(runtime::GetPlatformInfo::Results::Create(info))); |
| 565 } | 773 } |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 579 content::ChildProcessSecurityPolicy* policy = | 787 content::ChildProcessSecurityPolicy* policy = |
| 580 content::ChildProcessSecurityPolicy::GetInstance(); | 788 content::ChildProcessSecurityPolicy::GetInstance(); |
| 581 policy->GrantReadFileSystem(renderer_id, filesystem_id); | 789 policy->GrantReadFileSystem(renderer_id, filesystem_id); |
| 582 std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); | 790 std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); |
| 583 dict->SetString("fileSystemId", filesystem_id); | 791 dict->SetString("fileSystemId", filesystem_id); |
| 584 dict->SetString("baseName", relative_path); | 792 dict->SetString("baseName", relative_path); |
| 585 return RespondNow(OneArgument(std::move(dict))); | 793 return RespondNow(OneArgument(std::move(dict))); |
| 586 } | 794 } |
| 587 | 795 |
| 588 } // namespace extensions | 796 } // namespace extensions |
| OLD | NEW |