| // Copyright 2015 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 "media/blink/resource_multibuffer_data_provider.h" |
| |
| #include <stdint.h> |
| #include <algorithm> |
| #include <string> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/format_macros.h" |
| #include "base/macros.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/run_loop.h" |
| #include "base/strings/stringprintf.h" |
| #include "media/base/media_log.h" |
| #include "media/base/seekable_buffer.h" |
| #include "media/blink/mock_webassociatedurlloader.h" |
| #include "media/blink/url_index.h" |
| #include "net/base/net_errors.h" |
| #include "net/http/http_request_headers.h" |
| #include "net/http/http_util.h" |
| #include "third_party/WebKit/public/platform/WebString.h" |
| #include "third_party/WebKit/public/platform/WebURLError.h" |
| #include "third_party/WebKit/public/platform/WebURLRequest.h" |
| #include "third_party/WebKit/public/platform/WebURLResponse.h" |
| #include "third_party/WebKit/public/web/WebFrameClient.h" |
| #include "third_party/WebKit/public/web/WebLocalFrame.h" |
| #include "third_party/WebKit/public/web/WebView.h" |
| |
| using ::testing::_; |
| using ::testing::InSequence; |
| using ::testing::Return; |
| using ::testing::Truly; |
| using ::testing::NiceMock; |
| |
| using blink::WebLocalFrame; |
| using blink::WebString; |
| using blink::WebURLError; |
| using blink::WebURLResponse; |
| using blink::WebView; |
| |
| namespace media { |
| |
| const char kHttpUrl[] = "http://test"; |
| const char kHttpRedirect[] = "http://test/ing"; |
| const char kEtag[] = "\"arglebargle glopy-glyf?\""; |
| |
| const int kDataSize = 1024; |
| const int kHttpOK = 200; |
| const int kHttpPartialContent = 206; |
| |
| enum NetworkState { NONE, LOADED, LOADING }; |
| |
| // Predicate that tests that request disallows compressed data. |
| static bool CorrectAcceptEncodingAndEtag(const blink::WebURLRequest& request) { |
| std::string etag = |
| request.httpHeaderField(WebString::fromUTF8("If-Match")).utf8(); |
| EXPECT_EQ(etag, kEtag); |
| |
| std::string value = |
| request.httpHeaderField( |
| WebString::fromUTF8(net::HttpRequestHeaders::kAcceptEncoding)) |
| .utf8(); |
| return (value.find("identity;q=1") != std::string::npos) && |
| (value.find("*;q=0") != std::string::npos); |
| } |
| |
| class ResourceMultiBufferDataProviderTest : public testing::Test { |
| public: |
| ResourceMultiBufferDataProviderTest() |
| : view_(WebView::create(nullptr, blink::WebPageVisibilityStateVisible)) { |
| WebLocalFrame* frame = |
| WebLocalFrame::create(blink::WebTreeScopeType::Document, &client_); |
| view_->setMainFrame(frame); |
| url_index_.reset(new UrlIndex(frame, 0)); |
| |
| for (int i = 0; i < kDataSize; ++i) { |
| data_[i] = i; |
| } |
| } |
| |
| virtual ~ResourceMultiBufferDataProviderTest() { |
| view_->close(); |
| } |
| |
| void Initialize(const char* url, int first_position) { |
| gurl_ = GURL(url); |
| url_data_ = url_index_->GetByUrl(gurl_, UrlData::CORS_UNSPECIFIED); |
| url_data_->set_etag(kEtag); |
| DCHECK(url_data_); |
| DCHECK(url_data_->frame()); |
| url_data_->OnRedirect( |
| base::Bind(&ResourceMultiBufferDataProviderTest::RedirectCallback, |
| base::Unretained(this))); |
| |
| first_position_ = first_position; |
| |
| std::unique_ptr<ResourceMultiBufferDataProvider> loader( |
| new ResourceMultiBufferDataProvider(url_data_.get(), first_position_)); |
| loader_ = loader.get(); |
| url_data_->multibuffer()->AddProvider(std::move(loader)); |
| |
| // |test_loader_| will be used when Start() is called. |
| url_loader_ = new NiceMock<MockWebAssociatedURLLoader>(); |
| loader_->test_loader_ = |
| std::unique_ptr<blink::WebAssociatedURLLoader>(url_loader_); |
| } |
| |
| void Start() { |
| InSequence s; |
| EXPECT_CALL( |
| *url_loader_, |
| loadAsynchronously(Truly(CorrectAcceptEncodingAndEtag), loader_)); |
| |
| loader_->Start(); |
| } |
| |
| void FullResponse(int64_t instance_size, bool ok = true) { |
| WebURLResponse response(gurl_); |
| response.setHTTPHeaderField( |
| WebString::fromUTF8("Content-Length"), |
| WebString::fromUTF8(base::StringPrintf("%" PRId64, instance_size))); |
| response.setExpectedContentLength(instance_size); |
| response.setHTTPStatusCode(kHttpOK); |
| loader_->didReceiveResponse(response); |
| |
| if (ok) { |
| EXPECT_EQ(instance_size, url_data_->length()); |
| } |
| |
| EXPECT_FALSE(url_data_->range_supported()); |
| } |
| |
| void PartialResponse(int64_t first_position, |
| int64_t last_position, |
| int64_t instance_size) { |
| PartialResponse(first_position, last_position, instance_size, false, true); |
| } |
| |
| void PartialResponse(int64_t first_position, |
| int64_t last_position, |
| int64_t instance_size, |
| bool chunked, |
| bool accept_ranges) { |
| WebURLResponse response(gurl_); |
| response.setHTTPHeaderField( |
| WebString::fromUTF8("Content-Range"), |
| WebString::fromUTF8( |
| base::StringPrintf("bytes " |
| "%" PRId64 "-%" PRId64 "/%" PRId64, |
| first_position, last_position, instance_size))); |
| |
| // HTTP 1.1 doesn't permit Content-Length with Transfer-Encoding: chunked. |
| int64_t content_length = -1; |
| if (chunked) { |
| response.setHTTPHeaderField(WebString::fromUTF8("Transfer-Encoding"), |
| WebString::fromUTF8("chunked")); |
| } else { |
| content_length = last_position - first_position + 1; |
| } |
| response.setExpectedContentLength(content_length); |
| |
| // A server isn't required to return Accept-Ranges even though it might. |
| if (accept_ranges) { |
| response.setHTTPHeaderField(WebString::fromUTF8("Accept-Ranges"), |
| WebString::fromUTF8("bytes")); |
| } |
| |
| response.setHTTPStatusCode(kHttpPartialContent); |
| loader_->didReceiveResponse(response); |
| |
| EXPECT_EQ(instance_size, url_data_->length()); |
| |
| // A valid partial response should always result in this being true. |
| EXPECT_TRUE(url_data_->range_supported()); |
| } |
| |
| void Redirect(const char* url) { |
| GURL redirectUrl(url); |
| blink::WebURLRequest newRequest(redirectUrl); |
| blink::WebURLResponse redirectResponse(gurl_); |
| |
| EXPECT_CALL(*this, RedirectCallback(_)) |
| .WillOnce( |
| Invoke(this, &ResourceMultiBufferDataProviderTest::SetUrlData)); |
| |
| loader_->willFollowRedirect(newRequest, redirectResponse); |
| |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void StopWhenLoad() { |
| InSequence s; |
| EXPECT_CALL(*url_loader_, cancel()); |
| loader_ = nullptr; |
| url_data_ = nullptr; |
| } |
| |
| // Helper method to write to |loader_| from |data_|. |
| void WriteLoader(int position, int size) { |
| loader_->didReceiveData(reinterpret_cast<char*>(data_ + position), size); |
| } |
| |
| void WriteData(int size) { |
| std::unique_ptr<char[]> data(new char[size]); |
| loader_->didReceiveData(data.get(), size); |
| } |
| |
| // Verifies that data in buffer[0...size] is equal to data_[pos...pos+size]. |
| void VerifyBuffer(uint8_t* buffer, int pos, int size) { |
| EXPECT_EQ(0, memcmp(buffer, data_ + pos, size)); |
| } |
| |
| bool HasActiveLoader() { return loader_->active_loader_ != nullptr; } |
| MOCK_METHOD1(RedirectCallback, void(const scoped_refptr<UrlData>&)); |
| |
| void SetUrlData(const scoped_refptr<UrlData>& new_url_data) { |
| url_data_ = new_url_data; |
| } |
| |
| protected: |
| GURL gurl_; |
| int64_t first_position_; |
| |
| std::unique_ptr<UrlIndex> url_index_; |
| scoped_refptr<UrlData> url_data_; |
| scoped_refptr<UrlData> redirected_to_; |
| // The loader is owned by the UrlData above. |
| ResourceMultiBufferDataProvider* loader_; |
| NiceMock<MockWebAssociatedURLLoader>* url_loader_; |
| |
| blink::WebFrameClient client_; |
| WebView* view_; |
| |
| base::MessageLoop message_loop_; |
| |
| uint8_t data_[kDataSize]; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(ResourceMultiBufferDataProviderTest); |
| }; |
| |
| TEST_F(ResourceMultiBufferDataProviderTest, StartStop) { |
| Initialize(kHttpUrl, 0); |
| Start(); |
| StopWhenLoad(); |
| } |
| |
| // Tests that a bad HTTP response is recived, e.g. file not found. |
| TEST_F(ResourceMultiBufferDataProviderTest, BadHttpResponse) { |
| Initialize(kHttpUrl, 0); |
| Start(); |
| |
| EXPECT_CALL(*this, RedirectCallback(scoped_refptr<UrlData>(nullptr))); |
| |
| WebURLResponse response(gurl_); |
| response.setHTTPStatusCode(404); |
| response.setHTTPStatusText("Not Found\n"); |
| loader_->didReceiveResponse(response); |
| } |
| |
| // Tests that partial content is requested but not fulfilled. |
| TEST_F(ResourceMultiBufferDataProviderTest, NotPartialResponse) { |
| Initialize(kHttpUrl, 100); |
| Start(); |
| FullResponse(1024, false); |
| } |
| |
| // Tests that a 200 response is received. |
| TEST_F(ResourceMultiBufferDataProviderTest, FullResponse) { |
| Initialize(kHttpUrl, 0); |
| Start(); |
| FullResponse(1024); |
| StopWhenLoad(); |
| } |
| |
| // Tests that a partial content response is received. |
| TEST_F(ResourceMultiBufferDataProviderTest, PartialResponse) { |
| Initialize(kHttpUrl, 100); |
| Start(); |
| PartialResponse(100, 200, 1024); |
| StopWhenLoad(); |
| } |
| |
| TEST_F(ResourceMultiBufferDataProviderTest, PartialResponse_Chunked) { |
| Initialize(kHttpUrl, 100); |
| Start(); |
| PartialResponse(100, 200, 1024, true, true); |
| StopWhenLoad(); |
| } |
| |
| TEST_F(ResourceMultiBufferDataProviderTest, PartialResponse_NoAcceptRanges) { |
| Initialize(kHttpUrl, 100); |
| Start(); |
| PartialResponse(100, 200, 1024, false, false); |
| StopWhenLoad(); |
| } |
| |
| TEST_F(ResourceMultiBufferDataProviderTest, |
| PartialResponse_ChunkedNoAcceptRanges) { |
| Initialize(kHttpUrl, 100); |
| Start(); |
| PartialResponse(100, 200, 1024, true, false); |
| StopWhenLoad(); |
| } |
| |
| // Tests that an invalid partial response is received. |
| TEST_F(ResourceMultiBufferDataProviderTest, InvalidPartialResponse) { |
| Initialize(kHttpUrl, 0); |
| Start(); |
| |
| EXPECT_CALL(*this, RedirectCallback(scoped_refptr<UrlData>(nullptr))); |
| |
| WebURLResponse response(gurl_); |
| response.setHTTPHeaderField( |
| WebString::fromUTF8("Content-Range"), |
| WebString::fromUTF8(base::StringPrintf("bytes " |
| "%d-%d/%d", |
| 1, 10, 1024))); |
| response.setExpectedContentLength(10); |
| response.setHTTPStatusCode(kHttpPartialContent); |
| loader_->didReceiveResponse(response); |
| } |
| |
| TEST_F(ResourceMultiBufferDataProviderTest, TestRedirects) { |
| // Test redirect. |
| Initialize(kHttpUrl, 0); |
| Start(); |
| Redirect(kHttpRedirect); |
| FullResponse(1024); |
| StopWhenLoad(); |
| } |
| |
| } // namespace media |