OLD | NEW |
---|---|
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "content/browser/service_worker/service_worker_context_request_handler. h" | 5 #include "content/browser/service_worker/service_worker_context_request_handler. h" |
6 | 6 |
7 #include "base/time/time.h" | 7 #include "base/time/time.h" |
8 #include "content/browser/service_worker/service_worker_context_core.h" | 8 #include "content/browser/service_worker/service_worker_context_core.h" |
9 #include "content/browser/service_worker/service_worker_provider_host.h" | 9 #include "content/browser/service_worker/service_worker_provider_host.h" |
10 #include "content/browser/service_worker/service_worker_read_from_cache_job.h" | 10 #include "content/browser/service_worker/service_worker_read_from_cache_job.h" |
11 #include "content/browser/service_worker/service_worker_storage.h" | 11 #include "content/browser/service_worker/service_worker_storage.h" |
12 #include "content/browser/service_worker/service_worker_version.h" | 12 #include "content/browser/service_worker/service_worker_version.h" |
13 #include "content/browser/service_worker/service_worker_write_to_cache_job.h" | 13 #include "content/browser/service_worker/service_worker_write_to_cache_job.h" |
14 #include "content/public/browser/resource_context.h" | |
15 #include "content/public/common/resource_response_info.h" | 14 #include "content/public/common/resource_response_info.h" |
16 #include "net/base/load_flags.h" | 15 #include "net/base/load_flags.h" |
16 #include "net/log/net_log.h" | |
17 #include "net/log/net_log_event_type.h" | |
18 #include "net/log/net_log_with_source.h" | |
17 #include "net/url_request/url_request.h" | 19 #include "net/url_request/url_request.h" |
20 #include "net/url_request/url_request_error_job.h" | |
18 | 21 |
19 namespace content { | 22 namespace content { |
20 | 23 |
24 namespace { | |
25 | |
26 bool IsInstalled(const ServiceWorkerVersion* version) { | |
27 switch (version->status()) { | |
28 case ServiceWorkerVersion::NEW: | |
29 case ServiceWorkerVersion::INSTALLING: | |
30 return false; | |
31 case ServiceWorkerVersion::INSTALLED: | |
32 case ServiceWorkerVersion::ACTIVATING: | |
33 case ServiceWorkerVersion::ACTIVATED: | |
34 return true; | |
35 case ServiceWorkerVersion::REDUNDANT: | |
36 return false; | |
37 } | |
38 NOTREACHED(); | |
39 return false; | |
40 } | |
41 } | |
horo
2017/01/10 07:09:27
} // namespace
falken
2017/01/10 08:17:46
Done.
| |
42 | |
21 ServiceWorkerContextRequestHandler::ServiceWorkerContextRequestHandler( | 43 ServiceWorkerContextRequestHandler::ServiceWorkerContextRequestHandler( |
22 base::WeakPtr<ServiceWorkerContextCore> context, | 44 base::WeakPtr<ServiceWorkerContextCore> context, |
23 base::WeakPtr<ServiceWorkerProviderHost> provider_host, | 45 base::WeakPtr<ServiceWorkerProviderHost> provider_host, |
24 base::WeakPtr<storage::BlobStorageContext> blob_storage_context, | 46 base::WeakPtr<storage::BlobStorageContext> blob_storage_context, |
25 ResourceType resource_type) | 47 ResourceType resource_type) |
26 : ServiceWorkerRequestHandler(context, | 48 : ServiceWorkerRequestHandler(context, |
27 provider_host, | 49 provider_host, |
28 blob_storage_context, | 50 blob_storage_context, |
29 resource_type), | 51 resource_type), |
30 version_(provider_host_->running_hosted_version()) { | 52 version_(provider_host_->running_hosted_version()) { |
31 DCHECK(provider_host_->IsHostToRunningServiceWorker()); | 53 DCHECK(provider_host_->IsHostToRunningServiceWorker()); |
32 if (resource_type_ == RESOURCE_TYPE_SERVICE_WORKER) | 54 if (resource_type_ == RESOURCE_TYPE_SERVICE_WORKER) |
33 version_->NotifyMainScriptRequestHandlerCreated(); | 55 version_->NotifyMainScriptRequestHandlerCreated(); |
34 } | 56 } |
35 | 57 |
36 ServiceWorkerContextRequestHandler::~ServiceWorkerContextRequestHandler() { | 58 ServiceWorkerContextRequestHandler::~ServiceWorkerContextRequestHandler() { |
37 } | 59 } |
38 | 60 |
61 // static | |
62 std::string ServiceWorkerContextRequestHandler::CreateJobStatusToString( | |
63 CreateJobStatus status) { | |
64 switch (status) { | |
65 case CreateJobStatus::UNINITIALIZED: | |
66 return "UNINITIALIZED"; | |
67 case CreateJobStatus::WRITE_JOB_FOR_REGISTER: | |
68 return "WRITE_JOB_FOR_REGISTER"; | |
69 case CreateJobStatus::WRITE_JOB_FOR_UPDATE: | |
70 return "WRITE_JOB_FOR_UPDATE"; | |
71 case CreateJobStatus::READ_JOB: | |
72 return "READ_JOB"; | |
73 case CreateJobStatus::READ_JOB_FOR_DUPLICATE_SCRIPT_IMPORT: | |
74 return "READ_JOB_FOR_DUPLICATE_SCRIPT_IMPORT"; | |
75 case CreateJobStatus::ERROR_NO_PROVIDER: | |
76 return "ERROR_NO_PROVIDER"; | |
77 case CreateJobStatus::ERROR_NO_VERSION: | |
78 return "ERROR_NO_VERSION"; | |
79 case CreateJobStatus::ERROR_REDUNDANT_VERSION: | |
80 return "ERROR_REDUNDANT_VERSION"; | |
81 case CreateJobStatus::ERROR_NO_CONTEXT: | |
82 return "ERROR_NO_CONTEXT"; | |
83 case CreateJobStatus::ERROR_REDIRECT: | |
84 return "ERROR_REDIRECT"; | |
85 case CreateJobStatus::ERROR_UNINSTALLED_SCRIPT_IMPORT: | |
86 return "ERROR_UNINSTALLED_SCRIPT_IMPORT"; | |
87 case CreateJobStatus::ERROR_OUT_OF_RESOURCE_IDS: | |
88 return "ERROR_OUT_OF_RESOURCE_IDS"; | |
89 case CreateJobStatus::NUM_TYPES: | |
90 NOTREACHED(); | |
91 } | |
92 NOTREACHED() << static_cast<int>(status); | |
93 return "UNKNOWN"; | |
94 } | |
95 | |
39 net::URLRequestJob* ServiceWorkerContextRequestHandler::MaybeCreateJob( | 96 net::URLRequestJob* ServiceWorkerContextRequestHandler::MaybeCreateJob( |
40 net::URLRequest* request, | 97 net::URLRequest* request, |
41 net::NetworkDelegate* network_delegate, | 98 net::NetworkDelegate* network_delegate, |
42 ResourceContext* resource_context) { | 99 ResourceContext* resource_context) { |
43 CreateJobStatus status = CreateJobStatus::DID_NOT_SET_STATUS; | |
44 net::URLRequestJob* job = | |
45 MaybeCreateJobImpl(&status, request, network_delegate, resource_context); | |
46 if (resource_type_ == RESOURCE_TYPE_SERVICE_WORKER) { | |
47 version_->NotifyMainScriptJobCreated(status); | |
48 } | |
49 return job; | |
50 } | |
51 | |
52 net::URLRequestJob* ServiceWorkerContextRequestHandler::MaybeCreateJobImpl( | |
53 CreateJobStatus* out_status, | |
54 net::URLRequest* request, | |
55 net::NetworkDelegate* network_delegate, | |
56 ResourceContext* resource_context) { | |
57 if (!provider_host_) { | |
58 *out_status = CreateJobStatus::NO_PROVIDER; | |
59 return nullptr; | |
60 } | |
61 if (!version_) { | |
62 *out_status = CreateJobStatus::NO_VERSION; | |
63 return nullptr; | |
64 } | |
65 if (!context_) { | |
66 *out_status = CreateJobStatus::NO_CONTEXT; | |
67 return nullptr; | |
68 } | |
69 | |
70 // We currently have no use case for hijacking a redirected request. | |
71 if (request->url_chain().size() > 1) { | |
72 *out_status = CreateJobStatus::IGNORE_REDIRECT; | |
73 return nullptr; | |
74 } | |
75 | |
76 // We only use the script cache for main script loading and | 100 // We only use the script cache for main script loading and |
77 // importScripts(), even if a cached script is xhr'd, we don't | 101 // importScripts(), even if a cached script is xhr'd, we don't |
78 // retrieve it from the script cache. | 102 // retrieve it from the script cache. |
79 // TODO(michaeln): Get the desired behavior clarified in the spec, | 103 // TODO(michaeln): Get the desired behavior clarified in the spec, |
horo
2017/01/10 07:09:27
Remove or update this TODO comment.
falken
2017/01/10 08:17:46
I think it's still unresolved. I'll change michael
| |
80 // and make tweak the behavior here to match. | 104 // and make tweak the behavior here to match. |
81 if (resource_type_ != RESOURCE_TYPE_SERVICE_WORKER && | 105 if (resource_type_ != RESOURCE_TYPE_SERVICE_WORKER && |
82 resource_type_ != RESOURCE_TYPE_SCRIPT) { | 106 resource_type_ != RESOURCE_TYPE_SCRIPT) { |
83 *out_status = CreateJobStatus::IGNORE_NON_SCRIPT; | 107 // Fall back to network. |
84 return nullptr; | 108 return nullptr; |
85 } | 109 } |
86 | 110 |
87 if (ShouldAddToScriptCache(request->url())) { | 111 CreateJobStatus status = CreateJobStatus::UNINITIALIZED; |
88 ServiceWorkerRegistration* registration = | 112 net::URLRequestJob* job = |
89 context_->GetLiveRegistration(version_->registration_id()); | 113 MaybeCreateJobImpl(request, network_delegate, &status); |
90 DCHECK(registration); // We're registering or updating so must be there. | 114 const bool is_main_script = resource_type_ == RESOURCE_TYPE_SERVICE_WORKER; |
115 // TODO(falken): Add UMA for CreateJobStatus. | |
116 if (is_main_script) | |
117 version_->NotifyMainScriptJobCreated(status); | |
91 | 118 |
92 int64_t resource_id = context_->storage()->NewResourceId(); | 119 // If a job couldn't be created, return an error job rather than falling back |
93 if (resource_id == kInvalidServiceWorkerResourceId) { | 120 // to network. Otherwise the renderer may receive the response from network |
94 *out_status = CreateJobStatus::OUT_OF_RESOURCE_IDS; | 121 // and start a service worker whose browser-side ServiceWorkerVersion is not |
122 // properly initialized. | |
123 if (!job) { | |
horo
2017/01/10 07:09:27
Early return.
if (job)
return;
...
falken
2017/01/10 08:17:46
Done.
| |
124 // As an exception, allow installed service workers to use importScripts() | |
125 // to import non-installed scripts. | |
126 // TODO(falken): This is a spec violation that should be deprecated and | |
127 // removed. See https://github.com/w3c/ServiceWorker/issues/1021 | |
128 if (status == CreateJobStatus::ERROR_UNINSTALLED_SCRIPT_IMPORT) { | |
129 // Fall back to network. | |
95 return nullptr; | 130 return nullptr; |
96 } | 131 } |
97 | 132 |
98 // Bypass the browser cache for initial installs and update | 133 std::string error_str(CreateJobStatusToString(status)); |
99 // checks after 24 hours have passed. | 134 request->net_log().AddEvent( |
100 int extra_load_flags = 0; | 135 net::NetLogEventType:: |
101 base::TimeDelta time_since_last_check = | 136 SERVICE_WORKER_SCRIPT_LOAD_UNHANDLED_REQUEST_ERROR, |
102 base::Time::Now() - registration->last_update_check(); | 137 net::NetLog::StringCallback("error", &error_str)); |
103 if (time_since_last_check > base::TimeDelta::FromHours( | |
104 kServiceWorkerScriptMaxCacheAgeInHours) || | |
105 version_->force_bypass_cache_for_scripts()) { | |
106 extra_load_flags = net::LOAD_BYPASS_CACHE; | |
107 } | |
108 | 138 |
109 ServiceWorkerVersion* stored_version = registration->waiting_version() | 139 return new net::URLRequestErrorJob(request, network_delegate, |
110 ? registration->waiting_version() | 140 net::Error::ERR_FAILED); |
111 : registration->active_version(); | |
112 int64_t incumbent_resource_id = kInvalidServiceWorkerResourceId; | |
113 if (stored_version && stored_version->script_url() == request->url()) { | |
114 incumbent_resource_id = | |
115 stored_version->script_cache_map()->LookupResourceId(request->url()); | |
116 } | |
117 if (resource_type_ == RESOURCE_TYPE_SERVICE_WORKER) | |
118 version_->embedded_worker()->OnURLJobCreatedForMainScript(); | |
119 *out_status = incumbent_resource_id == kInvalidServiceWorkerResourceId | |
120 ? CreateJobStatus::CREATED_WRITE_JOB_FOR_REGISTER | |
121 : CreateJobStatus::CREATED_WRITE_JOB_FOR_UPDATE; | |
122 return new ServiceWorkerWriteToCacheJob( | |
123 request, network_delegate, resource_type_, context_, version_.get(), | |
124 extra_load_flags, resource_id, incumbent_resource_id); | |
125 } | 141 } |
126 | 142 |
127 int64_t resource_id = kInvalidServiceWorkerResourceId; | 143 return job; |
128 if (ShouldReadFromScriptCache(request->url(), &resource_id)) { | 144 } |
129 if (resource_type_ == RESOURCE_TYPE_SERVICE_WORKER) | 145 |
130 version_->embedded_worker()->OnURLJobCreatedForMainScript(); | 146 net::URLRequestJob* ServiceWorkerContextRequestHandler::MaybeCreateJobImpl( |
131 *out_status = CreateJobStatus::CREATED_READ_JOB; | 147 net::URLRequest* request, |
148 net::NetworkDelegate* network_delegate, | |
149 CreateJobStatus* out_status) { | |
150 if (!provider_host_) { | |
151 *out_status = CreateJobStatus::ERROR_NO_PROVIDER; | |
152 return nullptr; | |
153 } | |
154 if (!context_) { | |
155 *out_status = CreateJobStatus::ERROR_NO_CONTEXT; | |
156 return nullptr; | |
157 } | |
158 if (!version_) { | |
159 *out_status = CreateJobStatus::ERROR_NO_VERSION; | |
160 return nullptr; | |
161 } | |
162 // This could happen if browser-side has set the status to redundant but the | |
163 // worker has not yet stopped. The worker is already doomed so just reject the | |
164 // request. Handle it specially here because otherwise it'd be unclear whether | |
165 // "REDUNDANT" should count as installed or not installed when making | |
166 // decisions about how to handle the request and logging UMA. | |
167 if (version_->status() == ServiceWorkerVersion::REDUNDANT) { | |
168 *out_status = CreateJobStatus::ERROR_REDUNDANT_VERSION; | |
169 return nullptr; | |
170 } | |
171 | |
172 // We currently have no use case for hijacking a redirected request. | |
173 if (request->url_chain().size() > 1) { | |
174 *out_status = CreateJobStatus::ERROR_REDIRECT; | |
175 return nullptr; | |
176 } | |
177 | |
178 const bool is_main_script = resource_type_ == RESOURCE_TYPE_SERVICE_WORKER; | |
179 int resource_id = | |
180 version_->script_cache_map()->LookupResourceId(request->url()); | |
181 if (resource_id != kInvalidServiceWorkerResourceId) { | |
182 if (IsInstalled(version_.get())) { | |
183 // An installed worker is loading a stored script. | |
184 if (is_main_script) | |
185 version_->embedded_worker()->OnURLJobCreatedForMainScript(); | |
186 *out_status = CreateJobStatus::READ_JOB; | |
187 } else { | |
188 // A new worker is loading a stored script. The script was already | |
189 // imported (or the main script is being recursively imported). | |
190 *out_status = CreateJobStatus::READ_JOB_FOR_DUPLICATE_SCRIPT_IMPORT; | |
191 } | |
132 return new ServiceWorkerReadFromCacheJob(request, network_delegate, | 192 return new ServiceWorkerReadFromCacheJob(request, network_delegate, |
133 resource_type_, context_, version_, | 193 resource_type_, context_, version_, |
134 resource_id); | 194 resource_id); |
135 } | 195 } |
136 | 196 |
137 // NULL means use the network. | 197 // An installed worker is importing a non-stored script. |
138 *out_status = CreateJobStatus::IGNORE_UNKNOWN; | 198 if (IsInstalled(version_.get())) { |
139 return nullptr; | 199 DCHECK(!is_main_script); |
140 } | 200 *out_status = CreateJobStatus::ERROR_UNINSTALLED_SCRIPT_IMPORT; |
201 return nullptr; | |
202 } | |
141 | 203 |
142 bool ServiceWorkerContextRequestHandler::ShouldAddToScriptCache( | 204 // A new worker is loading a script for the first time. Create a write job to |
143 const GURL& url) { | 205 // store the script. |
144 // We only write imports that occur during the initial eval. | 206 ServiceWorkerRegistration* registration = |
145 if (version_->status() != ServiceWorkerVersion::NEW && | 207 context_->GetLiveRegistration(version_->registration_id()); |
146 version_->status() != ServiceWorkerVersion::INSTALLING) { | 208 DCHECK(registration); // We're registering or updating so must be there. |
147 return false; | 209 |
210 resource_id = context_->storage()->NewResourceId(); | |
211 if (resource_id == kInvalidServiceWorkerResourceId) { | |
212 *out_status = CreateJobStatus::ERROR_OUT_OF_RESOURCE_IDS; | |
213 return nullptr; | |
148 } | 214 } |
149 return version_->script_cache_map()->LookupResourceId(url) == | |
150 kInvalidServiceWorkerResourceId; | |
151 } | |
152 | 215 |
153 bool ServiceWorkerContextRequestHandler::ShouldReadFromScriptCache( | 216 // Bypass the browser cache for initial installs and update checks after 24 |
154 const GURL& url, | 217 // hours have passed. |
155 int64_t* resource_id_out) { | 218 int extra_load_flags = 0; |
156 // We don't read from the script cache until the version is INSTALLED. | 219 base::TimeDelta time_since_last_check = |
157 if (version_->status() == ServiceWorkerVersion::NEW || | 220 base::Time::Now() - registration->last_update_check(); |
158 version_->status() == ServiceWorkerVersion::INSTALLING) | 221 if (time_since_last_check > |
159 return false; | 222 base::TimeDelta::FromHours(kServiceWorkerScriptMaxCacheAgeInHours) || |
160 *resource_id_out = version_->script_cache_map()->LookupResourceId(url); | 223 version_->force_bypass_cache_for_scripts()) { |
161 return *resource_id_out != kInvalidServiceWorkerResourceId; | 224 extra_load_flags = net::LOAD_BYPASS_CACHE; |
225 } | |
226 | |
227 ServiceWorkerVersion* stored_version = registration->waiting_version() | |
228 ? registration->waiting_version() | |
229 : registration->active_version(); | |
230 int64_t incumbent_resource_id = kInvalidServiceWorkerResourceId; | |
231 if (stored_version && stored_version->script_url() == request->url()) { | |
232 incumbent_resource_id = | |
233 stored_version->script_cache_map()->LookupResourceId(request->url()); | |
234 } | |
235 if (resource_type_ == RESOURCE_TYPE_SERVICE_WORKER) | |
horo
2017/01/10 07:09:27
if (is_main_script)
falken
2017/01/10 08:17:46
Done.
| |
236 version_->embedded_worker()->OnURLJobCreatedForMainScript(); | |
237 *out_status = incumbent_resource_id == kInvalidServiceWorkerResourceId | |
238 ? CreateJobStatus::WRITE_JOB_FOR_REGISTER | |
239 : CreateJobStatus::WRITE_JOB_FOR_UPDATE; | |
240 return new ServiceWorkerWriteToCacheJob( | |
241 request, network_delegate, resource_type_, context_, version_.get(), | |
242 extra_load_flags, resource_id, incumbent_resource_id); | |
162 } | 243 } |
163 | 244 |
164 } // namespace content | 245 } // namespace content |
OLD | NEW |