1 // Copyright 2018 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/service_worker/service_worker_update_checker.h"
6
7 #include <utility>
8
9 #include "base/bind.h"
10 #include "base/task/post_task.h"
11 #include "base/trace_event/trace_event.h"
12 #include "content/browser/loader/browser_initiated_resource_request.h"
13 #include "content/browser/service_worker/service_worker_consts.h"
14 #include "content/browser/service_worker/service_worker_context_core.h"
15 #include "content/browser/service_worker/service_worker_context_wrapper.h"
16 #include "content/browser/service_worker/service_worker_storage.h"
17 #include "content/browser/service_worker/service_worker_version.h"
18 #include "content/browser/storage_partition_impl.h"
19 #include "content/public/browser/browser_task_traits.h"
20 #include "content/public/browser/browser_thread.h"
21 #include "content/public/browser/content_browser_client.h"
22 #include "content/public/common/content_client.h"
23 #include "content/public/common/content_features.h"
24 #include "content/public/common/content_switches.h"
25 #include "net/http/http_request_headers.h"
26 #include "services/network/public/cpp/constants.h"
27 #include "services/network/public/cpp/features.h"
28
29 namespace content {
30
31 namespace {
32
SetUpOnUI(base::WeakPtr<ServiceWorkerProcessManager> process_manager,void * trace_id,base::OnceCallback<void (net::HttpRequestHeaders,ServiceWorkerUpdatedScriptLoader::BrowserContextGetter)> callback)33 void SetUpOnUI(
34 base::WeakPtr<ServiceWorkerProcessManager> process_manager,
35 void* trace_id,
36 base::OnceCallback<void(
37 net::HttpRequestHeaders,
38 ServiceWorkerUpdatedScriptLoader::BrowserContextGetter)> callback) {
39 TRACE_EVENT_WITH_FLOW0(
40 "ServiceWorker", "ServiceWorkerUpdateChecker::anonymous::SetUpOnUI",
41 trace_id, TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
42
43 DCHECK_CURRENTLY_ON(BrowserThread::UI);
44 if (!process_manager || process_manager->IsShutdown()) {
45 // If it's being shut down, ServiceWorkerUpdateChecker is going to be
46 // destroyed after this task. We do nothing here.
47 return;
48 }
49
50 net::HttpRequestHeaders headers;
51
52 // Set the accept header to '*/*'.
53 // https://fetch.spec.whatwg.org/#concept-fetch
54 headers.SetHeader(net::HttpRequestHeaders::kAccept,
55 network::kDefaultAcceptHeaderValue);
56
57 BrowserContext* browser_context = process_manager->browser_context();
58 blink::mojom::RendererPreferences renderer_preferences;
59 GetContentClient()->browser()->UpdateRendererPreferencesForWorker(
60 browser_context, &renderer_preferences);
61 UpdateAdditionalHeadersForBrowserInitiatedRequest(
62 &headers, browser_context, /*should_update_existing_headers=*/false,
63 renderer_preferences);
64
65 ServiceWorkerUpdatedScriptLoader::BrowserContextGetter
66 browser_context_getter = base::BindRepeating(
67 [](base::WeakPtr<ServiceWorkerProcessManager> process_manager)
68 -> BrowserContext* {
69 DCHECK_CURRENTLY_ON(BrowserThread::UI);
70 if (process_manager)
71 return process_manager->browser_context();
72 return nullptr;
73 },
74 process_manager);
75
76 RunOrPostTaskOnThread(FROM_HERE, ServiceWorkerContext::GetCoreThreadId(),
77 base::BindOnce(std::move(callback), std::move(headers),
78 browser_context_getter));
79 }
80
81 } // namespace
82
ServiceWorkerUpdateChecker(std::vector<storage::mojom::ServiceWorkerResourceRecordPtr> scripts_to_compare,const GURL & main_script_url,int64_t main_script_resource_id,scoped_refptr<ServiceWorkerVersion> version_to_update,scoped_refptr<network::SharedURLLoaderFactory> loader_factory,bool force_bypass_cache,blink::mojom::ServiceWorkerUpdateViaCache update_via_cache,base::TimeDelta time_since_last_check,ServiceWorkerContextCore * context,blink::mojom::FetchClientSettingsObjectPtr fetch_client_settings_object)83 ServiceWorkerUpdateChecker::ServiceWorkerUpdateChecker(
84 std::vector<storage::mojom::ServiceWorkerResourceRecordPtr>
85 scripts_to_compare,
86 const GURL& main_script_url,
87 int64_t main_script_resource_id,
88 scoped_refptr<ServiceWorkerVersion> version_to_update,
89 scoped_refptr<network::SharedURLLoaderFactory> loader_factory,
90 bool force_bypass_cache,
91 blink::mojom::ServiceWorkerUpdateViaCache update_via_cache,
92 base::TimeDelta time_since_last_check,
93 ServiceWorkerContextCore* context,
94 blink::mojom::FetchClientSettingsObjectPtr fetch_client_settings_object)
95 : main_script_url_(main_script_url),
96 main_script_resource_id_(main_script_resource_id),
97 scripts_to_compare_(std::move(scripts_to_compare)),
98 version_to_update_(std::move(version_to_update)),
99 loader_factory_(std::move(loader_factory)),
100 force_bypass_cache_(force_bypass_cache),
101 update_via_cache_(update_via_cache),
102 time_since_last_check_(time_since_last_check),
103 context_(context),
104 fetch_client_settings_object_(std::move(fetch_client_settings_object)) {
105 DCHECK(context_);
106 DCHECK(fetch_client_settings_object_);
107 DCHECK(fetch_client_settings_object_->outgoing_referrer.is_valid());
108 }
109
110 ServiceWorkerUpdateChecker::~ServiceWorkerUpdateChecker() = default;
111
Start(UpdateStatusCallback callback)112 void ServiceWorkerUpdateChecker::Start(UpdateStatusCallback callback) {
113 TRACE_EVENT_WITH_FLOW1("ServiceWorker", "ServiceWorkerUpdateChecker::Start",
114 this, TRACE_EVENT_FLAG_FLOW_OUT, "main_script_url",
115 main_script_url_.spec());
116
117 DCHECK(!scripts_to_compare_.empty());
118 callback_ = std::move(callback);
119
120 RunOrPostTaskOnThread(
121 FROM_HERE, BrowserThread::UI,
122 base::BindOnce(&SetUpOnUI, context_->process_manager()->AsWeakPtr(),
123 base::Unretained(this),
124 base::BindOnce(&ServiceWorkerUpdateChecker::DidSetUpOnUI,
125 weak_factory_.GetWeakPtr())));
126 }
127
DidSetUpOnUI(net::HttpRequestHeaders header,ServiceWorkerUpdatedScriptLoader::BrowserContextGetter browser_context_getter)128 void ServiceWorkerUpdateChecker::DidSetUpOnUI(
129 net::HttpRequestHeaders header,
130 ServiceWorkerUpdatedScriptLoader::BrowserContextGetter
131 browser_context_getter) {
132 default_headers_ = std::move(header);
133 browser_context_getter_ = std::move(browser_context_getter);
134 CheckOneScript(main_script_url_, main_script_resource_id_);
135 }
136
OnOneUpdateCheckFinished(int64_t old_resource_id,const GURL & script_url,ServiceWorkerSingleScriptUpdateChecker::Result result,std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker::FailureInfo> failure_info,std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker::PausedState> paused_state)137 void ServiceWorkerUpdateChecker::OnOneUpdateCheckFinished(
138 int64_t old_resource_id,
139 const GURL& script_url,
140 ServiceWorkerSingleScriptUpdateChecker::Result result,
141 std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker::FailureInfo>
142 failure_info,
143 std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker::PausedState>
144 paused_state) {
145 TRACE_EVENT_WITH_FLOW2(
146 "ServiceWorker", "ServiceWorkerUpdateChecker::OnOneUpdateCheckFinished",
147 this, TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "script_url",
148 script_url.spec(), "result",
149 ServiceWorkerSingleScriptUpdateChecker::ResultToString(result));
150
151 bool is_main_script = script_url == main_script_url_;
152 // We only cares about the failures on the main script because an imported
153 // script might not exist anymore and fail to be loaded because it's not
154 // imported in a new script.
155 // See also https://github.com/w3c/ServiceWorker/issues/1374 for more details.
156 if (is_main_script &&
157 result == ServiceWorkerSingleScriptUpdateChecker::Result::kFailed) {
158 TRACE_EVENT_WITH_FLOW0(
159 "ServiceWorker",
160 "ServiceWorkerUpdateChecker::OnOneUpdateCheckFinished_MainScriptFailed",
161 this, TRACE_EVENT_FLAG_FLOW_IN);
162
163 std::move(callback_).Run(
164 ServiceWorkerSingleScriptUpdateChecker::Result::kFailed,
165 std::move(failure_info));
166 return;
167 }
168
169 script_check_results_.emplace(
170 script_url,
171 ComparedScriptInfo(old_resource_id, result, std::move(paused_state),
172 std::move(failure_info)));
173 if (running_checker_->network_accessed())
174 network_accessed_ = true;
175
176 if (is_main_script) {
177 cross_origin_embedder_policy_ =
178 running_checker_->cross_origin_embedder_policy();
179 }
180
181 if (ServiceWorkerSingleScriptUpdateChecker::Result::kDifferent == result) {
182 TRACE_EVENT_WITH_FLOW0(
183 "ServiceWorker",
184 "ServiceWorkerUpdateChecker::OnOneUpdateCheckFinished_UpdateFound",
185 this, TRACE_EVENT_FLAG_FLOW_IN);
186
187 updated_script_url_ = script_url;
188
189 // Found an updated script. Stop the comparison of scripts here and
190 // return to ServiceWorkerRegisterJob to continue the update.
191 // Note that running |callback_| will delete |this|.
192 std::move(callback_).Run(
193 ServiceWorkerSingleScriptUpdateChecker::Result::kDifferent,
194 nullptr /* failure_info */);
195 return;
196 }
197
198 if (next_script_index_to_compare_ >= scripts_to_compare_.size()) {
199 TRACE_EVENT_WITH_FLOW0(
200 "ServiceWorker",
201 "ServiceWorkerUpdateChecker::OnOneUpdateCheckFinished_NoUpdate", this,
202 TRACE_EVENT_FLAG_FLOW_IN);
203
204 // None of scripts had any updates.
205 // Running |callback_| will delete |this|.
206 std::move(callback_).Run(
207 ServiceWorkerSingleScriptUpdateChecker::Result::kIdentical,
208 nullptr /* failure_info */);
209 return;
210 }
211
212 // The main script should be skipped since it should be compared first.
213 if (scripts_to_compare_[next_script_index_to_compare_]->url ==
214 main_script_url_) {
215 next_script_index_to_compare_++;
216 if (next_script_index_to_compare_ >= scripts_to_compare_.size()) {
217 TRACE_EVENT_WITH_FLOW0(
218 "ServiceWorker",
219 "ServiceWorkerUpdateChecker::OnOneUpdateCheckFinished_NoUpdate", this,
220 TRACE_EVENT_FLAG_FLOW_IN);
221
222 // None of scripts had any updates.
223 // Running |callback_| will delete |this|.
224 std::move(callback_).Run(
225 ServiceWorkerSingleScriptUpdateChecker::Result::kIdentical,
226 nullptr /* failure_info */);
227 return;
228 }
229 }
230
231 const GURL& next_url =
232 scripts_to_compare_[next_script_index_to_compare_]->url;
233 int64_t next_resource_id =
234 scripts_to_compare_[next_script_index_to_compare_]->resource_id;
235 next_script_index_to_compare_++;
236 CheckOneScript(next_url, next_resource_id);
237 }
238
239 std::map<GURL, ServiceWorkerUpdateChecker::ComparedScriptInfo>
TakeComparedResults()240 ServiceWorkerUpdateChecker::TakeComparedResults() {
241 return std::move(script_check_results_);
242 }
243
CheckOneScript(const GURL & url,const int64_t resource_id)244 void ServiceWorkerUpdateChecker::CheckOneScript(const GURL& url,
245 const int64_t resource_id) {
246 TRACE_EVENT_WITH_FLOW1(
247 "ServiceWorker", "ServiceWorkerUpdateChecker::CheckOneScript", this,
248 TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "url", url.spec());
249
250 DCHECK_NE(blink::mojom::kInvalidServiceWorkerResourceId, resource_id)
251 << "All the target scripts should be stored in the storage.";
252
253 version_to_update_->context()->storage()->GetNewResourceId(base::BindOnce(
254 &ServiceWorkerUpdateChecker::OnResourceIdAssignedForOneScriptCheck,
255 weak_factory_.GetWeakPtr(), url, resource_id));
256 }
257
OnResourceIdAssignedForOneScriptCheck(const GURL & url,const int64_t resource_id,const int64_t new_resource_id)258 void ServiceWorkerUpdateChecker::OnResourceIdAssignedForOneScriptCheck(
259 const GURL& url,
260 const int64_t resource_id,
261 const int64_t new_resource_id) {
262 // When the url matches with the main script url, we can always think that
263 // it's the main script even if a main script imports itself because the
264 // second load (network load for imported script) should hit the script
265 // cache map and it doesn't issue network request.
266 const bool is_main_script = url == main_script_url_;
267
268 ServiceWorkerStorage* storage = version_to_update_->context()->storage();
269
270 // We need two identical readers for comparing and reading the resource for
271 // |resource_id| from the storage.
272 auto compare_reader = storage->CreateResponseReader(resource_id);
273 auto copy_reader = storage->CreateResponseReader(resource_id);
274
275 auto writer = storage->CreateResponseWriter(new_resource_id);
276 running_checker_ = std::make_unique<ServiceWorkerSingleScriptUpdateChecker>(
277 url, is_main_script, main_script_url_, version_to_update_->scope(),
278 force_bypass_cache_, update_via_cache_, fetch_client_settings_object_,
279 time_since_last_check_, default_headers_, browser_context_getter_,
280 loader_factory_, std::move(compare_reader), std::move(copy_reader),
281 std::move(writer),
282 base::BindOnce(&ServiceWorkerUpdateChecker::OnOneUpdateCheckFinished,
283 weak_factory_.GetWeakPtr(), resource_id));
284 }
285
286 ServiceWorkerUpdateChecker::ComparedScriptInfo::ComparedScriptInfo() = default;
287
ComparedScriptInfo(int64_t old_resource_id,ServiceWorkerSingleScriptUpdateChecker::Result result,std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker::PausedState> paused_state,std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker::FailureInfo> failure_info)288 ServiceWorkerUpdateChecker::ComparedScriptInfo::ComparedScriptInfo(
289 int64_t old_resource_id,
290 ServiceWorkerSingleScriptUpdateChecker::Result result,
291 std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker::PausedState>
292 paused_state,
293 std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker::FailureInfo>
294 failure_info)
295 : old_resource_id(old_resource_id),
296 result(result),
297 paused_state(std::move(paused_state)),
298 failure_info(std::move(failure_info)) {}
299
300 ServiceWorkerUpdateChecker::ComparedScriptInfo::~ComparedScriptInfo() = default;
301
302 ServiceWorkerUpdateChecker::ComparedScriptInfo::ComparedScriptInfo(
303 ServiceWorkerUpdateChecker::ComparedScriptInfo&& other) = default;
304
305 ServiceWorkerUpdateChecker::ComparedScriptInfo&
306 ServiceWorkerUpdateChecker::ComparedScriptInfo::operator=(
307 ServiceWorkerUpdateChecker::ComparedScriptInfo&& other) = default;
308 } // namespace content
309