diff options
16 files changed, 569 insertions, 143 deletions
diff --git a/chrome/browser/chromeos/extensions/external_filesystem_apitest.cc b/chrome/browser/chromeos/extensions/external_filesystem_apitest.cc index ba3add1..0d2f091 100644 --- a/chrome/browser/chromeos/extensions/external_filesystem_apitest.cc +++ b/chrome/browser/chromeos/extensions/external_filesystem_apitest.cc @@ -15,6 +15,7 @@ #include "chrome/browser/chromeos/gdata/drive_file_system.h" #include "chrome/browser/chromeos/gdata/drive_system_service.h" #include "chrome/browser/chromeos/gdata/mock_drive_service.h" +#include "chrome/browser/extensions/event_router.h" #include "chrome/browser/extensions/extension_apitest.h" #include "chrome/browser/extensions/extension_test_message_listener.h" #include "chrome/browser/google_apis/gdata_util.h" @@ -24,6 +25,7 @@ #include "chrome/common/chrome_notification_types.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_switches.h" +#include "chrome/test/base/ui_test_utils.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/notification_service.h" @@ -245,6 +247,35 @@ IN_PROC_BROWSER_TEST_F(FileSystemExtensionApiTest, FileBrowserTestLazy) { "filebrowser_component", "read.html", kComponentFlags)) << message_; } +IN_PROC_BROWSER_TEST_F(FileSystemExtensionApiTest, FileBrowserWebIntentTest) { + AddTmpMountPoint(); + + ResultCatcher catcher; + ScopedTempDir tmp_dir; + ASSERT_TRUE(tmp_dir.CreateUniqueTempDir()); + + // Create a test file inside the ScopedTempDir. + FilePath test_file = tmp_dir.path().AppendASCII("text_file.xul"); + CreateDownloadFile(test_file); + + ASSERT_TRUE(LoadExtension( + test_data_dir_.AppendASCII("webintent_handler"))) << message_; + + // Load the source component, with the fileUrl within the virtual mount + // point. + const extensions::Extension* extension = LoadExtensionAsComponent( + test_data_dir_.AppendASCII("filebrowser_component")); + ASSERT_TRUE(extension) << message_; + std::string path = "filesystem:chrome-extension://" + extension->id() + + "/external" + test_file.value(); + GURL url = extension->GetResourceURL("intent.html#" + path); + ui_test_utils::NavigateToURL(browser(), url); + + // The webintent_handler sends chrome.test.succeed() on successful receipt + // of the incoming Web Intent. + ASSERT_TRUE(catcher.GetNextResult()) << message_; +} + IN_PROC_BROWSER_TEST_F(FileSystemExtensionApiTest, FileBrowserTestWrite) { AddTmpMountPoint(); ASSERT_TRUE(RunExtensionTest("filesystem_handler_write")) << message_; diff --git a/chrome/browser/chromeos/extensions/file_browser_private_api.cc b/chrome/browser/chromeos/extensions/file_browser_private_api.cc index 22e88ee..c7dd48c 100644 --- a/chrome/browser/chromeos/extensions/file_browser_private_api.cc +++ b/chrome/browser/chromeos/extensions/file_browser_private_api.cc @@ -9,6 +9,9 @@ #include "base/base64.h" #include "base/bind.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/i18n/case_conversion.h" #include "base/json/json_writer.h" #include "base/logging.h" #include "base/memory/scoped_vector.h" @@ -16,6 +19,7 @@ #include "base/string_split.h" #include "base/stringprintf.h" #include "base/time.h" +#include "base/utf_string_conversions.h" #include "base/values.h" #include "chrome/browser/chromeos/cros/cros_library.h" #include "chrome/browser/chromeos/cros/network_library.h" @@ -32,8 +36,10 @@ #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_tab_util.h" #include "chrome/browser/extensions/process_map.h" +#include "chrome/browser/google_apis/operation_registry.h" #include "chrome/browser/google_apis/gdata_util.h" #include "chrome/browser/google_apis/gdata_wapi_parser.h" +#include "chrome/browser/intents/web_intents_util.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_window.h" @@ -53,6 +59,7 @@ #include "grit/generated_resources.h" #include "grit/platform_locale_settings.h" #include "net/base/escape.h" +#include "net/base/mime_util.h" #include "ui/base/dialogs/selected_file_info.h" #include "ui/base/l10n/l10n_util.h" #include "webkit/chromeos/fileapi/cros_mount_point_provider.h" @@ -62,6 +69,7 @@ #include "webkit/fileapi/file_system_types.h" #include "webkit/fileapi/file_system_url.h" #include "webkit/fileapi/file_system_util.h" +#include "webkit/glue/web_intent_service_data.h" using chromeos::disks::DiskMountManager; using content::BrowserContext; @@ -76,7 +84,7 @@ using gdata::InstalledApp; namespace { // Default icon path for drive docs. -const char kDefaultDriveIcon[] = "images/filetype_generic.png"; +const char kDefaultIcon[] = "images/filetype_generic.png"; const int kPreferredIconSize = 16; // Error messages. @@ -234,6 +242,45 @@ GURL FindPreferredIcon(const InstalledApp::IconList& icons, return result; } +// Finds the title of the given Web Intents |action|, if the passed extension +// supports this action for all specified |mime_types|. Returns true and +// provides the |title| as output on success. +bool FindTitleForActionWithTypes( + const Extension* extension, + const std::string& action, + const std::set<std::string>& mime_types, + std::string* title) { + DCHECK(!mime_types.empty()); + std::set<std::string> pending(mime_types.begin(), mime_types.end()); + std::string found_title; + + for (std::vector<webkit_glue::WebIntentServiceData>::const_iterator data = + extension->intents_services().begin(); + data != extension->intents_services().end(); ++data) { + if (pending.empty()) + break; + + if (UTF16ToUTF8(data->action) != action) + continue; + + std::set<std::string>::iterator pending_iter = pending.begin(); + while (pending_iter != pending.end()) { + std::set<std::string>::iterator current = pending_iter++; + if (net::MatchesMimeType(UTF16ToUTF8(data->type), *current)) + pending.erase(current); + } + if (found_title.empty()) + found_title = UTF16ToUTF8(data->title); + } + + // Not all mime-types have been found. + if (!pending.empty()) + return false; + + *title = found_title; + return true; +} + // Retrieves total and remaining available size on |mount_path|. void GetSizeStatsOnFileThread(const std::string& mount_path, size_t* total_size_kb, @@ -602,8 +649,8 @@ void GetFileTasksFileBrowserFunction::IntersectAvailableDriveTasks( app_info->insert(std::make_pair((*apps)->app_id, *apps)); // TODO(gspencer): For now, the action id is always "open-with", but we // could add any actions that the drive app supports. - std::string task_id = - file_handler_util::MakeDriveTaskID((*apps)->app_id, "open-with"); + std::string task_id = file_handler_util::MakeTaskID( + (*apps)->app_id, file_handler_util::kTaskDrive, "open-with"); tasks_for_this_file.insert(task_id); // If we failed to insert a task_id because there was a duplicate, then we // must delete it (since we own it). @@ -656,10 +703,12 @@ void GetFileTasksFileBrowserFunction::CreateDriveTasks( for (std::set<std::string>::const_iterator app_iter = available_tasks.begin(); app_iter != available_tasks.end(); ++app_iter) { std::string app_id; - bool result = file_handler_util::CrackDriveTaskID(*app_iter, &app_id, NULL); + std::string task_type; + bool result = file_handler_util::CrackTaskID( + *app_iter, &app_id, &task_type, NULL); DCHECK(result) << "Unable to parse Drive task id: " << *app_iter; - if (!result) - continue; + DCHECK_EQ(task_type, file_handler_util::kTaskDrive); + WebAppInfoMap::const_iterator info_iter = app_info.find(app_id); DCHECK(info_iter != app_info.end()); gdata::DriveWebAppInfo* info = info_iter->second; @@ -707,7 +756,6 @@ bool GetFileTasksFileBrowserFunction::FindDriveAppTasks( if (!system_service || !system_service->webapps_registry()) return true; - gdata::DriveWebAppsRegistry* registry = system_service->webapps_registry(); // Map of app_id to DriveWebAppInfo so we can look up the apps we've found @@ -729,6 +777,77 @@ bool GetFileTasksFileBrowserFunction::FindDriveAppTasks( return true; } +// Find Web Intent platform apps that support the View task, and add them to +// the |result_list|. These will be marked as kTaskWebIntent. +bool GetFileTasksFileBrowserFunction::FindWebIntentTasks( + const std::vector<GURL>& file_urls, + ListValue* result_list) { + DCHECK(!file_urls.empty()); + ExtensionService* service = profile_->GetExtensionService(); + if (!service) + return false; + + std::set<std::string> mime_types; + for (std::vector<GURL>::const_iterator iter = file_urls.begin(); + iter != file_urls.end(); ++iter) { + const FilePath file = FilePath(GURL(iter->spec()).ExtractFileName()); + const FilePath::StringType file_extension = + StringToLowerASCII(file.Extension()); + + // TODO(thorogood): Rearchitect this call so it can run on the File thread; + // GetMimeTypeFromFile requires this on Linux. Right now, we use + // Chrome-level knowledge only. + std::string mime_type; + if (file_extension.empty() || !net::GetWellKnownMimeTypeFromExtension( + file_extension.substr(1), &mime_type)) { + // If the file doesn't have an extension or its mime-type cannot be + // determined, then indicate that it has the empty mime-type. This will + // only be matched if the Web Intents accepts "*" or "*/*". + mime_types.insert(""); + } else { + mime_types.insert(mime_type); + } + } + + for (ExtensionSet::const_iterator iter = service->extensions()->begin(); + iter != service->extensions()->end(); + ++iter) { + const Extension* extension = *iter; + + // We don't support using hosted apps to open files. + if (!extension->is_platform_app()) + continue; + + if (profile_->IsOffTheRecord() && + !service->IsIncognitoEnabled(extension->id())) + continue; + + std::string title; + if (!FindTitleForActionWithTypes( + extension, web_intents::kActionView, mime_types, &title)) + continue; + + DictionaryValue* task = new DictionaryValue; + std::string task_id = file_handler_util::MakeTaskID(extension->id(), + file_handler_util::kTaskWebIntent, web_intents::kActionView); + task->SetString("taskId", task_id); + task->SetString("title", title); + task->SetBoolean("isDefault", false); + + GURL best_icon = extension->GetIconURL(kPreferredIconSize, + ExtensionIconSet::MATCH_BIGGER); + if (!best_icon.is_empty()) + task->SetString("iconUrl", best_icon.spec()); + else + task->SetString("iconUrl", kDefaultIcon); + + task->SetBoolean("driveApp", false); + result_list->Append(task); + } + + return true; +} + bool GetFileTasksFileBrowserFunction::RunImpl() { // First argument is the list of files to get tasks for. ListValue* files_list = NULL; @@ -798,8 +917,8 @@ bool GetFileTasksFileBrowserFunction::RunImpl() { const Extension* extension = service->GetExtensionById(extension_id, false); CHECK(extension); DictionaryValue* task = new DictionaryValue; - task->SetString("taskId", - file_handler_util::MakeTaskID(extension_id, handler->id())); + task->SetString("taskId", file_handler_util::MakeTaskID( + extension_id, file_handler_util::kTaskFile, handler->id())); task->SetString("title", handler->title()); // TODO(zelidrag): Figure out how to expose icon URL that task defined in // manifest instead of the default extension icon. @@ -823,6 +942,12 @@ bool GetFileTasksFileBrowserFunction::RunImpl() { result_list->Append(task); } + // Take the union of Web Intents (that platform apps may accept) and all + // previous Drive and extension tasks. As above, we know there aren't + // duplicates because they're entirely different kinds of tasks. + if (!FindWebIntentTasks(file_urls, result_list)) + return false; + if (VLOG_IS_ON(1)) { std::string result_json; base::JSONWriter::WriteWithOptions( @@ -833,8 +958,6 @@ bool GetFileTasksFileBrowserFunction::RunImpl() { VLOG(1) << "GetFileTasks result:\n" << result_json; } - // TODO(zelidrag, serya): Add intent content tasks to result_list once we - // implement that API. SendResponse(true); return true; } @@ -863,8 +986,10 @@ bool ExecuteTasksFileBrowserFunction::RunImpl() { return false; std::string extension_id; + std::string task_type; std::string action_id; - if (!file_handler_util::CrackTaskID(task_id, &extension_id, &action_id)) { + if (!file_handler_util::CrackTaskID( + task_id, &extension_id, &task_type, &action_id)) { LOG(WARNING) << "Invalid task " << task_id; return false; } @@ -886,6 +1011,7 @@ bool ExecuteTasksFileBrowserFunction::RunImpl() { FileTaskExecutor::Create(profile(), source_url(), extension_id, + task_type, action_id)); if (!executor->ExecuteAndNotify( @@ -2035,7 +2161,8 @@ void GetDriveFilePropertiesFunction::OnOperationComplete( file_specific_info.content_mime_type(), file_path.Extension()); std::string default_app_id; - file_handler_util::CrackDriveTaskID(default_task_id, &default_app_id, NULL); + file_handler_util::CrackTaskID( + default_task_id, &default_app_id, NULL, NULL); ListValue* apps = new ListValue(); property_dict->Set("driveApps", apps); diff --git a/chrome/browser/chromeos/extensions/file_browser_private_api.h b/chrome/browser/chromeos/extensions/file_browser_private_api.h index 81e5fe1..d08326e 100644 --- a/chrome/browser/chromeos/extensions/file_browser_private_api.h +++ b/chrome/browser/chromeos/extensions/file_browser_private_api.h @@ -170,6 +170,10 @@ class GetFileTasksFileBrowserFunction : public AsyncExtensionFunction { ListValue* result_list, bool* default_already_set); + // Find the list of Web Intent tasks that can be used with the given file + // types, appending them to the |result_list|. + bool FindWebIntentTasks(const std::vector<GURL>& file_urls, + ListValue* result_list); }; // Implements the chrome.fileBrowserPrivate.executeTask method. diff --git a/chrome/browser/chromeos/extensions/file_handler_util.cc b/chrome/browser/chromeos/extensions/file_handler_util.cc index afe5614..18058dc 100644 --- a/chrome/browser/chromeos/extensions/file_handler_util.cc +++ b/chrome/browser/chromeos/extensions/file_handler_util.cc @@ -20,6 +20,7 @@ #include "chrome/browser/extensions/extension_system.h" #include "chrome/browser/extensions/extension_tab_util.h" #include "chrome/browser/extensions/lazy_background_task_queue.h" +#include "chrome/browser/extensions/platform_app_launcher.h" #include "chrome/browser/prefs/scoped_user_pref_update.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_manager.h" @@ -39,6 +40,7 @@ #include "webkit/fileapi/file_system_context.h" #include "webkit/fileapi/file_system_url.h" #include "webkit/fileapi/file_system_util.h" +#include "webkit/fileapi/isolated_context.h" using content::BrowserContext; using content::BrowserThread; @@ -49,12 +51,17 @@ using extensions::Extension; namespace file_handler_util { -// The prefix used to differentiate drive extensions from Chrome extensions. -const char FileTaskExecutor::kDriveTaskExtensionPrefix[] = "drive-app:"; -const size_t FileTaskExecutor::kDriveTaskExtensionPrefixLength = - arraysize(FileTaskExecutor::kDriveTaskExtensionPrefix) - 1; +const char kTaskFile[] = "file"; +const char kTaskDrive[] = "drive"; +const char kTaskWebIntent[] = "web-intent"; namespace { + +// Legacy Drive task extension prefix, used by CrackTaskID. +const char kDriveTaskExtensionPrefix[] = "drive-app:"; +const size_t kDriveTaskExtensionPrefixLength = + arraysize(kDriveTaskExtensionPrefix) - 1; + typedef std::set<const FileBrowserHandler*> FileBrowserHandlerSet; const int kReadWriteFilePermissions = base::PLATFORM_FILE_OPEN | @@ -113,8 +120,9 @@ const FileBrowserHandler* FindFileBrowserHandler(const Extension* extension, return NULL; } -unsigned int GetAccessPermissionsForHandler(const Extension* extension, - const std::string& action_id) { +unsigned int GetAccessPermissionsForFileBrowserHandler( + const Extension* extension, + const std::string& action_id) { const FileBrowserHandler* action = FindFileBrowserHandler(extension, action_id); if (!action) @@ -128,7 +136,6 @@ unsigned int GetAccessPermissionsForHandler(const Extension* extension, return result; } - std::string EscapedUtf8ToLower(const std::string& str) { string16 utf16 = UTF8ToUTF16( net::UnescapeURLComponent(str, net::UnescapeRule::NORMAL)); @@ -246,52 +253,66 @@ int GetReadOnlyPermissions() { } std::string MakeTaskID(const std::string& extension_id, + const std::string& task_type, const std::string& action_id) { - return base::StringPrintf("%s|%s", extension_id.c_str(), action_id.c_str()); -} - -std::string MakeDriveTaskID(const std::string& app_id, - const std::string& action_id) { - return MakeTaskID(FileTaskExecutor::kDriveTaskExtensionPrefix + app_id, - action_id); -} - -bool CrackDriveTaskID(const std::string& task_id, - std::string* app_id, - std::string* action_id) { - std::string app_id_tmp; - std::string action_id_tmp; - if (!CrackTaskID(task_id, &app_id_tmp, &action_id_tmp)) - return false; - if (StartsWithASCII(app_id_tmp, - FileTaskExecutor::kDriveTaskExtensionPrefix, - false)) { - // Strip off the prefix from the extension ID so we convert it to an app id. - if (app_id) { - *app_id = app_id_tmp.substr( - FileTaskExecutor::kDriveTaskExtensionPrefixLength); - } - if (action_id) - *action_id = action_id_tmp; - return true; - } - return false; + DCHECK(task_type == kTaskFile || + task_type == kTaskDrive || + task_type == kTaskWebIntent); + return base::StringPrintf("%s|%s|%s", + extension_id.c_str(), + task_type.c_str(), + action_id.c_str()); } // Breaks down task_id that is used between getFileTasks() and executeTask() on // its building blocks. task_id field the following structure: -// <extension-id>|<task-action-id> +// <extension-id>|<task-type>|<task-action-id> bool CrackTaskID(const std::string& task_id, std::string* extension_id, + std::string* task_type, std::string* action_id) { std::vector<std::string> result; int count = Tokenize(task_id, std::string("|"), &result); - if (count != 2) + + // Parse historic task_id parameters that only contain two parts. Drive tasks + // are identified by a prefix "drive-app:" on the extension ID. + if (count == 2) { + if (StartsWithASCII(result[0], kDriveTaskExtensionPrefix, true)) { + if (task_type) + *task_type = kTaskDrive; + + if (extension_id) + *extension_id = result[0].substr(kDriveTaskExtensionPrefixLength); + } else { + if (task_type) + *task_type = kTaskFile; + + if (extension_id) + *extension_id = result[0]; + } + + if (action_id) + *action_id = result[1]; + + return true; + } + + if (count != 3) return false; + if (extension_id) *extension_id = result[0]; + + if (task_type) { + *task_type = result[1]; + DCHECK(*task_type == kTaskFile || + *task_type == kTaskDrive || + *task_type == kTaskWebIntent); + } + if (action_id) - *action_id = result[1]; + *action_id = result[2]; + return true; } @@ -309,6 +330,8 @@ FileBrowserHandlerSet::iterator FindHandler( return iter; } +// Given the list of selected files, returns array of file action tasks +// that are shared between them. void FindDefaultTasks(Profile* profile, const std::vector<GURL>& files_list, const FileBrowserHandlerSet& common_tasks, @@ -337,7 +360,7 @@ void FindDefaultTasks(Profile* profile, // from common_tasks. for (FileBrowserHandlerSet::const_iterator task_iter = common_tasks.begin(); task_iter != common_tasks.end(); ++task_iter) { - std::string task_id = MakeTaskID((*task_iter)->extension_id(), + std::string task_id = MakeTaskID((*task_iter)->extension_id(), kTaskFile, (*task_iter)->id()); for (std::set<std::string>::iterator default_iter = default_ids.begin(); default_iter != default_ids.end(); ++default_iter) { @@ -492,7 +515,6 @@ class ExtensionTaskExecutor : public FileTaskExecutor { void InitHandlerHostFileAccessPermissions( const FileDefinitionList& file_list, const extensions::Extension* handler_extension, - const std::string& action_id, const base::Closure& callback); // Invoked upon completion of InitHandlerHostFileAccessPermissions initiated @@ -507,11 +529,7 @@ class ExtensionTaskExecutor : public FileTaskExecutor { // ChildProcessSecurityPolicy for process with id |handler_pid|. void SetupHandlerHostFileAccessPermissions(int handler_pid); - // Helper function to get the extension pointer. - const extensions::Extension* GetExtension(); - const GURL source_url_; - const std::string extension_id_; const std::string action_id_; FileTaskFinishedCallback done_; @@ -519,28 +537,61 @@ class ExtensionTaskExecutor : public FileTaskExecutor { std::vector<std::pair<FilePath, int> > handler_host_permissions_; }; +class WebIntentTaskExecutor : public FileTaskExecutor { + public: + // FileTaskExecutor overrides. + virtual bool ExecuteAndNotify(const std::vector<GURL>& file_urls, + const FileTaskFinishedCallback& done) OVERRIDE; + + private: + // FileTaskExecutor is the only class allowed to create one. + friend class FileTaskExecutor; + + WebIntentTaskExecutor(Profile* profile, + const GURL source_url, + const std::string& extension_id, + const std::string& action_id); + virtual ~WebIntentTaskExecutor(); + + bool ExecuteForURL(const GURL& file_url); + + const GURL source_url_; + const std::string extension_id_; + const std::string action_id_; +}; + // static FileTaskExecutor* FileTaskExecutor::Create(Profile* profile, const GURL source_url, const std::string& extension_id, + const std::string& task_type, const std::string& action_id) { - // Check out the extension ID and see if this is a drive task, - // and instantiate drive-specific executor if so. - if (StartsWithASCII(extension_id, - FileTaskExecutor::kDriveTaskExtensionPrefix, - false)) { + if (task_type == kTaskFile) + return new ExtensionTaskExecutor(profile, + source_url, + extension_id, + action_id); + + if (task_type == kTaskDrive) return new gdata::DriveTaskExecutor(profile, - extension_id, // really app_id + extension_id, // really app_id action_id); - } else { - return new ExtensionTaskExecutor(profile, + + if (task_type == kTaskWebIntent) + return new WebIntentTaskExecutor(profile, source_url, extension_id, action_id); - } + + NOTREACHED(); + return NULL; } -FileTaskExecutor::FileTaskExecutor(Profile* profile) : profile_(profile) { +FileTaskExecutor::FileTaskExecutor( + Profile* profile, + const std::string& extension_id) + : profile_(profile), + extension_id_(extension_id) { } FileTaskExecutor::~FileTaskExecutor() { @@ -555,6 +606,14 @@ Browser* FileTaskExecutor::GetBrowser() const { profile_ ? profile_ : ProfileManager::GetDefaultProfileOrOffTheRecord()); } +const Extension* FileTaskExecutor::GetExtension() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + ExtensionService* service = profile()->GetExtensionService(); + return service ? service->GetExtensionById(extension_id_, false) : + NULL; +} + ExtensionTaskExecutor::FileDefinition::FileDefinition() : is_directory(false) { } @@ -724,10 +783,9 @@ ExtensionTaskExecutor::ExtensionTaskExecutor( const GURL source_url, const std::string& extension_id, const std::string& action_id) - : FileTaskExecutor(profile), - source_url_(source_url), - extension_id_(extension_id), - action_id_(action_id) { + : FileTaskExecutor(profile, extension_id), + source_url_(source_url), + action_id_(action_id) { } ExtensionTaskExecutor::~ExtensionTaskExecutor() {} @@ -735,12 +793,7 @@ ExtensionTaskExecutor::~ExtensionTaskExecutor() {} bool ExtensionTaskExecutor::ExecuteAndNotify( const std::vector<GURL>& file_urls, const FileTaskFinishedCallback& done) { - ExtensionService* service = profile()->GetExtensionService(); - if (!service) - return false; - - scoped_refptr<const Extension> handler = - service->GetExtensionById(extension_id_, false); + scoped_refptr<const Extension> handler = GetExtension(); if (!handler.get()) return false; @@ -797,14 +850,6 @@ void ExtensionTaskExecutor::ExecuteDoneOnUIThread(bool success) { done_.Reset(); } -const Extension* ExtensionTaskExecutor::GetExtension() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - - ExtensionService* service = profile()->GetExtensionService(); - return service ? service->GetExtensionById(extension_id_, false) : - NULL; -} - void ExtensionTaskExecutor::ExecuteFileActionsOnUIThread( const std::string& file_system_name, const GURL& file_system_root, @@ -821,7 +866,6 @@ void ExtensionTaskExecutor::ExecuteFileActionsOnUIThread( InitHandlerHostFileAccessPermissions( file_list, extension, - action_id_, base::Bind( &ExtensionTaskExecutor::OnInitAccessForExecuteFileActionsOnUIThread, this, @@ -857,7 +901,7 @@ void ExtensionTaskExecutor::OnInitAccessForExecuteFileActionsOnUIThread( return; } queue->AddPendingTask( - profile(), extension_id_, + profile(), extension_id(), base::Bind(&ExtensionTaskExecutor::SetupPermissionsAndDispatchEvent, this, file_system_name, file_system_root, file_list, handler_pid)); @@ -916,7 +960,7 @@ void ExtensionTaskExecutor::SetupPermissionsAndDispatchEvent( } event_router->DispatchEventToExtension( - extension_id_, std::string("fileBrowserHandler.onExecute"), + extension_id(), std::string("fileBrowserHandler.onExecute"), event_args.Pass(), profile(), GURL()); ExecuteDoneOnUIThread(true); } @@ -924,7 +968,6 @@ void ExtensionTaskExecutor::SetupPermissionsAndDispatchEvent( void ExtensionTaskExecutor::InitHandlerHostFileAccessPermissions( const FileDefinitionList& file_list, const Extension* handler_extension, - const std::string& action_id, const base::Closure& callback) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); @@ -933,9 +976,9 @@ void ExtensionTaskExecutor::InitHandlerHostFileAccessPermissions( iter != file_list.end(); ++iter) { // Setup permission for file's absolute file. - handler_host_permissions_.push_back(std::make_pair( - iter->absolute_path, - GetAccessPermissionsForHandler(handler_extension, action_id))); + handler_host_permissions_.push_back(std::make_pair(iter->absolute_path, + GetAccessPermissionsForFileBrowserHandler(handler_extension, + action_id_))); if (gdata::util::IsUnderDriveMountPoint(iter->absolute_path)) gdata_paths->push_back(iter->virtual_path); @@ -968,4 +1011,55 @@ void ExtensionTaskExecutor::SetupHandlerHostFileAccessPermissions( handler_host_permissions_.clear(); } +WebIntentTaskExecutor::WebIntentTaskExecutor( + Profile* profile, + const GURL source_url, + const std::string& extension_id, + const std::string& action_id) + : FileTaskExecutor(profile, extension_id), + source_url_(source_url), + action_id_(action_id) { +} + +WebIntentTaskExecutor::~WebIntentTaskExecutor() {} + +bool WebIntentTaskExecutor::ExecuteAndNotify( + const std::vector<GURL>& file_urls, + const FileTaskFinishedCallback& done) { + bool success = true; + + for (std::vector<GURL>::const_iterator i = file_urls.begin(); + i != file_urls.end(); ++i) { + if (!ExecuteForURL(*i)) + success = false; + } + + if (!done.is_null()) + done.Run(success); + + return true; +} + +bool WebIntentTaskExecutor::ExecuteForURL(const GURL& file_url) { + fileapi::FileSystemURL url(file_url); + if (!chromeos::CrosMountPointProvider::CanHandleURL(url)) + return false; + + scoped_refptr<fileapi::FileSystemContext> file_system_context = + BrowserContext::GetDefaultStoragePartition(profile())-> + GetFileSystemContext(); + fileapi::ExternalFileSystemMountPointProvider* external_provider = + file_system_context->external_provider(); + if (!external_provider || !external_provider->IsAccessAllowed(url)) + return false; + + // Make sure this url really being used by the right caller extension. + if (source_url_.GetOrigin() != url.origin()) + return false; + + FilePath local_path = url.path(); + extensions::LaunchPlatformAppWithPath(profile(), GetExtension(), local_path); + return true; +} + } // namespace file_handler_util diff --git a/chrome/browser/chromeos/extensions/file_handler_util.h b/chrome/browser/chromeos/extensions/file_handler_util.h index 8a0be47..8ab9e3d 100644 --- a/chrome/browser/chromeos/extensions/file_handler_util.h +++ b/chrome/browser/chromeos/extensions/file_handler_util.h @@ -5,6 +5,7 @@ #ifndef CHROME_BROWSER_CHROMEOS_EXTENSIONS_FILE_HANDLER_UTIL_H_ #define CHROME_BROWSER_CHROMEOS_EXTENSIONS_FILE_HANDLER_UTIL_H_ +#include <string> #include <vector> #include "base/callback.h" @@ -17,8 +18,20 @@ class Browser; class GURL; class Profile; +namespace extensions { +class Extension; +} // namespace extensions + namespace file_handler_util { +// Specifies the task type for a task id that represents some file action, Drive +// action, or Web Intent action. +extern const char kTaskFile[]; +extern const char kTaskDrive[]; +extern const char kTaskWebIntent[]; + +void UpdateFileHandlerUsageStats(Profile* profile, const std::string& task_id); + // Update the default file handler for the given sets of suffixes and MIME // types. void UpdateDefaultTask(Profile* profile, @@ -39,27 +52,18 @@ int GetReadWritePermissions(); // Gets read-only file access permission flags. int GetReadOnlyPermissions(); -// Generates file task id for the file action specified by the extension. +// Generates task id for the action specified by the extension. The |task_type| +// must be one of kTaskFile, kTaskDrive, or kTaskWebIntent. std::string MakeTaskID(const std::string& extension_id, + const std::string& task_type, const std::string& action_id); -// Make a task id specific to drive apps instead of extensions. -std::string MakeDriveTaskID(const std::string& app_id, - const std::string& action_id); - -// Returns the |target_id| and |action_id| of a drive app or extension, given -// the drive |task_id| created by MakeDriveTaskID. If the |task_id| is a drive -// task_id then it will return true. If not, or if parsing fails, will return -// false and not set |app_id| or |action_id|. -bool CrackDriveTaskID(const std::string& task_id, - std::string* app_id, - std::string* action_id); - -// Extracts action and extension id bound to the file task. Either +// Extracts action, type and extension id bound to the file task. Either // |target_extension_id| or |action_id| are allowed to be NULL if caller isn't // interested in those values. Returns false on failure to parse. bool CrackTaskID(const std::string& task_id, std::string* target_extension_id, + std::string* task_type, std::string* action_id); // This generates a list of default tasks (tasks set as default by the user in @@ -88,13 +92,11 @@ typedef base::Callback<void(bool)> FileTaskFinishedCallback; // Helper class for executing file browser file action. class FileTaskExecutor : public base::RefCountedThreadSafe<FileTaskExecutor> { public: - static const char kDriveTaskExtensionPrefix[]; - static const size_t kDriveTaskExtensionPrefixLength; - // Creates the appropriate FileTaskExecutor for the given |extension_id|. static FileTaskExecutor* Create(Profile* profile, const GURL source_url, const std::string& extension_id, + const std::string& task_type, const std::string& action_id); // Same as ExecuteAndNotify, but no notification is performed. @@ -110,7 +112,7 @@ class FileTaskExecutor : public base::RefCountedThreadSafe<FileTaskExecutor> { const FileTaskFinishedCallback& done) = 0; protected: - explicit FileTaskExecutor(Profile* profile); + explicit FileTaskExecutor(Profile* profile, const std::string& extension_id); virtual ~FileTaskExecutor(); // Returns the profile that this task was created with. @@ -118,10 +120,18 @@ class FileTaskExecutor : public base::RefCountedThreadSafe<FileTaskExecutor> { // Returns a browser to use for the current browser. Browser* GetBrowser() const; + + // Returns the extension for this profile. + const extensions::Extension* GetExtension(); + + // Returns the extension ID set for this FileTaskExecutor. + const std::string& extension_id() { return extension_id_; } + private: friend class base::RefCountedThreadSafe<FileTaskExecutor>; Profile* profile_; + const std::string extension_id_; }; } // namespace file_handler_util diff --git a/chrome/browser/chromeos/extensions/file_manager_util.cc b/chrome/browser/chromeos/extensions/file_manager_util.cc index e21c64a..283dbfa 100644 --- a/chrome/browser/chromeos/extensions/file_manager_util.cc +++ b/chrome/browser/chromeos/extensions/file_manager_util.cc @@ -594,8 +594,8 @@ bool ExecuteDefaultHandler(Profile* profile, const FilePath& path) { std::vector<GURL> urls; urls.push_back(url); - scoped_refptr<FileTaskExecutor> executor = FileTaskExecutor::Create( - profile, source_url, extension_id, action_id); + scoped_refptr<FileTaskExecutor> executor = FileTaskExecutor::Create(profile, + source_url, extension_id, file_handler_util::kTaskFile, action_id); executor->Execute(urls); return true; } diff --git a/chrome/browser/chromeos/gdata/drive_task_executor.cc b/chrome/browser/chromeos/gdata/drive_task_executor.cc index 9bd0895..67e54bb 100644 --- a/chrome/browser/chromeos/gdata/drive_task_executor.cc +++ b/chrome/browser/chromeos/gdata/drive_task_executor.cc @@ -31,17 +31,10 @@ using file_handler_util::FileTaskExecutor; DriveTaskExecutor::DriveTaskExecutor(Profile* profile, const std::string& app_id, const std::string& action_id) - : file_handler_util::FileTaskExecutor(profile), - app_id_(app_id), + : file_handler_util::FileTaskExecutor(profile, app_id), action_id_(action_id), current_index_(0) { DCHECK("open-with" == action_id_); - DCHECK(app_id.size() > FileTaskExecutor::kDriveTaskExtensionPrefixLength); - DCHECK(StartsWithASCII(app_id, - FileTaskExecutor::kDriveTaskExtensionPrefix, - false)); - // Strip off the prefix from the extension ID so we convert it to an app id. - app_id_ = app_id_.substr(FileTaskExecutor::kDriveTaskExtensionPrefixLength); } DriveTaskExecutor::~DriveTaskExecutor() { @@ -105,7 +98,7 @@ void DriveTaskExecutor::OnFileEntryFetched( // open-with-<app_id> urls from the document entry. drive_service->AuthorizeApp( GURL(entry_proto->edit_url()), - app_id_, + extension_id(), // really app_id base::Bind(&DriveTaskExecutor::OnAppAuthorized, this, entry_proto->resource_id())); @@ -138,7 +131,7 @@ void DriveTaskExecutor::OnAppAuthorized( link_list->GetDictionary(i, &entry); std::string app_id; entry->GetString("app_id", &app_id); - if (app_id == app_id_) { + if (app_id == extension_id()) { std::string href; entry->GetString("href", &href); open_with_url = GURL(href); diff --git a/chrome/browser/chromeos/gdata/drive_task_executor.h b/chrome/browser/chromeos/gdata/drive_task_executor.h index bb94fbc..4a3e3fe 100644 --- a/chrome/browser/chromeos/gdata/drive_task_executor.h +++ b/chrome/browser/chromeos/gdata/drive_task_executor.h @@ -46,7 +46,6 @@ class DriveTaskExecutor : public file_handler_util::FileTaskExecutor { void Done(bool success); const GURL source_url_; - std::string app_id_; const std::string action_id_; int current_index_; file_handler_util::FileTaskFinishedCallback done_; diff --git a/chrome/browser/extensions/extension_special_storage_policy.cc b/chrome/browser/extensions/extension_special_storage_policy.cc index 3d78c16..f5fb427 100644 --- a/chrome/browser/extensions/extension_special_storage_policy.cc +++ b/chrome/browser/extensions/extension_special_storage_policy.cc @@ -7,16 +7,36 @@ #include "base/bind.h" #include "base/logging.h" #include "base/stl_util.h" +#include "base/utf_string_conversions.h" #include "chrome/browser/content_settings/cookie_settings.h" +#include "chrome/browser/intents/web_intents_util.h" #include "chrome/common/content_settings.h" #include "chrome/common/content_settings_types.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/url_constants.h" #include "content/public/browser/browser_thread.h" +#include "webkit/glue/web_intent_service_data.h" using content::BrowserThread; using extensions::APIPermission; +namespace { + +// Does the specified extension support the passed Web Intent, |action|? +bool ExtensionSupportsIntentAction( + const extensions::Extension* extension, + const std::string& action) { + for (std::vector<webkit_glue::WebIntentServiceData>::const_iterator i = + extension->intents_services().begin(); + i != extension->intents_services().end(); ++i) { + if (UTF16ToUTF8(i->action) == action) + return true; + } + return false; +} + +} // namespace + ExtensionSpecialStoragePolicy::ExtensionSpecialStoragePolicy( CookieSettings* cookie_settings) : cookie_settings_(cookie_settings) {} @@ -63,7 +83,8 @@ bool ExtensionSpecialStoragePolicy::HasSessionOnlyOrigins() { bool ExtensionSpecialStoragePolicy::IsFileHandler( const std::string& extension_id) { base::AutoLock locker(lock_); - return file_handler_extensions_.ContainsExtension(extension_id); + return web_intent_extensions_.ContainsExtension(extension_id) || + file_handler_extensions_.ContainsExtension(extension_id); } bool ExtensionSpecialStoragePolicy::NeedsProtection( @@ -80,11 +101,14 @@ const ExtensionSet* ExtensionSpecialStoragePolicy::ExtensionsProtectingOrigin( void ExtensionSpecialStoragePolicy::GrantRightsForExtension( const extensions::Extension* extension) { DCHECK(extension); + const bool supports_intent_view = ExtensionSupportsIntentAction( + extension, web_intents::kActionView); if (!NeedsProtection(extension) && !extension->HasAPIPermission( APIPermission::kUnlimitedStorage) && !extension->HasAPIPermission( - APIPermission::kFileBrowserHandler)) { + APIPermission::kFileBrowserHandler) && + !supports_intent_view) { return; } { @@ -97,9 +121,10 @@ void ExtensionSpecialStoragePolicy::GrantRightsForExtension( if (extension->HasAPIPermission(APIPermission::kUnlimitedStorage)) unlimited_extensions_.Add(extension); if (extension->HasAPIPermission( - APIPermission::kFileBrowserHandler)) { + APIPermission::kFileBrowserHandler)) file_handler_extensions_.Add(extension); - } + if (supports_intent_view) + web_intent_extensions_.Add(extension); } NotifyChanged(); } @@ -107,11 +132,14 @@ void ExtensionSpecialStoragePolicy::GrantRightsForExtension( void ExtensionSpecialStoragePolicy::RevokeRightsForExtension( const extensions::Extension* extension) { DCHECK(extension); + const bool supports_intent_view = ExtensionSupportsIntentAction( + extension, web_intents::kActionView); if (!NeedsProtection(extension) && !extension->HasAPIPermission( APIPermission::kUnlimitedStorage) && !extension->HasAPIPermission( - APIPermission::kFileBrowserHandler)) { + APIPermission::kFileBrowserHandler) && + !supports_intent_view) { return; } { @@ -124,6 +152,8 @@ void ExtensionSpecialStoragePolicy::RevokeRightsForExtension( unlimited_extensions_.Remove(extension); if (extension->HasAPIPermission(APIPermission::kFileBrowserHandler)) file_handler_extensions_.Remove(extension); + if (supports_intent_view) + web_intent_extensions_.Remove(extension); } NotifyChanged(); } @@ -135,6 +165,7 @@ void ExtensionSpecialStoragePolicy::RevokeRightsForAllExtensions() { installed_apps_.Clear(); unlimited_extensions_.Clear(); file_handler_extensions_.Clear(); + web_intent_extensions_.Clear(); } NotifyChanged(); } diff --git a/chrome/browser/extensions/extension_special_storage_policy.h b/chrome/browser/extensions/extension_special_storage_policy.h index 54f0119..7a7a2c1 100644 --- a/chrome/browser/extensions/extension_special_storage_policy.h +++ b/chrome/browser/extensions/extension_special_storage_policy.h @@ -79,6 +79,7 @@ class ExtensionSpecialStoragePolicy : public quota::SpecialStoragePolicy { SpecialCollection installed_apps_; SpecialCollection unlimited_extensions_; SpecialCollection file_handler_extensions_; + SpecialCollection web_intent_extensions_; scoped_refptr<CookieSettings> cookie_settings_; }; diff --git a/chrome/browser/extensions/extension_special_storage_policy_unittest.cc b/chrome/browser/extensions/extension_special_storage_policy_unittest.cc index 0d5ff45..ed8f2d4 100644 --- a/chrome/browser/extensions/extension_special_storage_policy_unittest.cc +++ b/chrome/browser/extensions/extension_special_storage_policy_unittest.cc @@ -122,6 +122,39 @@ class ExtensionSpecialStoragePolicyTest : public testing::Test { return handler_app; } + scoped_refptr<Extension> CreateWebIntentViewApp() { +#if defined(OS_WIN) + FilePath path(FILE_PATH_LITERAL("c:\\bar")); +#elif defined(OS_POSIX) + FilePath path(FILE_PATH_LITERAL("/bar")); +#endif + DictionaryValue manifest; + manifest.SetString(keys::kName, "WebIntent"); + manifest.SetString(keys::kVersion, "1"); + manifest.SetString(keys::kLaunchWebURL, "http://explicit/unlimited/start"); + + ListValue* view_intent_types = new ListValue; + view_intent_types->Append(Value::CreateStringValue("text/plain")); + + DictionaryValue* view_intent = new DictionaryValue; + view_intent->SetString(keys::kIntentTitle, "Test Intent"); + view_intent->Set(keys::kIntentType, view_intent_types); + + ListValue* view_intent_list = new ListValue; + view_intent_list->Append(view_intent); + + DictionaryValue* intents = new DictionaryValue; + intents->SetWithoutPathExpansion("http://webintents.org/view", + view_intent_list); + manifest.Set(keys::kIntents, intents); + + std::string error; + scoped_refptr<Extension> intent_app = Extension::Create( + path, Extension::INVALID, manifest, Extension::NO_FLAGS, &error); + EXPECT_TRUE(intent_app.get()) << error; + return intent_app; + } + // Verifies that the set of extensions protecting |url| is *exactly* equal to // |expected_extensions|. Pass in an empty set to verify that an origin is not // protected. @@ -243,6 +276,16 @@ TEST_F(ExtensionSpecialStoragePolicyTest, OverlappingApps) { ExpectProtectedBy(empty_set, GURL("https://bar.wildcards/")); } +TEST_F(ExtensionSpecialStoragePolicyTest, WebIntentViewApp) { + scoped_refptr<Extension> intent_app(CreateWebIntentViewApp()); + + policy_->GrantRightsForExtension(intent_app); + EXPECT_TRUE(policy_->IsFileHandler(intent_app->id())); + + policy_->RevokeRightsForExtension(intent_app); + EXPECT_FALSE(policy_->IsFileHandler(intent_app->id())); +} + TEST_F(ExtensionSpecialStoragePolicyTest, HasSessionOnlyOrigins) { MessageLoop message_loop; content::TestBrowserThread ui_thread(BrowserThread::UI, &message_loop); diff --git a/chrome/browser/resources/file_manager/js/file_tasks.js b/chrome/browser/resources/file_manager/js/file_tasks.js index cc6fe91..baefa29 100644 --- a/chrome/browser/resources/file_manager/js/file_tasks.js +++ b/chrome/browser/resources/file_manager/js/file_tasks.js @@ -86,51 +86,55 @@ FileTasks.prototype.processTasks_ = function(tasks) { // Tweak images, titles of internal tasks. var task_parts = task.taskId.split('|'); - if (task_parts[0] == id) { - if (task_parts[1] == 'play') { + if (task_parts[0] == id && task_parts[1] == 'file') { + if (task_parts[2] == 'play') { // TODO(serya): This hack needed until task.iconUrl is working // (see GetFileTasksFileBrowserFunction::RunImpl). task.iconType = 'audio'; task.title = loadTimeData.getString('ACTION_LISTEN'); - } else if (task_parts[1] == 'mount-archive') { + } else if (task_parts[2] == 'mount-archive') { task.iconType = 'archive'; task.title = loadTimeData.getString('MOUNT_ARCHIVE'); - } else if (task_parts[1] == 'gallery') { + } else if (task_parts[2] == 'gallery') { task.iconType = 'image'; task.title = loadTimeData.getString('ACTION_OPEN'); - } else if (task_parts[1] == 'watch') { + } else if (task_parts[2] == 'watch') { task.iconType = 'video'; task.title = loadTimeData.getString('ACTION_WATCH'); - } else if (task_parts[1] == 'open-hosted-generic') { + } else if (task_parts[2] == 'open-hosted-generic') { if (this.urls_.length > 1) task.iconType = 'generic'; else // Use specific icon. task.iconType = FileType.getIcon(this.urls_[0]); task.title = loadTimeData.getString('ACTION_OPEN'); - } else if (task_parts[1] == 'open-hosted-gdoc') { + } else if (task_parts[2] == 'open-hosted-gdoc') { task.iconType = 'gdoc'; task.title = loadTimeData.getString('ACTION_OPEN_GDOC'); - } else if (task_parts[1] == 'open-hosted-gsheet') { + } else if (task_parts[2] == 'open-hosted-gsheet') { task.iconType = 'gsheet'; task.title = loadTimeData.getString('ACTION_OPEN_GSHEET'); - } else if (task_parts[1] == 'open-hosted-gslides') { + } else if (task_parts[2] == 'open-hosted-gslides') { task.iconType = 'gslides'; task.title = loadTimeData.getString('ACTION_OPEN_GSLIDES'); - } else if (task_parts[1] == 'view-pdf') { + } else if (task_parts[2] == 'view-pdf') { // Do not render this task if disabled. if (!loadTimeData.getBoolean('PDF_VIEW_ENABLED')) continue; task.iconType = 'pdf'; task.title = loadTimeData.getString('ACTION_VIEW'); - } else if (task_parts[1] == 'view-in-browser') { + } else if (task_parts[2] == 'view-in-browser') { task.iconType = 'generic'; task.title = loadTimeData.getString('ACTION_VIEW'); - } else if (task_parts[1] == 'install-crx') { + } else if (task_parts[2] == 'install-crx') { task.iconType = 'generic'; task.title = loadTimeData.getString('INSTALL_CRX'); } } + if (!task.iconType && task_parts[1] == 'web-intent') { + task.iconType = 'generic'; + } + this.tasks_.push(task); if (this.defaultTask_ == null && task.isDefault) { this.defaultTask_ = task; @@ -190,11 +194,11 @@ FileTasks.prototype.execute_ = function(taskId, opt_urls) { chrome.fileBrowserPrivate.executeTask(taskId, urls); var task_parts = taskId.split('|'); - if (task_parts[0] == util.getExtensionId()) { + if (task_parts[0] == util.getExtensionId() && task_parts[1] == 'file') { // For internal tasks we do not listen to the event to avoid // handling the same task instance from multiple tabs. // So, we manually execute the task. - this.executeInternalTask_(task_parts[1], urls); + this.executeInternalTask_(task_parts[2], urls); } }.bind(this)); }; diff --git a/chrome/test/data/extensions/api_test/filebrowser_component/intent.html b/chrome/test/data/extensions/api_test/filebrowser_component/intent.html new file mode 100644 index 0000000..4303faa --- /dev/null +++ b/chrome/test/data/extensions/api_test/filebrowser_component/intent.html @@ -0,0 +1,10 @@ +<!-- + * Copyright (c) 2012 The Chromium Authors. All rights reserved. Use of this + * source code is governed by a BSD-style license that can be found in the + * LICENSE file. +--> +<script src="intent.js"></script> +<html> +<head><title>file browser component test</title></head> +<body><h2>chrome.fileBrowserPrivate.* tests</h2></body> +</html> diff --git a/chrome/test/data/extensions/api_test/filebrowser_component/intent.js b/chrome/test/data/extensions/api_test/filebrowser_component/intent.js new file mode 100644 index 0000000..810c02d --- /dev/null +++ b/chrome/test/data/extensions/api_test/filebrowser_component/intent.js @@ -0,0 +1,31 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +function testForUrl(fileUrl) { + chrome.fileBrowserPrivate.getFileTasks([fileUrl], [], function(tasks) { + if (!tasks || !tasks.length) { + chrome.test.fail('No tasks registered'); + return; + } + console.log('Tasks: ' + tasks.length); + chrome.fileBrowserPrivate.executeTask(tasks[0].taskId, [fileUrl]); + }); +}; + +chrome.test.runTests([function() { + var hash = window.location.hash.toString(); + + if (!hash.length) { + chrome.test.fail('No fileUrl given to test'); + return; + } + var fileUrl = hash.substring(1); + + // The local filesystem is not explicitly used, but this test still needs to + // request it; it configures local access permissions. + chrome.fileBrowserPrivate.requestLocalFileSystem(chrome.test.callbackPass( + function(fs) { + testForUrl(fileUrl); + })); +}]); diff --git a/chrome/test/data/extensions/api_test/webintent_handler/background.js b/chrome/test/data/extensions/api_test/webintent_handler/background.js new file mode 100644 index 0000000..6d661af --- /dev/null +++ b/chrome/test/data/extensions/api_test/webintent_handler/background.js @@ -0,0 +1,25 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/* +This extension is a platform app that provides a Web Intent handler; it accepts +incoming requests and invokes chrome.test.succeed() immediately. +*/ + +function launchedListener(args) { + if (!(args && args['intent'])) { + chrome.test.fail('Expected web intent on args: ' + args); + return; + } + var intent = args['intent']; + chrome.test.assertEq('http://webintents.org/view', intent['action']); + chrome.test.succeed(); + + // Note that we're not using chrome.extension.sendRequest here to call back + // to the source app - the call is not available in v2 packaged apps. The + // most we can do for now is succeed or fail the test (to be caught by a + // ResultCatcher in external_filesystem_apitest.cc). +} + +chrome.app.runtime.onLaunched.addListener(launchedListener); diff --git a/chrome/test/data/extensions/api_test/webintent_handler/manifest.json b/chrome/test/data/extensions/api_test/webintent_handler/manifest.json new file mode 100644 index 0000000..5807ba3 --- /dev/null +++ b/chrome/test/data/extensions/api_test/webintent_handler/manifest.json @@ -0,0 +1,23 @@ +{ + "key": "MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQChptAQ0n4R56N03nWQ1ogR7DVRBjGo80Vw6G9KLjzZv44D8rq5Q5IkeQrtKgWyZfXevlsCe3LaLo18rcz8iZx6lK2xhLdUR+ORjsjuBfdEL5a5cWeRTSxf75AcqndQsmpwMBdrMTCZ8jQNusUI+XlrihLNNJuI5TM4vNINI5bYFQIBIw==", + "name": "ChromeOS Web Intent handler extension", + "version": "0.1", + "manifest_version": 2, + "description": "Tests of chrome.fileSystem.* methods for handling Web Intents", + "app": { + "background": { + "scripts": ["background.js"] + } + }, + "intents": { + "http://webintents.org/view": [{ + "title": "View xul action", + "type": ["application/vnd.mozilla.xul+xml"] + }] + }, + "permissions": [ + "experimental", + "fileSystem", + "unlimitedStorage" + ] +} |