| 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/permissions/permission_service_impl.h" | 5 #include "content/browser/permissions/permission_service_impl.h" |
| 6 | 6 |
| 7 #include "base/bind.h" | 7 #include "base/bind.h" |
| 8 #include "content/public/browser/browser_context.h" | 8 #include "content/public/browser/browser_context.h" |
| 9 #include "content/public/browser/permission_manager.h" | 9 #include "content/public/browser/permission_manager.h" |
| 10 #include "content/public/browser/permission_type.h" | 10 #include "content/public/browser/permission_type.h" |
| 11 | 11 |
| 12 namespace content { | 12 namespace content { |
| 13 | 13 |
| 14 namespace { | 14 namespace { |
| 15 | 15 |
| 16 PermissionType PermissionNameToPermissionType(PermissionName name) { | 16 PermissionType PermissionNameToPermissionType(permission::Name name) { |
| 17 switch(name) { | 17 switch(name) { |
| 18 case PERMISSION_NAME_GEOLOCATION: | 18 case permission::NAME_GEOLOCATION: |
| 19 return PermissionType::GEOLOCATION; | 19 return PermissionType::GEOLOCATION; |
| 20 case PERMISSION_NAME_NOTIFICATIONS: | 20 case permission::NAME_NOTIFICATIONS: |
| 21 return PermissionType::NOTIFICATIONS; | 21 return PermissionType::NOTIFICATIONS; |
| 22 case PERMISSION_NAME_PUSH_NOTIFICATIONS: | 22 case permission::NAME_PUSH_NOTIFICATIONS: |
| 23 return PermissionType::PUSH_MESSAGING; | 23 return PermissionType::PUSH_MESSAGING; |
| 24 case PERMISSION_NAME_MIDI: | 24 case permission::NAME_MIDI: |
| 25 return PermissionType::MIDI; | 25 return PermissionType::MIDI; |
| 26 case PERMISSION_NAME_MIDI_SYSEX: | 26 case permission::NAME_MIDI_SYSEX: |
| 27 return PermissionType::MIDI_SYSEX; | 27 return PermissionType::MIDI_SYSEX; |
| 28 case PERMISSION_NAME_PROTECTED_MEDIA_IDENTIFIER: | 28 case permission::NAME_PROTECTED_MEDIA_IDENTIFIER: |
| 29 return PermissionType::PROTECTED_MEDIA_IDENTIFIER; | 29 return PermissionType::PROTECTED_MEDIA_IDENTIFIER; |
| 30 case PERMISSION_NAME_DURABLE_STORAGE: | 30 case permission::NAME_DURABLE_STORAGE: |
| 31 return PermissionType::DURABLE_STORAGE; | 31 return PermissionType::DURABLE_STORAGE; |
| 32 case PERMISSION_NAME_AUDIO_CAPTURE: | 32 case permission::NAME_AUDIO_CAPTURE: |
| 33 return PermissionType::AUDIO_CAPTURE; | 33 return PermissionType::AUDIO_CAPTURE; |
| 34 case PERMISSION_NAME_VIDEO_CAPTURE: | 34 case permission::NAME_VIDEO_CAPTURE: |
| 35 return PermissionType::VIDEO_CAPTURE; | 35 return PermissionType::VIDEO_CAPTURE; |
| 36 } | 36 } |
| 37 | 37 |
| 38 NOTREACHED(); | 38 NOTREACHED(); |
| 39 return PermissionType::NUM; | 39 return PermissionType::NUM; |
| 40 } | 40 } |
| 41 | 41 |
| 42 } // anonymous namespace | 42 } // anonymous namespace |
| 43 | 43 |
| 44 PermissionServiceImpl::PendingRequest::PendingRequest( | 44 PermissionServiceImpl::PendingRequest::PendingRequest( |
| 45 PermissionType permission, | 45 PermissionType permission, |
| 46 const GURL& origin, | 46 const GURL& origin, |
| 47 const PermissionStatusCallback& callback) | 47 const PermissionStatusCallback& callback) |
| 48 : id(PermissionManager::kNoPendingOperation), | 48 : id(PermissionManager::kNoPendingOperation), |
| 49 permission(permission), | 49 permission(permission), |
| 50 origin(origin), | 50 origin(origin), |
| 51 callback(callback) { | 51 callback(callback) { |
| 52 } | 52 } |
| 53 | 53 |
| 54 PermissionServiceImpl::PendingRequest::~PendingRequest() { | 54 PermissionServiceImpl::PendingRequest::~PendingRequest() { |
| 55 if (!callback.is_null()) | 55 if (!callback.is_null()) |
| 56 callback.Run(PERMISSION_STATUS_ASK); | 56 callback.Run(permission::STATUS_ASK); |
| 57 } | 57 } |
| 58 | 58 |
| 59 PermissionServiceImpl::PendingSubscription::PendingSubscription( | 59 PermissionServiceImpl::PendingSubscription::PendingSubscription( |
| 60 PermissionType permission, | 60 PermissionType permission, |
| 61 const GURL& origin, | 61 const GURL& origin, |
| 62 const PermissionStatusCallback& callback) | 62 const PermissionStatusCallback& callback) |
| 63 : id(-1), | 63 : id(-1), |
| 64 permission(permission), | 64 permission(permission), |
| 65 origin(origin), | 65 origin(origin), |
| 66 callback(callback) { | 66 callback(callback) { |
| 67 } | 67 } |
| 68 | 68 |
| 69 PermissionServiceImpl::PendingSubscription::~PendingSubscription() { | 69 PermissionServiceImpl::PendingSubscription::~PendingSubscription() { |
| 70 if (!callback.is_null()) | 70 if (!callback.is_null()) |
| 71 callback.Run(PERMISSION_STATUS_ASK); | 71 callback.Run(permission::STATUS_ASK); |
| 72 } | 72 } |
| 73 | 73 |
| 74 PermissionServiceImpl::PermissionServiceImpl( | 74 PermissionServiceImpl::PermissionServiceImpl( |
| 75 PermissionServiceContext* context, | 75 PermissionServiceContext* context, |
| 76 mojo::InterfaceRequest<PermissionService> request) | 76 mojo::InterfaceRequest<permission::PermissionService> request) |
| 77 : context_(context), | 77 : context_(context), binding_(this, request.Pass()), weak_factory_(this) { |
| 78 binding_(this, request.Pass()), | |
| 79 weak_factory_(this) { | |
| 80 binding_.set_connection_error_handler( | 78 binding_.set_connection_error_handler( |
| 81 base::Bind(&PermissionServiceImpl::OnConnectionError, | 79 base::Bind(&PermissionServiceImpl::OnConnectionError, |
| 82 base::Unretained(this))); | 80 base::Unretained(this))); |
| 83 } | 81 } |
| 84 | 82 |
| 85 PermissionServiceImpl::~PermissionServiceImpl() { | 83 PermissionServiceImpl::~PermissionServiceImpl() { |
| 86 DCHECK(pending_requests_.IsEmpty()); | 84 DCHECK(pending_requests_.IsEmpty()); |
| 87 } | 85 } |
| 88 | 86 |
| 89 void PermissionServiceImpl::OnConnectionError() { | 87 void PermissionServiceImpl::OnConnectionError() { |
| 90 context_->ServiceHadConnectionError(this); | 88 context_->ServiceHadConnectionError(this); |
| 91 // After that call, |this| will be deleted. | 89 // After that call, |this| will be deleted. |
| 92 } | 90 } |
| 93 | 91 |
| 94 void PermissionServiceImpl::RequestPermission( | 92 void PermissionServiceImpl::RequestPermission( |
| 95 PermissionName permission, | 93 permission::Name permission, |
| 96 const mojo::String& origin, | 94 const mojo::String& origin, |
| 97 bool user_gesture, | 95 bool user_gesture, |
| 98 const PermissionStatusCallback& callback) { | 96 const PermissionStatusCallback& callback) { |
| 99 // This condition is valid if the call is coming from a ChildThread instead of | 97 // This condition is valid if the call is coming from a ChildThread instead of |
| 100 // a RenderFrame. Some consumers of the service run in Workers and some in | 98 // a RenderFrame. Some consumers of the service run in Workers and some in |
| 101 // Frames. In the context of a Worker, it is not possible to show a | 99 // Frames. In the context of a Worker, it is not possible to show a |
| 102 // permission prompt because there is no tab. In the context of a Frame, we | 100 // permission prompt because there is no tab. In the context of a Frame, we |
| 103 // can. Even if the call comes from a context where it is not possible to show | 101 // can. Even if the call comes from a context where it is not possible to show |
| 104 // any UI, we want to still return something relevant so the current | 102 // any UI, we want to still return something relevant so the current |
| 105 // permission status is returned. | 103 // permission status is returned. |
| 106 if (!context_->render_frame_host()) { | 104 if (!context_->render_frame_host()) { |
| 107 // There is no way to show a UI so the call will simply return the current | 105 // There is no way to show a UI so the call will simply return the current |
| 108 // permission. | 106 // permission. |
| 109 HasPermission(permission, origin, callback); | 107 HasPermission(permission, origin, callback); |
| 110 return; | 108 return; |
| 111 } | 109 } |
| 112 | 110 |
| 113 BrowserContext* browser_context = context_->GetBrowserContext(); | 111 BrowserContext* browser_context = context_->GetBrowserContext(); |
| 114 DCHECK(browser_context); | 112 DCHECK(browser_context); |
| 115 if (!browser_context->GetPermissionManager()) { | 113 if (!browser_context->GetPermissionManager()) { |
| 116 callback.Run(content::PERMISSION_STATUS_DENIED); | 114 callback.Run(permission::STATUS_DENIED); |
| 117 return; | 115 return; |
| 118 } | 116 } |
| 119 | 117 |
| 120 PermissionType permission_type = PermissionNameToPermissionType(permission); | 118 PermissionType permission_type = PermissionNameToPermissionType(permission); |
| 121 int pending_request_id = pending_requests_.Add( | 119 int pending_request_id = pending_requests_.Add( |
| 122 new PendingRequest(permission_type, GURL(origin), callback)); | 120 new PendingRequest(permission_type, GURL(origin), callback)); |
| 123 | 121 |
| 124 int id = browser_context->GetPermissionManager()->RequestPermission( | 122 int id = browser_context->GetPermissionManager()->RequestPermission( |
| 125 permission_type, | 123 permission_type, |
| 126 context_->render_frame_host(), | 124 context_->render_frame_host(), |
| 127 GURL(origin), | 125 GURL(origin), |
| 128 user_gesture, // TODO(mlamouri): should be removed (crbug.com/423770) | 126 user_gesture, // TODO(mlamouri): should be removed (crbug.com/423770) |
| 129 base::Bind(&PermissionServiceImpl::OnRequestPermissionResponse, | 127 base::Bind(&PermissionServiceImpl::OnRequestPermissionResponse, |
| 130 weak_factory_.GetWeakPtr(), | 128 weak_factory_.GetWeakPtr(), |
| 131 pending_request_id)); | 129 pending_request_id)); |
| 132 | 130 |
| 133 // Check if the request still exists. It might have been removed by the | 131 // Check if the request still exists. It might have been removed by the |
| 134 // callback if it was run synchronously. | 132 // callback if it was run synchronously. |
| 135 PendingRequest* pending_request = pending_requests_.Lookup( | 133 PendingRequest* pending_request = pending_requests_.Lookup( |
| 136 pending_request_id); | 134 pending_request_id); |
| 137 if (!pending_request) | 135 if (!pending_request) |
| 138 return; | 136 return; |
| 139 pending_request->id = id; | 137 pending_request->id = id; |
| 140 } | 138 } |
| 141 | 139 |
| 142 void PermissionServiceImpl::RequestPermissions( | 140 void PermissionServiceImpl::RequestPermissions( |
| 143 mojo::Array<PermissionName> permissions, | 141 mojo::Array<permission::Name> permissions, |
| 144 const mojo::String& origin, | 142 const mojo::String& origin, |
| 145 bool user_gesture, | 143 bool user_gesture, |
| 146 const PermissionsStatusCallback& callback) { | 144 const PermissionsStatusCallback& callback) { |
| 147 // TODO(lalitm,mlamouri): this is returning the current permission statuses | 145 // TODO(lalitm,mlamouri): this is returning the current permission statuses |
| 148 // in order for the call to successfully return. It will be changed later. | 146 // in order for the call to successfully return. It will be changed later. |
| 149 // See https://crbug.com/516626 | 147 // See https://crbug.com/516626 |
| 150 mojo::Array<PermissionStatus> result(permissions.size()); | 148 mojo::Array<permission::Status> result(permissions.size()); |
| 151 for (size_t i = 0; i < permissions.size(); ++i) | 149 for (size_t i = 0; i < permissions.size(); ++i) |
| 152 result[i] = GetPermissionStatusFromName(permissions[i], GURL(origin)); | 150 result[i] = GetPermissionStatusFromName(permissions[i], GURL(origin)); |
| 153 callback.Run(result.Pass()); | 151 callback.Run(result.Pass()); |
| 154 } | 152 } |
| 155 | 153 |
| 156 void PermissionServiceImpl::OnRequestPermissionResponse( | 154 void PermissionServiceImpl::OnRequestPermissionResponse( |
| 157 int request_id, | 155 int request_id, |
| 158 PermissionStatus status) { | 156 permission::Status status) { |
| 159 PendingRequest* request = pending_requests_.Lookup(request_id); | 157 PendingRequest* request = pending_requests_.Lookup(request_id); |
| 160 PermissionStatusCallback callback(request->callback); | 158 PermissionStatusCallback callback(request->callback); |
| 161 request->callback.reset(); | 159 request->callback.reset(); |
| 162 pending_requests_.Remove(request_id); | 160 pending_requests_.Remove(request_id); |
| 163 callback.Run(status); | 161 callback.Run(status); |
| 164 } | 162 } |
| 165 | 163 |
| 166 void PermissionServiceImpl::CancelPendingOperations() { | 164 void PermissionServiceImpl::CancelPendingOperations() { |
| 167 DCHECK(context_->GetBrowserContext()); | 165 DCHECK(context_->GetBrowserContext()); |
| 168 | 166 |
| (...skipping 16 matching lines...) Expand all Loading... |
| 185 it.GetCurrentValue()->callback.Run(GetPermissionStatusFromType( | 183 it.GetCurrentValue()->callback.Run(GetPermissionStatusFromType( |
| 186 it.GetCurrentValue()->permission, it.GetCurrentValue()->origin)); | 184 it.GetCurrentValue()->permission, it.GetCurrentValue()->origin)); |
| 187 it.GetCurrentValue()->callback.reset(); | 185 it.GetCurrentValue()->callback.reset(); |
| 188 permission_manager->UnsubscribePermissionStatusChange( | 186 permission_manager->UnsubscribePermissionStatusChange( |
| 189 it.GetCurrentValue()->id); | 187 it.GetCurrentValue()->id); |
| 190 } | 188 } |
| 191 pending_subscriptions_.Clear(); | 189 pending_subscriptions_.Clear(); |
| 192 } | 190 } |
| 193 | 191 |
| 194 void PermissionServiceImpl::HasPermission( | 192 void PermissionServiceImpl::HasPermission( |
| 195 PermissionName permission, | 193 permission::Name permission, |
| 196 const mojo::String& origin, | 194 const mojo::String& origin, |
| 197 const PermissionStatusCallback& callback) { | 195 const PermissionStatusCallback& callback) { |
| 198 callback.Run(GetPermissionStatusFromName(permission, GURL(origin))); | 196 callback.Run(GetPermissionStatusFromName(permission, GURL(origin))); |
| 199 } | 197 } |
| 200 | 198 |
| 201 void PermissionServiceImpl::RevokePermission( | 199 void PermissionServiceImpl::RevokePermission( |
| 202 PermissionName permission, | 200 permission::Name permission, |
| 203 const mojo::String& origin, | 201 const mojo::String& origin, |
| 204 const PermissionStatusCallback& callback) { | 202 const PermissionStatusCallback& callback) { |
| 205 GURL origin_url(origin); | 203 GURL origin_url(origin); |
| 206 PermissionType permission_type = PermissionNameToPermissionType(permission); | 204 PermissionType permission_type = PermissionNameToPermissionType(permission); |
| 207 PermissionStatus status = GetPermissionStatusFromType(permission_type, | 205 permission::Status status = |
| 208 origin_url); | 206 GetPermissionStatusFromType(permission_type, origin_url); |
| 209 | 207 |
| 210 // Resetting the permission should only be possible if the permission is | 208 // Resetting the permission should only be possible if the permission is |
| 211 // already granted. | 209 // already granted. |
| 212 if (status != PERMISSION_STATUS_GRANTED) { | 210 if (status != permission::STATUS_GRANTED) { |
| 213 callback.Run(status); | 211 callback.Run(status); |
| 214 return; | 212 return; |
| 215 } | 213 } |
| 216 | 214 |
| 217 ResetPermissionStatus(permission_type, origin_url); | 215 ResetPermissionStatus(permission_type, origin_url); |
| 218 | 216 |
| 219 callback.Run(GetPermissionStatusFromType(permission_type, origin_url)); | 217 callback.Run(GetPermissionStatusFromType(permission_type, origin_url)); |
| 220 } | 218 } |
| 221 | 219 |
| 222 void PermissionServiceImpl::GetNextPermissionChange( | 220 void PermissionServiceImpl::GetNextPermissionChange( |
| 223 PermissionName permission, | 221 permission::Name permission, |
| 224 const mojo::String& mojo_origin, | 222 const mojo::String& mojo_origin, |
| 225 PermissionStatus last_known_status, | 223 permission::Status last_known_status, |
| 226 const PermissionStatusCallback& callback) { | 224 const PermissionStatusCallback& callback) { |
| 227 GURL origin(mojo_origin); | 225 GURL origin(mojo_origin); |
| 228 PermissionStatus current_status = | 226 permission::Status current_status = |
| 229 GetPermissionStatusFromName(permission, origin); | 227 GetPermissionStatusFromName(permission, origin); |
| 230 if (current_status != last_known_status) { | 228 if (current_status != last_known_status) { |
| 231 callback.Run(current_status); | 229 callback.Run(current_status); |
| 232 return; | 230 return; |
| 233 } | 231 } |
| 234 | 232 |
| 235 BrowserContext* browser_context = context_->GetBrowserContext(); | 233 BrowserContext* browser_context = context_->GetBrowserContext(); |
| 236 DCHECK(browser_context); | 234 DCHECK(browser_context); |
| 237 if (!browser_context->GetPermissionManager()) { | 235 if (!browser_context->GetPermissionManager()) { |
| 238 callback.Run(current_status); | 236 callback.Run(current_status); |
| (...skipping 14 matching lines...) Expand all Loading... |
| 253 browser_context->GetPermissionManager()->SubscribePermissionStatusChange( | 251 browser_context->GetPermissionManager()->SubscribePermissionStatusChange( |
| 254 permission_type, | 252 permission_type, |
| 255 origin, | 253 origin, |
| 256 // If the embedding_origin is empty, we,ll use the |origin| instead. | 254 // If the embedding_origin is empty, we,ll use the |origin| instead. |
| 257 embedding_origin.is_empty() ? origin : embedding_origin, | 255 embedding_origin.is_empty() ? origin : embedding_origin, |
| 258 base::Bind(&PermissionServiceImpl::OnPermissionStatusChanged, | 256 base::Bind(&PermissionServiceImpl::OnPermissionStatusChanged, |
| 259 weak_factory_.GetWeakPtr(), | 257 weak_factory_.GetWeakPtr(), |
| 260 pending_subscription_id)); | 258 pending_subscription_id)); |
| 261 } | 259 } |
| 262 | 260 |
| 263 PermissionStatus PermissionServiceImpl::GetPermissionStatusFromName( | 261 permission::Status PermissionServiceImpl::GetPermissionStatusFromName( |
| 264 PermissionName permission, const GURL& origin) { | 262 permission::Name permission, |
| 263 const GURL& origin) { |
| 265 return GetPermissionStatusFromType(PermissionNameToPermissionType(permission), | 264 return GetPermissionStatusFromType(PermissionNameToPermissionType(permission), |
| 266 origin); | 265 origin); |
| 267 } | 266 } |
| 268 | 267 |
| 269 PermissionStatus PermissionServiceImpl::GetPermissionStatusFromType( | 268 permission::Status PermissionServiceImpl::GetPermissionStatusFromType( |
| 270 PermissionType type, const GURL& origin) { | 269 PermissionType type, |
| 270 const GURL& origin) { |
| 271 BrowserContext* browser_context = context_->GetBrowserContext(); | 271 BrowserContext* browser_context = context_->GetBrowserContext(); |
| 272 DCHECK(browser_context); | 272 DCHECK(browser_context); |
| 273 if (!browser_context->GetPermissionManager()) | 273 if (!browser_context->GetPermissionManager()) |
| 274 return PERMISSION_STATUS_DENIED; | 274 return permission::STATUS_DENIED; |
| 275 | 275 |
| 276 // If the embedding_origin is empty we'll use |origin| instead. | 276 // If the embedding_origin is empty we'll use |origin| instead. |
| 277 GURL embedding_origin = context_->GetEmbeddingOrigin(); | 277 GURL embedding_origin = context_->GetEmbeddingOrigin(); |
| 278 return browser_context->GetPermissionManager()->GetPermissionStatus( | 278 return browser_context->GetPermissionManager()->GetPermissionStatus( |
| 279 type, origin, embedding_origin.is_empty() ? origin : embedding_origin); | 279 type, origin, embedding_origin.is_empty() ? origin : embedding_origin); |
| 280 } | 280 } |
| 281 | 281 |
| 282 void PermissionServiceImpl::ResetPermissionStatus(PermissionType type, | 282 void PermissionServiceImpl::ResetPermissionStatus(PermissionType type, |
| 283 const GURL& origin) { | 283 const GURL& origin) { |
| 284 BrowserContext* browser_context = context_->GetBrowserContext(); | 284 BrowserContext* browser_context = context_->GetBrowserContext(); |
| 285 DCHECK(browser_context); | 285 DCHECK(browser_context); |
| 286 if (!browser_context->GetPermissionManager()) | 286 if (!browser_context->GetPermissionManager()) |
| 287 return; | 287 return; |
| 288 | 288 |
| 289 // If the embedding_origin is empty we'll use |origin| instead. | 289 // If the embedding_origin is empty we'll use |origin| instead. |
| 290 GURL embedding_origin = context_->GetEmbeddingOrigin(); | 290 GURL embedding_origin = context_->GetEmbeddingOrigin(); |
| 291 browser_context->GetPermissionManager()->ResetPermission( | 291 browser_context->GetPermissionManager()->ResetPermission( |
| 292 type, origin, embedding_origin.is_empty() ? origin : embedding_origin); | 292 type, origin, embedding_origin.is_empty() ? origin : embedding_origin); |
| 293 } | 293 } |
| 294 | 294 |
| 295 void PermissionServiceImpl::OnPermissionStatusChanged( | 295 void PermissionServiceImpl::OnPermissionStatusChanged( |
| 296 int pending_subscription_id, | 296 int pending_subscription_id, |
| 297 PermissionStatus status) { | 297 permission::Status status) { |
| 298 PendingSubscription* subscription = | 298 PendingSubscription* subscription = |
| 299 pending_subscriptions_.Lookup(pending_subscription_id); | 299 pending_subscriptions_.Lookup(pending_subscription_id); |
| 300 | 300 |
| 301 BrowserContext* browser_context = context_->GetBrowserContext(); | 301 BrowserContext* browser_context = context_->GetBrowserContext(); |
| 302 DCHECK(browser_context); | 302 DCHECK(browser_context); |
| 303 if (browser_context->GetPermissionManager()) { | 303 if (browser_context->GetPermissionManager()) { |
| 304 browser_context->GetPermissionManager()->UnsubscribePermissionStatusChange( | 304 browser_context->GetPermissionManager()->UnsubscribePermissionStatusChange( |
| 305 subscription->id); | 305 subscription->id); |
| 306 } | 306 } |
| 307 | 307 |
| 308 PermissionStatusCallback callback = subscription->callback; | 308 PermissionStatusCallback callback = subscription->callback; |
| 309 | 309 |
| 310 subscription->callback.reset(); | 310 subscription->callback.reset(); |
| 311 pending_subscriptions_.Remove(pending_subscription_id); | 311 pending_subscriptions_.Remove(pending_subscription_id); |
| 312 | 312 |
| 313 callback.Run(status); | 313 callback.Run(status); |
| 314 } | 314 } |
| 315 | 315 |
| 316 } // namespace content | 316 } // namespace content |
| OLD | NEW |