Home | History | Annotate | Download | only in file_manager
      1 // Copyright 2013 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/chromeos/extensions/file_manager/private_api_tasks.h"
      6 
      7 #include "chrome/browser/chromeos/drive/drive_app_registry.h"
      8 #include "chrome/browser/chromeos/drive/drive_integration_service.h"
      9 #include "chrome/browser/chromeos/extensions/file_manager/file_browser_handlers.h"
     10 #include "chrome/browser/chromeos/extensions/file_manager/file_manager_util.h"
     11 #include "chrome/browser/chromeos/extensions/file_manager/file_tasks.h"
     12 #include "chrome/browser/chromeos/extensions/file_manager/private_api_util.h"
     13 #include "chrome/browser/chromeos/fileapi/file_system_backend.h"
     14 #include "chrome/browser/extensions/api/file_handlers/app_file_handler_util.h"
     15 #include "chrome/browser/extensions/extension_service.h"
     16 #include "chrome/browser/extensions/extension_system.h"
     17 #include "chrome/browser/profiles/profile.h"
     18 #include "chrome/browser/ui/browser_finder.h"
     19 #include "chrome/browser/ui/webui/extensions/extension_icon_source.h"
     20 #include "chrome/common/extensions/api/file_browser_handlers/file_browser_handler.h"
     21 #include "content/public/browser/browser_context.h"
     22 #include "content/public/browser/render_view_host.h"
     23 #include "content/public/browser/storage_partition.h"
     24 #include "webkit/browser/fileapi/file_system_context.h"
     25 #include "webkit/browser/fileapi/file_system_url.h"
     26 
     27 using content::BrowserContext;
     28 using extensions::app_file_handler_util::FindFileHandlersForFiles;
     29 using extensions::app_file_handler_util::PathAndMimeTypeSet;
     30 using extensions::Extension;
     31 using fileapi::FileSystemURL;
     32 
     33 namespace file_manager {
     34 namespace {
     35 
     36 // Error messages.
     37 const char kInvalidFileUrl[] = "Invalid file URL";
     38 
     39 // Default icon path for drive docs.
     40 const char kDefaultIcon[] = "images/filetype_generic.png";
     41 
     42 // Logs the default task for debugging.
     43 void LogDefaultTask(const std::set<std::string>& mime_types,
     44                     const std::set<std::string>& suffixes,
     45                     const std::string& task_id) {
     46   if (!mime_types.empty()) {
     47     std::string mime_types_str;
     48     for (std::set<std::string>::const_iterator iter = mime_types.begin();
     49          iter != mime_types.end(); ++iter) {
     50       if (iter == mime_types.begin()) {
     51         mime_types_str = *iter;
     52       } else {
     53         mime_types_str += ", " + *iter;
     54       }
     55     }
     56     VLOG(1) << "Associating task " << task_id
     57             << " with the following MIME types: ";
     58     VLOG(1) << "  " << mime_types_str;
     59   }
     60 
     61   if (!suffixes.empty()) {
     62     std::string suffixes_str;
     63     for (std::set<std::string>::const_iterator iter = suffixes.begin();
     64          iter != suffixes.end(); ++iter) {
     65       if (iter == suffixes.begin()) {
     66         suffixes_str = *iter;
     67       } else {
     68         suffixes_str += ", " + *iter;
     69       }
     70     }
     71     VLOG(1) << "Associating task " << task_id
     72             << " with the following suffixes: ";
     73     VLOG(1) << "  " << suffixes_str;
     74   }
     75 }
     76 
     77 // Gets the mime types for the given file paths.
     78 void GetMimeTypesForFileURLs(const std::vector<base::FilePath>& file_paths,
     79                              PathAndMimeTypeSet* files) {
     80   for (std::vector<base::FilePath>::const_iterator iter = file_paths.begin();
     81        iter != file_paths.end(); ++iter) {
     82     files->insert(
     83         std::make_pair(*iter, util::GetMimeTypeForPath(*iter)));
     84   }
     85 }
     86 
     87 // Make a set of unique filename suffixes out of the list of file URLs.
     88 std::set<std::string> GetUniqueSuffixes(base::ListValue* file_url_list,
     89                                         fileapi::FileSystemContext* context) {
     90   std::set<std::string> suffixes;
     91   for (size_t i = 0; i < file_url_list->GetSize(); ++i) {
     92     std::string url_str;
     93     if (!file_url_list->GetString(i, &url_str))
     94       return std::set<std::string>();
     95     FileSystemURL url = context->CrackURL(GURL(url_str));
     96     if (!url.is_valid() || url.path().empty())
     97       return std::set<std::string>();
     98     // We'll skip empty suffixes.
     99     if (!url.path().Extension().empty())
    100       suffixes.insert(url.path().Extension());
    101   }
    102   return suffixes;
    103 }
    104 
    105 // Make a set of unique MIME types out of the list of MIME types.
    106 std::set<std::string> GetUniqueMimeTypes(base::ListValue* mime_type_list) {
    107   std::set<std::string> mime_types;
    108   for (size_t i = 0; i < mime_type_list->GetSize(); ++i) {
    109     std::string mime_type;
    110     if (!mime_type_list->GetString(i, &mime_type))
    111       return std::set<std::string>();
    112     // We'll skip empty MIME types.
    113     if (!mime_type.empty())
    114       mime_types.insert(mime_type);
    115   }
    116   return mime_types;
    117 }
    118 
    119 }  // namespace
    120 
    121 ExecuteTaskFunction::ExecuteTaskFunction() {
    122 }
    123 
    124 ExecuteTaskFunction::~ExecuteTaskFunction() {
    125 }
    126 
    127 bool ExecuteTaskFunction::RunImpl() {
    128   // First param is task id that was to the extension with getFileTasks call.
    129   std::string task_id;
    130   if (!args_->GetString(0, &task_id) || !task_id.size())
    131     return false;
    132 
    133   // TODO(kaznacheev): Crack the task_id here, store it in the Executor
    134   // and avoid passing it around.
    135 
    136   // The second param is the list of files that need to be executed with this
    137   // task.
    138   ListValue* files_list = NULL;
    139   if (!args_->GetList(1, &files_list))
    140     return false;
    141 
    142   file_tasks::TaskDescriptor task;
    143   if (!file_tasks::ParseTaskID(task_id, &task)) {
    144     LOG(WARNING) << "Invalid task " << task_id;
    145     return false;
    146   }
    147 
    148   if (!files_list->GetSize())
    149     return true;
    150 
    151   content::SiteInstance* site_instance = render_view_host()->GetSiteInstance();
    152   scoped_refptr<fileapi::FileSystemContext> file_system_context =
    153       BrowserContext::GetStoragePartition(profile(), site_instance)->
    154       GetFileSystemContext();
    155 
    156   std::vector<FileSystemURL> file_urls;
    157   for (size_t i = 0; i < files_list->GetSize(); i++) {
    158     std::string file_url_str;
    159     if (!files_list->GetString(i, &file_url_str)) {
    160       error_ = kInvalidFileUrl;
    161       return false;
    162     }
    163     FileSystemURL url = file_system_context->CrackURL(GURL(file_url_str));
    164     if (!chromeos::FileSystemBackend::CanHandleURL(url)) {
    165       error_ = kInvalidFileUrl;
    166       return false;
    167     }
    168     file_urls.push_back(url);
    169   }
    170 
    171   int32 tab_id = util::GetTabId(dispatcher());
    172   return file_tasks::ExecuteFileTask(
    173       profile(),
    174       source_url(),
    175       extension_->id(),
    176       tab_id,
    177       task,
    178       file_urls,
    179       base::Bind(&ExecuteTaskFunction::OnTaskExecuted, this));
    180 }
    181 
    182 void ExecuteTaskFunction::OnTaskExecuted(bool success) {
    183   SetResult(new base::FundamentalValue(success));
    184   SendResponse(true);
    185 }
    186 
    187 struct GetFileTasksFunction::FileInfo {
    188   GURL file_url;
    189   base::FilePath file_path;
    190   std::string mime_type;
    191 };
    192 
    193 struct GetFileTasksFunction::TaskInfo {
    194   TaskInfo(const string16& app_name, const GURL& icon_url)
    195       : app_name(app_name), icon_url(icon_url) {
    196   }
    197 
    198   string16 app_name;
    199   GURL icon_url;
    200 };
    201 
    202 GetFileTasksFunction::GetFileTasksFunction() {
    203 }
    204 
    205 GetFileTasksFunction::~GetFileTasksFunction() {
    206 }
    207 
    208 // static
    209 void GetFileTasksFunction::GetAvailableDriveTasks(
    210     drive::DriveAppRegistry* registry,
    211     const FileInfoList& file_info_list,
    212     TaskInfoMap* task_info_map) {
    213   DCHECK(registry);
    214   DCHECK(task_info_map);
    215   DCHECK(task_info_map->empty());
    216 
    217   bool is_first = true;
    218   for (size_t i = 0; i < file_info_list.size(); ++i) {
    219     const FileInfo& file_info = file_info_list[i];
    220     if (file_info.file_path.empty())
    221       continue;
    222 
    223     ScopedVector<drive::DriveAppInfo> app_info_list;
    224     registry->GetAppsForFile(
    225         file_info.file_path, file_info.mime_type, &app_info_list);
    226 
    227     if (is_first) {
    228       // For the first file, we store all the info.
    229       for (size_t j = 0; j < app_info_list.size(); ++j) {
    230         const drive::DriveAppInfo& app_info = *app_info_list[j];
    231         GURL icon_url = util::FindPreferredIcon(app_info.app_icons,
    232                                                 util::kPreferredIconSize);
    233         task_info_map->insert(std::pair<std::string, TaskInfo>(
    234             file_tasks::MakeDriveAppTaskId(app_info.app_id),
    235             TaskInfo(app_info.app_name, icon_url)));
    236       }
    237     } else {
    238       // For remaining files, take the intersection with the current result,
    239       // based on the task id.
    240       std::set<std::string> task_id_set;
    241       for (size_t j = 0; j < app_info_list.size(); ++j) {
    242         task_id_set.insert(
    243             file_tasks::MakeDriveAppTaskId(app_info_list[j]->app_id));
    244       }
    245       for (TaskInfoMap::iterator iter = task_info_map->begin();
    246            iter != task_info_map->end(); ) {
    247         if (task_id_set.find(iter->first) == task_id_set.end()) {
    248           task_info_map->erase(iter++);
    249         } else {
    250           ++iter;
    251         }
    252       }
    253     }
    254 
    255     is_first = false;
    256   }
    257 }
    258 
    259 void GetFileTasksFunction::FindDefaultDriveTasks(
    260     const FileInfoList& file_info_list,
    261     const TaskInfoMap& task_info_map,
    262     std::set<std::string>* default_tasks) {
    263   DCHECK(default_tasks);
    264 
    265   for (size_t i = 0; i < file_info_list.size(); ++i) {
    266     const FileInfo& file_info = file_info_list[i];
    267     std::string task_id = file_tasks::GetDefaultTaskIdFromPrefs(
    268         profile_, file_info.mime_type, file_info.file_path.Extension());
    269     if (task_info_map.find(task_id) != task_info_map.end())
    270       default_tasks->insert(task_id);
    271   }
    272 }
    273 
    274 // static
    275 void GetFileTasksFunction::CreateDriveTasks(
    276     const TaskInfoMap& task_info_map,
    277     const std::set<std::string>& default_tasks,
    278     ListValue* result_list,
    279     bool* default_already_set) {
    280   DCHECK(result_list);
    281   DCHECK(default_already_set);
    282 
    283   for (TaskInfoMap::const_iterator iter = task_info_map.begin();
    284        iter != task_info_map.end(); ++iter) {
    285     DictionaryValue* task = new DictionaryValue;
    286     task->SetString("taskId", iter->first);
    287     task->SetString("title", iter->second.app_name);
    288 
    289     const GURL& icon_url = iter->second.icon_url;
    290     if (!icon_url.is_empty())
    291       task->SetString("iconUrl", icon_url.spec());
    292 
    293     task->SetBoolean("driveApp", true);
    294 
    295     // Once we set a default app, we don't want to set any more.
    296     if (!(*default_already_set) &&
    297         default_tasks.find(iter->first) != default_tasks.end()) {
    298       task->SetBoolean("isDefault", true);
    299       *default_already_set = true;
    300     } else {
    301       task->SetBoolean("isDefault", false);
    302     }
    303     result_list->Append(task);
    304   }
    305 }
    306 
    307 void GetFileTasksFunction::FindDriveAppTasks(
    308     const FileInfoList& file_info_list,
    309     ListValue* result_list,
    310     bool* default_already_set) {
    311   DCHECK(result_list);
    312   DCHECK(default_already_set);
    313 
    314   if (file_info_list.empty())
    315     return;
    316 
    317   drive::DriveIntegrationService* integration_service =
    318       drive::DriveIntegrationServiceFactory::GetForProfile(profile_);
    319   // |integration_service| is NULL if Drive is disabled. We return true in this
    320   // case because there might be other extension tasks, even if we don't have
    321   // any to add.
    322   if (!integration_service || !integration_service->drive_app_registry())
    323     return;
    324 
    325   drive::DriveAppRegistry* registry =
    326       integration_service->drive_app_registry();
    327   DCHECK(registry);
    328 
    329   // Map of task_id to TaskInfo of available tasks.
    330   TaskInfoMap task_info_map;
    331   GetAvailableDriveTasks(registry, file_info_list, &task_info_map);
    332   std::set<std::string> default_tasks;
    333   FindDefaultDriveTasks(file_info_list, task_info_map, &default_tasks);
    334   CreateDriveTasks(
    335       task_info_map, default_tasks, result_list, default_already_set);
    336 }
    337 
    338 void GetFileTasksFunction::FindFileHandlerTasks(
    339     const std::vector<base::FilePath>& file_paths,
    340     ListValue* result_list,
    341     bool* default_already_set) {
    342   DCHECK(!file_paths.empty());
    343   DCHECK(result_list);
    344   DCHECK(default_already_set);
    345 
    346   ExtensionService* service = profile_->GetExtensionService();
    347   if (!service)
    348     return;
    349 
    350   PathAndMimeTypeSet files;
    351   GetMimeTypesForFileURLs(file_paths, &files);
    352   std::set<std::string> default_tasks;
    353   for (PathAndMimeTypeSet::iterator it = files.begin(); it != files.end();
    354        ++it) {
    355     default_tasks.insert(file_tasks::GetDefaultTaskIdFromPrefs(
    356         profile_, it->second, it->first.Extension()));
    357   }
    358 
    359   for (ExtensionSet::const_iterator iter = service->extensions()->begin();
    360        iter != service->extensions()->end();
    361        ++iter) {
    362     const Extension* extension = iter->get();
    363 
    364     // We don't support using hosted apps to open files.
    365     if (!extension->is_platform_app())
    366       continue;
    367 
    368     if (profile_->IsOffTheRecord() &&
    369         !service->IsIncognitoEnabled(extension->id()))
    370       continue;
    371 
    372     typedef std::vector<const extensions::FileHandlerInfo*> FileHandlerList;
    373     FileHandlerList file_handlers = FindFileHandlersForFiles(*extension, files);
    374     if (file_handlers.empty())
    375       continue;
    376 
    377     for (FileHandlerList::iterator i = file_handlers.begin();
    378          i != file_handlers.end(); ++i) {
    379       DictionaryValue* task = new DictionaryValue;
    380       std::string task_id = file_tasks::MakeTaskID(
    381           extension->id(), file_tasks::kFileHandlerTaskType, (*i)->id);
    382       task->SetString("taskId", task_id);
    383       task->SetString("title", (*i)->title);
    384       if (!(*default_already_set) && ContainsKey(default_tasks, task_id)) {
    385         task->SetBoolean("isDefault", true);
    386         *default_already_set = true;
    387       } else {
    388         task->SetBoolean("isDefault", false);
    389       }
    390 
    391       GURL best_icon = extensions::ExtensionIconSource::GetIconURL(
    392           extension,
    393           util::kPreferredIconSize,
    394           ExtensionIconSet::MATCH_BIGGER,
    395           false,  // grayscale
    396           NULL);  // exists
    397       if (!best_icon.is_empty())
    398         task->SetString("iconUrl", best_icon.spec());
    399       else
    400         task->SetString("iconUrl", kDefaultIcon);
    401 
    402       task->SetBoolean("driveApp", false);
    403       result_list->Append(task);
    404     }
    405   }
    406 }
    407 
    408 void GetFileTasksFunction::FindFileBrowserHandlerTasks(
    409     const std::vector<GURL>& file_urls,
    410     const std::vector<base::FilePath>& file_paths,
    411     ListValue* result_list,
    412     bool* default_already_set) {
    413   DCHECK(!file_paths.empty());
    414   DCHECK(!file_urls.empty());
    415   DCHECK(result_list);
    416   DCHECK(default_already_set);
    417 
    418   file_browser_handlers::FileBrowserHandlerList common_tasks =
    419       file_browser_handlers::FindCommonFileBrowserHandlers(profile_, file_urls);
    420   if (common_tasks.empty())
    421     return;
    422   file_browser_handlers::FileBrowserHandlerList default_tasks =
    423       file_browser_handlers::FindDefaultFileBrowserHandlers(
    424           profile_, file_paths, common_tasks);
    425 
    426   ExtensionService* service =
    427       extensions::ExtensionSystem::Get(profile_)->extension_service();
    428   for (file_browser_handlers::FileBrowserHandlerList::const_iterator iter =
    429            common_tasks.begin();
    430        iter != common_tasks.end();
    431        ++iter) {
    432     const FileBrowserHandler* handler = *iter;
    433     const std::string extension_id = handler->extension_id();
    434     const Extension* extension = service->GetExtensionById(extension_id, false);
    435     CHECK(extension);
    436     DictionaryValue* task = new DictionaryValue;
    437     task->SetString("taskId", file_tasks::MakeTaskID(
    438         extension_id, file_tasks::kFileBrowserHandlerTaskType, handler->id()));
    439     task->SetString("title", handler->title());
    440     // TODO(zelidrag): Figure out how to expose icon URL that task defined in
    441     // manifest instead of the default extension icon.
    442     GURL icon = extensions::ExtensionIconSource::GetIconURL(
    443         extension,
    444         extension_misc::EXTENSION_ICON_BITTY,
    445         ExtensionIconSet::MATCH_BIGGER,
    446         false,  // grayscale
    447         NULL);  // exists
    448     task->SetString("iconUrl", icon.spec());
    449     task->SetBoolean("driveApp", false);
    450 
    451     // Only set the default if there isn't already a default set.
    452     if (!*default_already_set &&
    453         std::find(default_tasks.begin(), default_tasks.end(), *iter) !=
    454         default_tasks.end()) {
    455       task->SetBoolean("isDefault", true);
    456       *default_already_set = true;
    457     } else {
    458       task->SetBoolean("isDefault", false);
    459     }
    460 
    461     result_list->Append(task);
    462   }
    463 }
    464 
    465 bool GetFileTasksFunction::RunImpl() {
    466   // First argument is the list of files to get tasks for.
    467   ListValue* files_list = NULL;
    468   if (!args_->GetList(0, &files_list))
    469     return false;
    470 
    471   if (files_list->GetSize() == 0)
    472     return false;
    473 
    474   // Second argument is the list of mime types of each of the files in the list.
    475   ListValue* mime_types_list = NULL;
    476   if (!args_->GetList(1, &mime_types_list))
    477     return false;
    478 
    479   // MIME types can either be empty, or there needs to be one for each file.
    480   if (mime_types_list->GetSize() != files_list->GetSize() &&
    481       mime_types_list->GetSize() != 0)
    482     return false;
    483 
    484   content::SiteInstance* site_instance = render_view_host()->GetSiteInstance();
    485   scoped_refptr<fileapi::FileSystemContext> file_system_context =
    486       BrowserContext::GetStoragePartition(profile(), site_instance)->
    487       GetFileSystemContext();
    488 
    489   // Collect all the URLs, convert them to GURLs, and crack all the urls into
    490   // file paths.
    491   FileInfoList info_list;
    492   std::vector<GURL> file_urls;
    493   std::vector<base::FilePath> file_paths;
    494   bool has_google_document = false;
    495   for (size_t i = 0; i < files_list->GetSize(); ++i) {
    496     FileInfo info;
    497     std::string file_url_str;
    498     if (!files_list->GetString(i, &file_url_str))
    499       return false;
    500 
    501     if (mime_types_list->GetSize() != 0 &&
    502         !mime_types_list->GetString(i, &info.mime_type))
    503       return false;
    504 
    505     GURL file_url(file_url_str);
    506     fileapi::FileSystemURL file_system_url(
    507         file_system_context->CrackURL(file_url));
    508     if (!chromeos::FileSystemBackend::CanHandleURL(file_system_url))
    509       continue;
    510 
    511     file_urls.push_back(file_url);
    512     file_paths.push_back(file_system_url.path());
    513 
    514     info.file_url = file_url;
    515     info.file_path = file_system_url.path();
    516     info_list.push_back(info);
    517 
    518     if (google_apis::ResourceEntry::ClassifyEntryKindByFileExtension(
    519             info.file_path) &
    520         google_apis::ResourceEntry::KIND_OF_GOOGLE_DOCUMENT) {
    521       has_google_document = true;
    522     }
    523   }
    524 
    525   ListValue* result_list = new ListValue();
    526   SetResult(result_list);
    527 
    528   // Find the Drive app tasks first, because we want them to take precedence
    529   // when setting the default app.
    530   bool default_already_set = false;
    531   // Google document are not opened by drive apps but file manager.
    532   if (!has_google_document)
    533     FindDriveAppTasks(info_list, result_list, &default_already_set);
    534 
    535   // Find and append file handler tasks. We know there aren't duplicates
    536   // because Drive apps and platform apps are entirely different kinds of
    537   // tasks.
    538   FindFileHandlerTasks(file_paths, result_list, &default_already_set);
    539 
    540   // Find and append file browser handler tasks. We know there aren't
    541   // duplicates because "file_browser_handlers" and "file_handlers" shouldn't
    542   // be used in the same manifest.json.
    543   FindFileBrowserHandlerTasks(file_urls,
    544                               file_paths,
    545                               result_list,
    546                               &default_already_set);
    547 
    548   SendResponse(true);
    549   return true;
    550 }
    551 
    552 SetDefaultTaskFunction::SetDefaultTaskFunction() {
    553 }
    554 
    555 SetDefaultTaskFunction::~SetDefaultTaskFunction() {
    556 }
    557 
    558 bool SetDefaultTaskFunction::RunImpl() {
    559   // First param is task id that was to the extension with setDefaultTask call.
    560   std::string task_id;
    561   if (!args_->GetString(0, &task_id) || !task_id.size())
    562     return false;
    563 
    564   base::ListValue* file_url_list;
    565   if (!args_->GetList(1, &file_url_list))
    566     return false;
    567 
    568   content::SiteInstance* site_instance = render_view_host()->GetSiteInstance();
    569   scoped_refptr<fileapi::FileSystemContext> context =
    570       BrowserContext::GetStoragePartition(profile(), site_instance)->
    571       GetFileSystemContext();
    572 
    573   std::set<std::string> suffixes =
    574       GetUniqueSuffixes(file_url_list, context.get());
    575 
    576   // MIME types are an optional parameter.
    577   base::ListValue* mime_type_list;
    578   std::set<std::string> mime_types;
    579   if (args_->GetList(2, &mime_type_list) && !mime_type_list->empty()) {
    580     if (mime_type_list->GetSize() != file_url_list->GetSize())
    581       return false;
    582     mime_types = GetUniqueMimeTypes(mime_type_list);
    583   }
    584 
    585   if (VLOG_IS_ON(1))
    586     LogDefaultTask(mime_types, suffixes, task_id);
    587 
    588   // If there weren't any mime_types, and all the suffixes were blank,
    589   // then we "succeed", but don't actually associate with anything.
    590   // Otherwise, any time we set the default on a file with no extension
    591   // on the local drive, we'd fail.
    592   // TODO(gspencer): Fix file manager so that it never tries to set default in
    593   // cases where extensionless local files are part of the selection.
    594   if (suffixes.empty() && mime_types.empty()) {
    595     SetResult(new base::FundamentalValue(true));
    596     return true;
    597   }
    598 
    599   file_tasks::UpdateDefaultTask(profile_, task_id, suffixes, mime_types);
    600 
    601   return true;
    602 }
    603 
    604 ViewFilesFunction::ViewFilesFunction() {
    605 }
    606 
    607 ViewFilesFunction::~ViewFilesFunction() {
    608 }
    609 
    610 bool ViewFilesFunction::RunImpl() {
    611   if (args_->GetSize() < 1) {
    612     return false;
    613   }
    614 
    615   ListValue* path_list = NULL;
    616   args_->GetList(0, &path_list);
    617   DCHECK(path_list);
    618 
    619   std::vector<base::FilePath> files;
    620   for (size_t i = 0; i < path_list->GetSize(); ++i) {
    621     std::string url_as_string;
    622     path_list->GetString(i, &url_as_string);
    623     base::FilePath path = util::GetLocalPathFromURL(
    624         render_view_host(), profile(), GURL(url_as_string));
    625     if (path.empty())
    626       return false;
    627     files.push_back(path);
    628   }
    629 
    630   Browser* browser = chrome::FindOrCreateTabbedBrowser(
    631       profile_, chrome::HOST_DESKTOP_TYPE_ASH);
    632   bool success = browser;
    633 
    634   if (browser) {
    635     for (size_t i = 0; i < files.size(); ++i) {
    636       bool handled = util::OpenFileWithBrowser(browser, files[i]);
    637       if (!handled && files.size() == 1)
    638         success = false;
    639     }
    640   }
    641 
    642   SetResult(Value::CreateBooleanValue(success));
    643   SendResponse(true);
    644   return true;
    645 }
    646 
    647 }  // namespace file_manager
    648