Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2012 The Chromium Authors. All rights reserved. | 1 // Copyright 2012 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 "chrome/browser/geolocation/geolocation_permission_context.h" | 5 #include "chrome/browser/geolocation/geolocation_permission_context.h" |
| 6 | 6 |
| 7 #include <functional> | 7 #include <functional> |
| 8 #include <string> | 8 #include <string> |
| 9 #include <vector> | 9 #include <vector> |
| 10 | 10 |
| (...skipping 17 matching lines...) Expand all Loading... | |
| 28 #include "net/base/net_util.h" | 28 #include "net/base/net_util.h" |
| 29 #include "ui/base/l10n/l10n_util.h" | 29 #include "ui/base/l10n/l10n_util.h" |
| 30 | 30 |
| 31 class GeolocationPermissionRequest : public PermissionBubbleRequest { | 31 class GeolocationPermissionRequest : public PermissionBubbleRequest { |
| 32 public: | 32 public: |
| 33 GeolocationPermissionRequest(GeolocationPermissionContext* context, | 33 GeolocationPermissionRequest(GeolocationPermissionContext* context, |
| 34 const PermissionRequestID& id, | 34 const PermissionRequestID& id, |
| 35 const GURL& requesting_frame, | 35 const GURL& requesting_frame, |
| 36 const GURL& embedder, | 36 const GURL& embedder, |
| 37 bool user_gesture, | 37 bool user_gesture, |
| 38 base::Callback<void(bool)> callback, | 38 base::Callback<void(int, bool)> callback, |
| 39 const std::string& display_languages); | 39 const std::string& display_languages); |
| 40 virtual ~GeolocationPermissionRequest(); | 40 virtual ~GeolocationPermissionRequest(); |
| 41 | 41 |
| 42 // PermissionBubbleDelegate: | 42 // PermissionBubbleDelegate: |
| 43 virtual PermissionBubbleRequest::Type GetType() const OVERRIDE; | |
| 43 virtual int GetIconID() const OVERRIDE; | 44 virtual int GetIconID() const OVERRIDE; |
| 44 virtual base::string16 GetMessageText() const OVERRIDE; | 45 virtual base::string16 GetMessageText() const OVERRIDE; |
| 45 virtual base::string16 GetMessageTextFragment() const OVERRIDE; | 46 virtual base::string16 GetMessageTextFragment() const OVERRIDE; |
| 46 virtual bool HasUserGesture() const OVERRIDE; | 47 virtual bool HasUserGesture() const OVERRIDE; |
| 47 virtual GURL GetRequestingHostname() const OVERRIDE; | 48 virtual GURL GetRequestingHostname() const OVERRIDE; |
| 48 virtual void PermissionGranted() OVERRIDE; | 49 virtual void PermissionGranted() OVERRIDE; |
| 50 void PermissionGranted(int choice); | |
|
meacer
2014/08/15 22:20:25
nit: Maybe you might want to rename this to someth
mhm
2014/08/15 23:18:57
Done.
| |
| 49 virtual void PermissionDenied() OVERRIDE; | 51 virtual void PermissionDenied() OVERRIDE; |
| 50 virtual void Cancelled() OVERRIDE; | 52 virtual void Cancelled() OVERRIDE; |
| 51 virtual void RequestFinished() OVERRIDE; | 53 virtual void RequestFinished() OVERRIDE; |
| 52 | 54 |
| 53 private: | 55 private: |
| 54 GeolocationPermissionContext* context_; | 56 GeolocationPermissionContext* context_; |
| 55 PermissionRequestID id_; | 57 PermissionRequestID id_; |
| 56 GURL requesting_frame_; | 58 GURL requesting_frame_; |
| 57 GURL embedder_; | 59 GURL embedder_; |
| 58 bool user_gesture_; | 60 bool user_gesture_; |
| 59 base::Callback<void(bool)> callback_; | 61 base::Callback<void(int, bool)> callback_; |
| 60 std::string display_languages_; | 62 std::string display_languages_; |
| 61 }; | 63 }; |
| 62 | 64 |
| 63 GeolocationPermissionRequest::GeolocationPermissionRequest( | 65 GeolocationPermissionRequest::GeolocationPermissionRequest( |
| 64 GeolocationPermissionContext* context, | 66 GeolocationPermissionContext* context, |
| 65 const PermissionRequestID& id, | 67 const PermissionRequestID& id, |
| 66 const GURL& requesting_frame, | 68 const GURL& requesting_frame, |
| 67 const GURL& embedder, | 69 const GURL& embedder, |
| 68 bool user_gesture, | 70 bool user_gesture, |
| 69 base::Callback<void(bool)> callback, | 71 base::Callback<void(int, bool)> callback, |
| 70 const std::string& display_languages) | 72 const std::string& display_languages) |
| 71 : context_(context), | 73 : context_(context), |
| 72 id_(id), | 74 id_(id), |
| 73 requesting_frame_(requesting_frame), | 75 requesting_frame_(requesting_frame), |
| 74 embedder_(embedder), | 76 embedder_(embedder), |
| 75 user_gesture_(user_gesture), | 77 user_gesture_(user_gesture), |
| 76 callback_(callback), | 78 callback_(callback), |
| 77 display_languages_(display_languages) {} | 79 display_languages_(display_languages) { |
| 80 } | |
| 78 | 81 |
| 79 GeolocationPermissionRequest::~GeolocationPermissionRequest() {} | 82 GeolocationPermissionRequest::~GeolocationPermissionRequest() {} |
| 80 | 83 |
| 84 PermissionBubbleRequest::Type GeolocationPermissionRequest::GetType() const { | |
| 85 return PermissionBubbleRequest::Type::kGeolocation; | |
| 86 } | |
| 87 | |
| 81 int GeolocationPermissionRequest::GetIconID() const { | 88 int GeolocationPermissionRequest::GetIconID() const { |
| 82 return IDR_INFOBAR_GEOLOCATION; | 89 return IDR_INFOBAR_GEOLOCATION; |
| 83 } | 90 } |
| 84 | 91 |
| 85 base::string16 GeolocationPermissionRequest::GetMessageText() const { | 92 base::string16 GeolocationPermissionRequest::GetMessageText() const { |
| 86 return l10n_util::GetStringFUTF16( | 93 return l10n_util::GetStringFUTF16( |
| 87 IDS_GEOLOCATION_INFOBAR_QUESTION, | 94 IDS_GEOLOCATION_INFOBAR_QUESTION, |
| 88 net::FormatUrl(requesting_frame_.GetOrigin(), display_languages_, | 95 net::FormatUrl(requesting_frame_.GetOrigin(), display_languages_, |
| 89 net::kFormatUrlOmitUsernamePassword | | 96 net::kFormatUrlOmitUsernamePassword | |
| 90 net::kFormatUrlOmitTrailingSlashOnBareHostname, | 97 net::kFormatUrlOmitTrailingSlashOnBareHostname, |
| 91 net::UnescapeRule::SPACES, NULL, NULL, NULL)); | 98 net::UnescapeRule::SPACES, NULL, NULL, NULL)); |
| 92 } | 99 } |
| 93 | 100 |
| 94 base::string16 GeolocationPermissionRequest::GetMessageTextFragment() const { | 101 base::string16 GeolocationPermissionRequest::GetMessageTextFragment() const { |
| 95 return l10n_util::GetStringUTF16(IDS_GEOLOCATION_INFOBAR_PERMISSION_FRAGMENT); | 102 return l10n_util::GetStringUTF16(IDS_GEOLOCATION_INFOBAR_PERMISSION_FRAGMENT); |
| 96 } | 103 } |
| 97 | 104 |
| 98 bool GeolocationPermissionRequest::HasUserGesture() const { | 105 bool GeolocationPermissionRequest::HasUserGesture() const { |
| 99 return user_gesture_; | 106 return user_gesture_; |
| 100 } | 107 } |
| 101 | 108 |
| 102 GURL GeolocationPermissionRequest::GetRequestingHostname() const { | 109 GURL GeolocationPermissionRequest::GetRequestingHostname() const { |
| 103 return requesting_frame_; | 110 return requesting_frame_; |
| 104 } | 111 } |
| 105 | 112 |
| 106 void GeolocationPermissionRequest::PermissionGranted() { | 113 void GeolocationPermissionRequest::PermissionGranted() { |
| 114 PermissionGranted(-1); | |
| 115 } | |
| 116 | |
| 117 void GeolocationPermissionRequest::PermissionGranted(int choice) { | |
| 118 // (mhm) Fix content setting to account for different choices. | |
|
meacer
2014/08/15 22:20:25
nit: TODO(mohammed) since that's your Chromium use
mhm
2014/08/15 23:18:57
Done.
| |
| 107 context_->QueueController()->UpdateContentSetting( | 119 context_->QueueController()->UpdateContentSetting( |
| 108 requesting_frame_, embedder_, true); | 120 requesting_frame_, embedder_, true); |
| 109 context_->NotifyPermissionSet(id_, requesting_frame_, callback_, true); | 121 context_->NotifyPermissionSet( |
| 122 id_, requesting_frame_, callback_, true, choice); | |
| 110 } | 123 } |
| 111 | 124 |
| 112 void GeolocationPermissionRequest::PermissionDenied() { | 125 void GeolocationPermissionRequest::PermissionDenied() { |
| 113 context_->QueueController()->UpdateContentSetting( | 126 context_->QueueController()->UpdateContentSetting( |
| 114 requesting_frame_, embedder_, false); | 127 requesting_frame_, embedder_, false); |
| 115 context_->NotifyPermissionSet(id_, requesting_frame_, callback_, false); | 128 context_->NotifyPermissionSet(id_, requesting_frame_, callback_, false); |
| 116 } | 129 } |
| 117 | 130 |
| 118 void GeolocationPermissionRequest::Cancelled() { | 131 void GeolocationPermissionRequest::Cancelled() { |
| 119 } | 132 } |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 136 // or the IO thread, but the PermissionQueueController must have been | 149 // or the IO thread, but the PermissionQueueController must have been |
| 137 // destroyed on the UI thread. | 150 // destroyed on the UI thread. |
| 138 DCHECK(!permission_queue_controller_.get()); | 151 DCHECK(!permission_queue_controller_.get()); |
| 139 } | 152 } |
| 140 | 153 |
| 141 void GeolocationPermissionContext::RequestGeolocationPermission( | 154 void GeolocationPermissionContext::RequestGeolocationPermission( |
| 142 content::WebContents* web_contents, | 155 content::WebContents* web_contents, |
| 143 int bridge_id, | 156 int bridge_id, |
| 144 const GURL& requesting_frame, | 157 const GURL& requesting_frame, |
| 145 bool user_gesture, | 158 bool user_gesture, |
| 146 base::Callback<void(bool)> result_callback, | 159 base::Callback<void(int, bool)> result_callback, |
| 147 base::Closure* cancel_callback) { | 160 base::Closure* cancel_callback) { |
| 148 GURL requesting_frame_origin = requesting_frame.GetOrigin(); | 161 GURL requesting_frame_origin = requesting_frame.GetOrigin(); |
| 149 if (shutting_down_) | 162 if (shutting_down_) |
| 150 return; | 163 return; |
| 151 | 164 |
| 152 int render_process_id = web_contents->GetRenderProcessHost()->GetID(); | 165 int render_process_id = web_contents->GetRenderProcessHost()->GetID(); |
| 153 int render_view_id = web_contents->GetRenderViewHost()->GetRoutingID(); | 166 int render_view_id = web_contents->GetRenderViewHost()->GetRoutingID(); |
| 154 if (cancel_callback) { | 167 if (cancel_callback) { |
| 155 *cancel_callback = base::Bind( | 168 *cancel_callback = base::Bind( |
| 156 &GeolocationPermissionContext::CancelGeolocationPermissionRequest, | 169 &GeolocationPermissionContext::CancelGeolocationPermissionRequest, |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 197 CancelPendingInfobarRequest(PermissionRequestID( | 210 CancelPendingInfobarRequest(PermissionRequestID( |
| 198 render_process_id, render_view_id, bridge_id, GURL())); | 211 render_process_id, render_view_id, bridge_id, GURL())); |
| 199 } | 212 } |
| 200 | 213 |
| 201 void GeolocationPermissionContext::DecidePermission( | 214 void GeolocationPermissionContext::DecidePermission( |
| 202 content::WebContents* web_contents, | 215 content::WebContents* web_contents, |
| 203 const PermissionRequestID& id, | 216 const PermissionRequestID& id, |
| 204 const GURL& requesting_frame, | 217 const GURL& requesting_frame, |
| 205 bool user_gesture, | 218 bool user_gesture, |
| 206 const GURL& embedder, | 219 const GURL& embedder, |
| 207 base::Callback<void(bool)> callback) { | 220 base::Callback<void(int, bool)> callback) { |
| 208 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | 221 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| 209 | 222 |
| 210 ContentSetting content_setting = | 223 ContentSetting content_setting = |
| 211 profile_->GetHostContentSettingsMap() | 224 profile_->GetHostContentSettingsMap() |
| 212 ->GetContentSettingAndMaybeUpdateLastUsage( | 225 ->GetContentSettingAndMaybeUpdateLastUsage( |
| 213 requesting_frame, | 226 requesting_frame, |
| 214 embedder, | 227 embedder, |
| 215 CONTENT_SETTINGS_TYPE_GEOLOCATION, | 228 CONTENT_SETTINGS_TYPE_GEOLOCATION, |
| 216 std::string()); | 229 std::string()); |
| 217 switch (content_setting) { | 230 switch (content_setting) { |
| 218 case CONTENT_SETTING_BLOCK: | 231 case CONTENT_SETTING_BLOCK: |
| 219 PermissionDecided(id, requesting_frame, embedder, callback, false); | 232 PermissionDecided(id, requesting_frame, embedder, callback, false); |
| 220 break; | 233 break; |
| 221 case CONTENT_SETTING_ALLOW: | 234 case CONTENT_SETTING_ALLOW: |
| 235 // We have to save the choice in the context setting to send it here. | |
| 236 // (mhm) fix. | |
|
meacer
2014/08/15 22:20:24
nit: TODO(mohammed): ...
mhm
2014/08/15 23:18:56
Done.
felt
2014/08/15 23:20:15
No TODOs for interns who are about to leave. :) Th
| |
| 222 PermissionDecided(id, requesting_frame, embedder, callback, true); | 237 PermissionDecided(id, requesting_frame, embedder, callback, true); |
| 223 break; | 238 break; |
| 224 default: | 239 default: |
| 225 if (PermissionBubbleManager::Enabled()) { | 240 if (PermissionBubbleManager::Enabled()) { |
| 226 PermissionBubbleManager* mgr = | 241 PermissionBubbleManager* mgr = |
| 227 PermissionBubbleManager::FromWebContents(web_contents); | 242 PermissionBubbleManager::FromWebContents(web_contents); |
| 228 if (mgr) { | 243 if (mgr) { |
| 229 scoped_ptr<GeolocationPermissionRequest> request_ptr( | 244 scoped_ptr<GeolocationPermissionRequest> request_ptr( |
| 230 new GeolocationPermissionRequest( | 245 new GeolocationPermissionRequest( |
| 231 this, id, requesting_frame, embedder, user_gesture, callback, | 246 this, id, requesting_frame, embedder, user_gesture, callback, |
| 232 profile_->GetPrefs()->GetString(prefs::kAcceptLanguages))); | 247 profile_->GetPrefs()->GetString(prefs::kAcceptLanguages))); |
| 233 GeolocationPermissionRequest* request = request_ptr.get(); | 248 GeolocationPermissionRequest* request = request_ptr.get(); |
| 234 pending_requests_.add(id.ToString(), request_ptr.Pass()); | 249 pending_requests_.add(id.ToString(), request_ptr.Pass()); |
| 235 mgr->AddRequest(request); | 250 mgr->AddRequest(request); |
| 236 } | 251 } |
| 237 } else { | 252 } else { |
| 238 // setting == ask. Prompt the user. | 253 // setting == ask. Prompt the user. |
| 254 base::Callback<void(bool)> InfoBarRequestCallback = | |
| 255 base::Bind(callback, -1); | |
| 239 QueueController()->CreateInfoBarRequest( | 256 QueueController()->CreateInfoBarRequest( |
| 240 id, requesting_frame, embedder, | 257 id, |
| 241 base::Bind( | 258 requesting_frame, |
| 242 &GeolocationPermissionContext::NotifyPermissionSet, | 259 embedder, |
| 243 base::Unretained(this), id, requesting_frame, callback)); | 260 base::Bind( |
| 261 &GeolocationPermissionContext::NotifyInfoBarPermissionSet, | |
| 262 base::Unretained(this), | |
| 263 id, | |
| 264 requesting_frame, | |
| 265 InfoBarRequestCallback)); | |
|
meacer
2014/08/15 22:20:25
Does passing base::Bind(callback, -1) directly not
mhm
2014/08/15 23:18:57
Done.
| |
| 244 } | 266 } |
| 245 } | 267 } |
| 246 } | 268 } |
| 247 | 269 |
| 248 void GeolocationPermissionContext::CreateInfoBarRequest( | 270 void GeolocationPermissionContext::CreateInfoBarRequest( |
| 249 const PermissionRequestID& id, | 271 const PermissionRequestID& id, |
| 250 const GURL& requesting_frame, | 272 const GURL& requesting_frame, |
| 251 const GURL& embedder, | 273 const GURL& embedder, |
| 252 base::Callback<void(bool)> callback) { | 274 base::Callback<void(bool)> callback) { |
| 253 QueueController()->CreateInfoBarRequest( | 275 QueueController()->CreateInfoBarRequest( |
| 254 id, requesting_frame, embedder, base::Bind( | 276 id, |
| 255 &GeolocationPermissionContext::NotifyPermissionSet, | 277 requesting_frame, |
| 256 base::Unretained(this), id, requesting_frame, callback)); | 278 embedder, |
| 279 base::Bind(&GeolocationPermissionContext::NotifyInfoBarPermissionSet, | |
| 280 base::Unretained(this), | |
| 281 id, | |
| 282 requesting_frame, | |
| 283 callback)); | |
| 257 } | 284 } |
| 258 | 285 |
| 259 void GeolocationPermissionContext::RequestFinished( | 286 void GeolocationPermissionContext::RequestFinished( |
| 260 GeolocationPermissionRequest* request) { | 287 GeolocationPermissionRequest* request) { |
| 261 base::ScopedPtrHashMap<std::string, | 288 base::ScopedPtrHashMap<std::string, |
| 262 GeolocationPermissionRequest>::iterator it; | 289 GeolocationPermissionRequest>::iterator it; |
| 263 for (it = pending_requests_.begin(); it != pending_requests_.end(); ++it) { | 290 for (it = pending_requests_.begin(); it != pending_requests_.end(); ++it) { |
| 264 if (it->second == request) { | 291 if (it->second == request) { |
| 265 pending_requests_.take_and_erase(it); | 292 pending_requests_.take_and_erase(it); |
| 266 return; | 293 return; |
| 267 } | 294 } |
| 268 } | 295 } |
| 269 } | 296 } |
| 270 | 297 |
| 271 | 298 |
| 272 void GeolocationPermissionContext::ShutdownOnUIThread() { | 299 void GeolocationPermissionContext::ShutdownOnUIThread() { |
| 273 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | 300 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| 274 permission_queue_controller_.reset(); | 301 permission_queue_controller_.reset(); |
| 275 shutting_down_ = true; | 302 shutting_down_ = true; |
| 276 } | 303 } |
| 277 | 304 |
| 278 void GeolocationPermissionContext::PermissionDecided( | 305 void GeolocationPermissionContext::PermissionDecided( |
| 279 const PermissionRequestID& id, | 306 const PermissionRequestID& id, |
| 280 const GURL& requesting_frame, | 307 const GURL& requesting_frame, |
| 281 const GURL& embedder, | 308 const GURL& embedder, |
| 282 base::Callback<void(bool)> callback, | 309 base::Callback<void(int, bool)> callback, |
| 283 bool allowed) { | 310 bool allowed) { |
| 284 NotifyPermissionSet(id, requesting_frame, callback, allowed); | 311 NotifyPermissionSet(id, requesting_frame, callback, allowed); |
| 285 } | 312 } |
| 286 | 313 |
| 287 void GeolocationPermissionContext::NotifyPermissionSet( | 314 void GeolocationPermissionContext::NotifyPermissionSet( |
| 288 const PermissionRequestID& id, | 315 const PermissionRequestID& id, |
| 289 const GURL& requesting_frame, | 316 const GURL& requesting_frame, |
| 317 base::Callback<void(int, bool)> callback, | |
| 318 bool allowed) { | |
| 319 NotifyPermissionSet(id, requesting_frame, callback, -1, allowed); | |
|
meacer
2014/08/15 22:20:24
nit: You can add a constant such as kUnknownPrecis
mhm
2014/08/15 23:18:57
Done.
| |
| 320 } | |
| 321 | |
| 322 void GeolocationPermissionContext::NotifyPermissionSet( | |
| 323 const PermissionRequestID& id, | |
| 324 const GURL& requesting_frame, | |
| 325 base::Callback<void(int, bool)> callback, | |
| 326 bool allowed, | |
| 327 int choice) { | |
| 328 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
| 329 | |
| 330 // WebContents may have gone away (or not exists for extension). | |
| 331 TabSpecificContentSettings* content_settings = | |
| 332 TabSpecificContentSettings::Get(id.render_process_id(), | |
| 333 id.render_view_id()); | |
| 334 if (content_settings) { | |
| 335 content_settings->OnGeolocationPermissionSet(requesting_frame.GetOrigin(), | |
| 336 allowed); | |
|
meacer
2014/08/15 22:20:25
This is fine, but do you need a TODO here, as choi
mhm
2014/08/15 23:18:57
Done.
| |
| 337 } | |
| 338 | |
| 339 callback.Run(allowed, choice); | |
| 340 } | |
| 341 | |
| 342 void GeolocationPermissionContext::NotifyInfoBarPermissionSet( | |
| 343 const PermissionRequestID& id, | |
| 344 const GURL& requesting_frame, | |
| 290 base::Callback<void(bool)> callback, | 345 base::Callback<void(bool)> callback, |
| 291 bool allowed) { | 346 bool allowed) { |
| 292 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | 347 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| 293 | 348 |
| 294 // WebContents may have gone away (or not exists for extension). | 349 // WebContents may have gone away (or not exists for extension). |
| 295 TabSpecificContentSettings* content_settings = | 350 TabSpecificContentSettings* content_settings = |
| 296 TabSpecificContentSettings::Get(id.render_process_id(), | 351 TabSpecificContentSettings::Get(id.render_process_id(), |
| 297 id.render_view_id()); | 352 id.render_view_id()); |
| 298 if (content_settings) { | 353 if (content_settings) { |
| 299 content_settings->OnGeolocationPermissionSet(requesting_frame.GetOrigin(), | 354 content_settings->OnGeolocationPermissionSet(requesting_frame.GetOrigin(), |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 333 if (cancelling != NULL && web_contents != NULL && | 388 if (cancelling != NULL && web_contents != NULL && |
| 334 PermissionBubbleManager::FromWebContents(web_contents) != NULL) { | 389 PermissionBubbleManager::FromWebContents(web_contents) != NULL) { |
| 335 PermissionBubbleManager::FromWebContents(web_contents)-> | 390 PermissionBubbleManager::FromWebContents(web_contents)-> |
| 336 CancelRequest(cancelling); | 391 CancelRequest(cancelling); |
| 337 } | 392 } |
| 338 return; | 393 return; |
| 339 } | 394 } |
| 340 | 395 |
| 341 QueueController()->CancelInfoBarRequest(id); | 396 QueueController()->CancelInfoBarRequest(id); |
| 342 } | 397 } |
| OLD | NEW |