| 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 "content/browser/devtools/devtools_http_handler_impl.h" | 5 #include "content/browser/devtools/devtools_http_handler_impl.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 #include <sstream> | 8 #include <sstream> |
| 9 #include <utility> | 9 #include <utility> |
| 10 | 10 |
| (...skipping 23 matching lines...) Expand all Loading... |
| 34 #include "content/public/common/url_constants.h" | 34 #include "content/public/common/url_constants.h" |
| 35 #include "content/public/common/user_agent.h" | 35 #include "content/public/common/user_agent.h" |
| 36 #include "content/public/common/user_agent.h" | 36 #include "content/public/common/user_agent.h" |
| 37 #include "grit/devtools_resources_map.h" | 37 #include "grit/devtools_resources_map.h" |
| 38 #include "net/base/escape.h" | 38 #include "net/base/escape.h" |
| 39 #include "net/base/io_buffer.h" | 39 #include "net/base/io_buffer.h" |
| 40 #include "net/base/ip_endpoint.h" | 40 #include "net/base/ip_endpoint.h" |
| 41 #include "net/base/net_errors.h" | 41 #include "net/base/net_errors.h" |
| 42 #include "net/server/http_server_request_info.h" | 42 #include "net/server/http_server_request_info.h" |
| 43 #include "net/server/http_server_response_info.h" | 43 #include "net/server/http_server_response_info.h" |
| 44 #include "net/socket/server_socket.h" |
| 44 | 45 |
| 45 #if defined(OS_ANDROID) | 46 #if defined(OS_ANDROID) |
| 46 #include "base/android/build_info.h" | 47 #include "base/android/build_info.h" |
| 47 #endif | 48 #endif |
| 48 | 49 |
| 49 namespace content { | 50 namespace content { |
| 50 | 51 |
| 51 namespace { | 52 namespace { |
| 52 | 53 |
| 53 const base::FilePath::CharType kDevToolsActivePortFileName[] = | 54 const base::FilePath::CharType kDevToolsActivePortFileName[] = |
| 54 FILE_PATH_LITERAL("DevToolsActivePort"); | 55 FILE_PATH_LITERAL("DevToolsActivePort"); |
| 55 | 56 |
| 56 const char kDevToolsHandlerThreadName[] = "Chrome_DevToolsHandlerThread"; | 57 const char kDevToolsHandlerThreadName[] = "Chrome_DevToolsHandlerThread"; |
| 57 | 58 |
| 58 const char kThumbUrlPrefix[] = "/thumb/"; | 59 const char kThumbUrlPrefix[] = "/thumb/"; |
| 59 const char kPageUrlPrefix[] = "/devtools/page/"; | 60 const char kPageUrlPrefix[] = "/devtools/page/"; |
| 60 | 61 |
| 61 const char kTargetIdField[] = "id"; | 62 const char kTargetIdField[] = "id"; |
| 62 const char kTargetParentIdField[] = "parentId"; | 63 const char kTargetParentIdField[] = "parentId"; |
| 63 const char kTargetTypeField[] = "type"; | 64 const char kTargetTypeField[] = "type"; |
| 64 const char kTargetTitleField[] = "title"; | 65 const char kTargetTitleField[] = "title"; |
| 65 const char kTargetDescriptionField[] = "description"; | 66 const char kTargetDescriptionField[] = "description"; |
| 66 const char kTargetUrlField[] = "url"; | 67 const char kTargetUrlField[] = "url"; |
| 67 const char kTargetThumbnailUrlField[] = "thumbnailUrl"; | 68 const char kTargetThumbnailUrlField[] = "thumbnailUrl"; |
| 68 const char kTargetFaviconUrlField[] = "faviconUrl"; | 69 const char kTargetFaviconUrlField[] = "faviconUrl"; |
| 69 const char kTargetWebSocketDebuggerUrlField[] = "webSocketDebuggerUrl"; | 70 const char kTargetWebSocketDebuggerUrlField[] = "webSocketDebuggerUrl"; |
| 70 const char kTargetDevtoolsFrontendUrlField[] = "devtoolsFrontendUrl"; | 71 const char kTargetDevtoolsFrontendUrlField[] = "devtoolsFrontendUrl"; |
| 71 | 72 |
| 73 // Maximum write buffer size of devtools websocket. |
| 74 const int32 kSendBufferSizeForDevToolsWebSocket = 100 * 1024 * 1024; // 100Mb |
| 75 |
| 72 // An internal implementation of DevToolsClientHost that delegates | 76 // An internal implementation of DevToolsClientHost that delegates |
| 73 // messages sent for DevToolsClient to a DebuggerShell instance. | 77 // messages sent for DevToolsClient to a DebuggerShell instance. |
| 74 class DevToolsClientHostImpl : public DevToolsClientHost { | 78 class DevToolsClientHostImpl : public DevToolsClientHost { |
| 75 public: | 79 public: |
| 76 DevToolsClientHostImpl(base::MessageLoop* message_loop, | 80 DevToolsClientHostImpl(base::MessageLoop* message_loop, |
| 77 net::HttpServer* server, | 81 base::WeakPtr<net::HttpServer> server, |
| 78 int connection_id) | 82 int connection_id) |
| 79 : message_loop_(message_loop), | 83 : message_loop_(message_loop), |
| 80 server_(server), | 84 server_(server), |
| 81 connection_id_(connection_id), | 85 connection_id_(connection_id), |
| 82 is_closed_(false), | 86 is_closed_(false), |
| 83 detach_reason_("target_closed") {} | 87 detach_reason_("target_closed") {} |
| 84 | 88 |
| 85 virtual ~DevToolsClientHostImpl() {} | 89 virtual ~DevToolsClientHostImpl() {} |
| 86 | 90 |
| 87 // DevToolsClientHost interface | 91 // DevToolsClientHost interface |
| (...skipping 28 matching lines...) Expand all Loading... |
| 116 connection_id_, | 120 connection_id_, |
| 117 data)); | 121 data)); |
| 118 } | 122 } |
| 119 | 123 |
| 120 virtual void ReplacedWithAnotherClient() OVERRIDE { | 124 virtual void ReplacedWithAnotherClient() OVERRIDE { |
| 121 detach_reason_ = "replaced_with_devtools"; | 125 detach_reason_ = "replaced_with_devtools"; |
| 122 } | 126 } |
| 123 | 127 |
| 124 private: | 128 private: |
| 125 base::MessageLoop* message_loop_; | 129 base::MessageLoop* message_loop_; |
| 126 net::HttpServer* server_; | 130 base::WeakPtr<net::HttpServer> server_; |
| 127 int connection_id_; | 131 int connection_id_; |
| 128 bool is_closed_; | 132 bool is_closed_; |
| 129 std::string detach_reason_; | 133 std::string detach_reason_; |
| 130 }; | 134 }; |
| 131 | 135 |
| 132 static bool TimeComparator(const DevToolsTarget* target1, | 136 static bool TimeComparator(const DevToolsTarget* target1, |
| 133 const DevToolsTarget* target2) { | 137 const DevToolsTarget* target2) { |
| 134 return target1->GetLastActivityTime() > target2->GetLastActivityTime(); | 138 return target1->GetLastActivityTime() > target2->GetLastActivityTime(); |
| 135 } | 139 } |
| 136 | 140 |
| 137 } // namespace | 141 } // namespace |
| 138 | 142 |
| 139 // static | 143 // static |
| 140 bool DevToolsHttpHandler::IsSupportedProtocolVersion( | 144 bool DevToolsHttpHandler::IsSupportedProtocolVersion( |
| 141 const std::string& version) { | 145 const std::string& version) { |
| 142 return devtools::IsSupportedProtocolVersion(version); | 146 return devtools::IsSupportedProtocolVersion(version); |
| 143 } | 147 } |
| 144 | 148 |
| 145 // static | 149 // static |
| 146 int DevToolsHttpHandler::GetFrontendResourceId(const std::string& name) { | 150 int DevToolsHttpHandler::GetFrontendResourceId(const std::string& name) { |
| 147 for (size_t i = 0; i < kDevtoolsResourcesSize; ++i) { | 151 for (size_t i = 0; i < kDevtoolsResourcesSize; ++i) { |
| 148 if (name == kDevtoolsResources[i].name) | 152 if (name == kDevtoolsResources[i].name) |
| 149 return kDevtoolsResources[i].value; | 153 return kDevtoolsResources[i].value; |
| 150 } | 154 } |
| 151 return -1; | 155 return -1; |
| 152 } | 156 } |
| 153 | 157 |
| 154 // static | 158 // static |
| 155 DevToolsHttpHandler* DevToolsHttpHandler::Start( | 159 DevToolsHttpHandler* DevToolsHttpHandler::Start( |
| 156 const net::StreamListenSocketFactory* socket_factory, | 160 scoped_ptr<net::ServerSocketFactory> server_socket_factory, |
| 157 const std::string& frontend_url, | 161 const std::string& frontend_url, |
| 158 DevToolsHttpHandlerDelegate* delegate, | 162 DevToolsHttpHandlerDelegate* delegate, |
| 159 const base::FilePath& active_port_output_directory) { | 163 const base::FilePath& active_port_output_directory) { |
| 160 DevToolsHttpHandlerImpl* http_handler = | 164 DevToolsHttpHandlerImpl* http_handler = |
| 161 new DevToolsHttpHandlerImpl(socket_factory, | 165 new DevToolsHttpHandlerImpl(server_socket_factory.Pass(), |
| 162 frontend_url, | 166 frontend_url, |
| 163 delegate, | 167 delegate, |
| 164 active_port_output_directory); | 168 active_port_output_directory); |
| 165 http_handler->Start(); | 169 http_handler->Start(); |
| 166 return http_handler; | 170 return http_handler; |
| 167 } | 171 } |
| 168 | 172 |
| 169 DevToolsHttpHandlerImpl::~DevToolsHttpHandlerImpl() { | 173 DevToolsHttpHandlerImpl::~DevToolsHttpHandlerImpl() { |
| 170 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 174 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 171 // Stop() must be called prior to destruction. | 175 // Stop() must be called prior to destruction. |
| (...skipping 153 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 325 void DevToolsHttpHandlerImpl::OnWebSocketRequest( | 329 void DevToolsHttpHandlerImpl::OnWebSocketRequest( |
| 326 int connection_id, | 330 int connection_id, |
| 327 const net::HttpServerRequestInfo& request) { | 331 const net::HttpServerRequestInfo& request) { |
| 328 std::string browser_prefix = "/devtools/browser"; | 332 std::string browser_prefix = "/devtools/browser"; |
| 329 size_t browser_pos = request.path.find(browser_prefix); | 333 size_t browser_pos = request.path.find(browser_prefix); |
| 330 if (browser_pos == 0) { | 334 if (browser_pos == 0) { |
| 331 if (browser_target_) { | 335 if (browser_target_) { |
| 332 server_->Send500(connection_id, "Another client already attached"); | 336 server_->Send500(connection_id, "Another client already attached"); |
| 333 return; | 337 return; |
| 334 } | 338 } |
| 335 browser_target_ = new DevToolsBrowserTarget(server_.get(), connection_id); | 339 browser_target_ = new DevToolsBrowserTarget(server_->GetWeakPtr(), |
| 340 connection_id); |
| 336 browser_target_->RegisterDomainHandler( | 341 browser_target_->RegisterDomainHandler( |
| 337 devtools::Tracing::kName, | 342 devtools::Tracing::kName, |
| 338 new DevToolsTracingHandler(DevToolsTracingHandler::Browser), | 343 new DevToolsTracingHandler(DevToolsTracingHandler::Browser), |
| 339 true /* handle on UI thread */); | 344 true /* handle on UI thread */); |
| 340 browser_target_->RegisterDomainHandler( | 345 browser_target_->RegisterDomainHandler( |
| 341 TetheringHandler::kDomain, | 346 TetheringHandler::kDomain, |
| 342 new TetheringHandler(delegate_.get()), | 347 new TetheringHandler(delegate_.get()), |
| 343 false /* handle on this thread */); | 348 false /* handle on this thread */); |
| 344 browser_target_->RegisterDomainHandler( | 349 browser_target_->RegisterDomainHandler( |
| 345 devtools::SystemInfo::kName, | 350 devtools::SystemInfo::kName, |
| (...skipping 260 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 606 return; | 611 return; |
| 607 } | 612 } |
| 608 | 613 |
| 609 if (agent->IsAttached()) { | 614 if (agent->IsAttached()) { |
| 610 Send500(connection_id, | 615 Send500(connection_id, |
| 611 "Target with given id is being inspected: " + page_id); | 616 "Target with given id is being inspected: " + page_id); |
| 612 return; | 617 return; |
| 613 } | 618 } |
| 614 | 619 |
| 615 DevToolsClientHostImpl* client_host = new DevToolsClientHostImpl( | 620 DevToolsClientHostImpl* client_host = new DevToolsClientHostImpl( |
| 616 thread_->message_loop(), server_.get(), connection_id); | 621 thread_->message_loop(), server_->GetWeakPtr(), connection_id); |
| 617 connection_to_client_host_ui_[connection_id] = client_host; | 622 connection_to_client_host_ui_[connection_id] = client_host; |
| 618 | 623 |
| 619 DevToolsManager::GetInstance()-> | 624 DevToolsManager::GetInstance()-> |
| 620 RegisterDevToolsClientHostFor(agent, client_host); | 625 RegisterDevToolsClientHostFor(agent, client_host); |
| 621 | 626 |
| 622 AcceptWebSocket(connection_id, request); | 627 AcceptWebSocket(connection_id, request); |
| 623 } | 628 } |
| 624 | 629 |
| 625 void DevToolsHttpHandlerImpl::OnWebSocketMessageUI( | 630 void DevToolsHttpHandlerImpl::OnWebSocketMessageUI( |
| 626 int connection_id, | 631 int connection_id, |
| (...skipping 13 matching lines...) Expand all Loading... |
| 640 if (it != connection_to_client_host_ui_.end()) { | 645 if (it != connection_to_client_host_ui_.end()) { |
| 641 DevToolsClientHostImpl* client_host = | 646 DevToolsClientHostImpl* client_host = |
| 642 static_cast<DevToolsClientHostImpl*>(it->second); | 647 static_cast<DevToolsClientHostImpl*>(it->second); |
| 643 DevToolsManager::GetInstance()->ClientHostClosing(client_host); | 648 DevToolsManager::GetInstance()->ClientHostClosing(client_host); |
| 644 delete client_host; | 649 delete client_host; |
| 645 connection_to_client_host_ui_.erase(connection_id); | 650 connection_to_client_host_ui_.erase(connection_id); |
| 646 } | 651 } |
| 647 } | 652 } |
| 648 | 653 |
| 649 DevToolsHttpHandlerImpl::DevToolsHttpHandlerImpl( | 654 DevToolsHttpHandlerImpl::DevToolsHttpHandlerImpl( |
| 650 const net::StreamListenSocketFactory* socket_factory, | 655 scoped_ptr<net::ServerSocketFactory> server_socket_factory, |
| 651 const std::string& frontend_url, | 656 const std::string& frontend_url, |
| 652 DevToolsHttpHandlerDelegate* delegate, | 657 DevToolsHttpHandlerDelegate* delegate, |
| 653 const base::FilePath& active_port_output_directory) | 658 const base::FilePath& active_port_output_directory) |
| 654 : frontend_url_(frontend_url), | 659 : frontend_url_(frontend_url), |
| 655 socket_factory_(socket_factory), | 660 server_socket_factory_(server_socket_factory.Pass()), |
| 656 delegate_(delegate), | 661 delegate_(delegate), |
| 657 active_port_output_directory_(active_port_output_directory) { | 662 active_port_output_directory_(active_port_output_directory) { |
| 658 if (frontend_url_.empty()) | 663 if (frontend_url_.empty()) |
| 659 frontend_url_ = "/devtools/devtools.html"; | 664 frontend_url_ = "/devtools/devtools.html"; |
| 660 | 665 |
| 661 // Balanced in ResetHandlerThreadAndRelease(). | 666 // Balanced in ResetHandlerThreadAndRelease(). |
| 662 AddRef(); | 667 AddRef(); |
| 663 } | 668 } |
| 664 | 669 |
| 665 // Runs on the handler thread | 670 // Runs on the handler thread |
| 666 void DevToolsHttpHandlerImpl::Init() { | 671 void DevToolsHttpHandlerImpl::Init() { |
| 667 server_ = new net::HttpServer(*socket_factory_.get(), this); | 672 server_.reset(new net::HttpServer(server_socket_factory_->CreateAndListen(), |
| 673 this)); |
| 668 if (!active_port_output_directory_.empty()) | 674 if (!active_port_output_directory_.empty()) |
| 669 WriteActivePortToUserProfile(); | 675 WriteActivePortToUserProfile(); |
| 670 } | 676 } |
| 671 | 677 |
| 672 // Runs on the handler thread | 678 // Runs on the handler thread |
| 673 void DevToolsHttpHandlerImpl::Teardown() { | 679 void DevToolsHttpHandlerImpl::Teardown() { |
| 674 server_ = NULL; | 680 server_.reset(NULL); |
| 675 } | 681 } |
| 676 | 682 |
| 677 // Runs on FILE thread to make sure that it is serialized against | 683 // Runs on FILE thread to make sure that it is serialized against |
| 678 // {Start|Stop}HandlerThread and to allow calling pthread_join. | 684 // {Start|Stop}HandlerThread and to allow calling pthread_join. |
| 679 void DevToolsHttpHandlerImpl::StopHandlerThread() { | 685 void DevToolsHttpHandlerImpl::StopHandlerThread() { |
| 680 if (!thread_->message_loop()) | 686 if (!thread_->message_loop()) |
| 681 return; | 687 return; |
| 682 thread_->message_loop()->PostTask( | 688 thread_->message_loop()->PostTask( |
| 683 FROM_HERE, | 689 FROM_HERE, |
| 684 base::Bind(&DevToolsHttpHandlerImpl::Teardown, this)); | 690 base::Bind(&DevToolsHttpHandlerImpl::Teardown, this)); |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 724 std::string json_message; | 730 std::string json_message; |
| 725 scoped_ptr<base::Value> message_object(new base::StringValue(message)); | 731 scoped_ptr<base::Value> message_object(new base::StringValue(message)); |
| 726 base::JSONWriter::Write(message_object.get(), &json_message); | 732 base::JSONWriter::Write(message_object.get(), &json_message); |
| 727 | 733 |
| 728 net::HttpServerResponseInfo response(status_code); | 734 net::HttpServerResponseInfo response(status_code); |
| 729 response.SetBody(json_value + message, "application/json; charset=UTF-8"); | 735 response.SetBody(json_value + message, "application/json; charset=UTF-8"); |
| 730 | 736 |
| 731 thread_->message_loop()->PostTask( | 737 thread_->message_loop()->PostTask( |
| 732 FROM_HERE, | 738 FROM_HERE, |
| 733 base::Bind(&net::HttpServer::SendResponse, | 739 base::Bind(&net::HttpServer::SendResponse, |
| 734 server_.get(), | 740 server_->GetWeakPtr(), |
| 735 connection_id, | 741 connection_id, |
| 736 response)); | 742 response)); |
| 737 } | 743 } |
| 738 | 744 |
| 739 void DevToolsHttpHandlerImpl::Send200(int connection_id, | 745 void DevToolsHttpHandlerImpl::Send200(int connection_id, |
| 740 const std::string& data, | 746 const std::string& data, |
| 741 const std::string& mime_type) { | 747 const std::string& mime_type) { |
| 742 if (!thread_) | 748 if (!thread_) |
| 743 return; | 749 return; |
| 744 thread_->message_loop()->PostTask( | 750 thread_->message_loop()->PostTask( |
| 745 FROM_HERE, | 751 FROM_HERE, |
| 746 base::Bind(&net::HttpServer::Send200, | 752 base::Bind(&net::HttpServer::Send200, |
| 747 server_.get(), | 753 server_->GetWeakPtr(), |
| 748 connection_id, | 754 connection_id, |
| 749 data, | 755 data, |
| 750 mime_type)); | 756 mime_type)); |
| 751 } | 757 } |
| 752 | 758 |
| 753 void DevToolsHttpHandlerImpl::Send404(int connection_id) { | 759 void DevToolsHttpHandlerImpl::Send404(int connection_id) { |
| 754 if (!thread_) | 760 if (!thread_) |
| 755 return; | 761 return; |
| 756 thread_->message_loop()->PostTask( | 762 thread_->message_loop()->PostTask( |
| 757 FROM_HERE, | 763 FROM_HERE, |
| 758 base::Bind(&net::HttpServer::Send404, server_.get(), connection_id)); | 764 base::Bind(&net::HttpServer::Send404, |
| 765 server_->GetWeakPtr(), |
| 766 connection_id)); |
| 759 } | 767 } |
| 760 | 768 |
| 761 void DevToolsHttpHandlerImpl::Send500(int connection_id, | 769 void DevToolsHttpHandlerImpl::Send500(int connection_id, |
| 762 const std::string& message) { | 770 const std::string& message) { |
| 763 if (!thread_) | 771 if (!thread_) |
| 764 return; | 772 return; |
| 765 thread_->message_loop()->PostTask( | 773 thread_->message_loop()->PostTask( |
| 766 FROM_HERE, | 774 FROM_HERE, |
| 767 base::Bind(&net::HttpServer::Send500, server_.get(), connection_id, | 775 base::Bind(&net::HttpServer::Send500, |
| 776 server_->GetWeakPtr(), |
| 777 connection_id, |
| 768 message)); | 778 message)); |
| 769 } | 779 } |
| 770 | 780 |
| 771 void DevToolsHttpHandlerImpl::AcceptWebSocket( | 781 void DevToolsHttpHandlerImpl::AcceptWebSocket( |
| 772 int connection_id, | 782 int connection_id, |
| 773 const net::HttpServerRequestInfo& request) { | 783 const net::HttpServerRequestInfo& request) { |
| 774 if (!thread_) | 784 if (!thread_) |
| 775 return; | 785 return; |
| 776 thread_->message_loop()->PostTask( | 786 thread_->message_loop()->PostTask( |
| 777 FROM_HERE, | 787 FROM_HERE, |
| 778 base::Bind(&net::HttpServer::AcceptWebSocket, server_.get(), | 788 base::Bind(&net::HttpServer::SetSendBufferSize, |
| 779 connection_id, request)); | 789 server_->GetWeakPtr(), |
| 790 connection_id, |
| 791 kSendBufferSizeForDevToolsWebSocket)); |
| 792 thread_->message_loop()->PostTask( |
| 793 FROM_HERE, |
| 794 base::Bind(&net::HttpServer::AcceptWebSocket, |
| 795 server_->GetWeakPtr(), |
| 796 connection_id, |
| 797 request)); |
| 780 } | 798 } |
| 781 | 799 |
| 782 base::DictionaryValue* DevToolsHttpHandlerImpl::SerializeTarget( | 800 base::DictionaryValue* DevToolsHttpHandlerImpl::SerializeTarget( |
| 783 const DevToolsTarget& target, | 801 const DevToolsTarget& target, |
| 784 const std::string& host) { | 802 const std::string& host) { |
| 785 base::DictionaryValue* dictionary = new base::DictionaryValue; | 803 base::DictionaryValue* dictionary = new base::DictionaryValue; |
| 786 | 804 |
| 787 std::string id = target.GetId(); | 805 std::string id = target.GetId(); |
| 788 dictionary->SetString(kTargetIdField, id); | 806 dictionary->SetString(kTargetIdField, id); |
| 789 std::string parent_id = target.GetParentId(); | 807 std::string parent_id = target.GetParentId(); |
| (...skipping 26 matching lines...) Expand all Loading... |
| 816 id.c_str(), | 834 id.c_str(), |
| 817 host); | 835 host); |
| 818 dictionary->SetString( | 836 dictionary->SetString( |
| 819 kTargetDevtoolsFrontendUrlField, devtools_frontend_url); | 837 kTargetDevtoolsFrontendUrlField, devtools_frontend_url); |
| 820 } | 838 } |
| 821 | 839 |
| 822 return dictionary; | 840 return dictionary; |
| 823 } | 841 } |
| 824 | 842 |
| 825 } // namespace content | 843 } // namespace content |
| OLD | NEW |