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 <stdint.h>
6
7 #include <string>
8
9 #include "content/browser/appcache/appcache.h"
10 #include "content/browser/appcache/appcache_group.h"
11 #include "content/browser/appcache/appcache_host.h"
12 #include "content/browser/appcache/appcache_update_job.h"
13 #include "content/browser/appcache/mock_appcache_service.h"
14 #include "content/public/test/browser_task_environment.h"
15 #include "mojo/public/cpp/bindings/pending_remote.h"
16 #include "mojo/public/cpp/bindings/receiver_set.h"
17 #include "mojo/public/cpp/bindings/remote.h"
18 #include "testing/gtest/include/gtest/gtest.h"
19 #include "third_party/blink/public/mojom/appcache/appcache.mojom.h"
20 #include "third_party/blink/public/mojom/appcache/appcache_info.mojom.h"
21 #include "third_party/blink/public/mojom/devtools/console_message.mojom.h"
22
23 namespace {
24
25 class TestAppCacheFrontend : public blink::mojom::AppCacheFrontend {
26 public:
TestAppCacheFrontend()27 TestAppCacheFrontend()
28 : last_cache_id_(-1),
29 last_status_(blink::mojom::AppCacheStatus::APPCACHE_STATUS_OBSOLETE) {}
30
CacheSelected(blink::mojom::AppCacheInfoPtr info)31 void CacheSelected(blink::mojom::AppCacheInfoPtr info) override {
32 last_host_id_ = receivers_.current_context();
33 last_cache_id_ = info->cache_id;
34 last_status_ = info->status;
35 }
36
EventRaised(blink::mojom::AppCacheEventID event_id)37 void EventRaised(blink::mojom::AppCacheEventID event_id) override {}
38
ErrorEventRaised(blink::mojom::AppCacheErrorDetailsPtr details)39 void ErrorEventRaised(
40 blink::mojom::AppCacheErrorDetailsPtr details) override {}
41
ProgressEventRaised(const GURL & url,int32_t num_total,int32_t num_complete)42 void ProgressEventRaised(const GURL& url,
43 int32_t num_total,
44 int32_t num_complete) override {}
45
LogMessage(blink::mojom::ConsoleMessageLevel log_level,const std::string & message)46 void LogMessage(blink::mojom::ConsoleMessageLevel log_level,
47 const std::string& message) override {}
48
SetSubresourceFactory(mojo::PendingRemote<network::mojom::URLLoaderFactory> url_loader_factory)49 void SetSubresourceFactory(
50 mojo::PendingRemote<network::mojom::URLLoaderFactory> url_loader_factory)
51 override {}
52
Bind(const base::UnguessableToken & host_id)53 mojo::PendingRemote<blink::mojom::AppCacheFrontend> Bind(
54 const base::UnguessableToken& host_id) {
55 mojo::PendingRemote<blink::mojom::AppCacheFrontend> result;
56 receivers_.Add(this, result.InitWithNewPipeAndPassReceiver(), host_id);
57 return result;
58 }
59
60 base::UnguessableToken last_host_id_;
61 int64_t last_cache_id_;
62 blink::mojom::AppCacheStatus last_status_;
63 mojo::ReceiverSet<blink::mojom::AppCacheFrontend, base::UnguessableToken>
64 receivers_;
65 };
66
67 } // namespace
68
69 namespace content {
70
71 class TestUpdateObserver : public AppCacheGroup::UpdateObserver {
72 public:
TestUpdateObserver()73 TestUpdateObserver() : update_completed_(false), group_has_cache_(false) {}
74
OnUpdateComplete(AppCacheGroup * group)75 void OnUpdateComplete(AppCacheGroup* group) override {
76 update_completed_ = true;
77 group_has_cache_ = group->HasCache();
78 }
79
OnContentBlocked(AppCacheGroup * group)80 virtual void OnContentBlocked(AppCacheGroup* group) {}
81
82 bool update_completed_;
83 bool group_has_cache_;
84 };
85
86 class TestAppCacheHost : public AppCacheHost {
87 public:
TestAppCacheHost(const base::UnguessableToken & host_id,TestAppCacheFrontend * frontend,AppCacheServiceImpl * service)88 TestAppCacheHost(const base::UnguessableToken& host_id,
89 TestAppCacheFrontend* frontend,
90 AppCacheServiceImpl* service)
91 : AppCacheHost(
92 host_id,
93 /*process_id=*/456,
94 /*render_frame_id=*/789,
95 ChildProcessSecurityPolicyImpl::GetInstance()->CreateHandle(
96 /*process_id=*/456),
97 frontend->Bind(host_id),
98 service),
99 update_completed_(false) {}
100
OnUpdateComplete(AppCacheGroup * group)101 void OnUpdateComplete(AppCacheGroup* group) override {
102 update_completed_ = true;
103 }
104
105 bool update_completed_;
106 };
107
108 class AppCacheGroupTest : public testing::Test {
109 private:
110 BrowserTaskEnvironment task_environment_;
111 };
112
TEST_F(AppCacheGroupTest,AddRemoveCache)113 TEST_F(AppCacheGroupTest, AddRemoveCache) {
114 MockAppCacheService service;
115 auto group = base::MakeRefCounted<AppCacheGroup>(service.storage(),
116 GURL("http://foo.com"), 111);
117
118 base::Time now = base::Time::Now();
119
120 auto cache1 = base::MakeRefCounted<AppCache>(service.storage(), 111);
121 cache1->set_complete(true);
122 cache1->set_update_time(now);
123 group->AddCache(cache1.get());
124 EXPECT_EQ(cache1.get(), group->newest_complete_cache());
125
126 // Adding older cache does not change newest complete cache.
127 auto cache2 = base::MakeRefCounted<AppCache>(service.storage(), 222);
128 cache2->set_complete(true);
129 cache2->set_update_time(now - base::TimeDelta::FromDays(1));
130 group->AddCache(cache2.get());
131 EXPECT_EQ(cache1.get(), group->newest_complete_cache());
132
133 // Adding newer cache does change newest complete cache.
134 auto cache3 = base::MakeRefCounted<AppCache>(service.storage(), 333);
135 cache3->set_complete(true);
136 cache3->set_update_time(now + base::TimeDelta::FromDays(1));
137 group->AddCache(cache3.get());
138 EXPECT_EQ(cache3.get(), group->newest_complete_cache());
139
140 // Adding cache with same update time uses one with larger ID.
141 auto cache4 = base::MakeRefCounted<AppCache>(service.storage(), 444);
142 cache4->set_complete(true);
143 cache4->set_update_time(now + base::TimeDelta::FromDays(1)); // same as 3
144 group->AddCache(cache4.get());
145 EXPECT_EQ(cache4.get(), group->newest_complete_cache());
146
147 // smaller id
148 auto cache5 = base::MakeRefCounted<AppCache>(service.storage(), 55);
149 cache5->set_complete(true);
150 cache5->set_update_time(now + base::TimeDelta::FromDays(1)); // same as 4
151 group->AddCache(cache5.get());
152 EXPECT_EQ(cache4.get(), group->newest_complete_cache()); // no change
153
154 // Old caches can always be removed.
155 group->RemoveCache(cache1.get());
156 EXPECT_FALSE(cache1->owning_group());
157 EXPECT_EQ(cache4.get(), group->newest_complete_cache()); // newest unchanged
158
159 // Remove rest of caches.
160 group->RemoveCache(cache2.get());
161 EXPECT_FALSE(cache2->owning_group());
162 EXPECT_EQ(cache4.get(), group->newest_complete_cache()); // newest unchanged
163 group->RemoveCache(cache3.get());
164 EXPECT_FALSE(cache3->owning_group());
165 EXPECT_EQ(cache4.get(), group->newest_complete_cache()); // newest unchanged
166 group->RemoveCache(cache5.get());
167 EXPECT_FALSE(cache5->owning_group());
168 EXPECT_EQ(cache4.get(), group->newest_complete_cache()); // newest unchanged
169 group->RemoveCache(cache4.get()); // newest removed
170 EXPECT_FALSE(cache4->owning_group());
171 EXPECT_FALSE(group->newest_complete_cache()); // no more newest cache
172
173 // Can remove newest cache if there are older caches.
174 group->AddCache(cache1.get());
175 EXPECT_EQ(cache1.get(), group->newest_complete_cache());
176 group->AddCache(cache4.get());
177 EXPECT_EQ(cache4.get(), group->newest_complete_cache());
178 group->RemoveCache(cache4.get()); // remove newest
179 EXPECT_FALSE(cache4->owning_group());
180 EXPECT_FALSE(group->newest_complete_cache()); // newest removed
181 }
182
TEST_F(AppCacheGroupTest,CleanupUnusedGroup)183 TEST_F(AppCacheGroupTest, CleanupUnusedGroup) {
184 MockAppCacheService service;
185 TestAppCacheFrontend frontend;
186 AppCacheGroup* group =
187 new AppCacheGroup(service.storage(), GURL("http://foo.com"), 111);
188
189 auto host1_id = base::UnguessableToken::Create();
190 const int kMockProcessId1 = 1;
191 const int kMockProcessId2 = 2;
192 AppCacheHost host1(
193 host1_id, kMockProcessId1, /*render_frame_id=*/1,
194 ChildProcessSecurityPolicyImpl::GetInstance()->CreateHandle(
195 kMockProcessId1),
196 frontend.Bind(host1_id), &service);
197 auto host2_id = base::UnguessableToken::Create();
198 AppCacheHost host2(
199 host2_id, kMockProcessId2, /*render_frame_id=*/2,
200 ChildProcessSecurityPolicyImpl::GetInstance()->CreateHandle(
201 kMockProcessId2),
202 frontend.Bind(host2_id), &service);
203
204 base::Time now = base::Time::Now();
205
206 AppCache* cache1 = new AppCache(service.storage(), 111);
207 cache1->set_complete(true);
208 cache1->set_update_time(now);
209 group->AddCache(cache1);
210 EXPECT_EQ(cache1, group->newest_complete_cache());
211
212 host1.AssociateCompleteCache(cache1);
213 base::RunLoop().RunUntilIdle();
214 EXPECT_EQ(frontend.last_host_id_, host1.host_id());
215 EXPECT_EQ(frontend.last_cache_id_, cache1->cache_id());
216 EXPECT_EQ(frontend.last_status_,
217 blink::mojom::AppCacheStatus::APPCACHE_STATUS_IDLE);
218
219 host2.AssociateCompleteCache(cache1);
220 base::RunLoop().RunUntilIdle();
221 EXPECT_EQ(frontend.last_host_id_, host2.host_id());
222 EXPECT_EQ(frontend.last_cache_id_, cache1->cache_id());
223 EXPECT_EQ(frontend.last_status_,
224 blink::mojom::AppCacheStatus::APPCACHE_STATUS_IDLE);
225
226 AppCache* cache2 = new AppCache(service.storage(), 222);
227 cache2->set_complete(true);
228 cache2->set_update_time(now + base::TimeDelta::FromDays(1));
229 group->AddCache(cache2);
230 EXPECT_EQ(cache2, group->newest_complete_cache());
231
232 // Unassociate all hosts from older cache.
233 host1.AssociateNoCache(GURL());
234 host2.AssociateNoCache(GURL());
235 base::RunLoop().RunUntilIdle();
236 EXPECT_EQ(frontend.last_host_id_, host2.host_id());
237 EXPECT_EQ(frontend.last_cache_id_, blink::mojom::kAppCacheNoCacheId);
238 EXPECT_EQ(frontend.last_status_,
239 blink::mojom::AppCacheStatus::APPCACHE_STATUS_UNCACHED);
240 }
241
TEST_F(AppCacheGroupTest,StartUpdate)242 TEST_F(AppCacheGroupTest, StartUpdate) {
243 MockAppCacheService service;
244 auto group = base::MakeRefCounted<AppCacheGroup>(service.storage(),
245 GURL("http://foo.com"), 111);
246
247 // Set state to checking to prevent update job from executing fetches.
248 group->update_status_ = AppCacheGroup::CHECKING;
249 group->StartUpdate();
250 AppCacheUpdateJob* update = group->update_job_;
251 EXPECT_TRUE(update != nullptr);
252
253 // Start another update, check that same update job is in use.
254 group->StartUpdateWithHost(nullptr);
255 EXPECT_EQ(update, group->update_job_);
256
257 // Deleting the update should restore the group to AppCacheGroup::IDLE.
258 delete update;
259 EXPECT_TRUE(group->update_job_ == nullptr);
260 EXPECT_EQ(AppCacheGroup::IDLE, group->update_status());
261 }
262
TEST_F(AppCacheGroupTest,CancelUpdate)263 TEST_F(AppCacheGroupTest, CancelUpdate) {
264 MockAppCacheService service;
265 auto group = base::MakeRefCounted<AppCacheGroup>(service.storage(),
266 GURL("http://foo.com"), 111);
267
268 // Set state to checking to prevent update job from executing fetches.
269 group->update_status_ = AppCacheGroup::CHECKING;
270 group->StartUpdate();
271 AppCacheUpdateJob* update = group->update_job_;
272 EXPECT_TRUE(update != nullptr);
273
274 // Deleting the group should cancel the update.
275 TestUpdateObserver observer;
276 group->AddUpdateObserver(&observer);
277 group = nullptr; // causes group to be deleted
278 EXPECT_TRUE(observer.update_completed_);
279 EXPECT_FALSE(observer.group_has_cache_);
280 }
281
TEST_F(AppCacheGroupTest,QueueUpdate)282 TEST_F(AppCacheGroupTest, QueueUpdate) {
283 MockAppCacheService service;
284 auto group = base::MakeRefCounted<AppCacheGroup>(service.storage(),
285 GURL("http://foo.com"), 111);
286
287 // Set state to checking to prevent update job from executing fetches.
288 group->update_status_ = AppCacheGroup::CHECKING;
289 group->StartUpdate();
290 EXPECT_TRUE(group->update_job_);
291
292 // Pretend group's update job is terminating so that next update is queued.
293 group->update_job_->internal_state_ =
294 AppCacheUpdateJobState::REFETCH_MANIFEST;
295 EXPECT_TRUE(group->update_job_->IsTerminating());
296
297 TestAppCacheFrontend frontend;
298 TestAppCacheHost host(base::UnguessableToken::Create(), &frontend, &service);
299 host.new_master_entry_url_ = GURL("http://foo.com/bar.txt");
300 group->StartUpdateWithNewMasterEntry(&host, host.new_master_entry_url_);
301 EXPECT_FALSE(group->queued_updates_.empty());
302
303 group->AddUpdateObserver(&host);
304 EXPECT_FALSE(group->FindObserver(&host, group->observers_));
305 EXPECT_TRUE(group->FindObserver(&host, group->queued_observers_));
306
307 // Delete update to cause it to complete. Verify no update complete notice
308 // sent to host.
309 delete group->update_job_;
310 EXPECT_EQ(AppCacheGroup::IDLE, group->update_status_);
311 EXPECT_FALSE(group->restart_update_task_.IsCancelled());
312 EXPECT_FALSE(host.update_completed_);
313
314 // Start another update. Cancels task and will run queued updates.
315 group->update_status_ = AppCacheGroup::CHECKING; // prevent actual fetches
316 group->StartUpdate();
317 EXPECT_TRUE(group->update_job_);
318 EXPECT_TRUE(group->restart_update_task_.IsCancelled());
319 EXPECT_TRUE(group->queued_updates_.empty());
320 EXPECT_FALSE(group->update_job_->pending_master_entries_.empty());
321 EXPECT_FALSE(group->FindObserver(&host, group->queued_observers_));
322 EXPECT_TRUE(group->FindObserver(&host, group->observers_));
323
324 // Delete update to cause it to complete. Verify host is notified.
325 delete group->update_job_;
326 EXPECT_EQ(AppCacheGroup::IDLE, group->update_status_);
327 EXPECT_TRUE(group->restart_update_task_.IsCancelled());
328 EXPECT_TRUE(host.update_completed_);
329 }
330
331 } // namespace content
332