Commit c6a39755 authored by raymes@google.com's avatar raymes@google.com

Implement chrome.streamsPrivate.abort() extensions function

This implements an extensions API to abort a stream that has been started. Aborting the stream will cause the original URL request to be cancelled, which may be desired if the client of the API wants to cancel the request before it is completely downloaded.

BUG=348464,303491
R=jam@chromium.org, kalman@chromium.org, mpearson@chromium.org, zork@chromium.org

Review URL: https://codereview.chromium.org/281513003

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@273087 0039d316-1c4b-4281-b951-d872f2087c98
parent ae8eecf3
...@@ -88,6 +88,26 @@ void StreamsPrivateAPI::ExecuteMimeTypeHandler( ...@@ -88,6 +88,26 @@ void StreamsPrivateAPI::ExecuteMimeTypeHandler(
streams_[extension_id][url] = make_linked_ptr(stream.release()); streams_[extension_id][url] = make_linked_ptr(stream.release());
} }
void StreamsPrivateAPI::AbortStream(const std::string& extension_id,
const GURL& stream_url,
const base::Closure& callback) {
StreamMap::iterator extension_it = streams_.find(extension_id);
if (extension_it == streams_.end()) {
callback.Run();
return;
}
StreamMap::mapped_type* url_map = &extension_it->second;
StreamMap::mapped_type::iterator url_it = url_map->find(stream_url);
if (url_it == url_map->end()) {
callback.Run();
return;
}
url_it->second->AddCloseListener(callback);
url_map->erase(url_it);
}
void StreamsPrivateAPI::OnExtensionUnloaded( void StreamsPrivateAPI::OnExtensionUnloaded(
content::BrowserContext* browser_context, content::BrowserContext* browser_context,
const Extension* extension, const Extension* extension,
...@@ -95,6 +115,23 @@ void StreamsPrivateAPI::OnExtensionUnloaded( ...@@ -95,6 +115,23 @@ void StreamsPrivateAPI::OnExtensionUnloaded(
streams_.erase(extension->id()); streams_.erase(extension->id());
} }
StreamsPrivateAbortFunction::StreamsPrivateAbortFunction() {
}
ExtensionFunction::ResponseAction StreamsPrivateAbortFunction::Run() {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &stream_url_));
StreamsPrivateAPI::Get(browser_context())->AbortStream(
extension_id(), GURL(stream_url_), base::Bind(
&StreamsPrivateAbortFunction::OnClose, this));
return RespondLater();
}
void StreamsPrivateAbortFunction::OnClose() {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
Respond(NoArguments());
}
static base::LazyInstance<BrowserContextKeyedAPIFactory<StreamsPrivateAPI> > static base::LazyInstance<BrowserContextKeyedAPIFactory<StreamsPrivateAPI> >
g_factory = LAZY_INSTANCE_INITIALIZER; g_factory = LAZY_INSTANCE_INITIALIZER;
......
...@@ -35,6 +35,10 @@ class StreamsPrivateAPI : public BrowserContextKeyedAPI, ...@@ -35,6 +35,10 @@ class StreamsPrivateAPI : public BrowserContextKeyedAPI,
scoped_ptr<content::StreamHandle> stream, scoped_ptr<content::StreamHandle> stream,
int64 expected_content_size); int64 expected_content_size);
void AbortStream(const std::string& extension_id,
const GURL& stream_url,
const base::Closure& callback);
// BrowserContextKeyedAPI implementation. // BrowserContextKeyedAPI implementation.
static BrowserContextKeyedAPIFactory<StreamsPrivateAPI>* GetFactoryInstance(); static BrowserContextKeyedAPIFactory<StreamsPrivateAPI>* GetFactoryInstance();
...@@ -66,6 +70,23 @@ class StreamsPrivateAPI : public BrowserContextKeyedAPI, ...@@ -66,6 +70,23 @@ class StreamsPrivateAPI : public BrowserContextKeyedAPI,
extension_registry_observer_; extension_registry_observer_;
}; };
class StreamsPrivateAbortFunction : public UIThreadExtensionFunction {
public:
StreamsPrivateAbortFunction();
DECLARE_EXTENSION_FUNCTION("streamsPrivate.abort", STREAMSPRIVATE_ABORT)
protected:
virtual ~StreamsPrivateAbortFunction() {}
// ExtensionFunction:
virtual ExtensionFunction::ResponseAction Run() OVERRIDE;
private:
void OnClose();
std::string stream_url_;
};
} // namespace extensions } // namespace extensions
#endif // CHROME_BROWSER_EXTENSIONS_API_STREAMS_PRIVATE_STREAMS_PRIVATE_API_H_ #endif // CHROME_BROWSER_EXTENSIONS_API_STREAMS_PRIVATE_STREAMS_PRIVATE_API_H_
...@@ -101,6 +101,14 @@ scoped_ptr<HttpResponse> HandleRequest(const HttpRequest& request) { ...@@ -101,6 +101,14 @@ scoped_ptr<HttpResponse> HandleRequest(const HttpRequest& request) {
return response.PassAs<HttpResponse>(); return response.PassAs<HttpResponse>();
} }
// RTF files for testing chrome.streamsPrivate.abort().
if (request.relative_url == "/abort.rtf" ||
request.relative_url == "/no_abort.rtf") {
response->set_code(net::HTTP_OK);
response->set_content_type("application/rtf");
return response.PassAs<HttpResponse>();
}
// Respond to /favicon.ico for navigating to the page. // Respond to /favicon.ico for navigating to the page.
if (request.relative_url == "/favicon.ico") { if (request.relative_url == "/favicon.ico") {
response->set_code(net::HTTP_NOT_FOUND); response->set_code(net::HTTP_NOT_FOUND);
...@@ -421,4 +429,26 @@ IN_PROC_BROWSER_TEST_F(StreamsPrivateApiTest, Headers) { ...@@ -421,4 +429,26 @@ IN_PROC_BROWSER_TEST_F(StreamsPrivateApiTest, Headers) {
EXPECT_TRUE(catcher.GetNextResult()); EXPECT_TRUE(catcher.GetNextResult());
} }
// Tests that chrome.streamsPrivate.abort() works correctly.
IN_PROC_BROWSER_TEST_F(StreamsPrivateApiTest, Abort) {
#if defined(OS_WIN) && defined(USE_ASH)
// Disable this test in Metro+Ash for now (http://crbug.com/262796).
if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAshBrowserTests))
return;
#endif
ASSERT_TRUE(LoadTestExtension()) << message_;
ResultCatcher catcher;
ui_test_utils::NavigateToURL(browser(),
test_server_->GetURL("/no_abort.rtf"));
base::MessageLoop::current()->RunUntilIdle();
EXPECT_TRUE(catcher.GetNextResult());
ui_test_utils::NavigateToURL(browser(),
test_server_->GetURL("/abort.rtf"));
base::MessageLoop::current()->RunUntilIdle();
EXPECT_TRUE(catcher.GetNextResult());
}
} // namespace } // namespace
...@@ -29,6 +29,17 @@ namespace streamsPrivate { ...@@ -29,6 +29,17 @@ namespace streamsPrivate {
object responseHeaders; object responseHeaders;
}; };
callback AbortCallback = void ();
interface Functions {
// Abort the URL request on the given stream.
// |streamUrl| : The URL of the stream to abort.
// |callback| : Called when the stream URL is guaranteed to be invalid. The
// underlying URL request may not yet have been aborted when this is run.
static void abort(DOMString streamUrl,
optional AbortCallback callback);
};
interface Events { interface Events {
// Fired when a resource is fetched which matches a mime type handled by // Fired when a resource is fetched which matches a mime type handled by
// this extension. The resource request is cancelled, and the extension is // this extension. The resource request is cancelled, and the extension is
......
...@@ -51,6 +51,42 @@ chrome.streamsPrivate.onExecuteMimeTypeHandler.addListener( ...@@ -51,6 +51,42 @@ chrome.streamsPrivate.onExecuteMimeTypeHandler.addListener(
return; return;
} }
// StreamsPrivateApiTest.Abort uses the application/pdf type to test aborting
// the stream.
if (params.mimeType == 'application/rtf') {
var url = params.originalUrl.split('/');
var filename = url[url.length - 1];
if (filename == 'no_abort.rtf') {
// Test a stream URL can be fetched properly.
var xhr = new XMLHttpRequest();
xhr.open("GET", params.streamUrl, false);
xhr.send(null);
if (xhr.status == 200) {
chrome.test.notifyPass();
} else {
chrome.test.notifyFail(
'Expected a stream URL response of 200, got ' + xhr.status + '.');
hasFailed = true;
}
}
if (filename == 'abort.rtf') {
// Test a stream URL fails to be fetched if it is aborted.
chrome.streamsPrivate.abort(params.streamUrl, function() {
var xhr = new XMLHttpRequest();
xhr.open("GET", params.streamUrl, false);
xhr.send(null);
if (xhr.status == 404) {
chrome.test.notifyPass();
} else {
chrome.test.notifyFail(
'Expected a stream URL response of 404, got ' + xhr.status + '.');
hasFailed = true;
}
});
}
return;
}
// MIME type 'test/done' is received only when tests for which no events // 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 // 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 // receives the 'test/done' and there were no previous failures, notify that
......
...@@ -7,8 +7,9 @@ ...@@ -7,8 +7,9 @@
"scripts": ["background.js"] "scripts": ["background.js"]
}, },
"mime_types": [ "mime_types": [
"application/msword",
"application/msexcel", "application/msexcel",
"application/msword",
"application/rtf",
"text/plain" "text/plain"
], ],
"permissions": [ "permissions": [
......
...@@ -12,6 +12,15 @@ ...@@ -12,6 +12,15 @@
namespace content { namespace content {
namespace {
void RunCloseListeners(const std::vector<base::Closure>& close_listeners) {
for (size_t i = 0; i < close_listeners.size(); ++i)
close_listeners[i].Run();
}
} // namespace
StreamHandleImpl::StreamHandleImpl( StreamHandleImpl::StreamHandleImpl(
const base::WeakPtr<Stream>& stream, const base::WeakPtr<Stream>& stream,
const GURL& original_url, const GURL& original_url,
...@@ -25,8 +34,9 @@ StreamHandleImpl::StreamHandleImpl( ...@@ -25,8 +34,9 @@ StreamHandleImpl::StreamHandleImpl(
stream_message_loop_(base::MessageLoopProxy::current().get()) {} stream_message_loop_(base::MessageLoopProxy::current().get()) {}
StreamHandleImpl::~StreamHandleImpl() { StreamHandleImpl::~StreamHandleImpl() {
stream_message_loop_->PostTask(FROM_HERE, stream_message_loop_->PostTaskAndReply(FROM_HERE,
base::Bind(&Stream::CloseHandle, stream_)); base::Bind(&Stream::CloseHandle, stream_),
base::Bind(&RunCloseListeners, close_listeners_));
} }
const GURL& StreamHandleImpl::GetURL() { const GURL& StreamHandleImpl::GetURL() {
...@@ -45,4 +55,8 @@ scoped_refptr<net::HttpResponseHeaders> StreamHandleImpl::GetResponseHeaders() { ...@@ -45,4 +55,8 @@ scoped_refptr<net::HttpResponseHeaders> StreamHandleImpl::GetResponseHeaders() {
return response_headers_; return response_headers_;
} }
void StreamHandleImpl::AddCloseListener(const base::Closure& callback) {
close_listeners_.push_back(callback);
}
} // namespace content } // namespace content
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
#ifndef CONTENT_BROWSER_STREAMS_STREAM_HANDLE_IMPL_H_ #ifndef CONTENT_BROWSER_STREAMS_STREAM_HANDLE_IMPL_H_
#define CONTENT_BROWSER_STREAMS_STREAM_HANDLE_IMPL_H_ #define CONTENT_BROWSER_STREAMS_STREAM_HANDLE_IMPL_H_
#include <vector>
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "base/synchronization/lock.h" #include "base/synchronization/lock.h"
#include "content/public/browser/stream_handle.h" #include "content/public/browser/stream_handle.h"
...@@ -31,6 +33,7 @@ class StreamHandleImpl : public StreamHandle { ...@@ -31,6 +33,7 @@ class StreamHandleImpl : public StreamHandle {
virtual const GURL& GetOriginalURL() OVERRIDE; virtual const GURL& GetOriginalURL() OVERRIDE;
virtual const std::string& GetMimeType() OVERRIDE; virtual const std::string& GetMimeType() OVERRIDE;
virtual scoped_refptr<net::HttpResponseHeaders> GetResponseHeaders() OVERRIDE; virtual scoped_refptr<net::HttpResponseHeaders> GetResponseHeaders() OVERRIDE;
virtual void AddCloseListener(const base::Closure& callback) OVERRIDE;
base::WeakPtr<Stream> stream_; base::WeakPtr<Stream> stream_;
GURL url_; GURL url_;
...@@ -38,6 +41,7 @@ class StreamHandleImpl : public StreamHandle { ...@@ -38,6 +41,7 @@ class StreamHandleImpl : public StreamHandle {
std::string mime_type_; std::string mime_type_;
scoped_refptr<net::HttpResponseHeaders> response_headers_; scoped_refptr<net::HttpResponseHeaders> response_headers_;
base::MessageLoopProxy* stream_message_loop_; base::MessageLoopProxy* stream_message_loop_;
std::vector<base::Closure> close_listeners_;
}; };
} // namespace content } // namespace content
......
...@@ -31,6 +31,9 @@ class CONTENT_EXPORT StreamHandle { ...@@ -31,6 +31,9 @@ class CONTENT_EXPORT StreamHandle {
// Get the HTTP response headers associated with this Stream. // Get the HTTP response headers associated with this Stream.
virtual scoped_refptr<net::HttpResponseHeaders> GetResponseHeaders() = 0; virtual scoped_refptr<net::HttpResponseHeaders> GetResponseHeaders() = 0;
// Add a callback which will be called when the Stream is closed.
virtual void AddCloseListener(const base::Closure& callback) = 0;
}; };
} // namespace content } // namespace content
......
...@@ -841,6 +841,7 @@ enum HistogramValue { ...@@ -841,6 +841,7 @@ enum HistogramValue {
ENTERPRISE_PLATFORMKEYS_IMPORTCERTIFICATE, ENTERPRISE_PLATFORMKEYS_IMPORTCERTIFICATE,
ENTERPRISE_PLATFORMKEYS_REMOVECERTIFICATE, ENTERPRISE_PLATFORMKEYS_REMOVECERTIFICATE,
FILEBROWSERPRIVATE_OPENINSPECTOR, FILEBROWSERPRIVATE_OPENINSPECTOR,
STREAMSPRIVATE_ABORT,
// Last entry: Add new entries above and ensure to update // Last entry: Add new entries above and ensure to update
// tools/metrics/histograms/histograms/histograms.xml. // tools/metrics/histograms/histograms/histograms.xml.
ENUM_BOUNDARY ENUM_BOUNDARY
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment