diff options
20 files changed, 1164 insertions, 9 deletions
diff --git a/android_webview/browser/renderer_host/aw_resource_dispatcher_host_delegate.cc b/android_webview/browser/renderer_host/aw_resource_dispatcher_host_delegate.cc index db605c3..1c3054c 100644 --- a/android_webview/browser/renderer_host/aw_resource_dispatcher_host_delegate.cc +++ b/android_webview/browser/renderer_host/aw_resource_dispatcher_host_delegate.cc @@ -237,6 +237,7 @@ void AwResourceDispatcherHostDelegate::DownloadStarting( int route_id, int request_id, bool is_content_initiated, + bool must_download, ScopedVector<content::ResourceThrottle>* throttles) { GURL url(request->url()); std::string user_agent; diff --git a/android_webview/browser/renderer_host/aw_resource_dispatcher_host_delegate.h b/android_webview/browser/renderer_host/aw_resource_dispatcher_host_delegate.h index 3a80a37..1741a89 100644 --- a/android_webview/browser/renderer_host/aw_resource_dispatcher_host_delegate.h +++ b/android_webview/browser/renderer_host/aw_resource_dispatcher_host_delegate.h @@ -41,6 +41,7 @@ class AwResourceDispatcherHostDelegate int route_id, int request_id, bool is_content_initiated, + bool must_download, ScopedVector<content::ResourceThrottle>* throttles) OVERRIDE; virtual bool AcceptAuthRequest(net::URLRequest* request, diff --git a/chrome/browser/chromeos/extensions/file_browser_resource_throttle.cc b/chrome/browser/chromeos/extensions/file_browser_resource_throttle.cc new file mode 100644 index 0000000..7649929 --- /dev/null +++ b/chrome/browser/chromeos/extensions/file_browser_resource_throttle.cc @@ -0,0 +1,207 @@ +// 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. + +#include "chrome/browser/chromeos/extensions/file_browser_resource_throttle.h" + +#include <string> + +#include "base/bind.h" +#include "base/values.h" +#include "chrome/browser/extensions/event_router.h" +#include "chrome/browser/extensions/extension_info_map.h" +#include "chrome/browser/extensions/extension_system.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/extension_constants.h" +#include "chrome/common/extensions/extension_set.h" +#include "chrome/common/extensions/file_browser_handler.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/resource_controller.h" +#include "content/public/browser/web_contents.h" +#include "net/url_request/url_request.h" + +using extensions::Event; +using extensions::Extension; +using extensions::ExtensionSystem; + +namespace { + +const char* const kOnExecuteContentHandlerEvent = + "fileBrowserHandler.onExecuteContentHandler"; + +// Goes through the extension's file browser handlers and checks it there is one +// that can handle the |mime_type|. +// |extension| must not be NULL. +bool CanHandleMimeType(const Extension* extension, + const std::string& mime_type) { + if (!extension->file_browser_handlers()) + return false; + + for (Extension::FileBrowserHandlerList::const_iterator handler = + extension->file_browser_handlers()->begin(); + handler != extension->file_browser_handlers()->end(); + ++handler) { + if ((*handler)->CanHandleMIMEType(mime_type)) { + return true; + } + } + + return false; +} + +// Retrieves Profile for a render view host specified by |render_process_id| and +// |render_view_id|. +Profile* GetProfile(int render_process_id, int render_view_id) { + content::RenderViewHost* render_view_host = + content::RenderViewHost::FromID(render_process_id, render_view_id); + if (!render_view_host) + return NULL; + + content::WebContents* web_contents = + content::WebContents::FromRenderViewHost(render_view_host); + if (!web_contents) + return NULL; + + content::BrowserContext* browser_context = web_contents->GetBrowserContext(); + if (!browser_context) + return NULL; + + return Profile::FromBrowserContext(browser_context); +} + +void DispatchEventOnUIThread(const std::string& mime_type, + const GURL& request_url, + int render_process_id, + int render_view_id, + const std::string& extension_id) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + + Profile* profile = GetProfile(render_process_id, render_view_id); + if (!profile) + return; + + // Create the event's arguments value. + scoped_ptr<ListValue> event_args(new ListValue()); + event_args->Append(new base::StringValue(mime_type)); + event_args->Append(new base::StringValue(request_url.spec())); + + scoped_ptr<Event> event(new Event(kOnExecuteContentHandlerEvent, + event_args.Pass())); + + ExtensionSystem::Get(profile)->event_router()->DispatchEventToExtension( + extension_id, event.Pass()); +} + +// Default implementation of FileBrowserHandlerEventRouter. +class FileBrowserHandlerEventRouterImpl + : public FileBrowserResourceThrottle::FileBrowserHandlerEventRouter { + public: + FileBrowserHandlerEventRouterImpl() {} + virtual ~FileBrowserHandlerEventRouterImpl() {} + + virtual void DispatchMimeTypeHandlerEvent( + int render_process_id, + int render_view_id, + const std::string& mime_type, + const GURL& request_url, + const std::string& extension_id) OVERRIDE { + // The event must be dispatched on the UI thread. + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, + base::Bind(&DispatchEventOnUIThread, mime_type, request_url, + render_process_id, render_view_id, extension_id)); + } +}; + +} // namespace + +// static +FileBrowserResourceThrottle* FileBrowserResourceThrottle::Create( + int render_process_id, + int render_view_id, + net::URLRequest* request, + bool profile_is_incognito, + const ExtensionInfoMap* extension_info_map) { + std::string mime_type; + request->GetMimeType(&mime_type); + scoped_ptr<FileBrowserHandlerEventRouter> event_router( + new FileBrowserHandlerEventRouterImpl()); + return new FileBrowserResourceThrottle(render_process_id, render_view_id, + mime_type, request->url(), profile_is_incognito, extension_info_map, + event_router.Pass()); +} + +// static +FileBrowserResourceThrottle* FileBrowserResourceThrottle::CreateForTest( + int render_process_id, + int render_view_id, + const std::string& mime_type, + const GURL& request_url, + bool profile_is_incognito, + const ExtensionInfoMap* extension_info_map, + scoped_ptr<FileBrowserHandlerEventRouter> event_router_in) { + scoped_ptr<FileBrowserHandlerEventRouter> event_router = + event_router_in.Pass(); + if (!event_router) + event_router.reset(new FileBrowserHandlerEventRouterImpl()); + return new FileBrowserResourceThrottle(render_process_id, render_view_id, + mime_type, request_url, profile_is_incognito, extension_info_map, + event_router.Pass()); +} + +FileBrowserResourceThrottle::FileBrowserResourceThrottle( + int render_process_id, + int render_view_id, + const std::string& mime_type, + const GURL& request_url, + bool profile_is_incognito, + const ExtensionInfoMap* extension_info_map, + scoped_ptr<FileBrowserHandlerEventRouter> event_router) + : render_process_id_(render_process_id), + render_view_id_(render_view_id), + mime_type_(mime_type), + request_url_(request_url), + profile_is_incognito_(profile_is_incognito), + extension_info_map_(extension_info_map), + event_router_(event_router.Pass()) { +} + +FileBrowserResourceThrottle::~FileBrowserResourceThrottle() {} + +void FileBrowserResourceThrottle::WillProcessResponse(bool* defer) { + std::vector<std::string> whitelist = + FileBrowserHandler::GetMIMETypeWhitelist(); + // Go through the white-listed extensions and try to use them to intercept + // the URL request. + for (size_t i = 0; i < whitelist.size(); ++i) { + if (MaybeInterceptWithExtension(whitelist[i])) + return; + } +} + +bool FileBrowserResourceThrottle::MaybeInterceptWithExtension( + const std::string& extension_id) { + const Extension* extension = + extension_info_map_->extensions().GetByID(extension_id); + // The white-listed extension may not be installed, so we have to NULL check + // |extension|. + if (!extension) + return false; + + // If in incognito mode, skip the extensions that are not incognito enabled. + if (profile_is_incognito_ && + !extension_info_map_->IsIncognitoEnabled(extension_id)) { + return false; + } + + if (CanHandleMimeType(extension, mime_type_)) { + event_router_->DispatchMimeTypeHandlerEvent(render_process_id_, + render_view_id_, mime_type_, request_url_, extension->id()); + controller()->CancelAndIgnore(); + return true; + } + return false; +} diff --git a/chrome/browser/chromeos/extensions/file_browser_resource_throttle.h b/chrome/browser/chromeos/extensions/file_browser_resource_throttle.h new file mode 100644 index 0000000..cf20a16 --- /dev/null +++ b/chrome/browser/chromeos/extensions/file_browser_resource_throttle.h @@ -0,0 +1,122 @@ +// 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. + +#ifndef CHROME_BROWSER_CHROMEOS_EXTENSIONS_FILE_BROWSER_RESOURCE_THROTTLE_H_ +#define CHROME_BROWSER_CHROMEOS_EXTENSIONS_FILE_BROWSER_RESOURCE_THROTTLE_H_ + +#include <string> + +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "content/public/browser/resource_throttle.h" +#include "googleurl/src/gurl.h" + +class ExtensionInfoMap; +class ExtensionSet; + +namespace net { +class URLRequest; +} + +// Resource throttle that intercepts URL requests that can be handled by an +// installed, white-listed file browser handler extension. +// The requests are intercepted before processing the response because the +// request's MIME type is needed to determine if it can be handled by a file +// browser handler. It the request can be handled by a file browser handler +// extension, it gets canceled and the extension is notified to handle +// the requested URL. +class FileBrowserResourceThrottle : public content::ResourceThrottle { + public: + // Used to dispatch fileBrowserHandler events to notify an extension it should + // handle an URL request. + // Can be used on any thread. The actual dispatch tasks will be posted to the + // UI thread. + class FileBrowserHandlerEventRouter { + public: + virtual ~FileBrowserHandlerEventRouter() {} + + // Used to dispatch fileBrowserHandler.onExecuteContentHandler to the + // extension with id |extension_id|. |mime_type| and |request_url| are the + // URL request's parameters that should be handed to the extension. + // |render_process_id| and |render_view_id| are used to determine the + // profile from which the URL request came and in which the event should be + // dispatched. + virtual void DispatchMimeTypeHandlerEvent( + int render_process_id, + int render_view_id, + const std::string& mime_type, + const GURL& request_url, + const std::string& extension_id) = 0; + }; + + // Creates a FileBrowserResourceThrottle for the |request|. + // The throtlle's |mime_type_| and |url_request_| members are extracted from + // the request. + // The throttle's |event_router_| is created and set (it's a + // FileBrowserHandlerEventRouterImpl instance; see the .cc file). + static FileBrowserResourceThrottle* Create( + int render_process_id, + int render_view_id, + net::URLRequest* request, + bool profile_is_incognito, + const ExtensionInfoMap* extension_info_map); + + // Creates a FileBrowserResourceThrottle to be used in a test. + // |event_router| can hold NULL, in which case the throttle's |event_router_| + // member is set to a FileBrowserHandlerEventRouterImpl instance, just like in + // FileBrowserResourceThrottle::Create. + static FileBrowserResourceThrottle* CreateForTest( + int render_process_id, + int renser_view_id, + const std::string& mime_type, + const GURL& request_url, + bool profile_is_incognito, + const ExtensionInfoMap* extension_info_map, + scoped_ptr<FileBrowserHandlerEventRouter> event_router); + + virtual ~FileBrowserResourceThrottle(); + + // content::ResourceThrottle implementation. + // Calls |MaybeInterceptWithExtension| for all extension_id's white-listed to + // use file browser handler MIME type filters. + virtual void WillProcessResponse(bool* defer) OVERRIDE; + + private: + // Use Create* methods to create a FileBrowserResourceThrottle instance. + FileBrowserResourceThrottle( + int render_process_id, + int render_view_id, + const std::string& mime_type, + const GURL& request_url, + bool profile_is_incognito, + const ExtensionInfoMap* extension_info_map, + scoped_ptr<FileBrowserHandlerEventRouter> event_router); + + // Checks if the extension has a registered file browser handler that can + // handle the request's mime_type. If this is the case, the request is + // canceled and fileBrowserHandler.onExtecuteContentHandler event is + // dispatched to the extension. + // Returns whether the URL request has been intercepted. + bool MaybeInterceptWithExtension(const std::string& extension_id); + + // Render process id from which the request came. + int render_process_id_; + // Render view id from which the request came. + int render_view_id_; + // The request's MIME type. + std::string mime_type_; + // The request's URL. + GURL request_url_; + // Whether the request came from an incognito profile. + bool profile_is_incognito_; + // Map holding list of installed extensions. Must be used exclusively on IO + // thread. + const scoped_refptr<const ExtensionInfoMap> extension_info_map_; + + // Event router to be used to dispatch the fileBrowserHandler events. + scoped_ptr<FileBrowserHandlerEventRouter> event_router_; +}; + +#endif // CHROME_BROWSER_CHROMEOS_EXTENSIONS_FILE_BROWSER_RESOURCE_THROTTLE_H_ diff --git a/chrome/browser/chromeos/extensions/file_browser_resource_throttle_browsertest.cc b/chrome/browser/chromeos/extensions/file_browser_resource_throttle_browsertest.cc new file mode 100644 index 0000000..bd24d9f --- /dev/null +++ b/chrome/browser/chromeos/extensions/file_browser_resource_throttle_browsertest.cc @@ -0,0 +1,394 @@ +// 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. + +#include "base/message_loop.h" +#include "chrome/browser/chromeos/extensions/file_browser_resource_throttle.h" +#include "chrome/browser/download/download_prefs.h" +#include "chrome/browser/extensions/event_router.h" +#include "chrome/browser/extensions/extension_apitest.h" +#include "chrome/browser/extensions/extension_info_map.h" +#include "chrome/browser/extensions/extension_system.h" +#include "chrome/browser/google_apis/test_server/http_server.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_tabstrip.cc" +#include "chrome/common/extensions/file_browser_handler.h" +#include "chrome/common/pref_names.cc" +#include "chrome/test/base/ui_test_utils.h" +#include "content/public/browser/download_item.h" +#include "content/public/browser/download_manager.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/resource_controller.h" +#include "content/public/browser/web_contents.h" +#include "content/public/test/download_test_observer.h" +#include "testing/gmock/include/gmock/gmock.h" + +using content::BrowserContext; +using content::BrowserThread; +using content::DownloadItem; +using content::DownloadManager; +using content::DownloadUrlParameters; +using content::ResourceController; +using content::WebContents; +using extensions::Event; +using extensions::ExtensionSystem; +using google_apis::test_server::HttpRequest; +using google_apis::test_server::HttpResponse; +using google_apis::test_server::HttpServer; +using testing::_; + +namespace { + +// Used in FileBrowserResourceThrottleExtensionApiTest.Basic test to mock the +// resource controller bound to the test resource throttle. +class MockResourceController : public ResourceController { + public: + virtual ~MockResourceController() {} + MOCK_METHOD0(Cancel, void()); + MOCK_METHOD0(CancelAndIgnore, void()); + MOCK_METHOD1(CancelWithError, void(int error_code)); + MOCK_METHOD0(Resume, void()); +}; + +// Creates and triggers a test FileBrowserResourceThrottle for the listed +// request parameters (child and routing id from which the request came, +// request's MIME type and URL), with extension info map |info_map| and resource +// controller |controller|. +// The created resource throttle is started by calling |WillProcessResponse|. +// Used in FileBrowserResourceThrottleExtensionApiTest.Basic test. +// +// Must be called on the IO thread. +void CreateAndTriggerThrottle(int child_id, + int routing_id, + const std::string& mime_type, + const GURL& url, + bool is_incognito, + scoped_refptr<ExtensionInfoMap> info_map, + ResourceController* controller) { + typedef FileBrowserResourceThrottle::FileBrowserHandlerEventRouter + HandlerEventRouter; + scoped_ptr<FileBrowserResourceThrottle> throttle( + FileBrowserResourceThrottle::CreateForTest(child_id, routing_id, + mime_type, url, is_incognito, info_map, + scoped_ptr<HandlerEventRouter>())); + + throttle->set_controller_for_testing(controller); + + bool defer; + throttle->WillProcessResponse(&defer); + EXPECT_FALSE(defer); +} + +// Test server's request handler. +// Returns response that should be sent by the test server. +scoped_ptr<HttpResponse> HandleRequest(const HttpRequest& request) { + scoped_ptr<HttpResponse> response(new HttpResponse()); + + // For relative path "/doc_path.doc", return success response with MIME type + // "application/msword". + if (request.relative_url == "/doc_path.doc") { + response->set_code(google_apis::test_server::SUCCESS); + response->set_content_type("application/msword"); + return response.Pass(); + } + + // For relative path "/test_path_attch.txt", return success response with + // MIME type "plain/text" and content "txt content". Also, set content + // disposition to be attachment. + if (request.relative_url == "/text_path_attch.txt") { + response->set_code(google_apis::test_server::SUCCESS); + response->set_content("txt content"); + response->set_content_type("plain/text"); + response->AddCustomHeader("Content-Disposition", + "attachment; filename=test_path.txt"); + return response.Pass(); + } + // For relative path "/test_path_attch.txt", return success response with + // MIME type "plain/text" and content "txt content". + if (request.relative_url == "/text_path.txt") { + response->set_code(google_apis::test_server::SUCCESS); + response->set_content("txt content"); + response->set_content_type("plain/text"); + return response.Pass(); + } + + // No other requests should be handled in the tests. + EXPECT_TRUE(false) << "NOTREACHED!"; + response->set_code(google_apis::test_server::NOT_FOUND); + return response.Pass(); +} + +// Tests to verify that resources are correctly intercepted by +// FileBrowserResourceThrottle. +// The test extension expects the resources that should be handed to the +// extension to have MIME type 'application/msword' and the resources that +// should be downloaded by the browser to have MIME type 'plain/text'. +class FileBrowserResourceThrottleExtensionApiTest : public ExtensionApiTest { + public: + FileBrowserResourceThrottleExtensionApiTest() {} + + virtual ~FileBrowserResourceThrottleExtensionApiTest() { + // Clear the test extension from the white-list. + FileBrowserHandler::set_extension_whitelisted_for_test(NULL); + } + + virtual void SetUpOnMainThread() { + // Init test server. + test_server_.reset(new HttpServer()); + test_server_->InitializeAndWaitUntilReady(); + test_server_->RegisterRequestHandler(base::Bind(&HandleRequest)); + + ExtensionApiTest::SetUpOnMainThread(); + } + + virtual void CleanUpOnMainThread() { + // Tear down the test server. + test_server_->ShutdownAndWaitUntilComplete(); + test_server_.reset(); + ExtensionApiTest::CleanUpOnMainThread(); + } + + void InitializeDownloadSettings() { + ASSERT_TRUE(browser()); + ASSERT_TRUE(downloads_dir_.CreateUniqueTempDir()); + + // Setup default downloads directory to the scoped tmp directory created for + // the test. + browser()->profile()->GetPrefs()->SetFilePath( + prefs::kDownloadDefaultDirectory, downloads_dir_.path()); + // Ensure there are no prompts for download during the test. + browser()->profile()->GetPrefs()->SetBoolean( + prefs::kPromptForDownload, false); + + DownloadManager* manager = GetDownloadManager(); + DownloadPrefs::FromDownloadManager(manager)->ResetAutoOpen(); + manager->RemoveAllDownloads(); + } + + // Sends onExecuteContentHandler event with the MIME type "test/done" to the + // test extension. + // The test extension calls 'chrome.test.notifySuccess' when it receives the + // event with the "test/done" MIME type (unless the 'chrome.test.notifyFail' + // has already been called). + void SendDoneEvent() { + scoped_ptr<ListValue> event_args(new ListValue()); + event_args->Append(new base::StringValue("test/done")); + event_args->Append(new base::StringValue("http://foo")); + + scoped_ptr<Event> event(new Event( + "fileBrowserHandler.onExecuteContentHandler", event_args.Pass())); + + ExtensionSystem::Get(browser()->profile())->event_router()-> + DispatchEventToExtension(test_extension_id_, event.Pass()); + } + + // Loads the test extension and set's up its file_browser_handler to handle + // 'application/msword' and 'plain/text' MIME types. + // The extension will notify success when it detects an event with the MIME + // type 'application/msword' and notify fail when it detects an event with the + // MIME type 'plain/text'. + const extensions::Extension* LoadTestExtension() { + const extensions::Extension* extension = LoadExtension( + test_data_dir_.AppendASCII("file_browser/handle_mime_type")); + if (!extension) + return NULL; + + if (!extension->file_browser_handlers() || + extension->file_browser_handlers()->size() == 0u) { + message_ = "No file browser handlers defined."; + return NULL; + } + + test_extension_id_ = extension->id(); + + FileBrowserHandler::set_extension_whitelisted_for_test(&test_extension_id_); + + // The MIME type filters cannot be defined directly in the extension's + // manifest because the extension installation would fail for the extension + // that is not white-listed to handle MIME types with its file browser + // handlers. + FileBrowserHandler* file_browser_handler = const_cast<FileBrowserHandler*>( + extension->file_browser_handlers()->at(0).get()); + file_browser_handler->AddMIMEType("application/msword"); + file_browser_handler->AddMIMEType("plain/text"); + + return extension; + } + + // Returns the download manager for the current browser. + DownloadManager* GetDownloadManager() const { + DownloadManager* download_manager = + BrowserContext::GetDownloadManager(browser()->profile()); + EXPECT_TRUE(download_manager); + return download_manager; + } + + // Deletes the download and waits until it's flushed. + // The |manager| should have |download| in its list of downloads. + void DeleteDownloadAndWaitForFlush(DownloadItem* download, + DownloadManager* manager) { + scoped_refptr<content::DownloadTestFlushObserver> flush_observer( + new content::DownloadTestFlushObserver(manager)); + download->Delete(DownloadItem::DELETE_DUE_TO_USER_DISCARD); + flush_observer->WaitForFlush(); + } + + protected: + std::string test_extension_id_; + // The HTTP server used in the tests. + scoped_ptr<HttpServer> test_server_; + base::ScopedTempDir downloads_dir_; +}; + +// Tests that invoking FileBrowserResourceThrottle with handleable MIME type +// actually invokes the fileBrowserHandler.onExecuteContnentHandler event. +IN_PROC_BROWSER_TEST_F(FileBrowserResourceThrottleExtensionApiTest, Basic) { + ASSERT_TRUE(LoadTestExtension()) << message_; + + ResultCatcher catcher; + + MockResourceController mock_resource_controller; + EXPECT_CALL(mock_resource_controller, Cancel()).Times(0); + EXPECT_CALL(mock_resource_controller, CancelAndIgnore()).Times(1); + EXPECT_CALL(mock_resource_controller, CancelWithError(_)).Times(0); + EXPECT_CALL(mock_resource_controller, Resume()).Times(0); + + // Get child and routing id from the current web contents (the real values + // should be used so the FileBrowserHandlerEventRouter can correctly extract + // profile from them). + WebContents* web_contents = chrome::GetActiveWebContents(browser()); + ASSERT_TRUE(web_contents); + int child_id = web_contents->GetRenderProcessHost()->GetID(); + int routing_id = web_contents->GetRoutingID(); + + scoped_refptr<ExtensionInfoMap> info_map = + extensions::ExtensionSystem::Get(browser()->profile())->info_map(); + + // The resource throttle must be created and invoked on IO thread. + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, + base::Bind(&CreateAndTriggerThrottle, child_id, routing_id, + "application/msword", GURL("http://foo"), false, info_map, + &mock_resource_controller)); + + // Wait for the test extension to received the onExecuteContentHandler event. + EXPECT_TRUE(catcher.GetNextResult()); +} + +// Tests that navigating to a resource with a MIME type handleable by an +// installed, white-listed extension invokes the extension's +// onExecuteContentHandler event (and does not start a download). +IN_PROC_BROWSER_TEST_F(FileBrowserResourceThrottleExtensionApiTest, Navigate) { + ASSERT_TRUE(LoadTestExtension()) << message_; + + ResultCatcher catcher; + + ui_test_utils::NavigateToURL(browser(), + test_server_->GetURL("/doc_path.doc")); + + // Wait for the response from the test server. + MessageLoop::current()->RunUntilIdle(); + + // There should be no downloads started by the navigation. + DownloadManager* download_manager = GetDownloadManager(); + std::vector<DownloadItem*> downloads; + download_manager->GetAllDownloads(&downloads); + ASSERT_EQ(0u, downloads.size()); + + // The test extension should receive onExecuteContentHandler event with MIME + // type 'application/msword' (and call chrome.test.notifySuccess). + EXPECT_TRUE(catcher.GetNextResult()); +} + +// Tests that navigation to an attachment starts a download, even if there is an +// extension with a file browser handler that can handle the attachment's MIME +// type. +IN_PROC_BROWSER_TEST_F(FileBrowserResourceThrottleExtensionApiTest, + NavigateToAnAttachment) { + InitializeDownloadSettings(); + + ASSERT_TRUE(LoadTestExtension()) << message_; + + ResultCatcher catcher; + + // The test should start a downloadm. + DownloadManager* download_manager = GetDownloadManager(); + scoped_ptr<content::DownloadTestObserver> download_observer( + new content::DownloadTestObserverInProgress(download_manager, 1)); + + ui_test_utils::NavigateToURL(browser(), + test_server_->GetURL("/text_path_attch.txt")); + + // Wait for the download to start. + download_observer->WaitForFinished(); + + // There should be one download started by the navigation. + DownloadManager::DownloadVector downloads; + download_manager->GetAllDownloads(&downloads); + ASSERT_EQ(1u, downloads.size()); + + // Cancel and delete the download started in the test. + DeleteDownloadAndWaitForFlush(downloads[0], download_manager); + + // The test extension should not receive any events by now. Send it an event + // with MIME type "test/done", so it stops waiting for the events. (If there + // was an event with MIME type 'plain/text', |catcher.GetNextResult()| will + // fail regardless of the sent event; chrome.test.notifySuccess will not be + // called by the extension). + SendDoneEvent(); + EXPECT_TRUE(catcher.GetNextResult()); +} + +// Tests that direct download requests don't get intercepted by +// FileBrowserResourceThrottle, even if there is an extension with a file +// browser handler that can handle the download's MIME type. +IN_PROC_BROWSER_TEST_F(FileBrowserResourceThrottleExtensionApiTest, + DirectDownload) { + InitializeDownloadSettings(); + + ASSERT_TRUE(LoadTestExtension()) << message_; + + ResultCatcher catcher; + + DownloadManager* download_manager = GetDownloadManager(); + scoped_ptr<content::DownloadTestObserver> download_observer( + new content::DownloadTestObserverInProgress(download_manager, 1)); + + // The resource's URL on the test server. + GURL url = test_server_->GetURL("/text_path.txt"); + + // The download's target file path. + FilePath target_path = + downloads_dir_.path().Append(FILE_PATH_LITERAL("download_target.txt")); + + // Set the downloads parameters. + content::WebContents* web_contents = chrome::GetActiveWebContents(browser()); + ASSERT_TRUE(web_contents); + scoped_ptr<DownloadUrlParameters> params( + DownloadUrlParameters::FromWebContents(web_contents, url)); + params->set_file_path(target_path); + + // Start download of the URL with a path "/text_path.txt" on the test server. + download_manager->DownloadUrl(params.Pass()); + + // Wait for the download to start. + download_observer->WaitForFinished(); + + // There should have been one download. + std::vector<DownloadItem*> downloads; + download_manager->GetAllDownloads(&downloads); + ASSERT_EQ(1u, downloads.size()); + + // Cancel and delete the download statred in the test. + DeleteDownloadAndWaitForFlush(downloads[0], download_manager); + + // The test extension should not receive any events by now. Send it an event + // with MIME type "test/done", so it stops waiting for the events. (If there + // was an event with MIME type 'plain/text', |catcher.GetNextResult()| will + // fail regardless of the sent event; chrome.test.notifySuccess will not be + // called by the extension). + SendDoneEvent(); + EXPECT_TRUE(catcher.GetNextResult()); +} + +} // namespace diff --git a/chrome/browser/chromeos/extensions/file_browser_resource_throttle_unittest.cc b/chrome/browser/chromeos/extensions/file_browser_resource_throttle_unittest.cc new file mode 100644 index 0000000..7456c46 --- /dev/null +++ b/chrome/browser/chromeos/extensions/file_browser_resource_throttle_unittest.cc @@ -0,0 +1,327 @@ +// 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. + +#include "base/message_loop.h" +#include "chrome/browser/chromeos/extensions/file_browser_resource_throttle.h" +#include "chrome/browser/extensions/extension_info_map.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/extension_builder.h" +#include "chrome/common/extensions/extension_constants.h" +#include "chrome/common/extensions/file_browser_handler.h" +#include "chrome/common/extensions/value_builder.h" +#include "content/public/browser/resource_controller.h" +#include "content/public/test/test_browser_thread.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using content::BrowserThread; +using extensions::DictionaryBuilder; +using extensions::Extension; +using extensions::ExtensionBuilder; +using extensions::ListBuilder; +using testing::_; + +namespace { + +// Mock file browser handler event router to be used in the test. +class MockFileBrowserHandlerEventRouter + : public FileBrowserResourceThrottle::FileBrowserHandlerEventRouter { + public: + virtual ~MockFileBrowserHandlerEventRouter() {} + + MOCK_METHOD5(DispatchMimeTypeHandlerEvent, + void(int render_process_id, + int render_process_view, + const std::string& mime_type, + const GURL& request_url, + const std::string& extension_id)); +}; + +// Resource controller to be used in the tests. +class MockResourceController : public content::ResourceController { + public: + virtual ~MockResourceController() {} + MOCK_METHOD0(Cancel, void()); + MOCK_METHOD0(CancelAndIgnore, void()); + MOCK_METHOD1(CancelWithError, void(int error_code)); + MOCK_METHOD0(Resume, void()); +}; + +class FileBrowserResourceThrottleTest : public testing::Test { + public: + typedef FileBrowserResourceThrottle::FileBrowserHandlerEventRouter + HandlerEventRouter; + + FileBrowserResourceThrottleTest() + : test_extension_id_("test_extension_id"), + test_render_process_id_(2), + test_render_view_id_(12), + test_request_url_("http://some_url/file.txt"), + ui_thread_(content::BrowserThread::UI, &message_loop_), + io_thread_(content::BrowserThread::IO, &message_loop_) { + } + + virtual ~FileBrowserResourceThrottleTest() {} + + virtual void SetUp() { + // Extension info map must be created before |CreateAndIstallTestExtension| + // is called (the method will add created extension to the info map). + extension_info_map_ = new ExtensionInfoMap(); + CreateAndInstallTestExtension(); + InitResourceController(); + } + + virtual void TearDown() { + FileBrowserHandler::set_extension_whitelisted_for_test(NULL); + } + + protected: + // Creates the test extension, and adds it to the |extension_info_map_|. + // The extension has separate file browser handlers that can handle + // 'plain/html' and 'plain/text' MIME types. + void CreateAndInstallTestExtension() { + // The extension must be white-listed in order to be successfully created. + FileBrowserHandler::set_extension_whitelisted_for_test( + &test_extension_id_); + + extension_ = + ExtensionBuilder() + .SetManifest(DictionaryBuilder() + .Set("name", "file browser handler test") + .Set("version", "1.0.0") + .Set("manifest_version", 2) + .Set("file_browser_handlers", ListBuilder() + .Append(DictionaryBuilder() + // Handler that handles 'plain/html', among others. + .Set("id", "ID_handle_html") + .Set("default_title", "Default title") + .Set("default_icon", "icon.png") + // file_filters_field is mandatory, even though + // it's not used in the tests. + .Set("file_filters", ListBuilder() + .Append("filesystem:*.html")) + .Set("mime_types", ListBuilder() + .Append("random/mime1") + .Append("random/mime2") + .Append("plain/html"))) + .Append(DictionaryBuilder() + // Handler that handles only 'plain/text'. + .Set("id", "ID_handle_text") + .Set("default_title", "Default title") + .Set("default_icon", "icon.png") + // file_filters_field is mandatory, even though + // it's not used in the tests. + .Set("file_filters", ListBuilder() + .Append("filesystem:*.txt")) + .Set("mime_types", ListBuilder() + .Append("plain/text"))))) + .SetID(test_extension_id_) + .Build(); + + // 'Install' the extension. + extension_info_map_->AddExtension(extension_, + base::Time(), // install time + true); // enable_incognito + } + + // Initiates the default mock_resource_controller_ expectations. + // By the default, resource controller should not be called at all. + void InitResourceController() { + EXPECT_CALL(mock_resource_controller_, Cancel()).Times(0); + EXPECT_CALL(mock_resource_controller_, CancelAndIgnore()).Times(0); + EXPECT_CALL(mock_resource_controller_, CancelWithError(_)).Times(0); + EXPECT_CALL(mock_resource_controller_, Resume()).Times(0); + } + + // Removes the test extension to |extension_info_map_|'s disabled extensions. + void DisableTestExtension() { + extension_info_map_->RemoveExtension(test_extension_id_, + extension_misc::UNLOAD_REASON_DISABLE); + } + + void ReloadTestExtensionIncognitoDisabled() { + extension_info_map_->RemoveExtension( + test_extension_id_, extension_misc::UNLOAD_REASON_UNINSTALL); + extension_info_map_->AddExtension(extension_, + base::Time(), // install_time + false); // enable incognito + } + + // Creates the resource throttle that should be tested. + // It's setup with |mock_event_router| passed to the method and + // |mock_resource_throttle_|. + // |mock_event_router|'s expectations must be set before calling this method. + scoped_ptr<FileBrowserResourceThrottle> CreateThrottleToTest( + bool is_incognito, + scoped_ptr<MockFileBrowserHandlerEventRouter> mock_event_router, + const std::string& mime_type) { + scoped_ptr<HandlerEventRouter> event_router(mock_event_router.release()); + scoped_ptr<FileBrowserResourceThrottle> test_throttle( + FileBrowserResourceThrottle::CreateForTest(test_render_process_id_, + test_render_view_id_, + mime_type, + test_request_url_, + is_incognito, + extension_info_map_.get(), + event_router.Pass())); + + test_throttle->set_controller_for_testing(&mock_resource_controller_); + + return test_throttle.Pass(); + } + + // The test extension's id. + std::string test_extension_id_; + // The test extension. + scoped_refptr<const Extension> extension_; + + // The extension info map used in the test. The extensions are considered + // installed/disabled depending on the extension_info_map_ state. + scoped_refptr<ExtensionInfoMap> extension_info_map_; + + MockResourceController mock_resource_controller_; + + // Parameters used to create the test resource throttle. Unlike, mime_type + // these can be selected at random. + int test_render_process_id_; + int test_render_view_id_; + GURL test_request_url_; + + private: + // ExtensionInfoMap needs IO thread. + MessageLoop message_loop_; + content::TestBrowserThread ui_thread_; + content::TestBrowserThread io_thread_; +}; + +// Tests that the request gets canceled (mock_resource_controller_.Cancel() is +// called) and the event_router is invoked when a white-listed extension has a +// file browser handler that can handle the request's MIME type. +TEST_F(FileBrowserResourceThrottleTest, HandlerWhiteListed) { + EXPECT_CALL(mock_resource_controller_, CancelAndIgnore()).Times(1); + + scoped_ptr<MockFileBrowserHandlerEventRouter> mock_event_router( + new MockFileBrowserHandlerEventRouter()); + EXPECT_CALL(*mock_event_router, + DispatchMimeTypeHandlerEvent(test_render_process_id_, + test_render_view_id_, + "plain/html", + test_request_url_, + test_extension_id_)) + .Times(1); + + scoped_ptr<FileBrowserResourceThrottle> throttle( + CreateThrottleToTest(false, mock_event_router.Pass(), "plain/html")); + + bool defer = false; + throttle->WillProcessResponse(&defer); + EXPECT_FALSE(defer); +} + +// Tests that the request gets canceled (mock_resource_controller_.Cancel() is +// called) and the event_router is invoked when a white-listed extension has a +// file browser handler that can handle the request's MIME type, even when the +// file browser handler is not first in the extension's file browser handler +// list. +TEST_F(FileBrowserResourceThrottleTest, SecondHandlerWhiteListed) { + EXPECT_CALL(mock_resource_controller_, CancelAndIgnore()).Times(1); + + scoped_ptr<MockFileBrowserHandlerEventRouter> mock_event_router( + new MockFileBrowserHandlerEventRouter()); + EXPECT_CALL(*mock_event_router, + DispatchMimeTypeHandlerEvent(test_render_process_id_, + test_render_view_id_, + "plain/text", + test_request_url_, + test_extension_id_)) + .Times(1); + + scoped_ptr<FileBrowserResourceThrottle> throttle( + CreateThrottleToTest(false, mock_event_router.Pass(), "plain/text")); + + bool defer = false; + throttle->WillProcessResponse(&defer); + EXPECT_FALSE(defer); +} + +// Tests that the request is not canceled and the event router is not invoked +// if there is no file browser handlers registered for the request's MIME type. +TEST_F(FileBrowserResourceThrottleTest, NoWhiteListedHandler) { + scoped_ptr<MockFileBrowserHandlerEventRouter> mock_event_router( + new MockFileBrowserHandlerEventRouter()); + EXPECT_CALL(*mock_event_router, DispatchMimeTypeHandlerEvent(_, _, _, _, _)) + .Times(0); + + scoped_ptr<FileBrowserResourceThrottle> throttle( + CreateThrottleToTest(false, mock_event_router.Pass(), + "random_mime_type")); + + bool defer = false; + throttle->WillProcessResponse(&defer); + EXPECT_FALSE(defer); +} + +// Tests that the request is not canceled and the event router is not invoked +// if there is an extension with the file browser handler that can handle the +// request's MIME type, but the extension is disabled. +TEST_F(FileBrowserResourceThrottleTest, HandlerWhiteListedAndDisabled) { + DisableTestExtension(); + + scoped_ptr<MockFileBrowserHandlerEventRouter> mock_event_router( + new MockFileBrowserHandlerEventRouter()); + EXPECT_CALL(*mock_event_router, DispatchMimeTypeHandlerEvent(_, _, _, _, _)) + .Times(0); + + scoped_ptr<FileBrowserResourceThrottle> throttle( + CreateThrottleToTest(false, mock_event_router.Pass(), "plain/text")); + + bool defer = false; + throttle->WillProcessResponse(&defer); + EXPECT_FALSE(defer); +} + +// Tests that the request is not canceled and the event router is not invoked +// in incognito mode if the extension that handles the request's MIME type is +// not incognito enabled. +TEST_F(FileBrowserResourceThrottleTest, IncognitoExtensionNotEnabled) { + ReloadTestExtensionIncognitoDisabled(); + + scoped_ptr<MockFileBrowserHandlerEventRouter> mock_event_router( + new MockFileBrowserHandlerEventRouter()); + EXPECT_CALL(*mock_event_router, DispatchMimeTypeHandlerEvent(_, _, _, _, _)) + .Times(0); + + scoped_ptr<FileBrowserResourceThrottle> throttle( + CreateThrottleToTest(true, mock_event_router.Pass(), "plain/text")); + + bool defer = false; + throttle->WillProcessResponse(&defer); + EXPECT_FALSE(defer); +} + +// Tests that the request gets canceled (mock_resource_controller_.Cancel() is +// called) and the event_router is invoked in incognito when a white-listed +// extension that handles request's MIME type is incognito enabled. +TEST_F(FileBrowserResourceThrottleTest, IncognitoExtensionEnabled) { + EXPECT_CALL(mock_resource_controller_, CancelAndIgnore()).Times(1); + + scoped_ptr<MockFileBrowserHandlerEventRouter> mock_event_router( + new MockFileBrowserHandlerEventRouter()); + EXPECT_CALL(*mock_event_router, + DispatchMimeTypeHandlerEvent(test_render_process_id_, + test_render_view_id_, + "plain/html", + test_request_url_, + test_extension_id_)) + .Times(1); + + scoped_ptr<FileBrowserResourceThrottle> throttle( + CreateThrottleToTest(true, mock_event_router.Pass(), "plain/html")); + + bool defer = false; + throttle->WillProcessResponse(&defer); + EXPECT_FALSE(defer); +} + +} // namespace diff --git a/chrome/browser/renderer_host/chrome_resource_dispatcher_host_delegate.cc b/chrome/browser/renderer_host/chrome_resource_dispatcher_host_delegate.cc index 02912d2..3be4456 100644 --- a/chrome/browser/renderer_host/chrome_resource_dispatcher_host_delegate.cc +++ b/chrome/browser/renderer_host/chrome_resource_dispatcher_host_delegate.cc @@ -57,8 +57,9 @@ #include "chrome/browser/managed_mode/managed_mode_resource_throttle.h" #endif -// TODO(oshima): Enable this for other platforms. #if defined(OS_CHROMEOS) +#include "chrome/browser/chromeos/extensions/file_browser_resource_throttle.h" +// TODO(oshima): Enable this for other platforms. #include "chrome/browser/renderer_host/offline_resource_throttle.h" #endif @@ -192,6 +193,7 @@ void ChromeResourceDispatcherHostDelegate::DownloadStarting( int route_id, int request_id, bool is_content_initiated, + bool must_download, ScopedVector<content::ResourceThrottle>* throttles) { BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, @@ -199,6 +201,16 @@ void ChromeResourceDispatcherHostDelegate::DownloadStarting( // If it's from the web, we don't trust it, so we push the throttle on. if (is_content_initiated) { +#if defined(OS_CHROMEOS) + if (!must_download) { + ProfileIOData* io_data = + ProfileIOData::FromResourceContext(resource_context); + throttles->push_back(FileBrowserResourceThrottle::Create( + child_id, route_id, request, io_data->is_incognito(), + io_data->GetExtensionInfoMap())); + } +#endif // defined(OS_CHROMEOS) + throttles->push_back(new DownloadResourceThrottle( download_request_limiter_, child_id, route_id, request_id, request->method())); diff --git a/chrome/browser/renderer_host/chrome_resource_dispatcher_host_delegate.h b/chrome/browser/renderer_host/chrome_resource_dispatcher_host_delegate.h index 22e66b80..7d82d5a 100644 --- a/chrome/browser/renderer_host/chrome_resource_dispatcher_host_delegate.h +++ b/chrome/browser/renderer_host/chrome_resource_dispatcher_host_delegate.h @@ -63,6 +63,7 @@ class ChromeResourceDispatcherHostDelegate int route_id, int request_id, bool is_content_initiated, + bool must_download, ScopedVector<content::ResourceThrottle>* throttles) OVERRIDE; virtual bool AcceptSSLClientCertificateRequest( net::URLRequest* request, diff --git a/chrome/chrome_browser_chromeos.gypi b/chrome/chrome_browser_chromeos.gypi index 4abc9bb..6f228bf 100644 --- a/chrome/chrome_browser_chromeos.gypi +++ b/chrome/chrome_browser_chromeos.gypi @@ -274,6 +274,8 @@ 'browser/chromeos/extensions/file_browser_event_router.h', 'browser/chromeos/extensions/file_browser_notifications.cc', 'browser/chromeos/extensions/file_browser_notifications.h', + 'browser/chromeos/extensions/file_browser_resource_throttle.cc', + 'browser/chromeos/extensions/file_browser_resource_throttle.h', 'browser/chromeos/extensions/file_handler_util.cc', 'browser/chromeos/extensions/file_handler_util.h', 'browser/chromeos/extensions/file_manager_util.cc', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 524c88b..61098bd 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -883,6 +883,7 @@ 'browser/chromeos/extensions/file_browser_handler_api_test.cc', 'browser/chromeos/extensions/file_browser_notifications_browsertest.cc', 'browser/chromeos/extensions/file_browser_private_apitest.cc', + 'browser/chromeos/extensions/file_browser_resource_throttle_browsertest.cc', 'browser/chromeos/extensions/info_private_apitest.cc', 'browser/chromeos/extensions/input_method_apitest_chromeos.cc', 'browser/chromeos/extensions/power/power_api_browsertest.cc', diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi index 3bbf8da..3ada3ac 100644 --- a/chrome/chrome_tests_unit.gypi +++ b/chrome/chrome_tests_unit.gypi @@ -558,6 +558,7 @@ 'browser/chromeos/drive/stale_cache_files_remover_unittest.cc', 'browser/chromeos/extensions/default_app_order_unittest.cc', 'browser/chromeos/extensions/file_browser_notifications_unittest.cc', + 'browser/chromeos/extensions/file_browser_resource_throttle_unittest.cc', 'browser/chromeos/extensions/wallpaper_private_api_unittest.cc', 'browser/chromeos/external_metrics_unittest.cc', 'browser/chromeos/imageburner/burn_manager_unittest.cc', diff --git a/chrome/common/extensions/file_browser_handler.cc b/chrome/common/extensions/file_browser_handler.cc index 8d77497..1587935 100644 --- a/chrome/common/extensions/file_browser_handler.cc +++ b/chrome/common/extensions/file_browser_handler.cc @@ -41,6 +41,8 @@ const char* const kMIMETypeHandlersWhitelist[] = { // static bool FileBrowserHandler::ExtensionWhitelistedForMIMETypes( const std::string& extension_id) { + if (g_test_extension_id_ && extension_id == *g_test_extension_id_) + return true; for (size_t i = 0; i < arraysize(kMIMETypeHandlersWhitelist); ++i) { if (extension_id == kMIMETypeHandlersWhitelist[i]) return true; @@ -48,6 +50,16 @@ bool FileBrowserHandler::ExtensionWhitelistedForMIMETypes( return false; } +// static +std::vector<std::string> FileBrowserHandler::GetMIMETypeWhitelist() { + std::vector<std::string> whitelist; + if (g_test_extension_id_) + whitelist.push_back(*g_test_extension_id_); + for (size_t i = 0; i < arraysize(kMIMETypeHandlersWhitelist); ++i) + whitelist.push_back(kMIMETypeHandlersWhitelist[i]); + return whitelist; +} + FileBrowserHandler::FileBrowserHandler() : file_access_permission_flags_(kPermissionsNotDefined) { } @@ -111,3 +123,5 @@ bool FileBrowserHandler::HasCreateAccessPermission() const { DCHECK(!(file_access_permission_flags_ & kInvalidPermission)); return (file_access_permission_flags_ & kCreatePermission) != 0; } + +std::string* FileBrowserHandler::g_test_extension_id_ = NULL; diff --git a/chrome/common/extensions/file_browser_handler.h b/chrome/common/extensions/file_browser_handler.h index 06d568d..90e2384 100644 --- a/chrome/common/extensions/file_browser_handler.h +++ b/chrome/common/extensions/file_browser_handler.h @@ -23,6 +23,15 @@ class FileBrowserHandler { // MIME type filters. static bool ExtensionWhitelistedForMIMETypes(const std::string& extension_id); + // Returns list of extensions' ids that are allowed to use MIME type filters. + static std::vector<std::string> GetMIMETypeWhitelist(); + + // Whitelists the extension to use MIME type filters for a test. + // |extension_id| should be owned by the test code. + static void set_extension_whitelisted_for_test(std::string* extension_id) { + g_test_extension_id_ = extension_id; + } + FileBrowserHandler(); ~FileBrowserHandler(); @@ -75,6 +84,10 @@ class FileBrowserHandler { bool HasCreateAccessPermission() const; private: + // The id of the extension that will be whitelisted to use MIME type filters + // during tests. + static std::string* g_test_extension_id_; + // The id for the extension this action belongs to (as defined in the // extension manifest). std::string extension_id_; diff --git a/chrome/test/data/extensions/api_test/file_browser/handle_mime_type/background.js b/chrome/test/data/extensions/api_test/file_browser/handle_mime_type/background.js new file mode 100644 index 0000000..16864b3 --- /dev/null +++ b/chrome/test/data/extensions/api_test/file_browser/handle_mime_type/background.js @@ -0,0 +1,36 @@ +// 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. + +// True iff the notifyFail has alerady been called. +var hasFailed = false; + +chrome.fileBrowserHandler.onExecuteContentHandler.addListener( + function(mime_type, content_url) { + // The tests are setup so resources with MIME type 'application/msword' are + // meant to be handled by the extension. The extension getting an event with + // the MIME type 'application/msword' means the test has succeeded. + if (mime_type == 'application/msword') { + chrome.test.notifyPass(); + return; + } + + // The tests are setup so resources with MIME type 'plain/text' are meant to + // be handled by the browser (i.e. downloaded). The extension getting event + // with MIME type 'plain/text' is thus a failure. + if (mime_type == 'plain/text') { + chrome.test.notifyFail( + 'Unexpected request to handle "plain/text" MIME type.'); + // Set |hasFailed| so notifyPass doesn't get called later (when event with + // MIME type 'test/done' is received). + hasFailed = true; + return; + } + + // MIME type 'test/done' is received only when tests for which no events + // should be raised to notify the extension it's job is done. If the extension + // receives the 'test/done' and there were no previous failures, notify that + // the test has succeeded. + if (!hasFailed && mime_type == 'test/done') + chrome.test.notifyPass(); +}); diff --git a/chrome/test/data/extensions/api_test/file_browser/handle_mime_type/manifest.json b/chrome/test/data/extensions/api_test/file_browser/handle_mime_type/manifest.json new file mode 100644 index 0000000..7b85ce1 --- /dev/null +++ b/chrome/test/data/extensions/api_test/file_browser/handle_mime_type/manifest.json @@ -0,0 +1,20 @@ +{ + "name": "Test Download redirect", + "version": "1", + "manifest_version": 2, + "background": { + "scripts": ["background.js"] + }, + "file_browser_handlers": [ + { + "id": "Some ID", + "default_title": "Some title", + "file_filters": [ + "filesystem:*.doc" + ] + } + ], + "permissions": [ + "fileBrowserHandler" + ] +} diff --git a/content/browser/loader/buffered_resource_handler.cc b/content/browser/loader/buffered_resource_handler.cc index 47d00df..44d7b22 100644 --- a/content/browser/loader/buffered_resource_handler.cc +++ b/content/browser/loader/buffered_resource_handler.cc @@ -315,7 +315,8 @@ bool BufferedResourceHandler::SelectNextHandler(bool* defer) { if (!info->allow_download()) return true; - if (!MustDownload()) { + bool must_download = MustDownload(); + if (!must_download) { if (net::IsSupportedMimeType(mime_type)) return true; @@ -339,6 +340,7 @@ bool BufferedResourceHandler::SelectNextHandler(bool* defer) { host_->CreateResourceHandlerForDownload( request_, true, // is_content_initiated + must_download, scoped_ptr<DownloadSaveInfo>(new DownloadSaveInfo()), DownloadResourceHandler::OnStartedCallback())); return UseAlternateNextHandler(handler.Pass()); diff --git a/content/browser/loader/resource_dispatcher_host_impl.cc b/content/browser/loader/resource_dispatcher_host_impl.cc index 420b087..fd945c7 100644 --- a/content/browser/loader/resource_dispatcher_host_impl.cc +++ b/content/browser/loader/resource_dispatcher_host_impl.cc @@ -550,7 +550,8 @@ net::Error ResourceDispatcherHostImpl::BeginDownload( // |started_callback|. scoped_ptr<ResourceHandler> handler( CreateResourceHandlerForDownload(request.get(), is_content_initiated, - save_info.Pass(), started_callback)); + true, save_info.Pass(), + started_callback)); BeginRequestInternal(request.Pass(), handler.Pass()); @@ -579,6 +580,7 @@ scoped_ptr<ResourceHandler> ResourceDispatcherHostImpl::CreateResourceHandlerForDownload( net::URLRequest* request, bool is_content_initiated, + bool must_download, scoped_ptr<DownloadSaveInfo> save_info, const DownloadResourceHandler::OnStartedCallback& started_cb) { scoped_ptr<ResourceHandler> handler( @@ -591,7 +593,7 @@ ResourceDispatcherHostImpl::CreateResourceHandlerForDownload( delegate_->DownloadStarting( request, request_info->GetContext(), request_info->GetChildID(), request_info->GetRouteID(), request_info->GetRequestID(), - is_content_initiated, &throttles); + is_content_initiated, must_download, &throttles); if (!throttles.empty()) { handler.reset( new ThrottlingResourceHandler( diff --git a/content/browser/loader/resource_dispatcher_host_impl.h b/content/browser/loader/resource_dispatcher_host_impl.h index 20b3d55..0b88ecd 100644 --- a/content/browser/loader/resource_dispatcher_host_impl.h +++ b/content/browser/loader/resource_dispatcher_host_impl.h @@ -204,6 +204,7 @@ class CONTENT_EXPORT ResourceDispatcherHostImpl scoped_ptr<ResourceHandler> CreateResourceHandlerForDownload( net::URLRequest* request, bool is_content_initiated, + bool must_download, scoped_ptr<DownloadSaveInfo> save_info, const DownloadResourceHandler::OnStartedCallback& started_cb); diff --git a/content/public/browser/resource_dispatcher_host_delegate.cc b/content/public/browser/resource_dispatcher_host_delegate.cc index 0739a11..ccca655 100644 --- a/content/public/browser/resource_dispatcher_host_delegate.cc +++ b/content/public/browser/resource_dispatcher_host_delegate.cc @@ -35,6 +35,7 @@ void ResourceDispatcherHostDelegate::DownloadStarting( int route_id, int request_id, bool is_content_initiated, + bool must_download, ScopedVector<ResourceThrottle>* throttles) { } diff --git a/content/public/browser/resource_dispatcher_host_delegate.h b/content/public/browser/resource_dispatcher_host_delegate.h index bee58fb..890f1c7 100644 --- a/content/public/browser/resource_dispatcher_host_delegate.h +++ b/content/public/browser/resource_dispatcher_host_delegate.h @@ -66,11 +66,7 @@ class CONTENT_EXPORT ResourceDispatcherHostDelegate { ScopedVector<ResourceThrottle>* throttles); // Allows an embedder to add additional resource handlers for a download. - // |is_new_request| is true if this is a request that is just starting, i.e. - // the content layer has just added its own resource handlers; it's false if - // this was originally a non-download request that had some resource handlers - // applied already and now we found out it's a download. - // |in_complete| is true if this is invoked from |OnResponseCompleted|. + // |must_download| is set if the request must be handled as a download. virtual void DownloadStarting( net::URLRequest* request, ResourceContext* resource_context, @@ -78,6 +74,7 @@ class CONTENT_EXPORT ResourceDispatcherHostDelegate { int route_id, int request_id, bool is_content_initiated, + bool must_download, ScopedVector<ResourceThrottle>* throttles); // Called when an SSL Client Certificate is requested. If false is returned, |