| OLD | NEW |
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 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 <vector> | 5 #include <vector> |
| 6 | 6 |
| 7 #include "base/file_path.h" | 7 #include "base/file_path.h" |
| 8 #include "base/message_loop.h" | 8 #include "base/message_loop.h" |
| 9 #include "base/process_util.h" | 9 #include "base/process_util.h" |
| 10 #include "chrome/common/chrome_plugin_lib.h" | 10 #include "chrome/common/chrome_plugin_lib.h" |
| 11 #include "chrome/common/render_messages.h" | 11 #include "chrome/common/render_messages.h" |
| 12 #include "chrome/common/render_messages_params.h" | 12 #include "chrome/common/render_messages_params.h" |
| 13 #include "chrome/common/resource_response.h" | |
| 14 #include "content/browser/browser_thread.h" | 13 #include "content/browser/browser_thread.h" |
| 15 #include "content/browser/child_process_security_policy.h" | 14 #include "content/browser/child_process_security_policy.h" |
| 16 #include "content/browser/renderer_host/resource_dispatcher_host.h" | 15 #include "content/browser/renderer_host/resource_dispatcher_host.h" |
| 17 #include "content/browser/renderer_host/resource_dispatcher_host_request_info.h" | 16 #include "content/browser/renderer_host/resource_dispatcher_host_request_info.h" |
| 18 #include "content/browser/renderer_host/resource_handler.h" | 17 #include "content/browser/renderer_host/resource_handler.h" |
| 19 #include "content/browser/renderer_host/resource_message_filter.h" | 18 #include "content/browser/renderer_host/resource_message_filter.h" |
| 19 #include "content/common/resource_messages.h" |
| 20 #include "content/common/resource_response.h" |
| 20 #include "net/base/net_errors.h" | 21 #include "net/base/net_errors.h" |
| 21 #include "net/base/upload_data.h" | 22 #include "net/base/upload_data.h" |
| 22 #include "net/http/http_util.h" | 23 #include "net/http/http_util.h" |
| 23 #include "net/url_request/url_request.h" | 24 #include "net/url_request/url_request.h" |
| 24 #include "net/url_request/url_request_job.h" | 25 #include "net/url_request/url_request_job.h" |
| 25 #include "net/url_request/url_request_test_job.h" | 26 #include "net/url_request/url_request_test_job.h" |
| 26 #include "testing/gtest/include/gtest/gtest.h" | 27 #include "testing/gtest/include/gtest/gtest.h" |
| 27 #include "webkit/appcache/appcache_interfaces.h" | 28 #include "webkit/appcache/appcache_interfaces.h" |
| 28 | 29 |
| 29 // TODO(eroman): Write unit tests for SafeBrowsing that exercise | 30 // TODO(eroman): Write unit tests for SafeBrowsing that exercise |
| 30 // SafeBrowsingResourceHandler. | 31 // SafeBrowsingResourceHandler. |
| 31 | 32 |
| 32 namespace { | 33 namespace { |
| 33 | 34 |
| 34 // Returns the resource response header structure for this request. | 35 // Returns the resource response header structure for this request. |
| 35 void GetResponseHead(const std::vector<IPC::Message>& messages, | 36 void GetResponseHead(const std::vector<IPC::Message>& messages, |
| 36 ResourceResponseHead* response_head) { | 37 ResourceResponseHead* response_head) { |
| 37 ASSERT_GE(messages.size(), 2U); | 38 ASSERT_GE(messages.size(), 2U); |
| 38 | 39 |
| 39 // The first messages should be received response. | 40 // The first messages should be received response. |
| 40 ASSERT_EQ(ViewMsg_Resource_ReceivedResponse::ID, messages[0].type()); | 41 ASSERT_EQ(ResourceMsg_ReceivedResponse::ID, messages[0].type()); |
| 41 | 42 |
| 42 void* iter = NULL; | 43 void* iter = NULL; |
| 43 int request_id; | 44 int request_id; |
| 44 ASSERT_TRUE(IPC::ReadParam(&messages[0], &iter, &request_id)); | 45 ASSERT_TRUE(IPC::ReadParam(&messages[0], &iter, &request_id)); |
| 45 ASSERT_TRUE(IPC::ReadParam(&messages[0], &iter, response_head)); | 46 ASSERT_TRUE(IPC::ReadParam(&messages[0], &iter, response_head)); |
| 46 } | 47 } |
| 47 | 48 |
| 48 } // namespace | 49 } // namespace |
| 49 | 50 |
| 50 static int RequestIDForMessage(const IPC::Message& msg) { | 51 static int RequestIDForMessage(const IPC::Message& msg) { |
| 51 int request_id = -1; | 52 int request_id = -1; |
| 52 switch (msg.type()) { | 53 switch (msg.type()) { |
| 53 case ViewMsg_Resource_UploadProgress::ID: | 54 case ResourceMsg_UploadProgress::ID: |
| 54 case ViewMsg_Resource_ReceivedResponse::ID: | 55 case ResourceMsg_ReceivedResponse::ID: |
| 55 case ViewMsg_Resource_ReceivedRedirect::ID: | 56 case ResourceMsg_ReceivedRedirect::ID: |
| 56 case ViewMsg_Resource_DataReceived::ID: | 57 case ResourceMsg_DataReceived::ID: |
| 57 case ViewMsg_Resource_RequestComplete::ID: | 58 case ResourceMsg_RequestComplete::ID: |
| 58 request_id = IPC::MessageIterator(msg).NextInt(); | 59 request_id = IPC::MessageIterator(msg).NextInt(); |
| 59 break; | 60 break; |
| 60 } | 61 } |
| 61 return request_id; | 62 return request_id; |
| 62 } | 63 } |
| 63 | 64 |
| 64 static ViewHostMsg_Resource_Request CreateResourceRequest( | 65 static ResourceHostMsg_Request CreateResourceRequest( |
| 65 const char* method, | 66 const char* method, |
| 66 ResourceType::Type type, | 67 ResourceType::Type type, |
| 67 const GURL& url) { | 68 const GURL& url) { |
| 68 ViewHostMsg_Resource_Request request; | 69 ResourceHostMsg_Request request; |
| 69 request.method = std::string(method); | 70 request.method = std::string(method); |
| 70 request.url = url; | 71 request.url = url; |
| 71 request.first_party_for_cookies = url; // bypass third-party cookie blocking | 72 request.first_party_for_cookies = url; // bypass third-party cookie blocking |
| 72 request.load_flags = 0; | 73 request.load_flags = 0; |
| 73 request.origin_pid = 0; | 74 request.origin_pid = 0; |
| 74 request.resource_type = type; | 75 request.resource_type = type; |
| 75 request.request_context = 0; | 76 request.request_context = 0; |
| 76 request.appcache_host_id = appcache::kNoHostId; | 77 request.appcache_host_id = appcache::kNoHostId; |
| 77 request.download_to_file = false; | 78 request.download_to_file = false; |
| 78 request.host_renderer_id = -1; | 79 request.host_renderer_id = -1; |
| (...skipping 199 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 278 int request_id, | 279 int request_id, |
| 279 const GURL& url) { | 280 const GURL& url) { |
| 280 MakeTestRequest(filter_.get(), render_view_id, request_id, url); | 281 MakeTestRequest(filter_.get(), render_view_id, request_id, url); |
| 281 } | 282 } |
| 282 | 283 |
| 283 void ResourceDispatcherHostTest::MakeTestRequest( | 284 void ResourceDispatcherHostTest::MakeTestRequest( |
| 284 ResourceMessageFilter* filter, | 285 ResourceMessageFilter* filter, |
| 285 int render_view_id, | 286 int render_view_id, |
| 286 int request_id, | 287 int request_id, |
| 287 const GURL& url) { | 288 const GURL& url) { |
| 288 ViewHostMsg_Resource_Request request = | 289 ResourceHostMsg_Request request = |
| 289 CreateResourceRequest("GET", resource_type_, url); | 290 CreateResourceRequest("GET", resource_type_, url); |
| 290 ViewHostMsg_RequestResource msg(render_view_id, request_id, request); | 291 ResourceHostMsg_RequestResource msg(render_view_id, request_id, request); |
| 291 bool msg_was_ok; | 292 bool msg_was_ok; |
| 292 host_.OnMessageReceived(msg, filter, &msg_was_ok); | 293 host_.OnMessageReceived(msg, filter, &msg_was_ok); |
| 293 KickOffRequest(); | 294 KickOffRequest(); |
| 294 } | 295 } |
| 295 | 296 |
| 296 void ResourceDispatcherHostTest::MakeCancelRequest(int request_id) { | 297 void ResourceDispatcherHostTest::MakeCancelRequest(int request_id) { |
| 297 host_.CancelRequest(filter_->child_id(), request_id, false); | 298 host_.CancelRequest(filter_->child_id(), request_id, false); |
| 298 } | 299 } |
| 299 | 300 |
| 300 void CheckSuccessfulRequest(const std::vector<IPC::Message>& messages, | 301 void CheckSuccessfulRequest(const std::vector<IPC::Message>& messages, |
| 301 const std::string& reference_data) { | 302 const std::string& reference_data) { |
| 302 // A successful request will have received 4 messages: | 303 // A successful request will have received 4 messages: |
| 303 // ReceivedResponse (indicates headers received) | 304 // ReceivedResponse (indicates headers received) |
| 304 // DataReceived (data) | 305 // DataReceived (data) |
| 305 // XXX DataReceived (0 bytes remaining from a read) | 306 // XXX DataReceived (0 bytes remaining from a read) |
| 306 // RequestComplete (request is done) | 307 // RequestComplete (request is done) |
| 307 // | 308 // |
| 308 // This function verifies that we received 4 messages and that they | 309 // This function verifies that we received 4 messages and that they |
| 309 // are appropriate. | 310 // are appropriate. |
| 310 ASSERT_EQ(3U, messages.size()); | 311 ASSERT_EQ(3U, messages.size()); |
| 311 | 312 |
| 312 // The first messages should be received response | 313 // The first messages should be received response |
| 313 ASSERT_EQ(ViewMsg_Resource_ReceivedResponse::ID, messages[0].type()); | 314 ASSERT_EQ(ResourceMsg_ReceivedResponse::ID, messages[0].type()); |
| 314 | 315 |
| 315 // followed by the data, currently we only do the data in one chunk, but | 316 // followed by the data, currently we only do the data in one chunk, but |
| 316 // should probably test multiple chunks later | 317 // should probably test multiple chunks later |
| 317 ASSERT_EQ(ViewMsg_Resource_DataReceived::ID, messages[1].type()); | 318 ASSERT_EQ(ResourceMsg_DataReceived::ID, messages[1].type()); |
| 318 | 319 |
| 319 void* iter = NULL; | 320 void* iter = NULL; |
| 320 int request_id; | 321 int request_id; |
| 321 ASSERT_TRUE(IPC::ReadParam(&messages[1], &iter, &request_id)); | 322 ASSERT_TRUE(IPC::ReadParam(&messages[1], &iter, &request_id)); |
| 322 base::SharedMemoryHandle shm_handle; | 323 base::SharedMemoryHandle shm_handle; |
| 323 ASSERT_TRUE(IPC::ReadParam(&messages[1], &iter, &shm_handle)); | 324 ASSERT_TRUE(IPC::ReadParam(&messages[1], &iter, &shm_handle)); |
| 324 uint32 data_len; | 325 uint32 data_len; |
| 325 ASSERT_TRUE(IPC::ReadParam(&messages[1], &iter, &data_len)); | 326 ASSERT_TRUE(IPC::ReadParam(&messages[1], &iter, &data_len)); |
| 326 | 327 |
| 327 ASSERT_EQ(reference_data.size(), data_len); | 328 ASSERT_EQ(reference_data.size(), data_len); |
| 328 base::SharedMemory shared_mem(shm_handle, true); // read only | 329 base::SharedMemory shared_mem(shm_handle, true); // read only |
| 329 shared_mem.Map(data_len); | 330 shared_mem.Map(data_len); |
| 330 const char* data = static_cast<char*>(shared_mem.memory()); | 331 const char* data = static_cast<char*>(shared_mem.memory()); |
| 331 ASSERT_EQ(0, memcmp(reference_data.c_str(), data, data_len)); | 332 ASSERT_EQ(0, memcmp(reference_data.c_str(), data, data_len)); |
| 332 | 333 |
| 333 // followed by a 0-byte read | 334 // followed by a 0-byte read |
| 334 //ASSERT_EQ(ViewMsg_Resource_DataReceived::ID, messages[2].type()); | 335 //ASSERT_EQ(ResourceMsg_DataReceived::ID, messages[2].type()); |
| 335 | 336 |
| 336 // the last message should be all data received | 337 // the last message should be all data received |
| 337 ASSERT_EQ(ViewMsg_Resource_RequestComplete::ID, messages[2].type()); | 338 ASSERT_EQ(ResourceMsg_RequestComplete::ID, messages[2].type()); |
| 338 } | 339 } |
| 339 | 340 |
| 340 // Tests whether many messages get dispatched properly. | 341 // Tests whether many messages get dispatched properly. |
| 341 TEST_F(ResourceDispatcherHostTest, TestMany) { | 342 TEST_F(ResourceDispatcherHostTest, TestMany) { |
| 342 EXPECT_EQ(0, host_.GetOutstandingRequestsMemoryCost(0)); | 343 EXPECT_EQ(0, host_.GetOutstandingRequestsMemoryCost(0)); |
| 343 | 344 |
| 344 MakeTestRequest(0, 1, net::URLRequestTestJob::test_url_1()); | 345 MakeTestRequest(0, 1, net::URLRequestTestJob::test_url_1()); |
| 345 MakeTestRequest(0, 2, net::URLRequestTestJob::test_url_2()); | 346 MakeTestRequest(0, 2, net::URLRequestTestJob::test_url_2()); |
| 346 MakeTestRequest(0, 3, net::URLRequestTestJob::test_url_3()); | 347 MakeTestRequest(0, 3, net::URLRequestTestJob::test_url_3()); |
| 347 | 348 |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 382 accum_.GetClassifiedMessages(&msgs); | 383 accum_.GetClassifiedMessages(&msgs); |
| 383 | 384 |
| 384 // there are three requests, so we should have gotten them classified as such | 385 // there are three requests, so we should have gotten them classified as such |
| 385 ASSERT_EQ(3U, msgs.size()); | 386 ASSERT_EQ(3U, msgs.size()); |
| 386 | 387 |
| 387 CheckSuccessfulRequest(msgs[0], net::URLRequestTestJob::test_data_1()); | 388 CheckSuccessfulRequest(msgs[0], net::URLRequestTestJob::test_data_1()); |
| 388 CheckSuccessfulRequest(msgs[2], net::URLRequestTestJob::test_data_3()); | 389 CheckSuccessfulRequest(msgs[2], net::URLRequestTestJob::test_data_3()); |
| 389 | 390 |
| 390 // Check that request 2 got canceled. | 391 // Check that request 2 got canceled. |
| 391 ASSERT_EQ(2U, msgs[1].size()); | 392 ASSERT_EQ(2U, msgs[1].size()); |
| 392 ASSERT_EQ(ViewMsg_Resource_ReceivedResponse::ID, msgs[1][0].type()); | 393 ASSERT_EQ(ResourceMsg_ReceivedResponse::ID, msgs[1][0].type()); |
| 393 ASSERT_EQ(ViewMsg_Resource_RequestComplete::ID, msgs[1][1].type()); | 394 ASSERT_EQ(ResourceMsg_RequestComplete::ID, msgs[1][1].type()); |
| 394 | 395 |
| 395 int request_id; | 396 int request_id; |
| 396 net::URLRequestStatus status; | 397 net::URLRequestStatus status; |
| 397 | 398 |
| 398 void* iter = NULL; | 399 void* iter = NULL; |
| 399 ASSERT_TRUE(IPC::ReadParam(&msgs[1][1], &iter, &request_id)); | 400 ASSERT_TRUE(IPC::ReadParam(&msgs[1][1], &iter, &request_id)); |
| 400 ASSERT_TRUE(IPC::ReadParam(&msgs[1][1], &iter, &status)); | 401 ASSERT_TRUE(IPC::ReadParam(&msgs[1][1], &iter, &status)); |
| 401 | 402 |
| 402 EXPECT_EQ(net::URLRequestStatus::CANCELED, status.status()); | 403 EXPECT_EQ(net::URLRequestStatus::CANCELED, status.status()); |
| 403 } | 404 } |
| (...skipping 18 matching lines...) Expand all Loading... |
| 422 } | 423 } |
| 423 bool has_canceled_; | 424 bool has_canceled_; |
| 424 int received_after_canceled_; | 425 int received_after_canceled_; |
| 425 }; | 426 }; |
| 426 | 427 |
| 427 // Tests CancelRequestsForProcess | 428 // Tests CancelRequestsForProcess |
| 428 TEST_F(ResourceDispatcherHostTest, TestProcessCancel) { | 429 TEST_F(ResourceDispatcherHostTest, TestProcessCancel) { |
| 429 scoped_refptr<TestFilter> test_filter = new TestFilter(); | 430 scoped_refptr<TestFilter> test_filter = new TestFilter(); |
| 430 | 431 |
| 431 // request 1 goes to the test delegate | 432 // request 1 goes to the test delegate |
| 432 ViewHostMsg_Resource_Request request = CreateResourceRequest( | 433 ResourceHostMsg_Request request = CreateResourceRequest( |
| 433 "GET", ResourceType::SUB_RESOURCE, net::URLRequestTestJob::test_url_1()); | 434 "GET", ResourceType::SUB_RESOURCE, net::URLRequestTestJob::test_url_1()); |
| 434 | 435 |
| 435 EXPECT_EQ(0, host_.GetOutstandingRequestsMemoryCost(0)); | 436 EXPECT_EQ(0, host_.GetOutstandingRequestsMemoryCost(0)); |
| 436 | 437 |
| 437 MakeTestRequest(test_filter.get(), 0, 1, | 438 MakeTestRequest(test_filter.get(), 0, 1, |
| 438 net::URLRequestTestJob::test_url_1()); | 439 net::URLRequestTestJob::test_url_1()); |
| 439 | 440 |
| 440 // request 2 goes to us | 441 // request 2 goes to us |
| 441 MakeTestRequest(0, 2, net::URLRequestTestJob::test_url_2()); | 442 MakeTestRequest(0, 2, net::URLRequestTestJob::test_url_2()); |
| 442 | 443 |
| (...skipping 308 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 751 // Check that the first kMaxRequests succeeded. | 752 // Check that the first kMaxRequests succeeded. |
| 752 for (size_t i = 0; i < kMaxRequests; ++i) | 753 for (size_t i = 0; i < kMaxRequests; ++i) |
| 753 CheckSuccessfulRequest(msgs[i], net::URLRequestTestJob::test_data_2()); | 754 CheckSuccessfulRequest(msgs[i], net::URLRequestTestJob::test_data_2()); |
| 754 | 755 |
| 755 // Check that the subsequent two requests (kMaxRequests + 1) and | 756 // Check that the subsequent two requests (kMaxRequests + 1) and |
| 756 // (kMaxRequests + 2) were failed, since the per-process bound was reached. | 757 // (kMaxRequests + 2) were failed, since the per-process bound was reached. |
| 757 for (int i = 0; i < 2; ++i) { | 758 for (int i = 0; i < 2; ++i) { |
| 758 // Should have sent a single RequestComplete message. | 759 // Should have sent a single RequestComplete message. |
| 759 int index = kMaxRequests + i; | 760 int index = kMaxRequests + i; |
| 760 EXPECT_EQ(1U, msgs[index].size()); | 761 EXPECT_EQ(1U, msgs[index].size()); |
| 761 EXPECT_EQ(ViewMsg_Resource_RequestComplete::ID, msgs[index][0].type()); | 762 EXPECT_EQ(ResourceMsg_RequestComplete::ID, msgs[index][0].type()); |
| 762 | 763 |
| 763 // The RequestComplete message should have had status | 764 // The RequestComplete message should have had status |
| 764 // (CANCELLED, ERR_INSUFFICIENT_RESOURCES). | 765 // (CANCELLED, ERR_INSUFFICIENT_RESOURCES). |
| 765 int request_id; | 766 int request_id; |
| 766 net::URLRequestStatus status; | 767 net::URLRequestStatus status; |
| 767 | 768 |
| 768 void* iter = NULL; | 769 void* iter = NULL; |
| 769 EXPECT_TRUE(IPC::ReadParam(&msgs[index][0], &iter, &request_id)); | 770 EXPECT_TRUE(IPC::ReadParam(&msgs[index][0], &iter, &request_id)); |
| 770 EXPECT_TRUE(IPC::ReadParam(&msgs[index][0], &iter, &status)); | 771 EXPECT_TRUE(IPC::ReadParam(&msgs[index][0], &iter, &status)); |
| 771 | 772 |
| (...skipping 143 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 915 while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} | 916 while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} |
| 916 | 917 |
| 917 EXPECT_EQ(0, host_.GetOutstandingRequestsMemoryCost(0)); | 918 EXPECT_EQ(0, host_.GetOutstandingRequestsMemoryCost(0)); |
| 918 | 919 |
| 919 // Sorts out all the messages we saw by request. | 920 // Sorts out all the messages we saw by request. |
| 920 ResourceIPCAccumulator::ClassifiedMessages msgs; | 921 ResourceIPCAccumulator::ClassifiedMessages msgs; |
| 921 accum_.GetClassifiedMessages(&msgs); | 922 accum_.GetClassifiedMessages(&msgs); |
| 922 | 923 |
| 923 // We should have gotten one RequestComplete message. | 924 // We should have gotten one RequestComplete message. |
| 924 ASSERT_EQ(1U, msgs[0].size()); | 925 ASSERT_EQ(1U, msgs[0].size()); |
| 925 EXPECT_EQ(ViewMsg_Resource_RequestComplete::ID, msgs[0][0].type()); | 926 EXPECT_EQ(ResourceMsg_RequestComplete::ID, msgs[0][0].type()); |
| 926 | 927 |
| 927 // The RequestComplete message should have had status | 928 // The RequestComplete message should have had status |
| 928 // (CANCELED, ERR_FILE_NOT_FOUND). | 929 // (CANCELED, ERR_FILE_NOT_FOUND). |
| 929 int request_id; | 930 int request_id; |
| 930 net::URLRequestStatus status; | 931 net::URLRequestStatus status; |
| 931 | 932 |
| 932 void* iter = NULL; | 933 void* iter = NULL; |
| 933 EXPECT_TRUE(IPC::ReadParam(&msgs[0][0], &iter, &request_id)); | 934 EXPECT_TRUE(IPC::ReadParam(&msgs[0][0], &iter, &request_id)); |
| 934 EXPECT_TRUE(IPC::ReadParam(&msgs[0][0], &iter, &status)); | 935 EXPECT_TRUE(IPC::ReadParam(&msgs[0][0], &iter, &status)); |
| 935 | 936 |
| (...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 994 return new ResourceDispatcherHostRequestInfo( | 995 return new ResourceDispatcherHostRequestInfo( |
| 995 resource_handler_.get(), ChildProcessInfo::RENDER_PROCESS, 0, 0, 0, | 996 resource_handler_.get(), ChildProcessInfo::RENDER_PROCESS, 0, 0, 0, |
| 996 ResourceType::STYLESHEET, 0U, false, false, false, -1, -1); | 997 ResourceType::STYLESHEET, 0U, false, false, false, -1, -1); |
| 997 } | 998 } |
| 998 | 999 |
| 999 scoped_ptr<GURL> url_; | 1000 scoped_ptr<GURL> url_; |
| 1000 ResourceType::Type resource_type_; | 1001 ResourceType::Type resource_type_; |
| 1001 scoped_ptr<DummyResourceHandler> resource_handler_; | 1002 scoped_ptr<DummyResourceHandler> resource_handler_; |
| 1002 scoped_ptr<ResourceDispatcherHostRequestInfo> request_info_; | 1003 scoped_ptr<ResourceDispatcherHostRequestInfo> request_info_; |
| 1003 }; | 1004 }; |
| OLD | NEW |