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