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