1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <algorithm>
6
7 #include "base/format_macros.h"
8 #include "base/stringprintf.h"
9 #include "net/base/net_errors.h"
10 #include "net/http/http_util.h"
11 #include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h"
12 #include "third_party/WebKit/Source/WebKit/chromium/public/WebFrameClient.h"
13 #include "third_party/WebKit/Source/WebKit/chromium/public/WebURLError.h"
14 #include "third_party/WebKit/Source/WebKit/chromium/public/WebURLResponse.h"
15 #include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h"
16 #include "webkit/glue/media/buffered_resource_loader.h"
17 #include "webkit/mocks/mock_webframe.h"
18 #include "webkit/mocks/mock_weburlloader.h"
19
20 using ::testing::_;
21 using ::testing::Assign;
22 using ::testing::AtLeast;
23 using ::testing::DeleteArg;
24 using ::testing::DoAll;
25 using ::testing::InSequence;
26 using ::testing::Invoke;
27 using ::testing::InvokeWithoutArgs;
28 using ::testing::NotNull;
29 using ::testing::Return;
30 using ::testing::ReturnRef;
31 using ::testing::SetArgumentPointee;
32 using ::testing::StrictMock;
33 using ::testing::NiceMock;
34 using ::testing::WithArgs;
35
36 using WebKit::WebURLError;
37 using WebKit::WebFrameClient;
38 using WebKit::WebURLResponse;
39 using WebKit::WebView;
40
41 namespace webkit_glue {
42
43 static const char* kHttpUrl = "http://test";
44 static const char kHttpRedirectToSameDomainUrl1[] = "http://test/ing";
45 static const char kHttpRedirectToSameDomainUrl2[] = "http://test/ing2";
46 static const char kHttpRedirectToDifferentDomainUrl1[] = "http://test2";
47 static const char kHttpRedirectToDifferentDomainUrl2[] = "http://test2/ing";
48
49 static const int kDataSize = 1024;
50 static const int kHttpOK = 200;
51 static const int kHttpPartialContent = 206;
52
53 enum NetworkState {
54 NONE,
55 LOADED,
56 LOADING
57 };
58
59 // Submit a request completed event to the resource loader due to request
60 // being canceled. Pretending the event is from external.
ACTION_P(RequestCanceled,loader)61 ACTION_P(RequestCanceled, loader) {
62 WebURLError error;
63 error.reason = net::ERR_ABORTED;
64 error.domain = WebString::fromUTF8(net::kErrorDomain);
65 loader->didFail(NULL, error);
66 }
67
68 class BufferedResourceLoaderTest : public testing::Test {
69 public:
BufferedResourceLoaderTest()70 BufferedResourceLoaderTest() {
71 for (int i = 0; i < kDataSize; ++i)
72 data_[i] = i;
73 }
74
~BufferedResourceLoaderTest()75 virtual ~BufferedResourceLoaderTest() {
76 }
77
Initialize(const char * url,int first_position,int last_position)78 void Initialize(const char* url, int first_position, int last_position) {
79 gurl_ = GURL(url);
80 first_position_ = first_position;
81 last_position_ = last_position;
82
83 frame_.reset(new NiceMock<MockWebFrame>());
84
85 url_loader_ = new NiceMock<MockWebURLLoader>();
86 loader_ = new BufferedResourceLoader(gurl_,
87 first_position_, last_position_);
88 loader_->SetURLLoaderForTest(url_loader_);
89 }
90
SetLoaderBuffer(size_t forward_capacity,size_t backward_capacity)91 void SetLoaderBuffer(size_t forward_capacity, size_t backward_capacity) {
92 loader_->buffer_.reset(
93 new media::SeekableBuffer(backward_capacity, forward_capacity));
94 }
95
Start()96 void Start() {
97 InSequence s;
98 EXPECT_CALL(*url_loader_, loadAsynchronously(_, loader_.get()));
99 loader_->Start(
100 NewCallback(this, &BufferedResourceLoaderTest::StartCallback),
101 NewCallback(this, &BufferedResourceLoaderTest::NetworkCallback),
102 frame_.get());
103 }
104
FullResponse(int64 instance_size)105 void FullResponse(int64 instance_size) {
106 FullResponse(instance_size, net::OK);
107 }
108
FullResponse(int64 instance_size,int status)109 void FullResponse(int64 instance_size, int status) {
110 EXPECT_CALL(*this, StartCallback(status));
111 if (status != net::OK) {
112 EXPECT_CALL(*url_loader_, cancel())
113 .WillOnce(RequestCanceled(loader_));
114 }
115
116 WebURLResponse response(gurl_);
117 response.setHTTPHeaderField(WebString::fromUTF8("Content-Length"),
118 WebString::fromUTF8(base::StringPrintf("%"
119 PRId64, instance_size)));
120 response.setExpectedContentLength(instance_size);
121 response.setHTTPStatusCode(kHttpOK);
122 loader_->didReceiveResponse(url_loader_, response);
123
124 if (status == net::OK) {
125 EXPECT_EQ(instance_size, loader_->content_length());
126 EXPECT_EQ(instance_size, loader_->instance_size());
127 }
128
129 EXPECT_FALSE(loader_->range_supported());
130 }
131
PartialResponse(int64 first_position,int64 last_position,int64 instance_size)132 void PartialResponse(int64 first_position, int64 last_position,
133 int64 instance_size) {
134 PartialResponse(first_position, last_position, instance_size, false, true);
135 }
136
PartialResponse(int64 first_position,int64 last_position,int64 instance_size,bool chunked,bool accept_ranges)137 void PartialResponse(int64 first_position, int64 last_position,
138 int64 instance_size, bool chunked, bool accept_ranges) {
139 EXPECT_CALL(*this, StartCallback(net::OK));
140
141 WebURLResponse response(gurl_);
142 response.setHTTPHeaderField(WebString::fromUTF8("Content-Range"),
143 WebString::fromUTF8(base::StringPrintf("bytes "
144 "%" PRId64 "-%" PRId64 "/%" PRId64,
145 first_position,
146 last_position,
147 instance_size)));
148
149 // HTTP 1.1 doesn't permit Content-Length with Transfer-Encoding: chunked.
150 int64 content_length = -1;
151 if (chunked) {
152 response.setHTTPHeaderField(WebString::fromUTF8("Transfer-Encoding"),
153 WebString::fromUTF8("chunked"));
154 } else {
155 content_length = last_position - first_position + 1;
156 }
157 response.setExpectedContentLength(content_length);
158
159 // A server isn't required to return Accept-Ranges even though it might.
160 if (accept_ranges) {
161 response.setHTTPHeaderField(WebString::fromUTF8("Accept-Ranges"),
162 WebString::fromUTF8("bytes"));
163 }
164
165 response.setHTTPStatusCode(kHttpPartialContent);
166 loader_->didReceiveResponse(url_loader_, response);
167
168 // XXX: what's the difference between these two? For example in the chunked
169 // range request case, Content-Length is unspecified (because it's chunked)
170 // but Content-Range: a-b/c can be returned, where c == Content-Length
171 //
172 // Can we eliminate one?
173 EXPECT_EQ(content_length, loader_->content_length());
174 EXPECT_EQ(instance_size, loader_->instance_size());
175
176 // A valid partial response should always result in this being true.
177 EXPECT_TRUE(loader_->range_supported());
178 }
179
Redirect(const char * url)180 void Redirect(const char* url) {
181 GURL redirectUrl(url);
182 WebKit::WebURLRequest newRequest(redirectUrl);
183 WebKit::WebURLResponse redirectResponse(gurl_);
184
185 loader_->willSendRequest(url_loader_, newRequest, redirectResponse);
186
187 MessageLoop::current()->RunAllPending();
188 }
189
StopWhenLoad()190 void StopWhenLoad() {
191 InSequence s;
192 EXPECT_CALL(*url_loader_, cancel())
193 .WillOnce(RequestCanceled(loader_));
194 loader_->Stop();
195 loader_ = NULL;
196 }
197
198 // Helper method to write to |loader_| from |data_|.
WriteLoader(int position,int size)199 void WriteLoader(int position, int size) {
200 EXPECT_CALL(*this, NetworkCallback())
201 .RetiresOnSaturation();
202 loader_->didReceiveData(url_loader_,
203 reinterpret_cast<char*>(data_ + position),
204 size,
205 size);
206 }
207
208 // Helper method to read from |loader_|.
ReadLoader(int64 position,int size,uint8 * buffer)209 void ReadLoader(int64 position, int size, uint8* buffer) {
210 loader_->Read(position, size, buffer,
211 NewCallback(this, &BufferedResourceLoaderTest::ReadCallback));
212 }
213
214 // Verifis that data in buffer[0...size] is equal to data_[pos...pos+size].
VerifyBuffer(uint8 * buffer,int pos,int size)215 void VerifyBuffer(uint8* buffer, int pos, int size) {
216 EXPECT_EQ(0, memcmp(buffer, data_ + pos, size));
217 }
218
ConfirmLoaderDeferredState(bool expectedVal)219 void ConfirmLoaderDeferredState(bool expectedVal) {
220 EXPECT_EQ(loader_->deferred_, expectedVal);
221 }
222
223 MOCK_METHOD1(StartCallback, void(int error));
224 MOCK_METHOD1(ReadCallback, void(int error));
225 MOCK_METHOD0(NetworkCallback, void());
226
227 protected:
228 GURL gurl_;
229 int64 first_position_;
230 int64 last_position_;
231
232 scoped_refptr<BufferedResourceLoader> loader_;
233 NiceMock<MockWebURLLoader>* url_loader_;
234 scoped_ptr<NiceMock<MockWebFrame> > frame_;
235
236 uint8 data_[kDataSize];
237
238 private:
239 DISALLOW_COPY_AND_ASSIGN(BufferedResourceLoaderTest);
240 };
241
TEST_F(BufferedResourceLoaderTest,StartStop)242 TEST_F(BufferedResourceLoaderTest, StartStop) {
243 Initialize(kHttpUrl, -1, -1);
244 Start();
245 StopWhenLoad();
246 }
247
248 // Tests that a bad HTTP response is recived, e.g. file not found.
TEST_F(BufferedResourceLoaderTest,BadHttpResponse)249 TEST_F(BufferedResourceLoaderTest, BadHttpResponse) {
250 Initialize(kHttpUrl, -1, -1);
251 Start();
252
253 EXPECT_CALL(*this, StartCallback(net::ERR_FAILED));
254 EXPECT_CALL(*url_loader_, cancel())
255 .WillOnce(RequestCanceled(loader_));
256
257 WebURLResponse response(gurl_);
258 response.setHTTPStatusCode(404);
259 response.setHTTPStatusText("Not Found\n");
260 loader_->didReceiveResponse(url_loader_, response);
261 }
262
263 // Tests that partial content is requested but not fulfilled.
TEST_F(BufferedResourceLoaderTest,NotPartialResponse)264 TEST_F(BufferedResourceLoaderTest, NotPartialResponse) {
265 Initialize(kHttpUrl, 100, -1);
266 Start();
267 FullResponse(1024, net::ERR_INVALID_RESPONSE);
268 }
269
270 // Tests that a 200 response is received.
TEST_F(BufferedResourceLoaderTest,FullResponse)271 TEST_F(BufferedResourceLoaderTest, FullResponse) {
272 Initialize(kHttpUrl, -1, -1);
273 Start();
274 FullResponse(1024);
275 StopWhenLoad();
276 }
277
278 // Tests that a partial content response is received.
TEST_F(BufferedResourceLoaderTest,PartialResponse)279 TEST_F(BufferedResourceLoaderTest, PartialResponse) {
280 Initialize(kHttpUrl, 100, 200);
281 Start();
282 PartialResponse(100, 200, 1024);
283 StopWhenLoad();
284 }
285
TEST_F(BufferedResourceLoaderTest,PartialResponse_Chunked)286 TEST_F(BufferedResourceLoaderTest, PartialResponse_Chunked) {
287 Initialize(kHttpUrl, 100, 200);
288 Start();
289 PartialResponse(100, 200, 1024, true, true);
290 StopWhenLoad();
291 }
292
TEST_F(BufferedResourceLoaderTest,PartialResponse_NoAcceptRanges)293 TEST_F(BufferedResourceLoaderTest, PartialResponse_NoAcceptRanges) {
294 Initialize(kHttpUrl, 100, 200);
295 Start();
296 PartialResponse(100, 200, 1024, false, false);
297 StopWhenLoad();
298 }
299
TEST_F(BufferedResourceLoaderTest,PartialResponse_ChunkedNoAcceptRanges)300 TEST_F(BufferedResourceLoaderTest, PartialResponse_ChunkedNoAcceptRanges) {
301 Initialize(kHttpUrl, 100, 200);
302 Start();
303 PartialResponse(100, 200, 1024, true, false);
304 StopWhenLoad();
305 }
306
307 // Tests that an invalid partial response is received.
TEST_F(BufferedResourceLoaderTest,InvalidPartialResponse)308 TEST_F(BufferedResourceLoaderTest, InvalidPartialResponse) {
309 Initialize(kHttpUrl, 0, 10);
310 Start();
311
312 EXPECT_CALL(*this, StartCallback(net::ERR_INVALID_RESPONSE));
313 EXPECT_CALL(*url_loader_, cancel())
314 .WillOnce(RequestCanceled(loader_));
315
316 WebURLResponse response(gurl_);
317 response.setHTTPHeaderField(WebString::fromUTF8("Content-Range"),
318 WebString::fromUTF8(base::StringPrintf("bytes "
319 "%d-%d/%d", 1, 10, 1024)));
320 response.setExpectedContentLength(10);
321 response.setHTTPStatusCode(kHttpPartialContent);
322 loader_->didReceiveResponse(url_loader_, response);
323 }
324
325 // Tests the logic of sliding window for data buffering and reading.
TEST_F(BufferedResourceLoaderTest,BufferAndRead)326 TEST_F(BufferedResourceLoaderTest, BufferAndRead) {
327 Initialize(kHttpUrl, 10, 29);
328 loader_->UpdateDeferStrategy(BufferedResourceLoader::kThresholdDefer);
329 Start();
330 PartialResponse(10, 29, 30);
331
332 uint8 buffer[10];
333 InSequence s;
334
335 // Writes 10 bytes and read them back.
336 WriteLoader(10, 10);
337 EXPECT_CALL(*this, ReadCallback(10));
338 ReadLoader(10, 10, buffer);
339 VerifyBuffer(buffer, 10, 10);
340
341 // Writes 10 bytes and read 2 times.
342 WriteLoader(20, 10);
343 EXPECT_CALL(*this, ReadCallback(5));
344 ReadLoader(20, 5, buffer);
345 VerifyBuffer(buffer, 20, 5);
346 EXPECT_CALL(*this, ReadCallback(5));
347 ReadLoader(25, 5, buffer);
348 VerifyBuffer(buffer, 25, 5);
349
350 // Read backward within buffer.
351 EXPECT_CALL(*this, ReadCallback(10));
352 ReadLoader(10, 10, buffer);
353 VerifyBuffer(buffer, 10, 10);
354
355 // Read backward outside buffer.
356 EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS));
357 ReadLoader(9, 10, buffer);
358
359 // Response has completed.
360 EXPECT_CALL(*this, NetworkCallback());
361 loader_->didFinishLoading(url_loader_, 0);
362
363 // Try to read 10 from position 25 will just return with 5 bytes.
364 EXPECT_CALL(*this, ReadCallback(5));
365 ReadLoader(25, 10, buffer);
366 VerifyBuffer(buffer, 25, 5);
367
368 // Try to read outside buffered range after request has completed.
369 EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS));
370 ReadLoader(5, 10, buffer);
371
372 // Try to read beyond the instance size.
373 EXPECT_CALL(*this, ReadCallback(0));
374 ReadLoader(30, 10, buffer);
375 }
376
TEST_F(BufferedResourceLoaderTest,ReadOutsideBuffer)377 TEST_F(BufferedResourceLoaderTest, ReadOutsideBuffer) {
378 Initialize(kHttpUrl, 10, 0x00FFFFFF);
379 loader_->UpdateDeferStrategy(BufferedResourceLoader::kThresholdDefer);
380 Start();
381 PartialResponse(10, 0x00FFFFFF, 0x01000000);
382
383 uint8 buffer[10];
384 InSequence s;
385
386 // Read very far aheard will get a cache miss.
387 EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS));
388 ReadLoader(0x00FFFFFF, 1, buffer);
389
390 // The following call will not call ReadCallback() because it is waiting for
391 // data to arrive.
392 ReadLoader(10, 10, buffer);
393
394 // Writing to loader will fulfill the read request.
395 EXPECT_CALL(*this, ReadCallback(10));
396 WriteLoader(10, 20);
397 VerifyBuffer(buffer, 10, 10);
398
399 // The following call cannot be fulfilled now.
400 ReadLoader(25, 10, buffer);
401
402 EXPECT_CALL(*this, ReadCallback(5));
403 EXPECT_CALL(*this, NetworkCallback());
404 loader_->didFinishLoading(url_loader_, 0);
405 }
406
TEST_F(BufferedResourceLoaderTest,RequestFailedWhenRead)407 TEST_F(BufferedResourceLoaderTest, RequestFailedWhenRead) {
408 Initialize(kHttpUrl, 10, 29);
409 Start();
410 PartialResponse(10, 29, 30);
411
412 uint8 buffer[10];
413 InSequence s;
414
415 ReadLoader(10, 10, buffer);
416 EXPECT_CALL(*this, ReadCallback(net::ERR_FAILED));
417 EXPECT_CALL(*this, NetworkCallback());
418 WebURLError error;
419 error.reason = net::ERR_FAILED;
420 loader_->didFail(url_loader_, error);
421 }
422
423 // Tests the data buffering logic of NeverDefer strategy.
TEST_F(BufferedResourceLoaderTest,NeverDeferStrategy)424 TEST_F(BufferedResourceLoaderTest, NeverDeferStrategy) {
425 Initialize(kHttpUrl, 10, 99);
426 SetLoaderBuffer(10, 20);
427 loader_->UpdateDeferStrategy(BufferedResourceLoader::kNeverDefer);
428 Start();
429 PartialResponse(10, 99, 100);
430
431 uint8 buffer[10];
432
433 // Read past the buffer size; should not defer regardless.
434 WriteLoader(10, 10);
435 WriteLoader(20, 50);
436 ConfirmLoaderDeferredState(false);
437
438 // Should move past window.
439 EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS));
440 ReadLoader(10, 10, buffer);
441
442 StopWhenLoad();
443 }
444
445 // Tests the data buffering logic of ReadThenDefer strategy.
TEST_F(BufferedResourceLoaderTest,ReadThenDeferStrategy)446 TEST_F(BufferedResourceLoaderTest, ReadThenDeferStrategy) {
447 Initialize(kHttpUrl, 10, 99);
448 SetLoaderBuffer(10, 20);
449 loader_->UpdateDeferStrategy(BufferedResourceLoader::kReadThenDefer);
450 Start();
451 PartialResponse(10, 99, 100);
452
453 uint8 buffer[10];
454
455 // Make an outstanding read request.
456 // We should disable deferring after the read request, so expect
457 // a network event.
458 EXPECT_CALL(*this, NetworkCallback());
459 ReadLoader(10, 10, buffer);
460
461 // Receive almost enough data to cover, shouldn't defer.
462 WriteLoader(10, 9);
463 ConfirmLoaderDeferredState(false);
464
465 // As soon as we have received enough data to fulfill the read, defer.
466 EXPECT_CALL(*this, NetworkCallback());
467 EXPECT_CALL(*this, ReadCallback(10));
468 WriteLoader(19, 1);
469
470 ConfirmLoaderDeferredState(true);
471 VerifyBuffer(buffer, 10, 10);
472
473 StopWhenLoad();
474 }
475
476 // Tests the data buffering logic of ThresholdDefer strategy.
TEST_F(BufferedResourceLoaderTest,ThresholdDeferStrategy)477 TEST_F(BufferedResourceLoaderTest, ThresholdDeferStrategy) {
478 Initialize(kHttpUrl, 10, 99);
479 SetLoaderBuffer(10, 20);
480 loader_->UpdateDeferStrategy(BufferedResourceLoader::kThresholdDefer);
481 Start();
482 PartialResponse(10, 99, 100);
483
484 uint8 buffer[10];
485
486 WriteLoader(10, 5);
487 // Haven't reached threshold, don't defer.
488 ConfirmLoaderDeferredState(false);
489
490 // We're at the threshold now, let's defer.
491 EXPECT_CALL(*this, NetworkCallback());
492 WriteLoader(15, 5);
493 ConfirmLoaderDeferredState(true);
494
495 // Now we've read over half of the buffer, disable deferring.
496 EXPECT_CALL(*this, ReadCallback(6));
497 EXPECT_CALL(*this, NetworkCallback());
498 ReadLoader(10, 6, buffer);
499
500 ConfirmLoaderDeferredState(false);
501 VerifyBuffer(buffer, 10, 6);
502
503 StopWhenLoad();
504 }
505
506 // NOTE: This test will need to be reworked a little once
507 // http://code.google.com/p/chromium/issues/detail?id=72578
508 // is fixed.
TEST_F(BufferedResourceLoaderTest,HasSingleOrigin)509 TEST_F(BufferedResourceLoaderTest, HasSingleOrigin) {
510 // Make sure no redirect case works as expected.
511 Initialize(kHttpUrl, -1, -1);
512 Start();
513 FullResponse(1024);
514 EXPECT_TRUE(loader_->HasSingleOrigin());
515 StopWhenLoad();
516
517 // Test redirect to the same domain.
518 Initialize(kHttpUrl, -1, -1);
519 Start();
520 Redirect(kHttpRedirectToSameDomainUrl1);
521 FullResponse(1024);
522 EXPECT_TRUE(loader_->HasSingleOrigin());
523 StopWhenLoad();
524
525 // Test redirect twice to the same domain.
526 Initialize(kHttpUrl, -1, -1);
527 Start();
528 Redirect(kHttpRedirectToSameDomainUrl1);
529 Redirect(kHttpRedirectToSameDomainUrl2);
530 FullResponse(1024);
531 EXPECT_TRUE(loader_->HasSingleOrigin());
532 StopWhenLoad();
533
534 // Test redirect to a different domain.
535 Initialize(kHttpUrl, -1, -1);
536 Start();
537 Redirect(kHttpRedirectToDifferentDomainUrl1);
538 EXPECT_FALSE(loader_->HasSingleOrigin());
539 StopWhenLoad();
540
541 // Test redirect to the same domain and then to a different domain.
542 Initialize(kHttpUrl, -1, -1);
543 Start();
544 Redirect(kHttpRedirectToSameDomainUrl1);
545 Redirect(kHttpRedirectToDifferentDomainUrl1);
546 EXPECT_FALSE(loader_->HasSingleOrigin());
547 StopWhenLoad();
548 }
549
550 // TODO(hclam): add unit test for defer loading.
551
552 } // namespace webkit_glue
553