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/child/push_messaging/push_provider.h" | 5 #include "content/child/push_messaging/push_provider.h" |
6 | 6 |
7 #include <memory> | 7 #include <memory> |
8 #include <utility> | 8 #include <utility> |
9 | 9 |
10 #include "base/bind_helpers.h" | |
10 #include "base/lazy_instance.h" | 11 #include "base/lazy_instance.h" |
11 #include "base/memory/ptr_util.h" | 12 #include "base/memory/ptr_util.h" |
12 #include "base/stl_util.h" | |
13 #include "base/threading/thread_local.h" | 13 #include "base/threading/thread_local.h" |
14 #include "content/child/push_messaging/push_dispatcher.h" | 14 #include "content/child/child_thread_impl.h" |
15 #include "content/child/service_worker/web_service_worker_registration_impl.h" | 15 #include "content/child/service_worker/web_service_worker_registration_impl.h" |
16 #include "content/child/thread_safe_sender.h" | |
17 #include "content/common/push_messaging_messages.h" | |
18 #include "content/public/common/child_process_host.h" | 16 #include "content/public/common/child_process_host.h" |
19 #include "content/public/common/push_subscription_options.h" | 17 #include "content/public/common/push_subscription_options.h" |
18 #include "services/service_manager/public/cpp/interface_provider.h" | |
20 #include "third_party/WebKit/public/platform/WebString.h" | 19 #include "third_party/WebKit/public/platform/WebString.h" |
21 #include "third_party/WebKit/public/platform/modules/push_messaging/WebPushSubsc ription.h" | 20 #include "third_party/WebKit/public/platform/modules/push_messaging/WebPushSubsc ription.h" |
22 #include "third_party/WebKit/public/platform/modules/push_messaging/WebPushSubsc riptionOptions.h" | 21 #include "third_party/WebKit/public/platform/modules/push_messaging/WebPushSubsc riptionOptions.h" |
23 | 22 |
24 namespace content { | 23 namespace content { |
25 namespace { | 24 namespace { |
26 | 25 |
27 int CurrentWorkerId() { | 26 int CurrentWorkerId() { |
28 return WorkerThread::GetCurrentId(); | 27 return WorkerThread::GetCurrentId(); |
29 } | 28 } |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
66 break; | 65 break; |
67 } | 66 } |
68 return blink::WebPushError( | 67 return blink::WebPushError( |
69 error_type, | 68 error_type, |
70 blink::WebString::fromUTF8(PushRegistrationStatusToString(status))); | 69 blink::WebString::fromUTF8(PushRegistrationStatusToString(status))); |
71 } | 70 } |
72 | 71 |
73 static base::LazyInstance<base::ThreadLocalPointer<PushProvider>>::Leaky | 72 static base::LazyInstance<base::ThreadLocalPointer<PushProvider>>::Leaky |
74 g_push_provider_tls = LAZY_INSTANCE_INITIALIZER; | 73 g_push_provider_tls = LAZY_INSTANCE_INITIALIZER; |
75 | 74 |
76 PushProvider::PushProvider(ThreadSafeSender* thread_safe_sender, | 75 PushProvider::PushProvider( |
77 PushDispatcher* push_dispatcher) | 76 scoped_refptr<base::SingleThreadTaskRunner>& main_thread_task_runner) { |
78 : thread_safe_sender_(thread_safe_sender), | 77 DCHECK(main_thread_task_runner.get()); |
Peter Beverloo
2017/02/16 16:08:32
micro nit: I don't think we need .get() here
ke.he
2017/02/17 08:22:30
Done.
| |
79 push_dispatcher_(push_dispatcher) { | 78 |
79 if (main_thread_task_runner) { | |
Peter Beverloo
2017/02/16 16:08:32
Should this be something like the following? We no
ke.he
2017/02/17 08:22:29
Yeah, no need to postTask to itself.
Done :)
| |
80 auto request = mojo::MakeRequest(&push_messaging_); | |
81 main_thread_task_runner->PostTask( | |
82 FROM_HERE, | |
83 base::Bind(&PushProvider::BindRequest, base::Passed(&request))); | |
84 } | |
80 g_push_provider_tls.Pointer()->Set(this); | 85 g_push_provider_tls.Pointer()->Set(this); |
81 } | 86 } |
82 | 87 |
83 PushProvider::~PushProvider() { | 88 PushProvider::~PushProvider() { |
84 g_push_provider_tls.Pointer()->Set(nullptr); | 89 g_push_provider_tls.Pointer()->Set(nullptr); |
85 } | 90 } |
86 | 91 |
87 PushProvider* PushProvider::ThreadSpecificInstance( | 92 PushProvider* PushProvider::ThreadSpecificInstance( |
88 ThreadSafeSender* thread_safe_sender, | 93 scoped_refptr<base::SingleThreadTaskRunner>& main_thread_task_runner) { |
89 PushDispatcher* push_dispatcher) { | |
90 if (g_push_provider_tls.Pointer()->Get()) | 94 if (g_push_provider_tls.Pointer()->Get()) |
91 return g_push_provider_tls.Pointer()->Get(); | 95 return g_push_provider_tls.Pointer()->Get(); |
92 | 96 |
93 PushProvider* provider = | 97 PushProvider* provider = new PushProvider(main_thread_task_runner); |
94 new PushProvider(thread_safe_sender, push_dispatcher); | |
95 if (CurrentWorkerId()) | 98 if (CurrentWorkerId()) |
96 WorkerThread::AddObserver(provider); | 99 WorkerThread::AddObserver(provider); |
97 return provider; | 100 return provider; |
98 } | 101 } |
99 | 102 |
103 // static | |
104 void PushProvider::BindRequest(mojom::PushMessagingRequest request) { | |
Peter Beverloo
2017/02/16 16:08:32
nit: could we call this GetInterface? It's not rea
ke.he
2017/02/17 08:22:29
Done.
| |
105 if (ChildThreadImpl::current()) | |
Peter Beverloo
2017/02/16 16:08:32
nit: {}
ke.he
2017/02/17 08:22:29
Done.
| |
106 ChildThreadImpl::current()->GetRemoteInterfaces()->GetInterface( | |
107 std::move(request)); | |
108 } | |
109 | |
100 void PushProvider::WillStopCurrentWorkerThread() { | 110 void PushProvider::WillStopCurrentWorkerThread() { |
101 delete this; | 111 delete this; |
102 } | 112 } |
103 | 113 |
104 void PushProvider::subscribe( | 114 void PushProvider::subscribe( |
105 blink::WebServiceWorkerRegistration* service_worker_registration, | 115 blink::WebServiceWorkerRegistration* service_worker_registration, |
106 const blink::WebPushSubscriptionOptions& options, | 116 const blink::WebPushSubscriptionOptions& options, |
107 std::unique_ptr<blink::WebPushSubscriptionCallbacks> callbacks) { | 117 std::unique_ptr<blink::WebPushSubscriptionCallbacks> callbacks) { |
108 DCHECK(service_worker_registration); | 118 DCHECK(service_worker_registration); |
109 DCHECK(callbacks); | 119 DCHECK(callbacks); |
110 int request_id = push_dispatcher_->GenerateRequestId(CurrentWorkerId()); | 120 |
111 subscription_callbacks_.AddWithID(std::move(callbacks), request_id); | |
112 int64_t service_worker_registration_id = | 121 int64_t service_worker_registration_id = |
113 GetServiceWorkerRegistrationId(service_worker_registration); | 122 GetServiceWorkerRegistrationId(service_worker_registration); |
114 PushSubscriptionOptions content_options; | 123 PushSubscriptionOptions content_options; |
115 content_options.user_visible_only = options.userVisibleOnly; | 124 content_options.user_visible_only = options.userVisibleOnly; |
116 | 125 |
117 // Just treat the server key as a string of bytes and pass it to the push | 126 // Just treat the server key as a string of bytes and pass it to the push |
118 // service. | 127 // service. |
119 content_options.sender_info = options.applicationServerKey.latin1(); | 128 content_options.sender_info = options.applicationServerKey.latin1(); |
120 thread_safe_sender_->Send(new PushMessagingHostMsg_Subscribe( | 129 if (push_messaging_) { |
Peter Beverloo
2017/02/16 16:08:32
Can we drop these if()s everywhere?
If we can't i
ke.he
2017/02/17 08:22:29
We can drop the if(). I found |if(push_messaging_)
| |
121 ChildProcessHost::kInvalidUniqueID, request_id, | 130 push_messaging_->Subscribe( |
122 service_worker_registration_id, content_options)); | 131 ChildProcessHost::kInvalidUniqueID, service_worker_registration_id, |
132 content_options, | |
133 base::Bind(&PushProvider::SubscribeCallback, base::Unretained(this), | |
Peter Beverloo
2017/02/16 16:08:32
Is it safe to use base::Unretained because |push_m
ke.he
2017/02/17 08:22:30
Comments are added when using base::Unretained.
Do
| |
134 base::Passed(&callbacks))); | |
135 } | |
136 } | |
137 | |
138 void PushProvider::SubscribeCallback( | |
139 std::unique_ptr<blink::WebPushSubscriptionCallbacks> callbacks, | |
140 content::PushRegistrationStatus status, | |
141 const base::Optional<GURL>& endpoint, | |
142 const base::Optional<content::PushSubscriptionOptions>& options, | |
143 const base::Optional<std::vector<uint8_t>>& p256dh, | |
144 const base::Optional<std::vector<uint8_t>>& auth) { | |
145 if (!callbacks) { | |
146 return; | |
147 } | |
148 | |
149 if (status == PUSH_REGISTRATION_STATUS_SUCCESS_FROM_PUSH_SERVICE || | |
150 status == PUSH_REGISTRATION_STATUS_SUCCESS_FROM_CACHE) { | |
151 DCHECK(endpoint != base::nullopt); | |
152 DCHECK(options != base::nullopt); | |
153 DCHECK(p256dh != base::nullopt); | |
154 DCHECK(auth != base::nullopt); | |
155 | |
156 callbacks->onSuccess(base::MakeUnique<blink::WebPushSubscription>( | |
157 endpoint.value(), options.value().user_visible_only, | |
158 blink::WebString::fromLatin1(options.value().sender_info), | |
159 p256dh.value(), auth.value())); | |
160 } else { | |
161 callbacks->onError(PushRegistrationStatusToWebPushError(status)); | |
162 } | |
123 } | 163 } |
124 | 164 |
125 void PushProvider::unsubscribe( | 165 void PushProvider::unsubscribe( |
126 blink::WebServiceWorkerRegistration* service_worker_registration, | 166 blink::WebServiceWorkerRegistration* service_worker_registration, |
127 std::unique_ptr<blink::WebPushUnsubscribeCallbacks> callbacks) { | 167 std::unique_ptr<blink::WebPushUnsubscribeCallbacks> callbacks) { |
128 DCHECK(service_worker_registration); | 168 DCHECK(service_worker_registration); |
129 DCHECK(callbacks); | 169 DCHECK(callbacks); |
130 | 170 |
131 int request_id = push_dispatcher_->GenerateRequestId(CurrentWorkerId()); | |
132 unsubscribe_callbacks_.AddWithID(std::move(callbacks), request_id); | |
133 int64_t service_worker_registration_id = | 171 int64_t service_worker_registration_id = |
134 GetServiceWorkerRegistrationId(service_worker_registration); | 172 GetServiceWorkerRegistrationId(service_worker_registration); |
135 thread_safe_sender_->Send(new PushMessagingHostMsg_Unsubscribe( | 173 |
136 request_id, service_worker_registration_id)); | 174 if (push_messaging_) { |
175 push_messaging_->Unsubscribe( | |
176 service_worker_registration_id, | |
177 base::Bind(&PushProvider::UnsubscribeCallback, base::Unretained(this), | |
178 base::Passed(&callbacks))); | |
179 } | |
180 } | |
181 | |
182 void PushProvider::UnsubscribeCallback( | |
183 std::unique_ptr<blink::WebPushUnsubscribeCallbacks> callbacks, | |
184 bool is_success, | |
185 bool did_unsubscribe, | |
186 blink::WebPushError::ErrorType error_type, | |
187 const base::Optional<std::string>& error_message) { | |
188 if (!callbacks) { | |
189 return; | |
190 } | |
191 | |
192 if (is_success) { | |
193 callbacks->onSuccess(did_unsubscribe); | |
194 } else { | |
195 callbacks->onError(blink::WebPushError( | |
196 error_type, blink::WebString::fromUTF8(error_message->c_str()))); | |
197 } | |
137 } | 198 } |
138 | 199 |
139 void PushProvider::getSubscription( | 200 void PushProvider::getSubscription( |
140 blink::WebServiceWorkerRegistration* service_worker_registration, | 201 blink::WebServiceWorkerRegistration* service_worker_registration, |
141 std::unique_ptr<blink::WebPushSubscriptionCallbacks> callbacks) { | 202 std::unique_ptr<blink::WebPushSubscriptionCallbacks> callbacks) { |
142 DCHECK(service_worker_registration); | 203 DCHECK(service_worker_registration); |
143 DCHECK(callbacks); | 204 DCHECK(callbacks); |
144 int request_id = push_dispatcher_->GenerateRequestId(CurrentWorkerId()); | 205 |
145 subscription_callbacks_.AddWithID(std::move(callbacks), request_id); | |
146 int64_t service_worker_registration_id = | 206 int64_t service_worker_registration_id = |
147 GetServiceWorkerRegistrationId(service_worker_registration); | 207 GetServiceWorkerRegistrationId(service_worker_registration); |
148 thread_safe_sender_->Send(new PushMessagingHostMsg_GetSubscription( | 208 |
149 request_id, service_worker_registration_id)); | 209 if (push_messaging_) { |
210 push_messaging_->GetSubscription( | |
211 service_worker_registration_id, | |
212 base::Bind(&PushProvider::GetSubscriptionCallback, | |
213 base::Unretained(this), base::Passed(&callbacks))); | |
214 } | |
215 } | |
216 | |
217 void PushProvider::GetSubscriptionCallback( | |
218 std::unique_ptr<blink::WebPushSubscriptionCallbacks> callbacks, | |
219 content::PushGetRegistrationStatus status, | |
220 const base::Optional<GURL>& endpoint, | |
221 const base::Optional<content::PushSubscriptionOptions>& options, | |
222 const base::Optional<std::vector<uint8_t>>& p256dh, | |
223 const base::Optional<std::vector<uint8_t>>& auth) { | |
224 if (!callbacks) | |
225 return; | |
Peter Beverloo
2017/02/16 16:08:32
When would this be NULL (elsewhere too)? Can we DC
ke.he
2017/02/17 08:22:33
I think DCHECK should be fine here. Anyway Let's f
| |
226 | |
227 if (status == PUSH_GETREGISTRATION_STATUS_SUCCESS) { | |
228 DCHECK(endpoint != base::nullopt); | |
229 DCHECK(options != base::nullopt); | |
230 DCHECK(p256dh != base::nullopt); | |
231 DCHECK(auth != base::nullopt); | |
Peter Beverloo
2017/02/16 16:08:32
nit: does DCHECK_NE work here?
ke.he
2017/02/17 08:22:32
compiling failed on DCHECK_NE here.
In optional.m
| |
232 | |
233 callbacks->onSuccess(base::MakeUnique<blink::WebPushSubscription>( | |
234 endpoint.value(), options.value().user_visible_only, | |
235 blink::WebString::fromLatin1(options.value().sender_info), | |
236 p256dh.value(), auth.value())); | |
237 } else { | |
238 // We are only expecting an error if we can't find a registration. | |
239 callbacks->onSuccess(nullptr); | |
240 } | |
150 } | 241 } |
151 | 242 |
152 void PushProvider::getPermissionStatus( | 243 void PushProvider::getPermissionStatus( |
153 blink::WebServiceWorkerRegistration* service_worker_registration, | 244 blink::WebServiceWorkerRegistration* service_worker_registration, |
154 const blink::WebPushSubscriptionOptions& options, | 245 const blink::WebPushSubscriptionOptions& options, |
155 std::unique_ptr<blink::WebPushPermissionStatusCallbacks> callbacks) { | 246 std::unique_ptr<blink::WebPushPermissionStatusCallbacks> callbacks) { |
156 DCHECK(service_worker_registration); | 247 DCHECK(service_worker_registration); |
157 DCHECK(callbacks); | 248 DCHECK(callbacks); |
158 int request_id = push_dispatcher_->GenerateRequestId(CurrentWorkerId()); | 249 |
159 permission_status_callbacks_.AddWithID(std::move(callbacks), request_id); | |
160 int64_t service_worker_registration_id = | 250 int64_t service_worker_registration_id = |
161 GetServiceWorkerRegistrationId(service_worker_registration); | 251 GetServiceWorkerRegistrationId(service_worker_registration); |
162 thread_safe_sender_->Send(new PushMessagingHostMsg_GetPermissionStatus( | 252 if (push_messaging_) { |
163 request_id, service_worker_registration_id, options.userVisibleOnly)); | 253 push_messaging_->GetPermissionStatus( |
254 service_worker_registration_id, options.userVisibleOnly, | |
255 base::Bind(&PushProvider::GetPermissionStatusCallback, | |
256 base::Unretained(this), base::Passed(&callbacks))); | |
257 } | |
164 } | 258 } |
165 | 259 |
166 bool PushProvider::OnMessageReceived(const IPC::Message& message) { | 260 void PushProvider::GetPermissionStatusCallback( |
167 bool handled = true; | 261 std::unique_ptr<blink::WebPushPermissionStatusCallbacks> callbacks, |
168 IPC_BEGIN_MESSAGE_MAP(PushProvider, message) | 262 bool is_success, |
169 IPC_MESSAGE_HANDLER(PushMessagingMsg_SubscribeFromWorkerSuccess, | 263 blink::WebPushPermissionStatus status, |
170 OnSubscribeFromWorkerSuccess); | 264 blink::WebPushError::ErrorType error) { |
171 IPC_MESSAGE_HANDLER(PushMessagingMsg_SubscribeFromWorkerError, | |
172 OnSubscribeFromWorkerError); | |
173 IPC_MESSAGE_HANDLER(PushMessagingMsg_UnsubscribeSuccess, | |
174 OnUnsubscribeSuccess); | |
175 IPC_MESSAGE_HANDLER(PushMessagingMsg_UnsubscribeError, OnUnsubscribeError); | |
176 IPC_MESSAGE_HANDLER(PushMessagingMsg_GetSubscriptionSuccess, | |
177 OnGetSubscriptionSuccess); | |
178 IPC_MESSAGE_HANDLER(PushMessagingMsg_GetSubscriptionError, | |
179 OnGetSubscriptionError); | |
180 IPC_MESSAGE_HANDLER(PushMessagingMsg_GetPermissionStatusSuccess, | |
181 OnGetPermissionStatusSuccess); | |
182 IPC_MESSAGE_HANDLER(PushMessagingMsg_GetPermissionStatusError, | |
183 OnGetPermissionStatusError); | |
184 IPC_MESSAGE_UNHANDLED(handled = false) | |
185 IPC_END_MESSAGE_MAP() | |
186 | |
187 return handled; | |
188 } | |
189 | |
190 void PushProvider::OnSubscribeFromWorkerSuccess( | |
191 int request_id, | |
192 const GURL& endpoint, | |
193 const PushSubscriptionOptions& options, | |
194 const std::vector<uint8_t>& p256dh, | |
195 const std::vector<uint8_t>& auth) { | |
196 blink::WebPushSubscriptionCallbacks* callbacks = | |
197 subscription_callbacks_.Lookup(request_id); | |
198 if (!callbacks) | 265 if (!callbacks) |
199 return; | 266 return; |
200 | 267 |
201 callbacks->onSuccess(base::MakeUnique<blink::WebPushSubscription>( | 268 if (is_success) { |
202 endpoint, options.user_visible_only, | 269 callbacks->onSuccess(status); |
203 blink::WebString::fromLatin1(options.sender_info), p256dh, auth)); | 270 } else { |
204 | 271 std::string error_message; |
205 subscription_callbacks_.Remove(request_id); | 272 if (error == blink::WebPushError::ErrorTypeNotSupported) { |
206 } | 273 error_message = |
207 | 274 "Push subscriptions that don't enable userVisibleOnly are not " |
208 void PushProvider::OnSubscribeFromWorkerError(int request_id, | 275 "supported."; |
209 PushRegistrationStatus status) { | 276 } |
210 blink::WebPushSubscriptionCallbacks* callbacks = | 277 callbacks->onError( |
211 subscription_callbacks_.Lookup(request_id); | 278 blink::WebPushError(error, blink::WebString::fromUTF8(error_message))); |
212 if (!callbacks) | |
213 return; | |
214 | |
215 callbacks->onError(PushRegistrationStatusToWebPushError(status)); | |
216 | |
217 subscription_callbacks_.Remove(request_id); | |
218 } | |
219 | |
220 void PushProvider::OnUnsubscribeSuccess(int request_id, bool did_unsubscribe) { | |
221 blink::WebPushUnsubscribeCallbacks* callbacks = | |
222 unsubscribe_callbacks_.Lookup(request_id); | |
223 if (!callbacks) | |
224 return; | |
225 | |
226 callbacks->onSuccess(did_unsubscribe); | |
227 | |
228 unsubscribe_callbacks_.Remove(request_id); | |
229 } | |
230 | |
231 void PushProvider::OnUnsubscribeError(int request_id, | |
232 blink::WebPushError::ErrorType error_type, | |
233 const std::string& error_message) { | |
234 blink::WebPushUnsubscribeCallbacks* callbacks = | |
235 unsubscribe_callbacks_.Lookup(request_id); | |
236 if (!callbacks) | |
237 return; | |
238 | |
239 callbacks->onError(blink::WebPushError( | |
240 error_type, blink::WebString::fromUTF8(error_message))); | |
241 | |
242 unsubscribe_callbacks_.Remove(request_id); | |
243 } | |
244 | |
245 void PushProvider::OnGetSubscriptionSuccess( | |
246 int request_id, | |
247 const GURL& endpoint, | |
248 const PushSubscriptionOptions& options, | |
249 const std::vector<uint8_t>& p256dh, | |
250 const std::vector<uint8_t>& auth) { | |
251 blink::WebPushSubscriptionCallbacks* callbacks = | |
252 subscription_callbacks_.Lookup(request_id); | |
253 if (!callbacks) | |
254 return; | |
255 | |
256 callbacks->onSuccess(base::MakeUnique<blink::WebPushSubscription>( | |
257 endpoint, options.user_visible_only, | |
258 blink::WebString::fromLatin1(options.sender_info), p256dh, auth)); | |
259 | |
260 subscription_callbacks_.Remove(request_id); | |
261 } | |
262 | |
263 void PushProvider::OnGetSubscriptionError(int request_id, | |
264 PushGetRegistrationStatus status) { | |
265 blink::WebPushSubscriptionCallbacks* callbacks = | |
266 subscription_callbacks_.Lookup(request_id); | |
267 if (!callbacks) | |
268 return; | |
269 | |
270 // We are only expecting an error if we can't find a registration. | |
271 callbacks->onSuccess(nullptr); | |
272 | |
273 subscription_callbacks_.Remove(request_id); | |
274 } | |
275 | |
276 void PushProvider::OnGetPermissionStatusSuccess( | |
277 int request_id, | |
278 blink::WebPushPermissionStatus status) { | |
279 blink::WebPushPermissionStatusCallbacks* callbacks = | |
280 permission_status_callbacks_.Lookup(request_id); | |
281 if (!callbacks) | |
282 return; | |
283 | |
284 callbacks->onSuccess(status); | |
285 | |
286 permission_status_callbacks_.Remove(request_id); | |
287 } | |
288 | |
289 void PushProvider::OnGetPermissionStatusError( | |
290 int request_id, | |
291 blink::WebPushError::ErrorType error) { | |
292 blink::WebPushPermissionStatusCallbacks* callbacks = | |
293 permission_status_callbacks_.Lookup(request_id); | |
294 if (!callbacks) | |
295 return; | |
296 | |
297 std::string error_message; | |
298 if (error == blink::WebPushError::ErrorTypeNotSupported) { | |
299 error_message = | |
300 "Push subscriptions that don't enable userVisibleOnly are not " | |
301 "supported."; | |
302 } | 279 } |
303 | |
304 callbacks->onError( | |
305 blink::WebPushError(error, blink::WebString::fromUTF8(error_message))); | |
306 | |
307 permission_status_callbacks_.Remove(request_id); | |
308 } | 280 } |
309 | 281 |
310 } // namespace content | 282 } // namespace content |
OLD | NEW |