| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 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/renderer/chrome_content_renderer_client.h" | 5 #include "chrome/renderer/chrome_content_renderer_client.h" |
| 6 | 6 |
| 7 #include "base/command_line.h" | 7 #include "base/command_line.h" |
| 8 #include "base/debug/crash_logging.h" | 8 #include "base/debug/crash_logging.h" |
| 9 #include "base/logging.h" | 9 #include "base/logging.h" |
| 10 #include "base/metrics/field_trial.h" | 10 #include "base/metrics/field_trial.h" |
| (...skipping 108 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 119 #include "extensions/common/extension_set.h" | 119 #include "extensions/common/extension_set.h" |
| 120 #include "extensions/common/extension_urls.h" | 120 #include "extensions/common/extension_urls.h" |
| 121 #include "extensions/common/switches.h" | 121 #include "extensions/common/switches.h" |
| 122 #include "extensions/renderer/dispatcher.h" | 122 #include "extensions/renderer/dispatcher.h" |
| 123 #include "extensions/renderer/extension_frame_helper.h" | 123 #include "extensions/renderer/extension_frame_helper.h" |
| 124 #include "extensions/renderer/extension_helper.h" | 124 #include "extensions/renderer/extension_helper.h" |
| 125 #include "extensions/renderer/extensions_render_frame_observer.h" | 125 #include "extensions/renderer/extensions_render_frame_observer.h" |
| 126 #include "extensions/renderer/guest_view/extensions_guest_view_container.h" | 126 #include "extensions/renderer/guest_view/extensions_guest_view_container.h" |
| 127 #include "extensions/renderer/guest_view/extensions_guest_view_container_dispatc
her.h" | 127 #include "extensions/renderer/guest_view/extensions_guest_view_container_dispatc
her.h" |
| 128 #include "extensions/renderer/guest_view/mime_handler_view/mime_handler_view_con
tainer.h" | 128 #include "extensions/renderer/guest_view/mime_handler_view/mime_handler_view_con
tainer.h" |
| 129 #include "extensions/renderer/renderer_extension_registry.h" |
| 129 #include "extensions/renderer/script_context.h" | 130 #include "extensions/renderer/script_context.h" |
| 130 #endif | 131 #endif |
| 131 | 132 |
| 132 #if defined(ENABLE_IPC_FUZZER) | 133 #if defined(ENABLE_IPC_FUZZER) |
| 133 #include "chrome/common/external_ipc_dumper.h" | 134 #include "chrome/common/external_ipc_dumper.h" |
| 134 #endif | 135 #endif |
| 135 | 136 |
| 136 #if defined(ENABLE_PLUGINS) | 137 #if defined(ENABLE_PLUGINS) |
| 137 #include "chrome/renderer/plugins/chrome_plugin_placeholder.h" | 138 #include "chrome/renderer/plugins/chrome_plugin_placeholder.h" |
| 138 #endif | 139 #endif |
| (...skipping 470 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 609 } | 610 } |
| 610 | 611 |
| 611 #if defined(ENABLE_EXTENSIONS) | 612 #if defined(ENABLE_EXTENSIONS) |
| 612 const Extension* ChromeContentRendererClient::GetExtensionByOrigin( | 613 const Extension* ChromeContentRendererClient::GetExtensionByOrigin( |
| 613 const WebSecurityOrigin& origin) const { | 614 const WebSecurityOrigin& origin) const { |
| 614 if (!base::EqualsASCII(base::StringPiece16(origin.protocol()), | 615 if (!base::EqualsASCII(base::StringPiece16(origin.protocol()), |
| 615 extensions::kExtensionScheme)) | 616 extensions::kExtensionScheme)) |
| 616 return NULL; | 617 return NULL; |
| 617 | 618 |
| 618 const std::string extension_id = origin.host().utf8().data(); | 619 const std::string extension_id = origin.host().utf8().data(); |
| 619 return extension_dispatcher_->extensions()->GetByID(extension_id); | 620 return extensions::RendererExtensionRegistry::Get()->GetByID(extension_id); |
| 620 } | 621 } |
| 621 #endif | 622 #endif |
| 622 | 623 |
| 623 scoped_ptr<blink::WebPluginPlaceholder> | 624 scoped_ptr<blink::WebPluginPlaceholder> |
| 624 ChromeContentRendererClient::CreatePluginPlaceholder( | 625 ChromeContentRendererClient::CreatePluginPlaceholder( |
| 625 content::RenderFrame* render_frame, | 626 content::RenderFrame* render_frame, |
| 626 blink::WebLocalFrame* frame, | 627 blink::WebLocalFrame* frame, |
| 627 const blink::WebPluginParams& orig_params) { | 628 const blink::WebPluginParams& orig_params) { |
| 628 return CreateShadowDOMPlaceholderForPluginInfo( | 629 return CreateShadowDOMPlaceholderForPluginInfo( |
| 629 render_frame, frame, orig_params); | 630 render_frame, frame, orig_params); |
| (...skipping 174 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 804 // Normal NaCl/PNaCl embed. The app URL is the page URL. | 805 // Normal NaCl/PNaCl embed. The app URL is the page URL. |
| 805 manifest_url = url; | 806 manifest_url = url; |
| 806 app_url = frame->top()->document().url(); | 807 app_url = frame->top()->document().url(); |
| 807 } else { | 808 } else { |
| 808 // NaCl is being invoked as a content handler. Look up the NaCl | 809 // NaCl is being invoked as a content handler. Look up the NaCl |
| 809 // module using the MIME type. The app URL is the manifest URL. | 810 // module using the MIME type. The app URL is the manifest URL. |
| 810 manifest_url = GetNaClContentHandlerURL(actual_mime_type, info); | 811 manifest_url = GetNaClContentHandlerURL(actual_mime_type, info); |
| 811 app_url = manifest_url; | 812 app_url = manifest_url; |
| 812 } | 813 } |
| 813 const Extension* extension = | 814 const Extension* extension = |
| 814 g_current_client->extension_dispatcher_->extensions()-> | 815 extensions::RendererExtensionRegistry::Get() |
| 815 GetExtensionOrAppByURL(manifest_url); | 816 ->GetExtensionOrAppByURL(manifest_url); |
| 816 if (!IsNaClAllowed(manifest_url, | 817 if (!IsNaClAllowed(manifest_url, |
| 817 app_url, | 818 app_url, |
| 818 is_nacl_unrestricted, | 819 is_nacl_unrestricted, |
| 819 extension, | 820 extension, |
| 820 ¶ms)) { | 821 ¶ms)) { |
| 821 WebString error_message; | 822 WebString error_message; |
| 822 if (is_nacl_mime_type) { | 823 if (is_nacl_mime_type) { |
| 823 error_message = | 824 error_message = |
| 824 "Only unpacked extensions and apps installed from the Chrome " | 825 "Only unpacked extensions and apps installed from the Chrome " |
| 825 "Web Store can load NaCl modules without enabling Native " | 826 "Web Store can load NaCl modules without enabling Native " |
| (...skipping 401 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1227 // to swap in the prerendered page on the browser process. If the prerendered | 1228 // to swap in the prerendered page on the browser process. If the prerendered |
| 1228 // page no longer exists by the time the OpenURL IPC is handled, a normal | 1229 // page no longer exists by the time the OpenURL IPC is handled, a normal |
| 1229 // navigation is attempted. | 1230 // navigation is attempted. |
| 1230 if (prerender_dispatcher_.get() && | 1231 if (prerender_dispatcher_.get() && |
| 1231 prerender_dispatcher_->IsPrerenderURL(url)) { | 1232 prerender_dispatcher_->IsPrerenderURL(url)) { |
| 1232 *send_referrer = true; | 1233 *send_referrer = true; |
| 1233 return true; | 1234 return true; |
| 1234 } | 1235 } |
| 1235 | 1236 |
| 1236 #if defined(ENABLE_EXTENSIONS) | 1237 #if defined(ENABLE_EXTENSIONS) |
| 1237 const extensions::ExtensionSet* extensions = | 1238 const extensions::RendererExtensionRegistry* extensions = |
| 1238 extension_dispatcher_->extensions(); | 1239 extensions::RendererExtensionRegistry::Get(); |
| 1239 | 1240 |
| 1240 // Determine if the new URL is an extension (excluding bookmark apps). | 1241 // Determine if the new URL is an extension (excluding bookmark apps). |
| 1241 const Extension* new_url_extension = extensions::GetNonBookmarkAppExtension( | 1242 const Extension* new_url_extension = extensions::GetNonBookmarkAppExtension( |
| 1242 *extensions, url); | 1243 *extensions->GetMainThreadExtensionSet(), url); |
| 1243 bool is_extension_url = !!new_url_extension; | 1244 bool is_extension_url = !!new_url_extension; |
| 1244 | 1245 |
| 1245 // If the navigation would cross an app extent boundary, we also need | 1246 // If the navigation would cross an app extent boundary, we also need |
| 1246 // to defer to the browser to ensure process isolation. This is not necessary | 1247 // to defer to the browser to ensure process isolation. This is not necessary |
| 1247 // for server redirects, which will be transferred to a new process by the | 1248 // for server redirects, which will be transferred to a new process by the |
| 1248 // browser process when they are ready to commit. It is necessary for client | 1249 // browser process when they are ready to commit. It is necessary for client |
| 1249 // redirects, which won't be transferred in the same way. | 1250 // redirects, which won't be transferred in the same way. |
| 1250 if (!is_server_redirect && | 1251 if (!is_server_redirect && |
| 1251 CrossesExtensionExtents(frame, url, *extensions, is_extension_url, | 1252 CrossesExtensionExtents(frame, url, is_extension_url, |
| 1252 is_initial_navigation)) { | 1253 is_initial_navigation)) { |
| 1253 // Include the referrer in this case since we're going from a hosted web | 1254 // Include the referrer in this case since we're going from a hosted web |
| 1254 // page. (the packaged case is handled previously by the extension | 1255 // page. (the packaged case is handled previously by the extension |
| 1255 // navigation test) | 1256 // navigation test) |
| 1256 *send_referrer = true; | 1257 *send_referrer = true; |
| 1257 | 1258 |
| 1258 const Extension* extension = | 1259 const Extension* extension = extensions->GetExtensionOrAppByURL(url); |
| 1259 extension_dispatcher_->extensions()->GetExtensionOrAppByURL(url); | |
| 1260 if (extension && extension->is_app()) { | 1260 if (extension && extension->is_app()) { |
| 1261 extensions::RecordAppLaunchType( | 1261 extensions::RecordAppLaunchType( |
| 1262 extension_misc::APP_LAUNCH_CONTENT_NAVIGATION, extension->GetType()); | 1262 extension_misc::APP_LAUNCH_CONTENT_NAVIGATION, extension->GetType()); |
| 1263 } | 1263 } |
| 1264 return true; | 1264 return true; |
| 1265 } | 1265 } |
| 1266 | 1266 |
| 1267 // If this is a reload, check whether it has the wrong process type. We | 1267 // If this is a reload, check whether it has the wrong process type. We |
| 1268 // should send it to the browser if it's an extension URL (e.g., hosted app) | 1268 // should send it to the browser if it's an extension URL (e.g., hosted app) |
| 1269 // in a normal process, or if it's a process for an extension that has been | 1269 // in a normal process, or if it's a process for an extension that has been |
| (...skipping 13 matching lines...) Expand all Loading... |
| 1283 bool ChromeContentRendererClient::WillSendRequest( | 1283 bool ChromeContentRendererClient::WillSendRequest( |
| 1284 blink::WebFrame* frame, | 1284 blink::WebFrame* frame, |
| 1285 ui::PageTransition transition_type, | 1285 ui::PageTransition transition_type, |
| 1286 const GURL& url, | 1286 const GURL& url, |
| 1287 const GURL& first_party_for_cookies, | 1287 const GURL& first_party_for_cookies, |
| 1288 GURL* new_url) { | 1288 GURL* new_url) { |
| 1289 // Check whether the request should be allowed. If not allowed, we reset the | 1289 // Check whether the request should be allowed. If not allowed, we reset the |
| 1290 // URL to something invalid to prevent the request and cause an error. | 1290 // URL to something invalid to prevent the request and cause an error. |
| 1291 #if defined(ENABLE_EXTENSIONS) | 1291 #if defined(ENABLE_EXTENSIONS) |
| 1292 if (url.SchemeIs(extensions::kExtensionScheme) && | 1292 if (url.SchemeIs(extensions::kExtensionScheme) && |
| 1293 !extensions::ResourceRequestPolicy::CanRequestResource( | 1293 !extensions::ResourceRequestPolicy::CanRequestResource(url, frame, |
| 1294 url, | 1294 transition_type)) { |
| 1295 frame, | |
| 1296 transition_type, | |
| 1297 extension_dispatcher_->extensions())) { | |
| 1298 *new_url = GURL(chrome::kExtensionInvalidRequestURL); | 1295 *new_url = GURL(chrome::kExtensionInvalidRequestURL); |
| 1299 return true; | 1296 return true; |
| 1300 } | 1297 } |
| 1301 | 1298 |
| 1302 if (url.SchemeIs(extensions::kExtensionResourceScheme) && | 1299 if (url.SchemeIs(extensions::kExtensionResourceScheme) && |
| 1303 !extensions::ResourceRequestPolicy::CanRequestExtensionResourceScheme( | 1300 !extensions::ResourceRequestPolicy::CanRequestExtensionResourceScheme( |
| 1304 url, | 1301 url, |
| 1305 frame)) { | 1302 frame)) { |
| 1306 *new_url = GURL(chrome::kExtensionResourceInvalidRequestURL); | 1303 *new_url = GURL(chrome::kExtensionResourceInvalidRequestURL); |
| 1307 return true; | 1304 return true; |
| (...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1364 } | 1361 } |
| 1365 | 1362 |
| 1366 extensions::Dispatcher* | 1363 extensions::Dispatcher* |
| 1367 ChromeContentRendererClient::GetExtensionDispatcherForTest() { | 1364 ChromeContentRendererClient::GetExtensionDispatcherForTest() { |
| 1368 return extension_dispatcher_.get(); | 1365 return extension_dispatcher_.get(); |
| 1369 } | 1366 } |
| 1370 | 1367 |
| 1371 bool ChromeContentRendererClient::CrossesExtensionExtents( | 1368 bool ChromeContentRendererClient::CrossesExtensionExtents( |
| 1372 blink::WebLocalFrame* frame, | 1369 blink::WebLocalFrame* frame, |
| 1373 const GURL& new_url, | 1370 const GURL& new_url, |
| 1374 const extensions::ExtensionSet& extensions, | |
| 1375 bool is_extension_url, | 1371 bool is_extension_url, |
| 1376 bool is_initial_navigation) { | 1372 bool is_initial_navigation) { |
| 1377 DCHECK(!frame->parent()); | 1373 DCHECK(!frame->parent()); |
| 1378 GURL old_url(frame->document().url()); | 1374 GURL old_url(frame->document().url()); |
| 1379 | 1375 |
| 1376 extensions::RendererExtensionRegistry* extensions = |
| 1377 extensions::RendererExtensionRegistry::Get(); |
| 1378 |
| 1380 // If old_url is still empty and this is an initial navigation, then this is | 1379 // If old_url is still empty and this is an initial navigation, then this is |
| 1381 // a window.open operation. We should look at the opener URL. Note that the | 1380 // a window.open operation. We should look at the opener URL. Note that the |
| 1382 // opener is a local frame in this case. | 1381 // opener is a local frame in this case. |
| 1383 if (is_initial_navigation && old_url.is_empty() && frame->opener()) { | 1382 if (is_initial_navigation && old_url.is_empty() && frame->opener()) { |
| 1384 WebLocalFrame* opener_frame = frame->opener()->toWebLocalFrame(); | 1383 WebLocalFrame* opener_frame = frame->opener()->toWebLocalFrame(); |
| 1385 | 1384 |
| 1386 // If we're about to open a normal web page from a same-origin opener stuck | 1385 // If we're about to open a normal web page from a same-origin opener stuck |
| 1387 // in an extension process, we want to keep it in process to allow the | 1386 // in an extension process, we want to keep it in process to allow the |
| 1388 // opener to script it. | 1387 // opener to script it. |
| 1389 WebDocument opener_document = opener_frame->document(); | 1388 WebDocument opener_document = opener_frame->document(); |
| 1390 WebSecurityOrigin opener_origin = opener_document.securityOrigin(); | 1389 WebSecurityOrigin opener_origin = opener_document.securityOrigin(); |
| 1391 bool opener_is_extension_url = | 1390 bool opener_is_extension_url = |
| 1392 !opener_origin.isUnique() && extensions.GetExtensionOrAppByURL( | 1391 !opener_origin.isUnique() && |
| 1393 opener_document.url()) != NULL; | 1392 extensions->GetExtensionOrAppByURL(opener_document.url()) != NULL; |
| 1394 if (!is_extension_url && | 1393 if (!is_extension_url && |
| 1395 !opener_is_extension_url && | 1394 !opener_is_extension_url && |
| 1396 IsStandaloneExtensionProcess() && | 1395 IsStandaloneExtensionProcess() && |
| 1397 opener_origin.canRequest(WebURL(new_url))) | 1396 opener_origin.canRequest(WebURL(new_url))) |
| 1398 return false; | 1397 return false; |
| 1399 | 1398 |
| 1400 // In all other cases, we want to compare against the URL that determines | 1399 // In all other cases, we want to compare against the URL that determines |
| 1401 // the type of process. In default Chrome, that's the URL of the opener's | 1400 // the type of process. In default Chrome, that's the URL of the opener's |
| 1402 // top frame and not the opener frame itself. In --site-per-process, we | 1401 // top frame and not the opener frame itself. In --site-per-process, we |
| 1403 // can use the opener frame itself. | 1402 // can use the opener frame itself. |
| 1404 // TODO(nick): Either wire this up to SiteIsolationPolicy, or to state on | 1403 // TODO(nick): Either wire this up to SiteIsolationPolicy, or to state on |
| 1405 // |opener_frame|/its ancestors. | 1404 // |opener_frame|/its ancestors. |
| 1406 if (base::CommandLine::ForCurrentProcess()->HasSwitch( | 1405 if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| 1407 switches::kSitePerProcess)) | 1406 switches::kSitePerProcess)) |
| 1408 old_url = opener_frame->document().url(); | 1407 old_url = opener_frame->document().url(); |
| 1409 else | 1408 else |
| 1410 old_url = opener_frame->top()->document().url(); | 1409 old_url = opener_frame->top()->document().url(); |
| 1411 } | 1410 } |
| 1412 | 1411 |
| 1413 // Only consider keeping non-app URLs in an app process if this window | 1412 // Only consider keeping non-app URLs in an app process if this window |
| 1414 // has an opener (in which case it might be an OAuth popup that tries to | 1413 // has an opener (in which case it might be an OAuth popup that tries to |
| 1415 // script an iframe within the app). | 1414 // script an iframe within the app). |
| 1416 bool should_consider_workaround = !!frame->opener(); | 1415 bool should_consider_workaround = !!frame->opener(); |
| 1417 | 1416 |
| 1418 return extensions::CrossesExtensionProcessBoundary( | 1417 return extensions::CrossesExtensionProcessBoundary( |
| 1419 extensions, old_url, new_url, should_consider_workaround); | 1418 *extensions->GetMainThreadExtensionSet(), old_url, new_url, |
| 1419 should_consider_workaround); |
| 1420 } | 1420 } |
| 1421 #endif // defined(ENABLE_EXTENSIONS) | 1421 #endif // defined(ENABLE_EXTENSIONS) |
| 1422 | 1422 |
| 1423 #if defined(ENABLE_SPELLCHECK) | 1423 #if defined(ENABLE_SPELLCHECK) |
| 1424 void ChromeContentRendererClient::SetSpellcheck(SpellCheck* spellcheck) { | 1424 void ChromeContentRendererClient::SetSpellcheck(SpellCheck* spellcheck) { |
| 1425 RenderThread* thread = RenderThread::Get(); | 1425 RenderThread* thread = RenderThread::Get(); |
| 1426 if (spellcheck_.get() && thread) | 1426 if (spellcheck_.get() && thread) |
| 1427 thread->RemoveObserver(spellcheck_.get()); | 1427 thread->RemoveObserver(spellcheck_.get()); |
| 1428 spellcheck_.reset(spellcheck); | 1428 spellcheck_.reset(spellcheck); |
| 1429 SpellCheckReplacer replacer(spellcheck_.get()); | 1429 SpellCheckReplacer replacer(spellcheck_.get()); |
| (...skipping 27 matching lines...) Expand all Loading... |
| 1457 // TODO(bbudge) remove this when the trusted NaCl plugin has been removed. | 1457 // TODO(bbudge) remove this when the trusted NaCl plugin has been removed. |
| 1458 // We must defer certain plugin events for NaCl instances since we switch | 1458 // We must defer certain plugin events for NaCl instances since we switch |
| 1459 // from the in-process to the out-of-process proxy after instantiating them. | 1459 // from the in-process to the out-of-process proxy after instantiating them. |
| 1460 return module_name == "Native Client"; | 1460 return module_name == "Native Client"; |
| 1461 } | 1461 } |
| 1462 | 1462 |
| 1463 #if defined(ENABLE_PLUGINS) && defined(ENABLE_EXTENSIONS) | 1463 #if defined(ENABLE_PLUGINS) && defined(ENABLE_EXTENSIONS) |
| 1464 bool ChromeContentRendererClient::IsExtensionOrSharedModuleWhitelisted( | 1464 bool ChromeContentRendererClient::IsExtensionOrSharedModuleWhitelisted( |
| 1465 const GURL& url, const std::set<std::string>& whitelist) { | 1465 const GURL& url, const std::set<std::string>& whitelist) { |
| 1466 const extensions::ExtensionSet* extension_set = | 1466 const extensions::ExtensionSet* extension_set = |
| 1467 g_current_client->extension_dispatcher_->extensions(); | 1467 extensions::RendererExtensionRegistry::Get()->GetMainThreadExtensionSet(); |
| 1468 return chrome::IsExtensionOrSharedModuleWhitelisted(url, extension_set, | 1468 return chrome::IsExtensionOrSharedModuleWhitelisted(url, extension_set, |
| 1469 whitelist); | 1469 whitelist); |
| 1470 } | 1470 } |
| 1471 #endif | 1471 #endif |
| 1472 | 1472 |
| 1473 blink::WebSpeechSynthesizer* | 1473 blink::WebSpeechSynthesizer* |
| 1474 ChromeContentRendererClient::OverrideSpeechSynthesizer( | 1474 ChromeContentRendererClient::OverrideSpeechSynthesizer( |
| 1475 blink::WebSpeechSynthesizerClient* client) { | 1475 blink::WebSpeechSynthesizerClient* client) { |
| 1476 return new TtsDispatcher(client); | 1476 return new TtsDispatcher(client); |
| 1477 } | 1477 } |
| (...skipping 149 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1627 WebString header_key(ASCIIToUTF16( | 1627 WebString header_key(ASCIIToUTF16( |
| 1628 data_reduction_proxy::chrome_proxy_header())); | 1628 data_reduction_proxy::chrome_proxy_header())); |
| 1629 if (!response.httpHeaderField(header_key).isNull() && | 1629 if (!response.httpHeaderField(header_key).isNull() && |
| 1630 response.httpHeaderField(header_key).utf8().find( | 1630 response.httpHeaderField(header_key).utf8().find( |
| 1631 data_reduction_proxy::chrome_proxy_lo_fi_directive()) != | 1631 data_reduction_proxy::chrome_proxy_lo_fi_directive()) != |
| 1632 std::string::npos) { | 1632 std::string::npos) { |
| 1633 (*properties)[data_reduction_proxy::chrome_proxy_header()] = | 1633 (*properties)[data_reduction_proxy::chrome_proxy_header()] = |
| 1634 data_reduction_proxy::chrome_proxy_lo_fi_directive(); | 1634 data_reduction_proxy::chrome_proxy_lo_fi_directive(); |
| 1635 } | 1635 } |
| 1636 } | 1636 } |
| OLD | NEW |