Home | History | Annotate | Download | only in runtime
      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
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/extensions/api/runtime/runtime_api.h"
      6 
      7 #include <utility>
      8 
      9 #include "base/logging.h"
     10 #include "base/memory/scoped_ptr.h"
     11 #include "base/values.h"
     12 #include "chrome/browser/browser_process.h"
     13 #include "chrome/browser/chrome_notification_types.h"
     14 #include "chrome/browser/extensions/extension_host.h"
     15 #include "chrome/browser/extensions/extension_service.h"
     16 #include "chrome/browser/extensions/extension_system.h"
     17 #include "chrome/browser/extensions/updater/extension_updater.h"
     18 #include "chrome/browser/profiles/profile.h"
     19 #include "chrome/browser/profiles/profile_manager.h"
     20 #include "chrome/browser/ui/browser_finder.h"
     21 #include "chrome/browser/ui/browser_navigator.h"
     22 #include "chrome/browser/ui/browser_window.h"
     23 #include "chrome/common/extensions/api/runtime.h"
     24 #include "chrome/common/omaha_query_params/omaha_query_params.h"
     25 #include "content/public/browser/child_process_security_policy.h"
     26 #include "content/public/browser/notification_service.h"
     27 #include "content/public/browser/render_process_host.h"
     28 #include "content/public/browser/render_view_host.h"
     29 #include "extensions/browser/event_router.h"
     30 #include "extensions/browser/extensions_browser_client.h"
     31 #include "extensions/browser/lazy_background_task_queue.h"
     32 #include "extensions/browser/process_manager.h"
     33 #include "extensions/common/error_utils.h"
     34 #include "extensions/common/extension.h"
     35 #include "extensions/common/manifest_handlers/background_info.h"
     36 #include "url/gurl.h"
     37 #include "webkit/browser/fileapi/isolated_context.h"
     38 
     39 #if defined(OS_CHROMEOS)
     40 #include "chrome/browser/chromeos/login/user_manager.h"
     41 #include "chromeos/dbus/dbus_thread_manager.h"
     42 #include "chromeos/dbus/power_manager_client.h"
     43 #endif
     44 
     45 using content::BrowserContext;
     46 
     47 namespace GetPlatformInfo = extensions::api::runtime::GetPlatformInfo;
     48 
     49 namespace extensions {
     50 
     51 namespace runtime = api::runtime;
     52 
     53 namespace {
     54 
     55 const char kNoBackgroundPageError[] = "You do not have a background page.";
     56 const char kPageLoadError[] = "Background page failed to load.";
     57 const char kInstallReason[] = "reason";
     58 const char kInstallReasonChromeUpdate[] = "chrome_update";
     59 const char kInstallReasonUpdate[] = "update";
     60 const char kInstallReasonInstall[] = "install";
     61 const char kInstallPreviousVersion[] = "previousVersion";
     62 const char kInvalidUrlError[] = "Invalid URL.";
     63 const char kUpdatesDisabledError[] = "Autoupdate is not enabled.";
     64 const char kUpdateFound[] = "update_available";
     65 const char kUpdateNotFound[] = "no_update";
     66 const char kUpdateThrottled[] = "throttled";
     67 
     68 // A preference key storing the url loaded when an extension is uninstalled.
     69 const char kUninstallUrl[] = "uninstall_url";
     70 
     71 // The name of the directory to be returned by getPackageDirectoryEntry. This
     72 // particular value does not matter to user code, but is chosen for consistency
     73 // with the equivalent Pepper API.
     74 const char kPackageDirectoryPath[] = "crxfs";
     75 
     76 static void DispatchOnStartupEventImpl(
     77     Profile* profile,
     78     const std::string& extension_id,
     79     bool first_call,
     80     ExtensionHost* host) {
     81   // A NULL host from the LazyBackgroundTaskQueue means the page failed to
     82   // load. Give up.
     83   if (!host && !first_call)
     84     return;
     85 
     86   // Don't send onStartup events to incognito profiles.
     87   if (profile->IsOffTheRecord())
     88     return;
     89 
     90   if (g_browser_process->IsShuttingDown() ||
     91       !g_browser_process->profile_manager()->IsValidProfile(profile))
     92     return;
     93   ExtensionSystem* system = ExtensionSystem::Get(profile);
     94   if (!system)
     95     return;
     96 
     97   // If this is a persistent background page, we want to wait for it to load
     98   // (it might not be ready, since this is startup). But only enqueue once.
     99   // If it fails to load the first time, don't bother trying again.
    100   const Extension* extension =
    101       system->extension_service()->extensions()->GetByID(extension_id);
    102   if (extension && BackgroundInfo::HasPersistentBackgroundPage(extension) &&
    103       first_call &&
    104       system->lazy_background_task_queue()->
    105           ShouldEnqueueTask(profile, extension)) {
    106     system->lazy_background_task_queue()->AddPendingTask(
    107         profile, extension_id,
    108         base::Bind(&DispatchOnStartupEventImpl,
    109                    profile, extension_id, false));
    110     return;
    111   }
    112 
    113   scoped_ptr<base::ListValue> event_args(new base::ListValue());
    114   scoped_ptr<Event> event(new Event(runtime::OnStartup::kEventName,
    115                                     event_args.Pass()));
    116   system->event_router()->DispatchEventToExtension(extension_id, event.Pass());
    117 }
    118 
    119 void SetUninstallUrl(ExtensionPrefs* prefs,
    120                      const std::string& extension_id,
    121                      const std::string& url_string) {
    122   prefs->UpdateExtensionPref(extension_id,
    123                              kUninstallUrl,
    124                              new base::StringValue(url_string));
    125 }
    126 
    127 #if defined(ENABLE_EXTENSIONS)
    128 std::string GetUninstallUrl(ExtensionPrefs* prefs,
    129                             const std::string& extension_id) {
    130   std::string url_string;
    131   prefs->ReadPrefAsString(extension_id, kUninstallUrl, &url_string);
    132   return url_string;
    133 }
    134 #endif  // defined(ENABLE_EXTENSIONS)
    135 
    136 }  // namespace
    137 
    138 ///////////////////////////////////////////////////////////////////////////////
    139 
    140 RuntimeAPI::RuntimeAPI(content::BrowserContext* context)
    141     : browser_context_(context),
    142       dispatch_chrome_updated_event_(false),
    143       registered_for_updates_(false) {
    144   registrar_.Add(this, chrome::NOTIFICATION_EXTENSIONS_READY,
    145                  content::Source<BrowserContext>(context));
    146   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
    147                  content::Source<BrowserContext>(context));
    148   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALLED,
    149                  content::Source<BrowserContext>(context));
    150   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNINSTALLED,
    151                  content::Source<BrowserContext>(context));
    152 
    153   // Check if registered events are up-to-date. We can only do this once
    154   // per browser context, since it updates internal state when called.
    155   dispatch_chrome_updated_event_ =
    156       ExtensionsBrowserClient::Get()->DidVersionUpdate(browser_context_);
    157 }
    158 
    159 RuntimeAPI::~RuntimeAPI() {
    160   if (registered_for_updates_) {
    161     ExtensionSystem::GetForBrowserContext(browser_context_)->
    162         extension_service()->RemoveUpdateObserver(this);
    163   }
    164 }
    165 
    166 void RuntimeAPI::Observe(int type,
    167                          const content::NotificationSource& source,
    168                          const content::NotificationDetails& details) {
    169   switch (type) {
    170     case chrome::NOTIFICATION_EXTENSIONS_READY: {
    171       OnExtensionsReady();
    172       break;
    173     }
    174     case chrome::NOTIFICATION_EXTENSION_LOADED: {
    175       const Extension* extension =
    176           content::Details<const Extension>(details).ptr();
    177       OnExtensionLoaded(extension);
    178       break;
    179     }
    180     case chrome::NOTIFICATION_EXTENSION_INSTALLED: {
    181       const Extension* extension =
    182           content::Details<const InstalledExtensionInfo>(details)->extension;
    183       OnExtensionInstalled(extension);
    184       break;
    185     }
    186     case chrome::NOTIFICATION_EXTENSION_UNINSTALLED: {
    187       const Extension* extension =
    188           content::Details<const Extension>(details).ptr();
    189       OnExtensionUninstalled(extension);
    190       break;
    191     }
    192     default:
    193       NOTREACHED();
    194       break;
    195   }
    196 }
    197 
    198 void RuntimeAPI::OnExtensionsReady() {
    199   // We're done restarting Chrome after an update.
    200   dispatch_chrome_updated_event_ = false;
    201 
    202   registered_for_updates_ = true;
    203 
    204   ExtensionSystem::GetForBrowserContext(browser_context_)->extension_service()->
    205       AddUpdateObserver(this);
    206 }
    207 
    208 void RuntimeAPI::OnExtensionLoaded(const Extension* extension) {
    209   if (!dispatch_chrome_updated_event_)
    210     return;
    211 
    212   // Dispatch the onInstalled event with reason "chrome_update".
    213   base::MessageLoop::current()->PostTask(
    214       FROM_HERE,
    215       base::Bind(&RuntimeEventRouter::DispatchOnInstalledEvent,
    216                  browser_context_,
    217                  extension->id(),
    218                  Version(),
    219                  true));
    220 }
    221 
    222 void RuntimeAPI::OnExtensionInstalled(const Extension* extension) {
    223   // Get the previous version to check if this is an upgrade.
    224   ExtensionService* service = ExtensionSystem::GetForBrowserContext(
    225       browser_context_)->extension_service();
    226   const Extension* old = service->GetExtensionById(extension->id(), true);
    227   Version old_version;
    228   if (old)
    229     old_version = *old->version();
    230 
    231   // Dispatch the onInstalled event.
    232   base::MessageLoop::current()->PostTask(
    233       FROM_HERE,
    234       base::Bind(&RuntimeEventRouter::DispatchOnInstalledEvent,
    235                  browser_context_,
    236                  extension->id(),
    237                  old_version,
    238                  false));
    239 
    240 }
    241 
    242 void RuntimeAPI::OnExtensionUninstalled(const Extension* extension) {
    243   Profile* profile = Profile::FromBrowserContext(browser_context_);
    244   RuntimeEventRouter::OnExtensionUninstalled(profile, extension->id());
    245 }
    246 
    247 void RuntimeAPI::OnAppUpdateAvailable(const Extension* extension) {
    248   Profile* profile = Profile::FromBrowserContext(browser_context_);
    249   RuntimeEventRouter::DispatchOnUpdateAvailableEvent(
    250       profile, extension->id(), extension->manifest()->value());
    251 }
    252 
    253 void RuntimeAPI::OnChromeUpdateAvailable() {
    254   Profile* profile = Profile::FromBrowserContext(browser_context_);
    255   RuntimeEventRouter::DispatchOnBrowserUpdateAvailableEvent(profile);
    256 }
    257 
    258 ///////////////////////////////////////////////////////////////////////////////
    259 
    260 // static
    261 void RuntimeEventRouter::DispatchOnStartupEvent(
    262     content::BrowserContext* context, const std::string& extension_id) {
    263   // TODO(jamescook): Convert to BrowserContext all the way down.
    264   Profile* profile = static_cast<Profile*>(context);
    265   DispatchOnStartupEventImpl(profile, extension_id, true, NULL);
    266 }
    267 
    268 // static
    269 void RuntimeEventRouter::DispatchOnInstalledEvent(
    270     content::BrowserContext* context,
    271     const std::string& extension_id,
    272     const Version& old_version,
    273     bool chrome_updated) {
    274   if (!ExtensionsBrowserClient::Get()->IsValidContext(context))
    275     return;
    276   ExtensionSystem* system = ExtensionSystem::GetForBrowserContext(context);
    277   if (!system)
    278     return;
    279 
    280   scoped_ptr<base::ListValue> event_args(new base::ListValue());
    281   base::DictionaryValue* info = new base::DictionaryValue();
    282   event_args->Append(info);
    283   if (old_version.IsValid()) {
    284     info->SetString(kInstallReason, kInstallReasonUpdate);
    285     info->SetString(kInstallPreviousVersion, old_version.GetString());
    286   } else if (chrome_updated) {
    287     info->SetString(kInstallReason, kInstallReasonChromeUpdate);
    288   } else {
    289     info->SetString(kInstallReason, kInstallReasonInstall);
    290   }
    291   DCHECK(system->event_router());
    292   scoped_ptr<Event> event(new Event(runtime::OnInstalled::kEventName,
    293                                     event_args.Pass()));
    294   system->event_router()->DispatchEventWithLazyListener(extension_id,
    295                                                         event.Pass());
    296 }
    297 
    298 // static
    299 void RuntimeEventRouter::DispatchOnUpdateAvailableEvent(
    300     Profile* profile,
    301     const std::string& extension_id,
    302     const base::DictionaryValue* manifest) {
    303   ExtensionSystem* system = ExtensionSystem::Get(profile);
    304   if (!system)
    305     return;
    306 
    307   scoped_ptr<base::ListValue> args(new base::ListValue);
    308   args->Append(manifest->DeepCopy());
    309   DCHECK(system->event_router());
    310   scoped_ptr<Event> event(new Event(runtime::OnUpdateAvailable::kEventName,
    311                                     args.Pass()));
    312   system->event_router()->DispatchEventToExtension(extension_id, event.Pass());
    313 }
    314 
    315 // static
    316 void RuntimeEventRouter::DispatchOnBrowserUpdateAvailableEvent(
    317     Profile* profile) {
    318   ExtensionSystem* system = ExtensionSystem::Get(profile);
    319   if (!system)
    320     return;
    321 
    322   scoped_ptr<base::ListValue> args(new base::ListValue);
    323   DCHECK(system->event_router());
    324   scoped_ptr<Event> event(new Event(
    325       runtime::OnBrowserUpdateAvailable::kEventName, args.Pass()));
    326   system->event_router()->BroadcastEvent(event.Pass());
    327 }
    328 
    329 // static
    330 void RuntimeEventRouter::DispatchOnRestartRequiredEvent(
    331     Profile* profile,
    332     const std::string& app_id,
    333     api::runtime::OnRestartRequired::Reason reason) {
    334   ExtensionSystem* system = ExtensionSystem::Get(profile);
    335   if (!system)
    336     return;
    337 
    338   scoped_ptr<Event> event(
    339       new Event(runtime::OnRestartRequired::kEventName,
    340                 api::runtime::OnRestartRequired::Create(reason)));
    341 
    342   DCHECK(system->event_router());
    343   system->event_router()->DispatchEventToExtension(app_id, event.Pass());
    344 }
    345 
    346 // static
    347 void RuntimeEventRouter::OnExtensionUninstalled(
    348     Profile* profile,
    349     const std::string& extension_id) {
    350 #if defined(ENABLE_EXTENSIONS)
    351   GURL uninstall_url(GetUninstallUrl(ExtensionPrefs::Get(profile),
    352                                      extension_id));
    353 
    354   if (uninstall_url.is_empty())
    355     return;
    356 
    357   Browser* browser = chrome::FindLastActiveWithProfile(profile,
    358       chrome::GetActiveDesktop());
    359   if (!browser)
    360     browser = new Browser(Browser::CreateParams(profile,
    361                                                 chrome::GetActiveDesktop()));
    362 
    363   chrome::NavigateParams params(browser, uninstall_url,
    364                                 content::PAGE_TRANSITION_CLIENT_REDIRECT);
    365   params.disposition = NEW_FOREGROUND_TAB;
    366   params.user_gesture = false;
    367   chrome::Navigate(&params);
    368 #endif  // defined(ENABLE_EXTENSIONS)
    369 }
    370 
    371 bool RuntimeGetBackgroundPageFunction::RunImpl() {
    372   ExtensionSystem* system = ExtensionSystem::Get(GetProfile());
    373   ExtensionHost* host = system->process_manager()->
    374       GetBackgroundHostForExtension(extension_id());
    375   if (system->lazy_background_task_queue()->ShouldEnqueueTask(GetProfile(),
    376                                                               GetExtension())) {
    377     system->lazy_background_task_queue()->AddPendingTask(
    378         GetProfile(),
    379         extension_id(),
    380         base::Bind(&RuntimeGetBackgroundPageFunction::OnPageLoaded, this));
    381   } else if (host) {
    382     OnPageLoaded(host);
    383   } else {
    384     error_ = kNoBackgroundPageError;
    385     return false;
    386   }
    387 
    388   return true;
    389 }
    390 
    391 void RuntimeGetBackgroundPageFunction::OnPageLoaded(ExtensionHost* host) {
    392   if (host) {
    393     SendResponse(true);
    394   } else {
    395     error_ = kPageLoadError;
    396     SendResponse(false);
    397   }
    398 }
    399 
    400 bool RuntimeSetUninstallUrlFunction::RunImpl() {
    401   std::string url_string;
    402   EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &url_string));
    403 
    404   GURL url(url_string);
    405   if (!url.is_valid()) {
    406     error_ = ErrorUtils::FormatErrorMessage(kInvalidUrlError, url_string);
    407     return false;
    408   }
    409 
    410   SetUninstallUrl(
    411       ExtensionPrefs::Get(GetProfile()), extension_id(), url_string);
    412   return true;
    413 }
    414 
    415 bool RuntimeReloadFunction::RunImpl() {
    416   // We can't call ReloadExtension directly, since when this method finishes
    417   // it tries to decrease the reference count for the extension, which fails
    418   // if the extension has already been reloaded; so instead we post a task.
    419   base::MessageLoop::current()->PostTask(
    420       FROM_HERE,
    421       base::Bind(&ExtensionService::ReloadExtension,
    422                  GetProfile()->GetExtensionService()->AsWeakPtr(),
    423                  extension_id()));
    424   return true;
    425 }
    426 
    427 RuntimeRequestUpdateCheckFunction::RuntimeRequestUpdateCheckFunction() {
    428   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UPDATE_FOUND,
    429                  content::NotificationService::AllSources());
    430 }
    431 
    432 bool RuntimeRequestUpdateCheckFunction::RunImpl() {
    433   ExtensionSystem* system = ExtensionSystem::Get(GetProfile());
    434   ExtensionService* service = system->extension_service();
    435   ExtensionUpdater* updater = service->updater();
    436   if (!updater) {
    437     error_ = kUpdatesDisabledError;
    438     return false;
    439   }
    440 
    441   did_reply_ = false;
    442   if (!updater->CheckExtensionSoon(extension_id(), base::Bind(
    443       &RuntimeRequestUpdateCheckFunction::CheckComplete, this))) {
    444     did_reply_ = true;
    445     SetResult(new base::StringValue(kUpdateThrottled));
    446     SendResponse(true);
    447   }
    448   return true;
    449 }
    450 
    451 void RuntimeRequestUpdateCheckFunction::CheckComplete() {
    452   if (did_reply_)
    453     return;
    454 
    455   did_reply_ = true;
    456 
    457   // Since no UPDATE_FOUND notification was seen, this generally would mean
    458   // that no update is found, but a previous update check might have already
    459   // queued up an update, so check for that here to make sure we return the
    460   // right value.
    461   ExtensionSystem* system = ExtensionSystem::Get(GetProfile());
    462   ExtensionService* service = system->extension_service();
    463   const Extension* update = service->GetPendingExtensionUpdate(extension_id());
    464   if (update) {
    465     ReplyUpdateFound(update->VersionString());
    466   } else {
    467     SetResult(new base::StringValue(kUpdateNotFound));
    468   }
    469   SendResponse(true);
    470 }
    471 
    472 void RuntimeRequestUpdateCheckFunction::Observe(
    473     int type,
    474     const content::NotificationSource& source,
    475     const content::NotificationDetails& details) {
    476   if (did_reply_)
    477     return;
    478 
    479   DCHECK(type == chrome::NOTIFICATION_EXTENSION_UPDATE_FOUND);
    480   typedef const std::pair<std::string, Version> UpdateDetails;
    481   const std::string& id = content::Details<UpdateDetails>(details)->first;
    482   const Version& version = content::Details<UpdateDetails>(details)->second;
    483   if (id == extension_id()) {
    484     ReplyUpdateFound(version.GetString());
    485   }
    486 }
    487 
    488 void RuntimeRequestUpdateCheckFunction::ReplyUpdateFound(
    489     const std::string& version) {
    490   did_reply_ = true;
    491   results_.reset(new base::ListValue);
    492   results_->AppendString(kUpdateFound);
    493   base::DictionaryValue* details = new base::DictionaryValue;
    494   results_->Append(details);
    495   details->SetString("version", version);
    496   SendResponse(true);
    497 }
    498 
    499 bool RuntimeRestartFunction::RunImpl() {
    500 #if defined(OS_CHROMEOS)
    501   if (chromeos::UserManager::Get()->IsLoggedInAsKioskApp()) {
    502     chromeos::DBusThreadManager::Get()
    503         ->GetPowerManagerClient()
    504         ->RequestRestart();
    505     return true;
    506   }
    507 #endif
    508   SetError("Function available only for ChromeOS kiosk mode.");
    509   return false;
    510 }
    511 
    512 bool RuntimeGetPlatformInfoFunction::RunImpl() {
    513   GetPlatformInfo::Results::PlatformInfo info;
    514 
    515   const char* os = chrome::OmahaQueryParams::getOS();
    516   if (strcmp(os, "mac") == 0) {
    517     info.os = GetPlatformInfo::Results::PlatformInfo::OS_MAC_;
    518   } else if (strcmp(os, "win") == 0) {
    519     info.os = GetPlatformInfo::Results::PlatformInfo::OS_WIN_;
    520   } else if (strcmp(os, "android") == 0) {
    521     info.os = GetPlatformInfo::Results::PlatformInfo::OS_ANDROID_;
    522   } else if (strcmp(os, "cros") == 0) {
    523     info.os = GetPlatformInfo::Results::PlatformInfo::OS_CROS_;
    524   } else if (strcmp(os, "linux") == 0) {
    525     info.os = GetPlatformInfo::Results::PlatformInfo::OS_LINUX_;
    526   } else if (strcmp(os, "openbsd") == 0) {
    527     info.os = GetPlatformInfo::Results::PlatformInfo::OS_OPENBSD_;
    528   } else {
    529     NOTREACHED();
    530     return false;
    531   }
    532 
    533   const char* arch = chrome::OmahaQueryParams::getArch();
    534   if (strcmp(arch, "arm") == 0) {
    535     info.arch = GetPlatformInfo::Results::PlatformInfo::ARCH_ARM;
    536   } else if (strcmp(arch, "x86") == 0) {
    537     info.arch = GetPlatformInfo::Results::PlatformInfo::ARCH_X86_32;
    538   } else if (strcmp(arch, "x64") == 0) {
    539     info.arch = GetPlatformInfo::Results::PlatformInfo::ARCH_X86_64;
    540   } else {
    541     NOTREACHED();
    542     return false;
    543   }
    544 
    545   const char* nacl_arch = chrome::OmahaQueryParams::getNaclArch();
    546   if (strcmp(nacl_arch, "arm") == 0) {
    547     info.nacl_arch = GetPlatformInfo::Results::PlatformInfo::NACL_ARCH_ARM;
    548   } else if (strcmp(nacl_arch, "x86-32") == 0) {
    549     info.nacl_arch = GetPlatformInfo::Results::PlatformInfo::NACL_ARCH_X86_32;
    550   } else if (strcmp(nacl_arch, "x86-64") == 0) {
    551     info.nacl_arch = GetPlatformInfo::Results::PlatformInfo::NACL_ARCH_X86_64;
    552   } else {
    553     NOTREACHED();
    554     return false;
    555   }
    556 
    557   results_ = GetPlatformInfo::Results::Create(info);
    558   return true;
    559 }
    560 
    561 bool RuntimeGetPackageDirectoryEntryFunction::RunImpl() {
    562   fileapi::IsolatedContext* isolated_context =
    563       fileapi::IsolatedContext::GetInstance();
    564   DCHECK(isolated_context);
    565 
    566   std::string relative_path = kPackageDirectoryPath;
    567   base::FilePath path = extension_->path();
    568   std::string filesystem_id = isolated_context->RegisterFileSystemForPath(
    569       fileapi::kFileSystemTypeNativeLocal, path, &relative_path);
    570 
    571   int renderer_id = render_view_host_->GetProcess()->GetID();
    572   content::ChildProcessSecurityPolicy* policy =
    573       content::ChildProcessSecurityPolicy::GetInstance();
    574   policy->GrantReadFileSystem(renderer_id, filesystem_id);
    575   base::DictionaryValue* dict = new base::DictionaryValue();
    576   SetResult(dict);
    577   dict->SetString("fileSystemId", filesystem_id);
    578   dict->SetString("baseName", relative_path);
    579   return true;
    580 }
    581 
    582 }   // namespace extensions
    583