1 // Copyright (c) 2012 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.
6 #include "base/bind_helpers.h"
7 #include "base/memory/scoped_ptr.h"
8 #include "base/message_loop/message_loop.h"
9 #include "net/url_request/url_request.h"
10 #include "testing/gtest/include/gtest/gtest.h"
11 #include "webkit/browser/appcache/appcache.h"
12 #include "webkit/browser/appcache/appcache_backend_impl.h"
13 #include "webkit/browser/appcache/appcache_group.h"
14 #include "webkit/browser/appcache/appcache_host.h"
15 #include "webkit/browser/appcache/mock_appcache_policy.h"
16 #include "webkit/browser/appcache/mock_appcache_service.h"
17 #include "webkit/browser/quota/quota_manager.h"
21 class AppCacheHostTest
: public testing::Test
{
24 get_status_callback_
=
25 base::Bind(&AppCacheHostTest::GetStatusCallback
,
26 base::Unretained(this));
27 start_update_callback_
=
28 base::Bind(&AppCacheHostTest::StartUpdateCallback
,
29 base::Unretained(this));
30 swap_cache_callback_
=
31 base::Bind(&AppCacheHostTest::SwapCacheCallback
,
32 base::Unretained(this));
35 class MockFrontend
: public AppCacheFrontend
{
38 : last_host_id_(-222), last_cache_id_(-222),
39 last_status_(appcache::OBSOLETE
),
40 last_status_changed_(appcache::OBSOLETE
),
41 last_event_id_(appcache::OBSOLETE_EVENT
),
42 content_blocked_(false) {
45 virtual void OnCacheSelected(
46 int host_id
, const appcache::AppCacheInfo
& info
) OVERRIDE
{
47 last_host_id_
= host_id
;
48 last_cache_id_
= info
.cache_id
;
49 last_status_
= info
.status
;
52 virtual void OnStatusChanged(const std::vector
<int>& host_ids
,
53 appcache::Status status
) OVERRIDE
{
54 last_status_changed_
= status
;
57 virtual void OnEventRaised(const std::vector
<int>& host_ids
,
58 appcache::EventID event_id
) OVERRIDE
{
59 last_event_id_
= event_id
;
62 virtual void OnErrorEventRaised(const std::vector
<int>& host_ids
,
63 const std::string
& message
) OVERRIDE
{
64 last_event_id_
= ERROR_EVENT
;
67 virtual void OnProgressEventRaised(const std::vector
<int>& host_ids
,
70 int num_complete
) OVERRIDE
{
71 last_event_id_
= PROGRESS_EVENT
;
74 virtual void OnLogMessage(int host_id
,
75 appcache::LogLevel log_level
,
76 const std::string
& message
) OVERRIDE
{
79 virtual void OnContentBlocked(int host_id
,
80 const GURL
& manifest_url
) OVERRIDE
{
81 content_blocked_
= true;
86 appcache::Status last_status_
;
87 appcache::Status last_status_changed_
;
88 appcache::EventID last_event_id_
;
89 bool content_blocked_
;
92 class MockQuotaManagerProxy
: public quota::QuotaManagerProxy
{
94 MockQuotaManagerProxy() : QuotaManagerProxy(NULL
, NULL
) {}
96 // Not needed for our tests.
97 virtual void RegisterClient(quota::QuotaClient
* client
) OVERRIDE
{}
98 virtual void NotifyStorageAccessed(quota::QuotaClient::ID client_id
,
100 quota::StorageType type
) OVERRIDE
{}
101 virtual void NotifyStorageModified(quota::QuotaClient::ID client_id
,
103 quota::StorageType type
,
104 int64 delta
) OVERRIDE
{}
106 virtual void NotifyOriginInUse(const GURL
& origin
) OVERRIDE
{
110 virtual void NotifyOriginNoLongerInUse(const GURL
& origin
) OVERRIDE
{
114 int GetInUseCount(const GURL
& origin
) {
115 return inuse_
[origin
];
122 // Map from origin to count of inuse notifications.
123 std::map
<GURL
, int> inuse_
;
126 virtual ~MockQuotaManagerProxy() {}
129 void GetStatusCallback(Status status
, void* param
) {
130 last_status_result_
= status
;
131 last_callback_param_
= param
;
134 void StartUpdateCallback(bool result
, void* param
) {
135 last_start_result_
= result
;
136 last_callback_param_
= param
;
139 void SwapCacheCallback(bool result
, void* param
) {
140 last_swap_result_
= result
;
141 last_callback_param_
= param
;
144 base::MessageLoop message_loop_
;
146 // Mock classes for the 'host' to work with
147 MockAppCacheService service_
;
148 MockFrontend mock_frontend_
;
150 // Mock callbacks we expect to receive from the 'host'
151 appcache::GetStatusCallback get_status_callback_
;
152 appcache::StartUpdateCallback start_update_callback_
;
153 appcache::SwapCacheCallback swap_cache_callback_
;
155 Status last_status_result_
;
156 bool last_swap_result_
;
157 bool last_start_result_
;
158 void* last_callback_param_
;
161 TEST_F(AppCacheHostTest
, Basic
) {
162 // Construct a host and test what state it appears to be in.
163 AppCacheHost
host(1, &mock_frontend_
, &service_
);
164 EXPECT_EQ(1, host
.host_id());
165 EXPECT_EQ(&service_
, host
.service());
166 EXPECT_EQ(&mock_frontend_
, host
.frontend());
167 EXPECT_EQ(NULL
, host
.associated_cache());
168 EXPECT_FALSE(host
.is_selection_pending());
170 // See that the callbacks are delivered immediately
171 // and respond as if there is no cache selected.
172 last_status_result_
= OBSOLETE
;
173 host
.GetStatusWithCallback(get_status_callback_
, reinterpret_cast<void*>(1));
174 EXPECT_EQ(UNCACHED
, last_status_result_
);
175 EXPECT_EQ(reinterpret_cast<void*>(1), last_callback_param_
);
177 last_start_result_
= true;
178 host
.StartUpdateWithCallback(start_update_callback_
,
179 reinterpret_cast<void*>(2));
180 EXPECT_FALSE(last_start_result_
);
181 EXPECT_EQ(reinterpret_cast<void*>(2), last_callback_param_
);
183 last_swap_result_
= true;
184 host
.SwapCacheWithCallback(swap_cache_callback_
, reinterpret_cast<void*>(3));
185 EXPECT_FALSE(last_swap_result_
);
186 EXPECT_EQ(reinterpret_cast<void*>(3), last_callback_param_
);
189 TEST_F(AppCacheHostTest
, SelectNoCache
) {
190 scoped_refptr
<MockQuotaManagerProxy
> mock_quota_proxy(
191 new MockQuotaManagerProxy
);
192 service_
.set_quota_manager_proxy(mock_quota_proxy
.get());
194 // Reset our mock frontend
195 mock_frontend_
.last_cache_id_
= -333;
196 mock_frontend_
.last_host_id_
= -333;
197 mock_frontend_
.last_status_
= OBSOLETE
;
199 const GURL
kDocAndOriginUrl(GURL("http://whatever/").GetOrigin());
201 AppCacheHost
host(1, &mock_frontend_
, &service_
);
202 host
.SelectCache(kDocAndOriginUrl
, kNoCacheId
, GURL());
203 EXPECT_EQ(1, mock_quota_proxy
->GetInUseCount(kDocAndOriginUrl
));
205 // We should have received an OnCacheSelected msg
206 EXPECT_EQ(1, mock_frontend_
.last_host_id_
);
207 EXPECT_EQ(kNoCacheId
, mock_frontend_
.last_cache_id_
);
208 EXPECT_EQ(UNCACHED
, mock_frontend_
.last_status_
);
210 // Otherwise, see that it respond as if there is no cache selected.
211 EXPECT_EQ(1, host
.host_id());
212 EXPECT_EQ(&service_
, host
.service());
213 EXPECT_EQ(&mock_frontend_
, host
.frontend());
214 EXPECT_EQ(NULL
, host
.associated_cache());
215 EXPECT_FALSE(host
.is_selection_pending());
216 EXPECT_TRUE(host
.preferred_manifest_url().is_empty());
218 EXPECT_EQ(0, mock_quota_proxy
->GetInUseCount(kDocAndOriginUrl
));
219 service_
.set_quota_manager_proxy(NULL
);
222 TEST_F(AppCacheHostTest
, ForeignEntry
) {
223 // Reset our mock frontend
224 mock_frontend_
.last_cache_id_
= -333;
225 mock_frontend_
.last_host_id_
= -333;
226 mock_frontend_
.last_status_
= OBSOLETE
;
228 // Precondition, a cache with an entry that is not marked as foreign.
229 const int kCacheId
= 22;
230 const GURL
kDocumentURL("http://origin/document");
231 scoped_refptr
<AppCache
> cache
= new AppCache(service_
.storage(), kCacheId
);
232 cache
->AddEntry(kDocumentURL
, AppCacheEntry(AppCacheEntry::EXPLICIT
));
234 AppCacheHost
host(1, &mock_frontend_
, &service_
);
235 host
.MarkAsForeignEntry(kDocumentURL
, kCacheId
);
237 // We should have received an OnCacheSelected msg for kNoCacheId.
238 EXPECT_EQ(1, mock_frontend_
.last_host_id_
);
239 EXPECT_EQ(kNoCacheId
, mock_frontend_
.last_cache_id_
);
240 EXPECT_EQ(UNCACHED
, mock_frontend_
.last_status_
);
242 // See that it respond as if there is no cache selected.
243 EXPECT_EQ(1, host
.host_id());
244 EXPECT_EQ(&service_
, host
.service());
245 EXPECT_EQ(&mock_frontend_
, host
.frontend());
246 EXPECT_EQ(NULL
, host
.associated_cache());
247 EXPECT_FALSE(host
.is_selection_pending());
249 // See that the entry was marked as foreign.
250 EXPECT_TRUE(cache
->GetEntry(kDocumentURL
)->IsForeign());
253 TEST_F(AppCacheHostTest
, ForeignFallbackEntry
) {
254 // Reset our mock frontend
255 mock_frontend_
.last_cache_id_
= -333;
256 mock_frontend_
.last_host_id_
= -333;
257 mock_frontend_
.last_status_
= OBSOLETE
;
259 // Precondition, a cache with a fallback entry that is not marked as foreign.
260 const int kCacheId
= 22;
261 const GURL
kFallbackURL("http://origin/fallback_resource");
262 scoped_refptr
<AppCache
> cache
= new AppCache(service_
.storage(), kCacheId
);
263 cache
->AddEntry(kFallbackURL
, AppCacheEntry(AppCacheEntry::FALLBACK
));
265 AppCacheHost
host(1, &mock_frontend_
, &service_
);
266 host
.NotifyMainResourceIsNamespaceEntry(kFallbackURL
);
267 host
.MarkAsForeignEntry(GURL("http://origin/missing_document"), kCacheId
);
269 // We should have received an OnCacheSelected msg for kNoCacheId.
270 EXPECT_EQ(1, mock_frontend_
.last_host_id_
);
271 EXPECT_EQ(kNoCacheId
, mock_frontend_
.last_cache_id_
);
272 EXPECT_EQ(UNCACHED
, mock_frontend_
.last_status_
);
274 // See that the fallback entry was marked as foreign.
275 EXPECT_TRUE(cache
->GetEntry(kFallbackURL
)->IsForeign());
278 TEST_F(AppCacheHostTest
, FailedCacheLoad
) {
279 // Reset our mock frontend
280 mock_frontend_
.last_cache_id_
= -333;
281 mock_frontend_
.last_host_id_
= -333;
282 mock_frontend_
.last_status_
= OBSOLETE
;
284 AppCacheHost
host(1, &mock_frontend_
, &service_
);
285 EXPECT_FALSE(host
.is_selection_pending());
287 const int kMockCacheId
= 333;
289 // Put it in a state where we're waiting on a cache
290 // load prior to finishing cache selection.
291 host
.pending_selected_cache_id_
= kMockCacheId
;
292 EXPECT_TRUE(host
.is_selection_pending());
294 // The callback should not occur until we finish cache selection.
295 last_status_result_
= OBSOLETE
;
296 last_callback_param_
= reinterpret_cast<void*>(-1);
297 host
.GetStatusWithCallback(get_status_callback_
, reinterpret_cast<void*>(1));
298 EXPECT_EQ(OBSOLETE
, last_status_result_
);
299 EXPECT_EQ(reinterpret_cast<void*>(-1), last_callback_param_
);
301 // Satisfy the load with NULL, a failure.
302 host
.OnCacheLoaded(NULL
, kMockCacheId
);
304 // Cache selection should have finished
305 EXPECT_FALSE(host
.is_selection_pending());
306 EXPECT_EQ(1, mock_frontend_
.last_host_id_
);
307 EXPECT_EQ(kNoCacheId
, mock_frontend_
.last_cache_id_
);
308 EXPECT_EQ(UNCACHED
, mock_frontend_
.last_status_
);
310 // Callback should have fired upon completing the cache load too.
311 EXPECT_EQ(UNCACHED
, last_status_result_
);
312 EXPECT_EQ(reinterpret_cast<void*>(1), last_callback_param_
);
315 TEST_F(AppCacheHostTest
, FailedGroupLoad
) {
316 AppCacheHost
host(1, &mock_frontend_
, &service_
);
318 const GURL
kMockManifestUrl("http://foo.bar/baz");
320 // Put it in a state where we're waiting on a cache
321 // load prior to finishing cache selection.
322 host
.pending_selected_manifest_url_
= kMockManifestUrl
;
323 EXPECT_TRUE(host
.is_selection_pending());
325 // The callback should not occur until we finish cache selection.
326 last_status_result_
= OBSOLETE
;
327 last_callback_param_
= reinterpret_cast<void*>(-1);
328 host
.GetStatusWithCallback(get_status_callback_
, reinterpret_cast<void*>(1));
329 EXPECT_EQ(OBSOLETE
, last_status_result_
);
330 EXPECT_EQ(reinterpret_cast<void*>(-1), last_callback_param_
);
332 // Satisfy the load will NULL, a failure.
333 host
.OnGroupLoaded(NULL
, kMockManifestUrl
);
335 // Cache selection should have finished
336 EXPECT_FALSE(host
.is_selection_pending());
337 EXPECT_EQ(1, mock_frontend_
.last_host_id_
);
338 EXPECT_EQ(kNoCacheId
, mock_frontend_
.last_cache_id_
);
339 EXPECT_EQ(UNCACHED
, mock_frontend_
.last_status_
);
341 // Callback should have fired upon completing the group load.
342 EXPECT_EQ(UNCACHED
, last_status_result_
);
343 EXPECT_EQ(reinterpret_cast<void*>(1), last_callback_param_
);
346 TEST_F(AppCacheHostTest
, SetSwappableCache
) {
347 AppCacheHost
host(1, &mock_frontend_
, &service_
);
348 host
.SetSwappableCache(NULL
);
349 EXPECT_FALSE(host
.swappable_cache_
.get());
351 scoped_refptr
<AppCacheGroup
> group1(new AppCacheGroup(
352 service_
.storage(), GURL(), service_
.storage()->NewGroupId()));
353 host
.SetSwappableCache(group1
.get());
354 EXPECT_FALSE(host
.swappable_cache_
.get());
356 AppCache
* cache1
= new AppCache(service_
.storage(), 111);
357 cache1
->set_complete(true);
358 group1
->AddCache(cache1
);
359 host
.SetSwappableCache(group1
.get());
360 EXPECT_EQ(cache1
, host
.swappable_cache_
.get());
362 mock_frontend_
.last_host_id_
= -222; // to verify we received OnCacheSelected
364 host
.AssociateCompleteCache(cache1
);
365 EXPECT_FALSE(host
.swappable_cache_
.get()); // was same as associated cache
366 EXPECT_EQ(appcache::IDLE
, host
.GetStatus());
367 // verify OnCacheSelected was called
368 EXPECT_EQ(host
.host_id(), mock_frontend_
.last_host_id_
);
369 EXPECT_EQ(cache1
->cache_id(), mock_frontend_
.last_cache_id_
);
370 EXPECT_EQ(appcache::IDLE
, mock_frontend_
.last_status_
);
372 AppCache
* cache2
= new AppCache(service_
.storage(), 222);
373 cache2
->set_complete(true);
374 group1
->AddCache(cache2
);
375 EXPECT_EQ(cache2
, host
.swappable_cache_
.get()); // updated to newest
377 scoped_refptr
<AppCacheGroup
> group2(
378 new AppCacheGroup(service_
.storage(), GURL("http://foo.com"),
379 service_
.storage()->NewGroupId()));
380 AppCache
* cache3
= new AppCache(service_
.storage(), 333);
381 cache3
->set_complete(true);
382 group2
->AddCache(cache3
);
384 AppCache
* cache4
= new AppCache(service_
.storage(), 444);
385 cache4
->set_complete(true);
386 group2
->AddCache(cache4
);
387 EXPECT_EQ(cache2
, host
.swappable_cache_
.get()); // unchanged
389 host
.AssociateCompleteCache(cache3
);
390 EXPECT_EQ(cache4
, host
.swappable_cache_
.get()); // newest cache in group2
391 EXPECT_FALSE(group1
->HasCache()); // both caches in group1 have refcount 0
393 host
.AssociateNoCache(GURL());
394 EXPECT_FALSE(host
.swappable_cache_
.get());
395 EXPECT_FALSE(group2
->HasCache()); // both caches in group2 have refcount 0
397 // Host adds reference to newest cache when an update is complete.
398 AppCache
* cache5
= new AppCache(service_
.storage(), 555);
399 cache5
->set_complete(true);
400 group2
->AddCache(cache5
);
401 host
.group_being_updated_
= group2
;
402 host
.OnUpdateComplete(group2
.get());
403 EXPECT_FALSE(host
.group_being_updated_
.get());
404 EXPECT_EQ(cache5
, host
.swappable_cache_
.get());
406 group2
->RemoveCache(cache5
);
407 EXPECT_FALSE(group2
->HasCache());
408 host
.group_being_updated_
= group2
;
409 host
.OnUpdateComplete(group2
.get());
410 EXPECT_FALSE(host
.group_being_updated_
.get());
411 EXPECT_FALSE(host
.swappable_cache_
.get()); // group2 had no newest cache
414 TEST_F(AppCacheHostTest
, ForDedicatedWorker
) {
415 const int kMockProcessId
= 1;
416 const int kParentHostId
= 1;
417 const int kWorkerHostId
= 2;
419 AppCacheBackendImpl backend_impl
;
420 backend_impl
.Initialize(&service_
, &mock_frontend_
, kMockProcessId
);
421 backend_impl
.RegisterHost(kParentHostId
);
422 backend_impl
.RegisterHost(kWorkerHostId
);
424 AppCacheHost
* parent_host
= backend_impl
.GetHost(kParentHostId
);
425 EXPECT_FALSE(parent_host
->is_for_dedicated_worker());
427 AppCacheHost
* worker_host
= backend_impl
.GetHost(kWorkerHostId
);
428 worker_host
->SelectCacheForWorker(kParentHostId
, kMockProcessId
);
429 EXPECT_TRUE(worker_host
->is_for_dedicated_worker());
430 EXPECT_EQ(parent_host
, worker_host
->GetParentAppCacheHost());
432 // We should have received an OnCacheSelected msg for the worker_host.
433 // The host for workers always indicates 'no cache selected' regardless
434 // of its parent's state. This is OK because the worker cannot access
435 // the scriptable interface, the only function available is resource
436 // loading (see appcache_request_handler_unittests those tests).
437 EXPECT_EQ(kWorkerHostId
, mock_frontend_
.last_host_id_
);
438 EXPECT_EQ(kNoCacheId
, mock_frontend_
.last_cache_id_
);
439 EXPECT_EQ(UNCACHED
, mock_frontend_
.last_status_
);
441 // Simulate the parent being torn down.
442 backend_impl
.UnregisterHost(kParentHostId
);
444 EXPECT_EQ(NULL
, backend_impl
.GetHost(kParentHostId
));
445 EXPECT_EQ(NULL
, worker_host
->GetParentAppCacheHost());
448 TEST_F(AppCacheHostTest
, SelectCacheAllowed
) {
449 scoped_refptr
<MockQuotaManagerProxy
> mock_quota_proxy(
450 new MockQuotaManagerProxy
);
451 MockAppCachePolicy mock_appcache_policy
;
452 mock_appcache_policy
.can_create_return_value_
= true;
453 service_
.set_quota_manager_proxy(mock_quota_proxy
.get());
454 service_
.set_appcache_policy(&mock_appcache_policy
);
456 // Reset our mock frontend
457 mock_frontend_
.last_cache_id_
= -333;
458 mock_frontend_
.last_host_id_
= -333;
459 mock_frontend_
.last_status_
= OBSOLETE
;
460 mock_frontend_
.last_event_id_
= OBSOLETE_EVENT
;
461 mock_frontend_
.content_blocked_
= false;
463 const GURL
kDocAndOriginUrl(GURL("http://whatever/").GetOrigin());
464 const GURL
kManifestUrl(GURL("http://whatever/cache.manifest"));
466 AppCacheHost
host(1, &mock_frontend_
, &service_
);
467 host
.first_party_url_
= kDocAndOriginUrl
;
468 host
.SelectCache(kDocAndOriginUrl
, kNoCacheId
, kManifestUrl
);
469 EXPECT_EQ(1, mock_quota_proxy
->GetInUseCount(kDocAndOriginUrl
));
471 // MockAppCacheService::LoadOrCreateGroup is asynchronous, so we shouldn't
472 // have received an OnCacheSelected msg yet.
473 EXPECT_EQ(-333, mock_frontend_
.last_host_id_
);
474 EXPECT_EQ(-333, mock_frontend_
.last_cache_id_
);
475 EXPECT_EQ(OBSOLETE
, mock_frontend_
.last_status_
);
476 // No error events either
477 EXPECT_EQ(OBSOLETE_EVENT
, mock_frontend_
.last_event_id_
);
478 EXPECT_FALSE(mock_frontend_
.content_blocked_
);
480 EXPECT_TRUE(host
.is_selection_pending());
482 EXPECT_EQ(0, mock_quota_proxy
->GetInUseCount(kDocAndOriginUrl
));
483 service_
.set_quota_manager_proxy(NULL
);
486 TEST_F(AppCacheHostTest
, SelectCacheBlocked
) {
487 scoped_refptr
<MockQuotaManagerProxy
> mock_quota_proxy(
488 new MockQuotaManagerProxy
);
489 MockAppCachePolicy mock_appcache_policy
;
490 mock_appcache_policy
.can_create_return_value_
= false;
491 service_
.set_quota_manager_proxy(mock_quota_proxy
.get());
492 service_
.set_appcache_policy(&mock_appcache_policy
);
494 // Reset our mock frontend
495 mock_frontend_
.last_cache_id_
= -333;
496 mock_frontend_
.last_host_id_
= -333;
497 mock_frontend_
.last_status_
= OBSOLETE
;
498 mock_frontend_
.last_event_id_
= OBSOLETE_EVENT
;
499 mock_frontend_
.content_blocked_
= false;
501 const GURL
kDocAndOriginUrl(GURL("http://whatever/").GetOrigin());
502 const GURL
kManifestUrl(GURL("http://whatever/cache.manifest"));
504 AppCacheHost
host(1, &mock_frontend_
, &service_
);
505 host
.first_party_url_
= kDocAndOriginUrl
;
506 host
.SelectCache(kDocAndOriginUrl
, kNoCacheId
, kManifestUrl
);
507 EXPECT_EQ(1, mock_quota_proxy
->GetInUseCount(kDocAndOriginUrl
));
509 // We should have received an OnCacheSelected msg
510 EXPECT_EQ(1, mock_frontend_
.last_host_id_
);
511 EXPECT_EQ(kNoCacheId
, mock_frontend_
.last_cache_id_
);
512 EXPECT_EQ(UNCACHED
, mock_frontend_
.last_status_
);
514 // Also, an error event was raised
515 EXPECT_EQ(ERROR_EVENT
, mock_frontend_
.last_event_id_
);
516 EXPECT_TRUE(mock_frontend_
.content_blocked_
);
518 // Otherwise, see that it respond as if there is no cache selected.
519 EXPECT_EQ(1, host
.host_id());
520 EXPECT_EQ(&service_
, host
.service());
521 EXPECT_EQ(&mock_frontend_
, host
.frontend());
522 EXPECT_EQ(NULL
, host
.associated_cache());
523 EXPECT_FALSE(host
.is_selection_pending());
524 EXPECT_TRUE(host
.preferred_manifest_url().is_empty());
526 EXPECT_EQ(0, mock_quota_proxy
->GetInUseCount(kDocAndOriginUrl
));
527 service_
.set_quota_manager_proxy(NULL
);
530 } // namespace appcache