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_internals_ui.h" | 5 #include "content/browser/service_worker/service_worker_internals_ui.h" |
6 | 6 |
7 #include <string> | 7 #include <string> |
8 #include <vector> | 8 #include <vector> |
9 | 9 |
10 #include "base/bind.h" | 10 #include "base/bind.h" |
(...skipping 17 matching lines...) Expand all Loading... |
28 | 28 |
29 using base::DictionaryValue; | 29 using base::DictionaryValue; |
30 using base::FundamentalValue; | 30 using base::FundamentalValue; |
31 using base::ListValue; | 31 using base::ListValue; |
32 using base::StringValue; | 32 using base::StringValue; |
33 using base::Value; | 33 using base::Value; |
34 using base::WeakPtr; | 34 using base::WeakPtr; |
35 | 35 |
36 namespace content { | 36 namespace content { |
37 | 37 |
38 // This class proxies calls to the ServiceWorker APIs on the IO | 38 namespace { |
39 // thread, and then calls back JavaScript on the UI thread. | 39 |
40 class ServiceWorkerInternalsUI::OperationProxy | 40 void OperationCompleteCallback(WeakPtr<ServiceWorkerInternalsUI> internals, |
41 : public base::RefCountedThreadSafe< | 41 int callback_id, |
42 ServiceWorkerInternalsUI::OperationProxy> { | 42 ServiceWorkerStatusCode status) { |
43 public: | 43 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { |
44 OperationProxy(const WeakPtr<ServiceWorkerInternalsUI> internals, | 44 BrowserThread::PostTask( |
45 scoped_ptr<ListValue> original_args) | 45 BrowserThread::UI, |
46 : internals_(internals), original_args_(original_args.Pass()) {} | 46 FROM_HERE, |
47 | 47 base::Bind(OperationCompleteCallback, internals, callback_id, status)); |
48 void GetRegistrationsOnIOThread(int partition_id, | 48 return; |
49 ServiceWorkerContextWrapper* context, | 49 } |
50 const base::FilePath& context_path); | 50 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
51 void UnregisterOnIOThread(scoped_refptr<ServiceWorkerContextWrapper> context, | 51 if (internals) { |
52 const GURL& scope); | 52 internals->web_ui()->CallJavascriptFunction( |
53 void StartWorkerOnIOThread(scoped_refptr<ServiceWorkerContextWrapper> context, | 53 "serviceworker.onOperationComplete", |
54 const GURL& scope); | 54 FundamentalValue(static_cast<int>(status)), |
55 void StopWorkerOnIOThread(scoped_refptr<ServiceWorkerContextWrapper> context, | 55 FundamentalValue(callback_id)); |
56 const GURL& scope); | 56 } |
57 void DispatchSyncEventToWorkerOnIOThread( | 57 } |
58 scoped_refptr<ServiceWorkerContextWrapper> context, | 58 |
59 const GURL& scope); | 59 void CallServiceWorkerVersionMethodWithVersionID( |
60 void InspectWorkerOnIOThread( | 60 ServiceWorkerInternalsUI::ServiceWorkerVersionMethod method, |
61 scoped_refptr<ServiceWorkerContextWrapper> context, | 61 scoped_refptr<ServiceWorkerContextWrapper> context, |
62 const GURL& scope); | 62 int64 version_id, |
63 | 63 const ServiceWorkerInternalsUI::StatusCallback& callback) { |
64 private: | 64 if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) { |
65 friend class base::RefCountedThreadSafe<OperationProxy>; | 65 BrowserThread::PostTask( |
66 ~OperationProxy() {} | 66 BrowserThread::IO, |
67 void OnHaveRegistrations( | 67 FROM_HERE, |
68 int partition_id, | 68 base::Bind(CallServiceWorkerVersionMethodWithVersionID, |
69 const base::FilePath& context_path, | 69 method, |
70 const std::vector<ServiceWorkerRegistrationInfo>& registrations); | 70 context, |
71 | 71 version_id, |
72 void OperationComplete(ServiceWorkerStatusCode status); | 72 callback)); |
73 | 73 return; |
74 void StartActiveWorker( | 74 } |
75 ServiceWorkerStatusCode status, | 75 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
76 const scoped_refptr<ServiceWorkerRegistration>& registration); | 76 scoped_refptr<ServiceWorkerVersion> version = |
77 | 77 context->context()->GetLiveVersion(version_id); |
78 void StopActiveWorker( | 78 if (!version) { |
79 ServiceWorkerStatusCode status, | 79 callback.Run(SERVICE_WORKER_ERROR_NOT_FOUND); |
80 const scoped_refptr<ServiceWorkerRegistration>& registration); | 80 return; |
81 | 81 } |
82 void DispatchSyncEventToActiveWorker( | 82 (*version.*method)(callback); |
83 ServiceWorkerStatusCode status, | 83 } |
84 const scoped_refptr<ServiceWorkerRegistration>& registration); | 84 |
85 | 85 void UnregisterWithScope( |
86 void InspectActiveWorker( | 86 scoped_refptr<ServiceWorkerContextWrapper> context, |
87 const ServiceWorkerContextCore* const service_worker_context, | 87 const GURL& scope, |
88 ServiceWorkerStatusCode status, | 88 const ServiceWorkerInternalsUI::StatusCallback& callback) { |
89 const scoped_refptr<ServiceWorkerRegistration>& registration); | 89 if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) { |
90 | 90 BrowserThread::PostTask( |
91 void InspectWorkerOnUIThread( | 91 BrowserThread::IO, |
92 const ServiceWorkerContextCore* const service_worker_context, | 92 FROM_HERE, |
93 int64 version_id); | 93 base::Bind(UnregisterWithScope, context, scope, callback)); |
94 | 94 return; |
95 WeakPtr<ServiceWorkerInternalsUI> internals_; | 95 } |
96 scoped_ptr<ListValue> original_args_; | 96 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
97 }; | 97 context->context()->UnregisterServiceWorker(scope, callback); |
| 98 } |
| 99 |
| 100 void WorkerStarted(const scoped_refptr<ServiceWorkerRegistration>& registration, |
| 101 const ServiceWorkerInternalsUI::StatusCallback& callback, |
| 102 ServiceWorkerStatusCode status) { |
| 103 callback.Run(status); |
| 104 } |
| 105 |
| 106 void StartActiveWorker( |
| 107 const ServiceWorkerInternalsUI::StatusCallback& callback, |
| 108 ServiceWorkerStatusCode status, |
| 109 const scoped_refptr<ServiceWorkerRegistration>& registration) { |
| 110 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 111 if (status == SERVICE_WORKER_OK) { |
| 112 // Pass the reference of |registration| to WorkerStarted callback to prevent |
| 113 // it from being deleted while starting the worker. If the refcount of |
| 114 // |registration| is 1, it will be deleted after WorkerStarted is called. |
| 115 registration->active_version()->StartWorker( |
| 116 base::Bind(WorkerStarted, registration, callback)); |
| 117 return; |
| 118 } |
| 119 callback.Run(SERVICE_WORKER_ERROR_NOT_FOUND); |
| 120 } |
| 121 |
| 122 void FindRegistrationForPattern( |
| 123 scoped_refptr<ServiceWorkerContextWrapper> context, |
| 124 const GURL& scope, |
| 125 const ServiceWorkerStorage::FindRegistrationCallback callback) { |
| 126 if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) { |
| 127 BrowserThread::PostTask( |
| 128 BrowserThread::IO, |
| 129 FROM_HERE, |
| 130 base::Bind(FindRegistrationForPattern, context, scope, callback)); |
| 131 return; |
| 132 } |
| 133 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 134 context->context()->storage()->FindRegistrationForPattern(scope, callback); |
| 135 } |
| 136 |
| 137 void UpdateVersionInfo(const ServiceWorkerVersionInfo& version, |
| 138 DictionaryValue* info) { |
| 139 switch (version.running_status) { |
| 140 case ServiceWorkerVersion::STOPPED: |
| 141 info->SetString("running_status", "STOPPED"); |
| 142 break; |
| 143 case ServiceWorkerVersion::STARTING: |
| 144 info->SetString("running_status", "STARTING"); |
| 145 break; |
| 146 case ServiceWorkerVersion::RUNNING: |
| 147 info->SetString("running_status", "RUNNING"); |
| 148 break; |
| 149 case ServiceWorkerVersion::STOPPING: |
| 150 info->SetString("running_status", "STOPPING"); |
| 151 break; |
| 152 } |
| 153 |
| 154 switch (version.status) { |
| 155 case ServiceWorkerVersion::NEW: |
| 156 info->SetString("status", "NEW"); |
| 157 break; |
| 158 case ServiceWorkerVersion::INSTALLING: |
| 159 info->SetString("status", "INSTALLING"); |
| 160 break; |
| 161 case ServiceWorkerVersion::INSTALLED: |
| 162 info->SetString("status", "INSTALLED"); |
| 163 break; |
| 164 case ServiceWorkerVersion::ACTIVATING: |
| 165 info->SetString("status", "ACTIVATING"); |
| 166 break; |
| 167 case ServiceWorkerVersion::ACTIVE: |
| 168 info->SetString("status", "ACTIVE"); |
| 169 break; |
| 170 case ServiceWorkerVersion::DEACTIVATED: |
| 171 info->SetString("status", "DEACTIVATED"); |
| 172 break; |
| 173 } |
| 174 info->SetString("version_id", base::Int64ToString(version.version_id)); |
| 175 info->SetInteger("process_id", version.process_id); |
| 176 info->SetInteger("thread_id", version.thread_id); |
| 177 info->SetInteger("devtools_agent_route_id", version.devtools_agent_route_id); |
| 178 } |
| 179 |
| 180 ListValue* GetRegistrationListValue( |
| 181 const std::vector<ServiceWorkerRegistrationInfo>& registrations) { |
| 182 ListValue* result = new ListValue(); |
| 183 for (std::vector<ServiceWorkerRegistrationInfo>::const_iterator it = |
| 184 registrations.begin(); |
| 185 it != registrations.end(); |
| 186 ++it) { |
| 187 const ServiceWorkerRegistrationInfo& registration = *it; |
| 188 DictionaryValue* registration_info = new DictionaryValue(); |
| 189 registration_info->SetString("scope", registration.pattern.spec()); |
| 190 registration_info->SetString("script_url", registration.script_url.spec()); |
| 191 registration_info->SetString( |
| 192 "registration_id", base::Int64ToString(registration.registration_id)); |
| 193 |
| 194 if (!registration.active_version.is_null) { |
| 195 DictionaryValue* active_info = new DictionaryValue(); |
| 196 UpdateVersionInfo(registration.active_version, active_info); |
| 197 registration_info->Set("active", active_info); |
| 198 } |
| 199 |
| 200 if (!registration.pending_version.is_null) { |
| 201 DictionaryValue* pending_info = new DictionaryValue(); |
| 202 UpdateVersionInfo(registration.pending_version, pending_info); |
| 203 registration_info->Set("pending", pending_info); |
| 204 } |
| 205 |
| 206 result->Append(registration_info); |
| 207 } |
| 208 return result; |
| 209 } |
| 210 |
| 211 ListValue* GetVersionListValue( |
| 212 const std::vector<ServiceWorkerVersionInfo>& versions) { |
| 213 ListValue* result = new ListValue(); |
| 214 for (std::vector<ServiceWorkerVersionInfo>::const_iterator it = |
| 215 versions.begin(); |
| 216 it != versions.end(); |
| 217 ++it) { |
| 218 DictionaryValue* info = new DictionaryValue(); |
| 219 UpdateVersionInfo(*it, info); |
| 220 result->Append(info); |
| 221 } |
| 222 return result; |
| 223 } |
| 224 |
| 225 void GetRegistrationsOnIOThread( |
| 226 scoped_refptr<ServiceWorkerContextWrapper> context, |
| 227 base::Callback<void(const std::vector<ServiceWorkerRegistrationInfo>&)> |
| 228 callback) { |
| 229 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 230 context->context()->storage()->GetAllRegistrations(callback); |
| 231 } |
| 232 |
| 233 void OnStoredRegistrations( |
| 234 scoped_refptr<ServiceWorkerContextWrapper> context, |
| 235 base::Callback<void(const std::vector<ServiceWorkerRegistrationInfo>&, |
| 236 const std::vector<ServiceWorkerVersionInfo>&, |
| 237 const std::vector<ServiceWorkerRegistrationInfo>&)> |
| 238 callback, |
| 239 const std::vector<ServiceWorkerRegistrationInfo>& stored_registrations) { |
| 240 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 241 BrowserThread::PostTask( |
| 242 BrowserThread::UI, |
| 243 FROM_HERE, |
| 244 base::Bind(callback, |
| 245 context->context()->GetAllLiveRegistrationInfo(), |
| 246 context->context()->GetAllLiveVersionInfo(), |
| 247 stored_registrations)); |
| 248 } |
| 249 |
| 250 void OnAllRegistrations( |
| 251 WeakPtr<ServiceWorkerInternalsUI> internals, |
| 252 int partition_id, |
| 253 const base::FilePath& context_path, |
| 254 const std::vector<ServiceWorkerRegistrationInfo>& live_registrations, |
| 255 const std::vector<ServiceWorkerVersionInfo>& live_versions, |
| 256 const std::vector<ServiceWorkerRegistrationInfo>& stored_registrations) { |
| 257 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 258 if (!internals) |
| 259 return; |
| 260 |
| 261 ScopedVector<const Value> args; |
| 262 args.push_back(GetRegistrationListValue(live_registrations)); |
| 263 args.push_back(GetVersionListValue(live_versions)); |
| 264 args.push_back(GetRegistrationListValue(stored_registrations)); |
| 265 args.push_back(new FundamentalValue(partition_id)); |
| 266 args.push_back(new StringValue(context_path.value())); |
| 267 internals->web_ui()->CallJavascriptFunction("serviceworker.onPartitionData", |
| 268 args.get()); |
| 269 } |
| 270 |
| 271 } // namespace |
98 | 272 |
99 class ServiceWorkerInternalsUI::PartitionObserver | 273 class ServiceWorkerInternalsUI::PartitionObserver |
100 : public ServiceWorkerContextObserver { | 274 : public ServiceWorkerContextObserver { |
101 public: | 275 public: |
102 PartitionObserver(int partition_id, WebUI* web_ui) | 276 PartitionObserver(int partition_id, WebUI* web_ui) |
103 : partition_id_(partition_id), web_ui_(web_ui) {} | 277 : partition_id_(partition_id), web_ui_(web_ui) {} |
104 virtual ~PartitionObserver() {} | 278 virtual ~PartitionObserver() {} |
105 // ServiceWorkerContextObserver overrides: | 279 // ServiceWorkerContextObserver overrides: |
106 virtual void OnWorkerStarted(int64 version_id, | 280 virtual void OnWorkerStarted(int64 version_id, |
107 int process_id, | 281 int process_id, |
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
202 | 376 |
203 BrowserContext* browser_context = | 377 BrowserContext* browser_context = |
204 web_ui->GetWebContents()->GetBrowserContext(); | 378 web_ui->GetWebContents()->GetBrowserContext(); |
205 WebUIDataSource::Add(browser_context, source); | 379 WebUIDataSource::Add(browser_context, source); |
206 | 380 |
207 web_ui->RegisterMessageCallback( | 381 web_ui->RegisterMessageCallback( |
208 "getAllRegistrations", | 382 "getAllRegistrations", |
209 base::Bind(&ServiceWorkerInternalsUI::GetAllRegistrations, | 383 base::Bind(&ServiceWorkerInternalsUI::GetAllRegistrations, |
210 base::Unretained(this))); | 384 base::Unretained(this))); |
211 web_ui->RegisterMessageCallback( | 385 web_ui->RegisterMessageCallback( |
212 "start", | 386 "stop", |
213 base::Bind(&ServiceWorkerInternalsUI::StartWorker, | 387 base::Bind(&ServiceWorkerInternalsUI::CallServiceWorkerVersionMethod, |
214 base::Unretained(this))); | 388 base::Unretained(this), |
| 389 &ServiceWorkerVersion::StopWorker)); |
215 web_ui->RegisterMessageCallback( | 390 web_ui->RegisterMessageCallback( |
216 "stop", | 391 "sync", |
217 base::Bind(&ServiceWorkerInternalsUI::StopWorker, | 392 base::Bind(&ServiceWorkerInternalsUI::CallServiceWorkerVersionMethod, |
| 393 base::Unretained(this), |
| 394 &ServiceWorkerVersion::DispatchSyncEvent)); |
| 395 web_ui->RegisterMessageCallback( |
| 396 "inspect", |
| 397 base::Bind(&ServiceWorkerInternalsUI::InspectWorker, |
218 base::Unretained(this))); | 398 base::Unretained(this))); |
219 web_ui->RegisterMessageCallback( | 399 web_ui->RegisterMessageCallback( |
220 "unregister", | 400 "unregister", |
221 base::Bind(&ServiceWorkerInternalsUI::Unregister, | 401 base::Bind(&ServiceWorkerInternalsUI::Unregister, |
222 base::Unretained(this))); | 402 base::Unretained(this))); |
223 web_ui->RegisterMessageCallback( | 403 web_ui->RegisterMessageCallback( |
224 "sync", | 404 "start", |
225 base::Bind(&ServiceWorkerInternalsUI::DispatchSyncEventToWorker, | 405 base::Bind(&ServiceWorkerInternalsUI::StartWorker, |
226 base::Unretained(this))); | |
227 web_ui->RegisterMessageCallback( | |
228 "inspect", | |
229 base::Bind(&ServiceWorkerInternalsUI::InspectWorker, | |
230 base::Unretained(this))); | 406 base::Unretained(this))); |
231 } | 407 } |
232 | 408 |
233 ServiceWorkerInternalsUI::~ServiceWorkerInternalsUI() { | 409 ServiceWorkerInternalsUI::~ServiceWorkerInternalsUI() { |
234 BrowserContext* browser_context = | 410 BrowserContext* browser_context = |
235 web_ui()->GetWebContents()->GetBrowserContext(); | 411 web_ui()->GetWebContents()->GetBrowserContext(); |
236 // Safe to use base::Unretained(this) because | 412 // Safe to use base::Unretained(this) because |
237 // ForEachStoragePartition is synchronous. | 413 // ForEachStoragePartition is synchronous. |
238 BrowserContext::StoragePartitionCallback remove_observer_cb = | 414 BrowserContext::StoragePartitionCallback remove_observer_cb = |
239 base::Bind(&ServiceWorkerInternalsUI::RemoveObserverFromStoragePartition, | 415 base::Bind(&ServiceWorkerInternalsUI::RemoveObserverFromStoragePartition, |
240 base::Unretained(this)); | 416 base::Unretained(this)); |
241 BrowserContext::ForEachStoragePartition(browser_context, remove_observer_cb); | 417 BrowserContext::ForEachStoragePartition(browser_context, remove_observer_cb); |
242 } | 418 } |
243 | 419 |
244 void ServiceWorkerInternalsUI::GetAllRegistrations(const ListValue* args) { | 420 void ServiceWorkerInternalsUI::GetAllRegistrations(const ListValue* args) { |
245 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 421 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
246 | |
247 BrowserContext* browser_context = | 422 BrowserContext* browser_context = |
248 web_ui()->GetWebContents()->GetBrowserContext(); | 423 web_ui()->GetWebContents()->GetBrowserContext(); |
249 | |
250 // Safe to use base::Unretained(this) because | 424 // Safe to use base::Unretained(this) because |
251 // ForEachStoragePartition is synchronous. | 425 // ForEachStoragePartition is synchronous. |
252 BrowserContext::StoragePartitionCallback add_context_cb = | 426 BrowserContext::StoragePartitionCallback add_context_cb = |
253 base::Bind(&ServiceWorkerInternalsUI::AddContextFromStoragePartition, | 427 base::Bind(&ServiceWorkerInternalsUI::AddContextFromStoragePartition, |
254 base::Unretained(this)); | 428 base::Unretained(this)); |
255 BrowserContext::ForEachStoragePartition(browser_context, add_context_cb); | 429 BrowserContext::ForEachStoragePartition(browser_context, add_context_cb); |
256 } | 430 } |
257 | 431 |
258 void ServiceWorkerInternalsUI::AddContextFromStoragePartition( | 432 void ServiceWorkerInternalsUI::AddContextFromStoragePartition( |
259 StoragePartition* partition) { | 433 StoragePartition* partition) { |
260 int partition_id = 0; | 434 int partition_id = 0; |
261 scoped_refptr<ServiceWorkerContextWrapper> context = | 435 scoped_refptr<ServiceWorkerContextWrapper> context = |
262 static_cast<ServiceWorkerContextWrapper*>( | 436 static_cast<ServiceWorkerContextWrapper*>( |
263 partition->GetServiceWorkerContext()); | 437 partition->GetServiceWorkerContext()); |
264 if (PartitionObserver* observer = | 438 if (PartitionObserver* observer = |
265 observers_.get(reinterpret_cast<uintptr_t>(partition))) { | 439 observers_.get(reinterpret_cast<uintptr_t>(partition))) { |
266 partition_id = observer->partition_id(); | 440 partition_id = observer->partition_id(); |
267 } else { | 441 } else { |
268 partition_id = next_partition_id_++; | 442 partition_id = next_partition_id_++; |
269 scoped_ptr<PartitionObserver> new_observer( | 443 scoped_ptr<PartitionObserver> new_observer( |
270 new PartitionObserver(partition_id, web_ui())); | 444 new PartitionObserver(partition_id, web_ui())); |
271 context->AddObserver(new_observer.get()); | 445 context->AddObserver(new_observer.get()); |
272 observers_.set(reinterpret_cast<uintptr_t>(partition), new_observer.Pass()); | 446 observers_.set(reinterpret_cast<uintptr_t>(partition), new_observer.Pass()); |
273 } | 447 } |
274 BrowserThread::PostTask( | 448 BrowserThread::PostTask( |
275 BrowserThread::IO, | 449 BrowserThread::IO, |
276 FROM_HERE, | 450 FROM_HERE, |
277 base::Bind( | 451 base::Bind(GetRegistrationsOnIOThread, |
278 &ServiceWorkerInternalsUI::OperationProxy::GetRegistrationsOnIOThread, | 452 context, |
279 new OperationProxy(AsWeakPtr(), scoped_ptr<ListValue>()), | 453 base::Bind(OnStoredRegistrations, |
280 partition_id, | 454 context, |
281 context, | 455 base::Bind(OnAllRegistrations, |
282 partition->GetPath())); | 456 AsWeakPtr(), |
| 457 partition_id, |
| 458 partition->GetPath())))); |
283 } | 459 } |
284 | 460 |
285 void ServiceWorkerInternalsUI::RemoveObserverFromStoragePartition( | 461 void ServiceWorkerInternalsUI::RemoveObserverFromStoragePartition( |
286 StoragePartition* partition) { | 462 StoragePartition* partition) { |
287 scoped_ptr<PartitionObserver> observer( | 463 scoped_ptr<PartitionObserver> observer( |
288 observers_.take_and_erase(reinterpret_cast<uintptr_t>(partition))); | 464 observers_.take_and_erase(reinterpret_cast<uintptr_t>(partition))); |
289 if (!observer.get()) | 465 if (!observer.get()) |
290 return; | 466 return; |
291 scoped_refptr<ServiceWorkerContextWrapper> context = | 467 scoped_refptr<ServiceWorkerContextWrapper> context = |
292 static_cast<ServiceWorkerContextWrapper*>( | 468 static_cast<ServiceWorkerContextWrapper*>( |
293 partition->GetServiceWorkerContext()); | 469 partition->GetServiceWorkerContext()); |
294 context->RemoveObserver(observer.get()); | 470 context->RemoveObserver(observer.get()); |
295 } | 471 } |
296 | 472 |
297 namespace { | 473 void ServiceWorkerInternalsUI::FindContext( |
298 void FindContext(const base::FilePath& partition_path, | 474 int partition_id, |
299 StoragePartition** result_partition, | 475 StoragePartition** result_partition, |
300 scoped_refptr<ServiceWorkerContextWrapper>* result_context, | 476 StoragePartition* storage_partition) const { |
301 StoragePartition* storage_partition) { | 477 PartitionObserver* observer = |
302 if (storage_partition->GetPath() == partition_path) { | 478 observers_.get(reinterpret_cast<uintptr_t>(storage_partition)); |
| 479 if (observer && partition_id == observer->partition_id()) { |
303 *result_partition = storage_partition; | 480 *result_partition = storage_partition; |
304 *result_context = static_cast<ServiceWorkerContextWrapper*>( | |
305 storage_partition->GetServiceWorkerContext()); | |
306 } | 481 } |
307 } | 482 } |
308 } // namespace | |
309 | 483 |
310 bool ServiceWorkerInternalsUI::GetRegistrationInfo( | 484 bool ServiceWorkerInternalsUI::GetServiceWorkerContext( |
311 const ListValue* args, | 485 int partition_id, |
312 base::FilePath* partition_path, | |
313 GURL* scope, | |
314 scoped_refptr<ServiceWorkerContextWrapper>* context) const { | 486 scoped_refptr<ServiceWorkerContextWrapper>* context) const { |
315 base::FilePath::StringType path_string; | |
316 if (!args->GetString(0, &path_string)) | |
317 return false; | |
318 *partition_path = base::FilePath(path_string); | |
319 | |
320 std::string scope_string; | |
321 if (!args->GetString(1, &scope_string)) | |
322 return false; | |
323 *scope = GURL(scope_string); | |
324 | |
325 BrowserContext* browser_context = | 487 BrowserContext* browser_context = |
326 web_ui()->GetWebContents()->GetBrowserContext(); | 488 web_ui()->GetWebContents()->GetBrowserContext(); |
327 | |
328 StoragePartition* result_partition(NULL); | 489 StoragePartition* result_partition(NULL); |
329 BrowserContext::StoragePartitionCallback find_context_cb = | 490 BrowserContext::StoragePartitionCallback find_context_cb = |
330 base::Bind(&FindContext, *partition_path, &result_partition, context); | 491 base::Bind(&ServiceWorkerInternalsUI::FindContext, |
| 492 base::Unretained(this), |
| 493 partition_id, |
| 494 &result_partition); |
331 BrowserContext::ForEachStoragePartition(browser_context, find_context_cb); | 495 BrowserContext::ForEachStoragePartition(browser_context, find_context_cb); |
332 | 496 if (!result_partition) |
333 if (!result_partition || !(*context)) | |
334 return false; | 497 return false; |
335 | 498 *context = static_cast<ServiceWorkerContextWrapper*>( |
| 499 result_partition->GetServiceWorkerContext()); |
336 return true; | 500 return true; |
337 } | 501 } |
338 | 502 |
339 void ServiceWorkerInternalsUI::DispatchSyncEventToWorker( | 503 void ServiceWorkerInternalsUI::CallServiceWorkerVersionMethod( |
| 504 ServiceWorkerVersionMethod method, |
340 const ListValue* args) { | 505 const ListValue* args) { |
341 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 506 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
342 base::FilePath partition_path; | 507 int callback_id; |
343 GURL scope; | 508 int partition_id; |
| 509 int64 version_id; |
| 510 std::string version_id_string; |
| 511 const DictionaryValue* cmd_args = NULL; |
344 scoped_refptr<ServiceWorkerContextWrapper> context; | 512 scoped_refptr<ServiceWorkerContextWrapper> context; |
345 if (!GetRegistrationInfo(args, &partition_path, &scope, &context)) | 513 if (!args->GetInteger(0, &callback_id) || |
| 514 !args->GetDictionary(1, &cmd_args) || |
| 515 !cmd_args->GetInteger("partition_id", &partition_id) || |
| 516 !GetServiceWorkerContext(partition_id, &context) || |
| 517 !cmd_args->GetString("version_id", &version_id_string) || |
| 518 !base::StringToInt64(version_id_string, &version_id)) { |
346 return; | 519 return; |
| 520 } |
347 | 521 |
348 scoped_ptr<ListValue> args_copy(args->DeepCopy()); | 522 base::Callback<void(ServiceWorkerStatusCode)> callback = |
349 BrowserThread::PostTask( | 523 base::Bind(OperationCompleteCallback, AsWeakPtr(), callback_id); |
350 BrowserThread::IO, | 524 CallServiceWorkerVersionMethodWithVersionID( |
351 FROM_HERE, | 525 method, context, version_id, callback); |
352 base::Bind(&ServiceWorkerInternalsUI::OperationProxy:: | |
353 DispatchSyncEventToWorkerOnIOThread, | |
354 new OperationProxy(AsWeakPtr(), args_copy.Pass()), | |
355 context, | |
356 scope)); | |
357 } | 526 } |
358 | 527 |
359 void ServiceWorkerInternalsUI::InspectWorker(const ListValue* args) { | 528 void ServiceWorkerInternalsUI::InspectWorker(const ListValue* args) { |
360 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 529 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
361 base::FilePath partition_path; | 530 int callback_id; |
362 GURL scope; | 531 int process_id; |
| 532 int devtools_agent_route_id; |
| 533 const DictionaryValue* cmd_args = NULL; |
363 scoped_refptr<ServiceWorkerContextWrapper> context; | 534 scoped_refptr<ServiceWorkerContextWrapper> context; |
364 if (!GetRegistrationInfo(args, &partition_path, &scope, &context)) | 535 if (!args->GetInteger(0, &callback_id) || |
| 536 !args->GetDictionary(1, &cmd_args) || |
| 537 !cmd_args->GetInteger("process_id", &process_id) || |
| 538 !cmd_args->GetInteger("devtools_agent_route_id", |
| 539 &devtools_agent_route_id)) { |
365 return; | 540 return; |
366 scoped_ptr<ListValue> args_copy(args->DeepCopy()); | 541 } |
367 BrowserThread::PostTask( | 542 base::Callback<void(ServiceWorkerStatusCode)> callback = |
368 BrowserThread::IO, | 543 base::Bind(OperationCompleteCallback, AsWeakPtr(), callback_id); |
369 FROM_HERE, | 544 scoped_refptr<DevToolsAgentHost> agent_host( |
370 base::Bind( | 545 EmbeddedWorkerDevToolsManager::GetInstance() |
371 &ServiceWorkerInternalsUI::OperationProxy::InspectWorkerOnIOThread, | 546 ->GetDevToolsAgentHostForWorker(process_id, devtools_agent_route_id)); |
372 new OperationProxy(AsWeakPtr(), args_copy.Pass()), | 547 if (!agent_host) { |
373 context, | 548 callback.Run(SERVICE_WORKER_ERROR_NOT_FOUND); |
374 scope)); | 549 return; |
| 550 } |
| 551 DevToolsManagerImpl::GetInstance()->Inspect( |
| 552 web_ui()->GetWebContents()->GetBrowserContext(), agent_host.get()); |
| 553 callback.Run(SERVICE_WORKER_OK); |
375 } | 554 } |
376 | 555 |
377 void ServiceWorkerInternalsUI::Unregister(const ListValue* args) { | 556 void ServiceWorkerInternalsUI::Unregister(const ListValue* args) { |
378 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 557 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
379 base::FilePath partition_path; | 558 int callback_id; |
380 GURL scope; | 559 int partition_id; |
| 560 std::string scope_string; |
| 561 const DictionaryValue* cmd_args = NULL; |
381 scoped_refptr<ServiceWorkerContextWrapper> context; | 562 scoped_refptr<ServiceWorkerContextWrapper> context; |
382 if (!GetRegistrationInfo(args, &partition_path, &scope, &context)) | 563 if (!args->GetInteger(0, &callback_id) || |
| 564 !args->GetDictionary(1, &cmd_args) || |
| 565 !cmd_args->GetInteger("partition_id", &partition_id) || |
| 566 !GetServiceWorkerContext(partition_id, &context) || |
| 567 !cmd_args->GetString("scope", &scope_string)) { |
383 return; | 568 return; |
| 569 } |
384 | 570 |
385 scoped_ptr<ListValue> args_copy(args->DeepCopy()); | 571 base::Callback<void(ServiceWorkerStatusCode)> callback = |
386 BrowserThread::PostTask( | 572 base::Bind(OperationCompleteCallback, AsWeakPtr(), callback_id); |
387 BrowserThread::IO, | 573 UnregisterWithScope(context, GURL(scope_string), callback); |
388 FROM_HERE, | |
389 base::Bind( | |
390 &ServiceWorkerInternalsUI::OperationProxy::UnregisterOnIOThread, | |
391 new OperationProxy(AsWeakPtr(), args_copy.Pass()), | |
392 context, | |
393 scope)); | |
394 } | 574 } |
395 | 575 |
396 void ServiceWorkerInternalsUI::StartWorker(const ListValue* args) { | 576 void ServiceWorkerInternalsUI::StartWorker(const ListValue* args) { |
397 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 577 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
398 base::FilePath partition_path; | 578 int callback_id; |
399 GURL scope; | 579 int partition_id; |
| 580 std::string scope_string; |
| 581 const DictionaryValue* cmd_args = NULL; |
400 scoped_refptr<ServiceWorkerContextWrapper> context; | 582 scoped_refptr<ServiceWorkerContextWrapper> context; |
401 if (!GetRegistrationInfo(args, &partition_path, &scope, &context)) | 583 if (!args->GetInteger(0, &callback_id) || |
402 return; | 584 !args->GetDictionary(1, &cmd_args) || |
403 | 585 !cmd_args->GetInteger("partition_id", &partition_id) || |
404 scoped_ptr<ListValue> args_copy(args->DeepCopy()); | 586 !GetServiceWorkerContext(partition_id, &context) || |
405 BrowserThread::PostTask( | 587 !cmd_args->GetString("scope", &scope_string)) { |
406 BrowserThread::IO, | |
407 FROM_HERE, | |
408 base::Bind( | |
409 &ServiceWorkerInternalsUI::OperationProxy::StartWorkerOnIOThread, | |
410 new OperationProxy(AsWeakPtr(), args_copy.Pass()), | |
411 context, | |
412 scope)); | |
413 } | |
414 | |
415 void ServiceWorkerInternalsUI::StopWorker(const ListValue* args) { | |
416 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
417 base::FilePath partition_path; | |
418 GURL scope; | |
419 scoped_refptr<ServiceWorkerContextWrapper> context; | |
420 if (!GetRegistrationInfo(args, &partition_path, &scope, &context)) | |
421 return; | |
422 | |
423 scoped_ptr<ListValue> args_copy(args->DeepCopy()); | |
424 BrowserThread::PostTask( | |
425 BrowserThread::IO, | |
426 FROM_HERE, | |
427 base::Bind( | |
428 &ServiceWorkerInternalsUI::OperationProxy::StopWorkerOnIOThread, | |
429 new OperationProxy(AsWeakPtr(), args_copy.Pass()), | |
430 context, | |
431 scope)); | |
432 } | |
433 | |
434 void ServiceWorkerInternalsUI::OperationProxy::GetRegistrationsOnIOThread( | |
435 int partition_id, | |
436 ServiceWorkerContextWrapper* context, | |
437 const base::FilePath& context_path) { | |
438 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
439 | |
440 context->context()->storage()->GetAllRegistrations( | |
441 base::Bind(&ServiceWorkerInternalsUI::OperationProxy::OnHaveRegistrations, | |
442 this, | |
443 partition_id, | |
444 context_path)); | |
445 } | |
446 | |
447 void ServiceWorkerInternalsUI::OperationProxy::UnregisterOnIOThread( | |
448 scoped_refptr<ServiceWorkerContextWrapper> context, | |
449 const GURL& scope) { | |
450 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
451 context->context()->UnregisterServiceWorker( | |
452 scope, | |
453 base::Bind(&ServiceWorkerInternalsUI::OperationProxy::OperationComplete, | |
454 this)); | |
455 } | |
456 | |
457 void ServiceWorkerInternalsUI::OperationProxy::StartWorkerOnIOThread( | |
458 scoped_refptr<ServiceWorkerContextWrapper> context, | |
459 const GURL& scope) { | |
460 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
461 // TODO(alecflett): Add support for starting/stopping workers for | |
462 // pending versions too. | |
463 context->context()->storage()->FindRegistrationForPattern( | |
464 scope, | |
465 base::Bind(&ServiceWorkerInternalsUI::OperationProxy::StartActiveWorker, | |
466 this)); | |
467 } | |
468 | |
469 void ServiceWorkerInternalsUI::OperationProxy::StopWorkerOnIOThread( | |
470 scoped_refptr<ServiceWorkerContextWrapper> context, | |
471 const GURL& scope) { | |
472 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
473 // TODO(alecflett): Add support for starting/stopping workers for | |
474 // pending versions too. | |
475 context->context()->storage()->FindRegistrationForPattern( | |
476 scope, | |
477 base::Bind(&ServiceWorkerInternalsUI::OperationProxy::StopActiveWorker, | |
478 this)); | |
479 } | |
480 | |
481 void | |
482 ServiceWorkerInternalsUI::OperationProxy::DispatchSyncEventToWorkerOnIOThread( | |
483 scoped_refptr<ServiceWorkerContextWrapper> context, | |
484 const GURL& scope) { | |
485 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
486 context->context()->storage()->FindRegistrationForPattern( | |
487 scope, | |
488 base::Bind(&ServiceWorkerInternalsUI::OperationProxy:: | |
489 DispatchSyncEventToActiveWorker, | |
490 this)); | |
491 } | |
492 | |
493 void ServiceWorkerInternalsUI::OperationProxy::InspectWorkerOnIOThread( | |
494 scoped_refptr<ServiceWorkerContextWrapper> context, | |
495 const GURL& scope) { | |
496 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
497 context->context()->storage()->FindRegistrationForPattern( | |
498 scope, | |
499 base::Bind(&ServiceWorkerInternalsUI::OperationProxy::InspectActiveWorker, | |
500 this, | |
501 context->context())); | |
502 } | |
503 | |
504 namespace { | |
505 void UpdateVersionInfo(const ServiceWorkerVersionInfo& version, | |
506 DictionaryValue* info) { | |
507 switch (version.running_status) { | |
508 case ServiceWorkerVersion::STOPPED: | |
509 info->SetString("running_status", "STOPPED"); | |
510 break; | |
511 case ServiceWorkerVersion::STARTING: | |
512 info->SetString("running_status", "STARTING"); | |
513 break; | |
514 case ServiceWorkerVersion::RUNNING: | |
515 info->SetString("running_status", "RUNNING"); | |
516 break; | |
517 case ServiceWorkerVersion::STOPPING: | |
518 info->SetString("running_status", "STOPPING"); | |
519 break; | |
520 } | |
521 | |
522 switch (version.status) { | |
523 case ServiceWorkerVersion::NEW: | |
524 info->SetString("status", "NEW"); | |
525 break; | |
526 case ServiceWorkerVersion::INSTALLING: | |
527 info->SetString("status", "INSTALLING"); | |
528 break; | |
529 case ServiceWorkerVersion::INSTALLED: | |
530 info->SetString("status", "INSTALLED"); | |
531 break; | |
532 case ServiceWorkerVersion::ACTIVATING: | |
533 info->SetString("status", "ACTIVATING"); | |
534 break; | |
535 case ServiceWorkerVersion::ACTIVE: | |
536 info->SetString("status", "ACTIVE"); | |
537 break; | |
538 case ServiceWorkerVersion::DEACTIVATED: | |
539 info->SetString("status", "DEACTIVATED"); | |
540 break; | |
541 } | |
542 info->SetString("version_id", base::Int64ToString(version.version_id)); | |
543 info->SetInteger("process_id", version.process_id); | |
544 info->SetInteger("thread_id", version.thread_id); | |
545 } | |
546 } // namespace | |
547 | |
548 void ServiceWorkerInternalsUI::OperationProxy::OnHaveRegistrations( | |
549 int partition_id, | |
550 const base::FilePath& context_path, | |
551 const std::vector<ServiceWorkerRegistrationInfo>& registrations) { | |
552 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { | |
553 BrowserThread::PostTask( | |
554 BrowserThread::UI, | |
555 FROM_HERE, | |
556 base::Bind( | |
557 &ServiceWorkerInternalsUI::OperationProxy::OnHaveRegistrations, | |
558 this, | |
559 partition_id, | |
560 context_path, | |
561 registrations)); | |
562 return; | 588 return; |
563 } | 589 } |
564 | 590 |
565 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 591 base::Callback<void(ServiceWorkerStatusCode)> callback = |
566 ListValue result; | 592 base::Bind(OperationCompleteCallback, AsWeakPtr(), callback_id); |
567 for (std::vector<ServiceWorkerRegistrationInfo>::const_iterator it = | 593 FindRegistrationForPattern( |
568 registrations.begin(); | 594 context, GURL(scope_string), base::Bind(StartActiveWorker, callback)); |
569 it != registrations.end(); | |
570 ++it) { | |
571 const ServiceWorkerRegistrationInfo& registration = *it; | |
572 DictionaryValue* registration_info = new DictionaryValue(); | |
573 registration_info->SetString("scope", registration.pattern.spec()); | |
574 registration_info->SetString("script_url", registration.script_url.spec()); | |
575 | |
576 if (!registration.active_version.is_null) { | |
577 DictionaryValue* active_info = new DictionaryValue(); | |
578 UpdateVersionInfo(registration.active_version, active_info); | |
579 registration_info->Set("active", active_info); | |
580 } | |
581 | |
582 if (!registration.pending_version.is_null) { | |
583 DictionaryValue* pending_info = new DictionaryValue(); | |
584 UpdateVersionInfo(registration.pending_version, pending_info); | |
585 registration_info->Set("pending", pending_info); | |
586 } | |
587 | |
588 result.Append(registration_info); | |
589 } | |
590 | |
591 if (internals_) | |
592 internals_->web_ui()->CallJavascriptFunction( | |
593 "serviceworker.onPartitionData", | |
594 result, | |
595 FundamentalValue(partition_id), | |
596 StringValue(context_path.value())); | |
597 } | |
598 | |
599 void ServiceWorkerInternalsUI::OperationProxy::OperationComplete( | |
600 ServiceWorkerStatusCode status) { | |
601 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { | |
602 BrowserThread::PostTask( | |
603 BrowserThread::UI, | |
604 FROM_HERE, | |
605 base::Bind(&ServiceWorkerInternalsUI::OperationProxy::OperationComplete, | |
606 this, | |
607 status)); | |
608 return; | |
609 } | |
610 | |
611 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
612 original_args_->Insert(0, new FundamentalValue(static_cast<int>(status))); | |
613 if (internals_) | |
614 internals_->web_ui()->CallJavascriptFunction( | |
615 "serviceworker.onOperationComplete", | |
616 std::vector<const Value*>(original_args_->begin(), | |
617 original_args_->end())); | |
618 } | |
619 | |
620 void ServiceWorkerInternalsUI::OperationProxy::StartActiveWorker( | |
621 ServiceWorkerStatusCode status, | |
622 const scoped_refptr<ServiceWorkerRegistration>& registration) { | |
623 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
624 if (status == SERVICE_WORKER_OK) { | |
625 registration->active_version()->StartWorker(base::Bind( | |
626 &ServiceWorkerInternalsUI::OperationProxy::OperationComplete, this)); | |
627 return; | |
628 } | |
629 | |
630 OperationComplete(status); | |
631 } | |
632 | |
633 void ServiceWorkerInternalsUI::OperationProxy::StopActiveWorker( | |
634 ServiceWorkerStatusCode status, | |
635 const scoped_refptr<ServiceWorkerRegistration>& registration) { | |
636 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
637 if (status == SERVICE_WORKER_OK) { | |
638 registration->active_version()->StopWorker(base::Bind( | |
639 &ServiceWorkerInternalsUI::OperationProxy::OperationComplete, this)); | |
640 return; | |
641 } | |
642 | |
643 OperationComplete(status); | |
644 } | |
645 | |
646 void ServiceWorkerInternalsUI::OperationProxy::DispatchSyncEventToActiveWorker( | |
647 ServiceWorkerStatusCode status, | |
648 const scoped_refptr<ServiceWorkerRegistration>& registration) { | |
649 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
650 if (status == SERVICE_WORKER_OK && registration->active_version() && | |
651 registration->active_version()->status() == | |
652 ServiceWorkerVersion::ACTIVE) { | |
653 registration->active_version()->DispatchSyncEvent(base::Bind( | |
654 &ServiceWorkerInternalsUI::OperationProxy::OperationComplete, this)); | |
655 return; | |
656 } | |
657 | |
658 OperationComplete(SERVICE_WORKER_ERROR_FAILED); | |
659 } | |
660 | |
661 void ServiceWorkerInternalsUI::OperationProxy::InspectActiveWorker( | |
662 const ServiceWorkerContextCore* const service_worker_context, | |
663 ServiceWorkerStatusCode status, | |
664 const scoped_refptr<ServiceWorkerRegistration>& registration) { | |
665 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
666 if (status == SERVICE_WORKER_OK) { | |
667 BrowserThread::PostTask( | |
668 BrowserThread::UI, | |
669 FROM_HERE, | |
670 base::Bind(&OperationProxy::InspectWorkerOnUIThread, | |
671 this, | |
672 service_worker_context, | |
673 registration->active_version()->version_id())); | |
674 return; | |
675 } | |
676 | |
677 OperationComplete(status); | |
678 } | |
679 | |
680 void ServiceWorkerInternalsUI::OperationProxy::InspectWorkerOnUIThread( | |
681 const ServiceWorkerContextCore* const service_worker_context, | |
682 int64 version_id) { | |
683 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
684 scoped_refptr<DevToolsAgentHost> agent_host( | |
685 EmbeddedWorkerDevToolsManager::GetInstance() | |
686 ->GetDevToolsAgentHostForServiceWorker( | |
687 EmbeddedWorkerDevToolsManager::ServiceWorkerIdentifier( | |
688 service_worker_context, version_id))); | |
689 if (agent_host) { | |
690 DevToolsManagerImpl::GetInstance()->Inspect( | |
691 internals_->web_ui()->GetWebContents()->GetBrowserContext(), | |
692 agent_host.get()); | |
693 OperationComplete(SERVICE_WORKER_OK); | |
694 return; | |
695 } | |
696 OperationComplete(SERVICE_WORKER_ERROR_NOT_FOUND); | |
697 } | 595 } |
698 | 596 |
699 } // namespace content | 597 } // namespace content |
OLD | NEW |