1 // Copyright 2014 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 "content/browser/appcache/appcache_host.h"
6
7 #include <stdint.h>
8
9 #include <map>
10 #include <memory>
11 #include <string>
12 #include <utility>
13
14 #include "base/bind.h"
15 #include "base/callback_helpers.h"
16 #include "base/test/task_environment.h"
17 #include "content/browser/appcache/appcache.h"
18 #include "content/browser/appcache/appcache_backend_impl.h"
19 #include "content/browser/appcache/appcache_group.h"
20 #include "content/browser/appcache/appcache_request_handler.h"
21 #include "content/browser/appcache/mock_appcache_policy.h"
22 #include "content/browser/appcache/mock_appcache_service.h"
23 #include "content/browser/child_process_security_policy_impl.h"
24 #include "content/browser/isolation_context.h"
25 #include "content/public/test/test_browser_context.h"
26 #include "content/public/test/test_renderer_host.h"
27 #include "content/test/test_web_contents.h"
28 #include "mojo/public/cpp/bindings/remote.h"
29 #include "mojo/public/cpp/test_support/test_utils.h"
30 #include "storage/browser/quota/quota_client_type.h"
31 #include "storage/browser/quota/quota_manager.h"
32 #include "testing/gtest/include/gtest/gtest.h"
33 #include "third_party/blink/public/mojom/appcache/appcache.mojom.h"
34 #include "third_party/blink/public/mojom/appcache/appcache_info.mojom.h"
35 #include "third_party/blink/public/mojom/devtools/console_message.mojom.h"
36 #include "url/origin.h"
37
38 namespace content {
39
40 namespace {
41 const int64_t kUnsetCacheId = -222;
42 }
43
44 class AppCacheHostTest : public testing::Test {
45 public:
AppCacheHostTest()46 AppCacheHostTest()
47 : web_contents_(TestWebContents::Create(&browser_context_, nullptr)),
48 kProcessIdForTest(web_contents_->GetMainFrame()->GetProcess()->GetID()),
49 kRenderFrameIdForTest(web_contents_->GetMainFrame()->GetRoutingID()),
50 mock_frontend_(web_contents_.get()) {
51 get_status_callback_ = base::BindRepeating(
52 &AppCacheHostTest::GetStatusCallback, base::Unretained(this));
53 AppCacheRequestHandler::SetRunningInTests(true);
54 }
55
~AppCacheHostTest()56 ~AppCacheHostTest() override {
57 AppCacheRequestHandler::SetRunningInTests(false);
58 }
59
60 class MockFrontend : public blink::mojom::AppCacheFrontend,
61 public WebContentsObserver {
62 public:
MockFrontend(WebContents * web_contents)63 explicit MockFrontend(WebContents* web_contents)
64 : WebContentsObserver(web_contents),
65 last_cache_id_(kUnsetCacheId),
66 last_status_(blink::mojom::AppCacheStatus::APPCACHE_STATUS_OBSOLETE),
67 last_event_id_(
68 blink::mojom::AppCacheEventID::APPCACHE_OBSOLETE_EVENT),
69 content_blocked_(false) {}
70
CacheSelected(blink::mojom::AppCacheInfoPtr info)71 void CacheSelected(blink::mojom::AppCacheInfoPtr info) override {
72 last_cache_id_ = info->cache_id;
73 last_status_ = info->status;
74 }
75
EventRaised(blink::mojom::AppCacheEventID event_id)76 void EventRaised(blink::mojom::AppCacheEventID event_id) override {
77 last_event_id_ = event_id;
78 }
79
ErrorEventRaised(blink::mojom::AppCacheErrorDetailsPtr details)80 void ErrorEventRaised(
81 blink::mojom::AppCacheErrorDetailsPtr details) override {
82 last_event_id_ = blink::mojom::AppCacheEventID::APPCACHE_ERROR_EVENT;
83 }
84
ProgressEventRaised(const GURL & url,int32_t num_total,int32_t num_complete)85 void ProgressEventRaised(const GURL& url,
86 int32_t num_total,
87 int32_t num_complete) override {
88 last_event_id_ = blink::mojom::AppCacheEventID::APPCACHE_PROGRESS_EVENT;
89 }
90
LogMessage(blink::mojom::ConsoleMessageLevel log_level,const std::string & message)91 void LogMessage(blink::mojom::ConsoleMessageLevel log_level,
92 const std::string& message) override {}
93
SetSubresourceFactory(mojo::PendingRemote<network::mojom::URLLoaderFactory> url_loader_factory)94 void SetSubresourceFactory(
95 mojo::PendingRemote<network::mojom::URLLoaderFactory>
96 url_loader_factory) override {}
97
98 // WebContentsObserver:
AppCacheAccessed(const GURL & manifest_url,bool blocked_by_policy)99 void AppCacheAccessed(const GURL& manifest_url,
100 bool blocked_by_policy) override {
101 appcache_accessed_ = true;
102 if (blocked_by_policy)
103 content_blocked_ = true;
104 }
105
106 int64_t last_cache_id_;
107 blink::mojom::AppCacheStatus last_status_;
108 blink::mojom::AppCacheEventID last_event_id_;
109 bool content_blocked_;
110 bool appcache_accessed_ = false;
111 };
112
113 class MockQuotaManagerProxy : public storage::QuotaManagerProxy {
114 public:
MockQuotaManagerProxy()115 MockQuotaManagerProxy() : QuotaManagerProxy(nullptr, nullptr) {}
116
117 // Not needed for our tests.
RegisterClient(scoped_refptr<storage::QuotaClient> client,storage::QuotaClientType client_type,const std::vector<blink::mojom::StorageType> & storage_types)118 void RegisterClient(
119 scoped_refptr<storage::QuotaClient> client,
120 storage::QuotaClientType client_type,
121 const std::vector<blink::mojom::StorageType>& storage_types) override {}
NotifyStorageAccessed(const url::Origin & origin,blink::mojom::StorageType type)122 void NotifyStorageAccessed(const url::Origin& origin,
123 blink::mojom::StorageType type) override {}
NotifyStorageModified(storage::QuotaClientType client_id,const url::Origin & origin,blink::mojom::StorageType type,int64_t delta)124 void NotifyStorageModified(storage::QuotaClientType client_id,
125 const url::Origin& origin,
126 blink::mojom::StorageType type,
127 int64_t delta) override {}
SetUsageCacheEnabled(storage::QuotaClientType client_id,const url::Origin & origin,blink::mojom::StorageType type,bool enabled)128 void SetUsageCacheEnabled(storage::QuotaClientType client_id,
129 const url::Origin& origin,
130 blink::mojom::StorageType type,
131 bool enabled) override {}
GetUsageAndQuota(base::SequencedTaskRunner * original_task_runner,const url::Origin & origin,blink::mojom::StorageType type,UsageAndQuotaCallback callback)132 void GetUsageAndQuota(base::SequencedTaskRunner* original_task_runner,
133 const url::Origin& origin,
134 blink::mojom::StorageType type,
135 UsageAndQuotaCallback callback) override {}
136
NotifyOriginInUse(const url::Origin & origin)137 void NotifyOriginInUse(const url::Origin& origin) override {
138 inuse_[origin] += 1;
139 }
140
NotifyOriginNoLongerInUse(const url::Origin & origin)141 void NotifyOriginNoLongerInUse(const url::Origin& origin) override {
142 inuse_[origin] -= 1;
143 }
144
GetInUseCount(const url::Origin & origin)145 int GetInUseCount(const url::Origin& origin) { return inuse_[origin]; }
146
is_empty() const147 bool is_empty() const { return inuse_.empty(); }
reset()148 void reset() { inuse_.clear(); }
149
150 // Map from origin to count of inuse notifications.
151 std::map<url::Origin, int> inuse_;
152
153 protected:
154 ~MockQuotaManagerProxy() override = default;
155 };
156
GetStatusCallback(blink::mojom::AppCacheStatus status)157 void GetStatusCallback(blink::mojom::AppCacheStatus status) {
158 last_status_result_ = status;
159 }
160
StartUpdateCallback(bool result)161 void StartUpdateCallback(bool result) { last_start_result_ = result; }
162
SwapCacheCallback(bool result)163 void SwapCacheCallback(bool result) { last_swap_result_ = result; }
164
LockProcessToURL(const GURL & url)165 void LockProcessToURL(const GURL& url) {
166 ChildProcessSecurityPolicyImpl::GetInstance()->LockProcessForTesting(
167 web_contents_->GetMainFrame()->GetSiteInstance()->GetIsolationContext(),
168 kProcessIdForTest, url);
169 }
170
171 BrowserTaskEnvironment task_environment_;
172 RenderViewHostTestEnabler rvh_enabler_;
173 TestBrowserContext browser_context_;
174 std::unique_ptr<TestWebContents> web_contents_;
175
176 const int kProcessIdForTest;
177 const int kRenderFrameIdForTest;
178 const base::UnguessableToken kHostIdForTest =
179 base::UnguessableToken::Create();
180
181 // Mock classes for the 'host' to work with
182 MockAppCacheService service_;
183 MockFrontend mock_frontend_;
184
185 // Mock callbacks we expect to receive from the 'host'
186 blink::mojom::AppCacheHost::GetStatusCallback get_status_callback_;
187
188 blink::mojom::AppCacheStatus last_status_result_;
189 bool last_swap_result_;
190 bool last_start_result_;
191 };
192
TEST_F(AppCacheHostTest,Basic)193 TEST_F(AppCacheHostTest, Basic) {
194 // Construct a host and test what state it appears to be in.
195 AppCacheHost host(kHostIdForTest, kProcessIdForTest, kRenderFrameIdForTest,
196 ChildProcessSecurityPolicyImpl::GetInstance()->CreateHandle(
197 kProcessIdForTest),
198 mojo::NullRemote(), &service_);
199 host.set_frontend_for_testing(&mock_frontend_);
200 EXPECT_EQ(kHostIdForTest, host.host_id());
201 EXPECT_EQ(kProcessIdForTest, host.process_id());
202 EXPECT_TRUE(host.security_policy_handle());
203 EXPECT_TRUE(host.security_policy_handle()->is_valid());
204 EXPECT_EQ(&service_, host.service());
205 EXPECT_EQ(nullptr, host.associated_cache());
206 EXPECT_FALSE(host.is_selection_pending());
207
208 // See that the callbacks are delivered immediately
209 // and respond as if there is no cache selected.
210 last_status_result_ = blink::mojom::AppCacheStatus::APPCACHE_STATUS_OBSOLETE;
211 host.GetStatus(std::move(get_status_callback_));
212 EXPECT_EQ(blink::mojom::AppCacheStatus::APPCACHE_STATUS_UNCACHED,
213 last_status_result_);
214
215 last_start_result_ = true;
216 host.StartUpdate(base::BindOnce(&AppCacheHostTest::StartUpdateCallback,
217 base::Unretained(this)));
218 EXPECT_FALSE(last_start_result_);
219
220 last_swap_result_ = true;
221 host.SwapCache(base::BindOnce(&AppCacheHostTest::SwapCacheCallback,
222 base::Unretained(this)));
223 EXPECT_FALSE(last_swap_result_);
224 }
225
TEST_F(AppCacheHostTest,SelectNoCache)226 TEST_F(AppCacheHostTest, SelectNoCache) {
227 // Lock process with |kInitialDocumentURL| so we can only accept URLs that
228 // generate the same lock as |kInitialDocumentURL|.
229 const GURL kInitialDocumentURL("http://whatever/document");
230 LockProcessToURL(kInitialDocumentURL);
231
232 const std::vector<GURL> kDocumentURLs = {
233 GURL("http://whatever/"),
234 GURL("blob:http://whatever/6f7dc725-2131-4f8b-85ed-4f43d175324e"),
235 GURL("about:blank"), GURL("about:srcdoc"),
236 GURL("blob:null/6f7dc725-2131-4f8b-85ed-4f43d175324e")};
237 for (const GURL& document_url : kDocumentURLs) {
238 scoped_refptr<MockQuotaManagerProxy> mock_quota_proxy =
239 base::MakeRefCounted<MockQuotaManagerProxy>();
240 service_.set_quota_manager_proxy(mock_quota_proxy.get());
241
242 // Reset our mock frontend
243 mock_frontend_.last_cache_id_ = -333;
244 mock_frontend_.last_status_ =
245 blink::mojom::AppCacheStatus::APPCACHE_STATUS_OBSOLETE;
246
247 const url::Origin kOrigin(url::Origin::Create(document_url));
248 {
249 AppCacheHost host(
250 kHostIdForTest, kProcessIdForTest, kRenderFrameIdForTest,
251 ChildProcessSecurityPolicyImpl::GetInstance()->CreateHandle(
252 kProcessIdForTest),
253 mojo::NullRemote(), &service_);
254 host.set_frontend_for_testing(&mock_frontend_);
255
256 {
257 mojo::test::BadMessageObserver bad_message_observer;
258 host.SelectCache(document_url, blink::mojom::kAppCacheNoCacheId,
259 GURL());
260
261 base::RunLoop().RunUntilIdle();
262 EXPECT_FALSE(bad_message_observer.got_bad_message());
263 }
264
265 if (kOrigin.opaque()) {
266 EXPECT_TRUE(mock_quota_proxy->is_empty());
267 } else {
268 EXPECT_EQ(1, mock_quota_proxy->GetInUseCount(kOrigin))
269 << " document_url " << document_url;
270 }
271
272 // We should have received an OnCacheSelected msg
273 EXPECT_EQ(blink::mojom::kAppCacheNoCacheId,
274 mock_frontend_.last_cache_id_);
275 EXPECT_EQ(blink::mojom::AppCacheStatus::APPCACHE_STATUS_UNCACHED,
276 mock_frontend_.last_status_);
277
278 // Otherwise, see that it respond as if there is no cache selected.
279 EXPECT_EQ(kHostIdForTest, host.host_id());
280 EXPECT_EQ(&service_, host.service());
281 EXPECT_EQ(nullptr, host.associated_cache());
282 EXPECT_FALSE(host.is_selection_pending());
283 EXPECT_TRUE(host.preferred_manifest_url().is_empty());
284 }
285 EXPECT_EQ(0, mock_quota_proxy->GetInUseCount(kOrigin));
286 service_.set_quota_manager_proxy(nullptr);
287 }
288 }
289
TEST_F(AppCacheHostTest,ForeignEntry)290 TEST_F(AppCacheHostTest, ForeignEntry) {
291 // Reset our mock frontend
292 mock_frontend_.last_cache_id_ = -333;
293 mock_frontend_.last_status_ =
294 blink::mojom::AppCacheStatus::APPCACHE_STATUS_OBSOLETE;
295
296 // Precondition, a cache with an entry that is not marked as foreign.
297 const int kCacheId = 22;
298 const GURL kDocumentURL("http://origin/document");
299 auto cache = base::MakeRefCounted<AppCache>(service_.storage(), kCacheId);
300 cache->AddEntry(kDocumentURL, AppCacheEntry(AppCacheEntry::EXPLICIT));
301
302 AppCacheHost host(kHostIdForTest, kProcessIdForTest, kRenderFrameIdForTest,
303 ChildProcessSecurityPolicyImpl::GetInstance()->CreateHandle(
304 kProcessIdForTest),
305 mojo::NullRemote(), &service_);
306 host.set_frontend_for_testing(&mock_frontend_);
307 host.MarkAsForeignEntry(kDocumentURL, kCacheId);
308
309 // We should have received an OnCacheSelected msg for kAppCacheNoCacheId.
310 EXPECT_EQ(blink::mojom::kAppCacheNoCacheId, mock_frontend_.last_cache_id_);
311 EXPECT_EQ(blink::mojom::AppCacheStatus::APPCACHE_STATUS_UNCACHED,
312 mock_frontend_.last_status_);
313
314 // See that it respond as if there is no cache selected.
315 EXPECT_EQ(kHostIdForTest, host.host_id());
316 EXPECT_EQ(&service_, host.service());
317 EXPECT_EQ(nullptr, host.associated_cache());
318 EXPECT_FALSE(host.is_selection_pending());
319
320 // See that the entry was marked as foreign.
321 EXPECT_TRUE(cache->GetEntry(kDocumentURL)->IsForeign());
322 }
323
TEST_F(AppCacheHostTest,ForeignFallbackEntry)324 TEST_F(AppCacheHostTest, ForeignFallbackEntry) {
325 // Reset our mock frontend
326 mock_frontend_.last_cache_id_ = -333;
327 mock_frontend_.last_status_ =
328 blink::mojom::AppCacheStatus::APPCACHE_STATUS_OBSOLETE;
329
330 // Precondition, a cache with a fallback entry that is not marked as foreign.
331 const int kCacheId = 22;
332 const GURL kFallbackURL("http://origin/fallback_resource");
333 scoped_refptr<AppCache> cache =
334 base::MakeRefCounted<AppCache>(service_.storage(), kCacheId);
335 cache->AddEntry(kFallbackURL, AppCacheEntry(AppCacheEntry::FALLBACK));
336
337 AppCacheHost host(kHostIdForTest, kProcessIdForTest, kRenderFrameIdForTest,
338 ChildProcessSecurityPolicyImpl::GetInstance()->CreateHandle(
339 kProcessIdForTest),
340 mojo::NullRemote(), &service_);
341 host.set_frontend_for_testing(&mock_frontend_);
342 host.NotifyMainResourceIsNamespaceEntry(kFallbackURL);
343 host.MarkAsForeignEntry(GURL("http://origin/missing_document"), kCacheId);
344
345 // We should have received an OnCacheSelected msg for kAppCacheNoCacheId.
346 EXPECT_EQ(blink::mojom::kAppCacheNoCacheId, mock_frontend_.last_cache_id_);
347 EXPECT_EQ(blink::mojom::AppCacheStatus::APPCACHE_STATUS_UNCACHED,
348 mock_frontend_.last_status_);
349
350 // See that the fallback entry was marked as foreign.
351 EXPECT_TRUE(cache->GetEntry(kFallbackURL)->IsForeign());
352 }
353
TEST_F(AppCacheHostTest,FailedCacheLoad)354 TEST_F(AppCacheHostTest, FailedCacheLoad) {
355 // Reset our mock frontend
356 mock_frontend_.last_cache_id_ = -333;
357 mock_frontend_.last_status_ =
358 blink::mojom::AppCacheStatus::APPCACHE_STATUS_OBSOLETE;
359
360 AppCacheHost host(kHostIdForTest, kProcessIdForTest, kRenderFrameIdForTest,
361 ChildProcessSecurityPolicyImpl::GetInstance()->CreateHandle(
362 kProcessIdForTest),
363 mojo::NullRemote(), &service_);
364 host.set_frontend_for_testing(&mock_frontend_);
365 EXPECT_FALSE(host.is_selection_pending());
366
367 const int kMockCacheId = 333;
368
369 // Put it in a state where we're waiting on a cache
370 // load prior to finishing cache selection.
371 host.pending_selected_cache_id_ = kMockCacheId;
372 EXPECT_TRUE(host.is_selection_pending());
373
374 // The callback should not occur until we finish cache selection.
375 last_status_result_ = blink::mojom::AppCacheStatus::APPCACHE_STATUS_OBSOLETE;
376 host.GetStatus(std::move(get_status_callback_));
377 EXPECT_EQ(blink::mojom::AppCacheStatus::APPCACHE_STATUS_OBSOLETE,
378 last_status_result_);
379
380 // Satisfy the load with NULL, a failure.
381 host.OnCacheLoaded(nullptr, kMockCacheId);
382
383 // Cache selection should have finished
384 EXPECT_FALSE(host.is_selection_pending());
385 EXPECT_EQ(blink::mojom::kAppCacheNoCacheId, mock_frontend_.last_cache_id_);
386 EXPECT_EQ(blink::mojom::AppCacheStatus::APPCACHE_STATUS_UNCACHED,
387 mock_frontend_.last_status_);
388
389 // Callback should have fired upon completing the cache load too.
390 EXPECT_EQ(blink::mojom::AppCacheStatus::APPCACHE_STATUS_UNCACHED,
391 last_status_result_);
392 }
393
TEST_F(AppCacheHostTest,FailedGroupLoad)394 TEST_F(AppCacheHostTest, FailedGroupLoad) {
395 AppCacheHost host(kHostIdForTest, kProcessIdForTest, kRenderFrameIdForTest,
396 ChildProcessSecurityPolicyImpl::GetInstance()->CreateHandle(
397 kProcessIdForTest),
398 mojo::NullRemote(), &service_);
399 host.set_frontend_for_testing(&mock_frontend_);
400
401 const GURL kMockManifestUrl("http://foo.bar/baz");
402
403 // Put it in a state where we're waiting on a cache
404 // load prior to finishing cache selection.
405 host.pending_selected_manifest_url_ = kMockManifestUrl;
406 EXPECT_TRUE(host.is_selection_pending());
407
408 // The callback should not occur until we finish cache selection.
409 last_status_result_ = blink::mojom::AppCacheStatus::APPCACHE_STATUS_OBSOLETE;
410 host.GetStatus(std::move(get_status_callback_));
411 EXPECT_EQ(blink::mojom::AppCacheStatus::APPCACHE_STATUS_OBSOLETE,
412 last_status_result_);
413
414 // Satisfy the load will NULL, a failure.
415 host.OnGroupLoaded(nullptr, kMockManifestUrl);
416
417 // Cache selection should have finished
418 EXPECT_FALSE(host.is_selection_pending());
419 EXPECT_EQ(blink::mojom::kAppCacheNoCacheId, mock_frontend_.last_cache_id_);
420 EXPECT_EQ(blink::mojom::AppCacheStatus::APPCACHE_STATUS_UNCACHED,
421 mock_frontend_.last_status_);
422
423 // Callback should have fired upon completing the group load.
424 EXPECT_EQ(blink::mojom::AppCacheStatus::APPCACHE_STATUS_UNCACHED,
425 last_status_result_);
426 }
427
TEST_F(AppCacheHostTest,SetSwappableCache)428 TEST_F(AppCacheHostTest, SetSwappableCache) {
429 AppCacheHost host(kHostIdForTest, kProcessIdForTest, kRenderFrameIdForTest,
430 ChildProcessSecurityPolicyImpl::GetInstance()->CreateHandle(
431 kProcessIdForTest),
432 mojo::NullRemote(), &service_);
433 host.set_frontend_for_testing(&mock_frontend_);
434 host.SetSwappableCache(nullptr);
435 EXPECT_FALSE(host.swappable_cache_.get());
436
437 const GURL kGroup1ManifestUrl("http://bar.com");
438 scoped_refptr<AppCacheGroup> group1 = base::MakeRefCounted<AppCacheGroup>(
439 service_.storage(), kGroup1ManifestUrl, service_.storage()->NewGroupId());
440 host.SetSwappableCache(group1.get());
441 EXPECT_FALSE(host.swappable_cache_.get());
442
443 scoped_refptr<AppCache> cache1 =
444 base::MakeRefCounted<AppCache>(service_.storage(), 111);
445 cache1->set_complete(true);
446 group1->AddCache(cache1.get());
447 host.SetSwappableCache(group1.get());
448 EXPECT_EQ(cache1, host.swappable_cache_.get());
449
450 mock_frontend_.last_cache_id_ =
451 kUnsetCacheId; // to verify we received OnCacheSelected
452
453 host.AssociateCompleteCache(cache1.get());
454 EXPECT_FALSE(host.swappable_cache_.get()); // was same as associated cache
455 EXPECT_EQ(blink::mojom::AppCacheStatus::APPCACHE_STATUS_IDLE,
456 host.GetStatusSync());
457 // verify OnCacheSelected was called
458 EXPECT_EQ(cache1->cache_id(), mock_frontend_.last_cache_id_);
459 EXPECT_EQ(blink::mojom::AppCacheStatus::APPCACHE_STATUS_IDLE,
460 mock_frontend_.last_status_);
461
462 scoped_refptr<AppCache> cache2 =
463 base::MakeRefCounted<AppCache>(service_.storage(), 222);
464 cache2->set_complete(true);
465 group1->AddCache(cache2.get());
466 EXPECT_EQ(cache2.get(), host.swappable_cache_.get()); // updated to newest
467
468 const GURL kGroup2ManifestUrl("http://foo.com/");
469 scoped_refptr<AppCacheGroup> group2 = base::MakeRefCounted<AppCacheGroup>(
470 service_.storage(), kGroup2ManifestUrl, service_.storage()->NewGroupId());
471 scoped_refptr<AppCache> cache3 =
472 base::MakeRefCounted<AppCache>(service_.storage(), 333);
473 cache3->set_complete(true);
474 group2->AddCache(cache3.get());
475
476 scoped_refptr<AppCache> cache4 =
477 base::MakeRefCounted<AppCache>(service_.storage(), 444);
478 cache4->set_complete(true);
479 group2->AddCache(cache4.get());
480 EXPECT_EQ(cache2.get(), host.swappable_cache_.get()); // unchanged
481
482 cache1.reset();
483 cache2.reset();
484
485 host.AssociateCompleteCache(cache3.get());
486 EXPECT_EQ(cache4.get(),
487 host.swappable_cache_.get()); // newest cache in group2
488 EXPECT_FALSE(group1->HasCache()); // both caches in group1 have refcount 0
489
490 cache3.reset();
491 cache4.reset();
492
493 host.AssociateNoCache(kGroup1ManifestUrl);
494 EXPECT_FALSE(host.swappable_cache_.get());
495 EXPECT_FALSE(group2->HasCache()); // both caches in group2 have refcount 0
496
497 // Host adds reference to newest cache when an update is complete.
498 scoped_refptr<AppCache> cache5 =
499 base::MakeRefCounted<AppCache>(service_.storage(), 555);
500 cache5->set_complete(true);
501 group2->AddCache(cache5.get());
502 host.group_being_updated_ = group2;
503 host.OnUpdateComplete(group2.get());
504 EXPECT_FALSE(host.group_being_updated_.get());
505 EXPECT_EQ(cache5.get(), host.swappable_cache_.get());
506
507 group2->RemoveCache(cache5.get());
508 EXPECT_FALSE(group2->HasCache());
509 host.group_being_updated_ = group2;
510 host.OnUpdateComplete(group2.get());
511 EXPECT_FALSE(host.group_being_updated_.get());
512 EXPECT_FALSE(host.swappable_cache_.get()); // group2 had no newest cache
513 }
514
TEST_F(AppCacheHostTest,SelectCacheAllowed)515 TEST_F(AppCacheHostTest, SelectCacheAllowed) {
516 scoped_refptr<MockQuotaManagerProxy> mock_quota_proxy =
517 base::MakeRefCounted<MockQuotaManagerProxy>();
518 MockAppCachePolicy mock_appcache_policy;
519 mock_appcache_policy.can_create_return_value_ = true;
520 service_.set_quota_manager_proxy(mock_quota_proxy.get());
521 service_.set_appcache_policy(&mock_appcache_policy);
522
523 // Reset our mock frontend
524 mock_frontend_.last_cache_id_ = -333;
525 mock_frontend_.last_status_ =
526 blink::mojom::AppCacheStatus::APPCACHE_STATUS_OBSOLETE;
527 mock_frontend_.last_event_id_ =
528 blink::mojom::AppCacheEventID::APPCACHE_OBSOLETE_EVENT;
529 mock_frontend_.content_blocked_ = false;
530 mock_frontend_.appcache_accessed_ = false;
531
532 const GURL kDocAndOriginUrl("http://whatever/");
533 const url::Origin kOrigin(url::Origin::Create(kDocAndOriginUrl));
534 const GURL kManifestUrl("http://whatever/cache.manifest");
535 {
536 AppCacheHost host(
537 kHostIdForTest, kProcessIdForTest, kRenderFrameIdForTest,
538 ChildProcessSecurityPolicyImpl::GetInstance()->CreateHandle(
539 kProcessIdForTest),
540 mojo::NullRemote(), &service_);
541 host.set_frontend_for_testing(&mock_frontend_);
542 host.SetSiteForCookiesForTesting(
543 net::SiteForCookies::FromUrl(kDocAndOriginUrl));
544 host.SelectCache(kDocAndOriginUrl, blink::mojom::kAppCacheNoCacheId,
545 kManifestUrl);
546 EXPECT_EQ(1, mock_quota_proxy->GetInUseCount(kOrigin));
547
548 // MockAppCacheService::LoadOrCreateGroup is asynchronous, so we shouldn't
549 // have received an OnCacheSelected msg yet.
550 EXPECT_EQ(-333, mock_frontend_.last_cache_id_);
551 EXPECT_EQ(blink::mojom::AppCacheStatus::APPCACHE_STATUS_OBSOLETE,
552 mock_frontend_.last_status_);
553 // No error events either
554 EXPECT_EQ(blink::mojom::AppCacheEventID::APPCACHE_OBSOLETE_EVENT,
555 mock_frontend_.last_event_id_);
556 EXPECT_FALSE(mock_frontend_.content_blocked_);
557
558 EXPECT_TRUE(host.is_selection_pending());
559
560 base::RunLoop().RunUntilIdle();
561 EXPECT_FALSE(mock_frontend_.content_blocked_);
562 EXPECT_TRUE(mock_frontend_.appcache_accessed_);
563 }
564 EXPECT_EQ(0, mock_quota_proxy->GetInUseCount(kOrigin));
565 service_.set_quota_manager_proxy(nullptr);
566 }
567
TEST_F(AppCacheHostTest,SelectCacheBlocked)568 TEST_F(AppCacheHostTest, SelectCacheBlocked) {
569 scoped_refptr<MockQuotaManagerProxy> mock_quota_proxy =
570 base::MakeRefCounted<MockQuotaManagerProxy>();
571 MockAppCachePolicy mock_appcache_policy;
572 mock_appcache_policy.can_create_return_value_ = false;
573 service_.set_quota_manager_proxy(mock_quota_proxy.get());
574 service_.set_appcache_policy(&mock_appcache_policy);
575
576 // Reset our mock frontend
577 mock_frontend_.last_cache_id_ = -333;
578 mock_frontend_.last_status_ =
579 blink::mojom::AppCacheStatus::APPCACHE_STATUS_OBSOLETE;
580 mock_frontend_.last_event_id_ =
581 blink::mojom::AppCacheEventID::APPCACHE_OBSOLETE_EVENT;
582 mock_frontend_.content_blocked_ = false;
583 mock_frontend_.appcache_accessed_ = false;
584
585 const GURL kDocAndOriginUrl(GURL("http://whatever/").GetOrigin());
586 const url::Origin kOrigin(url::Origin::Create(kDocAndOriginUrl));
587 const GURL kManifestUrl(GURL("http://whatever/cache.manifest"));
588 {
589 AppCacheHost host(
590 kHostIdForTest, kProcessIdForTest, kRenderFrameIdForTest,
591 ChildProcessSecurityPolicyImpl::GetInstance()->CreateHandle(
592 kProcessIdForTest),
593 mojo::NullRemote(), &service_);
594 host.set_frontend_for_testing(&mock_frontend_);
595 host.SetSiteForCookiesForTesting(
596 net::SiteForCookies::FromUrl(kDocAndOriginUrl));
597 host.SelectCache(kDocAndOriginUrl, blink::mojom::kAppCacheNoCacheId,
598 kManifestUrl);
599 EXPECT_EQ(1, mock_quota_proxy->GetInUseCount(kOrigin));
600
601 // We should have received an OnCacheSelected msg
602 EXPECT_EQ(blink::mojom::kAppCacheNoCacheId, mock_frontend_.last_cache_id_);
603 EXPECT_EQ(blink::mojom::AppCacheStatus::APPCACHE_STATUS_UNCACHED,
604 mock_frontend_.last_status_);
605
606 // Also, an error event was raised
607 EXPECT_EQ(blink::mojom::AppCacheEventID::APPCACHE_ERROR_EVENT,
608 mock_frontend_.last_event_id_);
609
610 // Otherwise, see that it respond as if there is no cache selected.
611 EXPECT_EQ(kHostIdForTest, host.host_id());
612 EXPECT_EQ(&service_, host.service());
613 EXPECT_EQ(nullptr, host.associated_cache());
614 EXPECT_FALSE(host.is_selection_pending());
615 EXPECT_TRUE(host.preferred_manifest_url().is_empty());
616
617 base::RunLoop().RunUntilIdle();
618 EXPECT_TRUE(mock_frontend_.content_blocked_);
619 EXPECT_TRUE(mock_frontend_.appcache_accessed_);
620 }
621 EXPECT_EQ(0, mock_quota_proxy->GetInUseCount(kOrigin));
622 service_.set_quota_manager_proxy(nullptr);
623 }
624
TEST_F(AppCacheHostTest,SelectCacheTwice)625 TEST_F(AppCacheHostTest, SelectCacheTwice) {
626 const GURL kDocAndOriginUrl(GURL("http://whatever/").GetOrigin());
627 AppCacheHost host(kHostIdForTest, kProcessIdForTest, kRenderFrameIdForTest,
628 ChildProcessSecurityPolicyImpl::GetInstance()->CreateHandle(
629 kProcessIdForTest),
630 mojo::NullRemote(), &service_);
631 host.set_frontend_for_testing(&mock_frontend_);
632 mojo::Remote<blink::mojom::AppCacheHost> host_remote;
633 host.BindReceiver(host_remote.BindNewPipeAndPassReceiver());
634
635 {
636 mojo::test::BadMessageObserver bad_message_observer;
637 host_remote->SelectCache(kDocAndOriginUrl, blink::mojom::kAppCacheNoCacheId,
638 GURL());
639
640 base::RunLoop().RunUntilIdle();
641 EXPECT_FALSE(bad_message_observer.got_bad_message());
642 }
643
644 // Select methods should bail if cache has already been selected.
645 {
646 mojo::test::BadMessageObserver bad_message_observer;
647 host_remote->SelectCache(kDocAndOriginUrl, blink::mojom::kAppCacheNoCacheId,
648 GURL());
649 EXPECT_EQ("ACH_SELECT_CACHE", bad_message_observer.WaitForBadMessage());
650 }
651 {
652 mojo::test::BadMessageObserver bad_message_observer;
653 host_remote->SelectCacheForWorker(blink::mojom::kAppCacheNoCacheId);
654 EXPECT_EQ("ACH_SELECT_CACHE_FOR_WORKER",
655 bad_message_observer.WaitForBadMessage());
656 }
657 {
658 mojo::test::BadMessageObserver bad_message_observer;
659 host_remote->MarkAsForeignEntry(kDocAndOriginUrl,
660 blink::mojom::kAppCacheNoCacheId);
661 EXPECT_EQ("ACH_MARK_AS_FOREIGN_ENTRY",
662 bad_message_observer.WaitForBadMessage());
663 }
664 }
665
TEST_F(AppCacheHostTest,SelectCacheInvalidCacheId)666 TEST_F(AppCacheHostTest, SelectCacheInvalidCacheId) {
667 const GURL kDocAndOriginUrl(GURL("http://whatever/").GetOrigin());
668
669 // A cache that the document wasn't actually loaded from. Trying to select it
670 // should cause a BadMessage.
671 const int kCacheId = 22;
672 const GURL kDocumentURL("http://origin/document");
673 auto cache = base::MakeRefCounted<AppCache>(service_.storage(), kCacheId);
674 AppCacheHost host(kHostIdForTest, kProcessIdForTest, kRenderFrameIdForTest,
675 ChildProcessSecurityPolicyImpl::GetInstance()->CreateHandle(
676 kProcessIdForTest),
677 mojo::NullRemote(), &service_);
678 host.set_frontend_for_testing(&mock_frontend_);
679 mojo::Remote<blink::mojom::AppCacheHost> host_remote;
680 host.BindReceiver(host_remote.BindNewPipeAndPassReceiver());
681
682 {
683 mojo::test::BadMessageObserver bad_message_observer;
684 host_remote->SelectCache(kDocAndOriginUrl, kCacheId, GURL());
685
686 EXPECT_EQ("ACH_SELECT_CACHE_ID_NOT_OWNED",
687 bad_message_observer.WaitForBadMessage());
688 }
689 }
690
TEST_F(AppCacheHostTest,SelectCacheURLsForWrongSite)691 TEST_F(AppCacheHostTest, SelectCacheURLsForWrongSite) {
692 // Lock process with |kInitialDocumentURL| so we can only accept URLs that
693 // generate the same lock as |kInitialDocumentURL|.
694 const GURL kInitialDocumentURL("http://foo.com/document");
695 LockProcessToURL(kInitialDocumentURL);
696
697 AppCacheHost host(kHostIdForTest, kProcessIdForTest, kRenderFrameIdForTest,
698 ChildProcessSecurityPolicyImpl::GetInstance()->CreateHandle(
699 kProcessIdForTest),
700 mojo::NullRemote(), &service_);
701 host.set_frontend_for_testing(&mock_frontend_);
702 mojo::Remote<blink::mojom::AppCacheHost> host_remote;
703 host.BindReceiver(host_remote.BindNewPipeAndPassReceiver());
704
705 // Verify that a document URL from the wrong site triggers a bad message.
706 {
707 const GURL kWrongSiteDocumentURL("http://whatever/");
708 mojo::test::BadMessageObserver bad_message_observer;
709 host_remote->SelectCache(kWrongSiteDocumentURL,
710 blink::mojom::kAppCacheNoCacheId, GURL());
711
712 EXPECT_EQ("ACH_SELECT_CACHE_DOCUMENT_URL_ACCESS_NOT_ALLOWED",
713 bad_message_observer.WaitForBadMessage());
714 }
715
716 // Verify that a document URL with an inner hostname from the wrong site
717 // triggers a bad message.
718 {
719 const GURL kDocumentURL = kInitialDocumentURL;
720 mojo::test::BadMessageObserver bad_message_observer;
721 host_remote->SelectCache(
722 kDocumentURL, blink::mojom::kAppCacheNoCacheId,
723 GURL("blob:http://whatever/6f7dc725-2131-4f8b-85ed-4f43d175324e"));
724
725 EXPECT_EQ("ACH_SELECT_CACHE_MANIFEST_URL_ACCESS_NOT_ALLOWED",
726 bad_message_observer.WaitForBadMessage());
727 }
728
729 // Verify that a manifest URL from the wrong site triggers a bad message.
730 {
731 const GURL kDocumentURL = kInitialDocumentURL;
732 const GURL kManifestURL("http://whatever/");
733 mojo::test::BadMessageObserver bad_message_observer;
734 host_remote->SelectCache(kDocumentURL, blink::mojom::kAppCacheNoCacheId,
735 kManifestURL);
736
737 EXPECT_EQ("ACH_SELECT_CACHE_MANIFEST_URL_ACCESS_NOT_ALLOWED",
738 bad_message_observer.WaitForBadMessage());
739 }
740 }
741
TEST_F(AppCacheHostTest,ForeignEntryForWrongSite)742 TEST_F(AppCacheHostTest, ForeignEntryForWrongSite) {
743 // Lock process with |kInitialDocumentURL| so we can only accept URLs that
744 // generate the same lock as |kInitialDocumentURL|.
745 const GURL kInitialDocumentURL("http://foo.com");
746 LockProcessToURL(kInitialDocumentURL);
747
748 AppCacheHost host(kHostIdForTest, kProcessIdForTest, kRenderFrameIdForTest,
749 ChildProcessSecurityPolicyImpl::GetInstance()->CreateHandle(
750 kProcessIdForTest),
751 mojo::NullRemote(), &service_);
752 host.set_frontend_for_testing(&mock_frontend_);
753 mojo::Remote<blink::mojom::AppCacheHost> host_remote;
754 host.BindReceiver(host_remote.BindNewPipeAndPassReceiver());
755
756 // Verify that a document URL from the wrong site triggers a bad message.
757 {
758 const GURL kWrongSiteDocumentURL("http://origin/document");
759 mojo::test::BadMessageObserver bad_message_observer;
760 host_remote->MarkAsForeignEntry(kWrongSiteDocumentURL,
761 blink::mojom::kAppCacheNoCacheId);
762 EXPECT_EQ("ACH_MARK_AS_FOREIGN_ENTRY_DOCUMENT_URL_ACCESS_NOT_ALLOWED",
763 bad_message_observer.WaitForBadMessage());
764 }
765 }
766
TEST_F(AppCacheHostTest,SelectCacheAfterProcessCleanup)767 TEST_F(AppCacheHostTest, SelectCacheAfterProcessCleanup) {
768 // Lock process with |kDocumentURL| so we can only accept URLs that
769 // generate the same lock as |kDocumentURL|.
770 const GURL kDocumentURL("http://foo.com/document");
771 const GURL kManifestURL("http://foo.com/manifest");
772
773 auto* security_policy = ChildProcessSecurityPolicyImpl::GetInstance();
774 LockProcessToURL(kDocumentURL);
775
776 AppCacheHost host(kHostIdForTest, kProcessIdForTest, kRenderFrameIdForTest,
777 ChildProcessSecurityPolicyImpl::GetInstance()->CreateHandle(
778 kProcessIdForTest),
779 mojo::NullRemote(), &service_);
780 host.set_frontend_for_testing(&mock_frontend_);
781 mojo::Remote<blink::mojom::AppCacheHost> host_remote;
782 host.BindReceiver(host_remote.BindNewPipeAndPassReceiver());
783
784 EXPECT_TRUE(
785 security_policy->CanAccessDataForOrigin(kProcessIdForTest, kDocumentURL));
786
787 // Destroy the WebContents so the process gets cleaned up.
788 web_contents_.reset();
789 base::RunLoop().RunUntilIdle();
790
791 // Since |host| for kProcessIdForTest is still alive, the corresponding
792 // SecurityState in ChildProcessSecurityPolicy should also be kept alive,
793 // allowing access for kDocumentURL.
794 EXPECT_TRUE(
795 security_policy->CanAccessDataForOrigin(kProcessIdForTest, kDocumentURL));
796
797 // Verify that the document and manifest URLs do not trigger a bad message.
798 {
799 mojo::test::BadMessageObserver bad_message_observer;
800
801 EXPECT_EQ(kUnsetCacheId, mock_frontend_.last_cache_id_);
802 EXPECT_EQ(blink::mojom::AppCacheStatus::APPCACHE_STATUS_OBSOLETE,
803 mock_frontend_.last_status_);
804
805 host_remote->SelectCache(kDocumentURL, blink::mojom::kAppCacheNoCacheId,
806 kManifestURL);
807
808 // Run loop to allow the bad message code to run if a bad message was
809 // triggered.
810 base::RunLoop().RunUntilIdle();
811 EXPECT_FALSE(bad_message_observer.got_bad_message());
812
813 // Verify the frontend was still called.
814 EXPECT_EQ(blink::mojom::kAppCacheNoCacheId, mock_frontend_.last_cache_id_);
815 EXPECT_EQ(blink::mojom::AppCacheStatus::APPCACHE_STATUS_UNCACHED,
816 mock_frontend_.last_status_);
817 }
818 }
819
TEST_F(AppCacheHostTest,ForeignEntryAfterProcessCleanup)820 TEST_F(AppCacheHostTest, ForeignEntryAfterProcessCleanup) {
821 // Lock process with |kDocumentURL| so we can only accept URLs that
822 // generate the same lock as |kDocumentURL|.
823 const GURL kDocumentURL("http://foo.com/document");
824
825 auto* security_policy = ChildProcessSecurityPolicyImpl::GetInstance();
826 LockProcessToURL(kDocumentURL);
827
828 AppCacheHost host(kHostIdForTest, kProcessIdForTest, kRenderFrameIdForTest,
829 ChildProcessSecurityPolicyImpl::GetInstance()->CreateHandle(
830 kProcessIdForTest),
831 mojo::NullRemote(), &service_);
832 host.set_frontend_for_testing(&mock_frontend_);
833 mojo::Remote<blink::mojom::AppCacheHost> host_remote;
834 host.BindReceiver(host_remote.BindNewPipeAndPassReceiver());
835
836 EXPECT_TRUE(
837 security_policy->CanAccessDataForOrigin(kProcessIdForTest, kDocumentURL));
838
839 // Destroy the WebContents so the process gets cleaned up.
840 web_contents_.reset();
841 base::RunLoop().RunUntilIdle();
842
843 // Since |host| for kProcessIdForTest is still alive, the corresponding
844 // SecurityState in ChildProcessSecurityPolicy should also be kept alive,
845 // allowing access for kDocumentURL.
846 EXPECT_TRUE(
847 security_policy->CanAccessDataForOrigin(kProcessIdForTest, kDocumentURL));
848
849 // Verify that a document URL does not trigger a bad message.
850 {
851 mojo::test::BadMessageObserver bad_message_observer;
852
853 EXPECT_EQ(kUnsetCacheId, mock_frontend_.last_cache_id_);
854 EXPECT_EQ(blink::mojom::AppCacheStatus::APPCACHE_STATUS_OBSOLETE,
855 mock_frontend_.last_status_);
856
857 host_remote->MarkAsForeignEntry(kDocumentURL,
858 blink::mojom::kAppCacheNoCacheId);
859
860 // Run loop to allow the bad message code to run if a bad message was
861 // triggered.
862 base::RunLoop().RunUntilIdle();
863 EXPECT_FALSE(bad_message_observer.got_bad_message());
864
865 // Verify the frontend was still called.
866 EXPECT_EQ(blink::mojom::kAppCacheNoCacheId, mock_frontend_.last_cache_id_);
867 EXPECT_EQ(blink::mojom::AppCacheStatus::APPCACHE_STATUS_UNCACHED,
868 mock_frontend_.last_status_);
869 }
870 }
871 } // namespace content
872