| OLD | NEW |
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. | 1 // Copyright 2017 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/loader/navigation_url_loader_network_service.h" | 5 #include "content/browser/loader/navigation_url_loader_network_service.h" |
| 6 | 6 |
| 7 #include "base/bind.h" | 7 #include "base/bind.h" |
| 8 #include "base/bind_helpers.h" | 8 #include "base/bind_helpers.h" |
| 9 #include "base/memory/ptr_util.h" | 9 #include "base/memory/ptr_util.h" |
| 10 #include "base/trace_event/trace_event.h" | 10 #include "base/trace_event/trace_event.h" |
| (...skipping 16 matching lines...) Expand all Loading... |
| 27 #include "content/browser/webui/web_ui_url_loader_factory.h" | 27 #include "content/browser/webui/web_ui_url_loader_factory.h" |
| 28 #include "content/public/browser/browser_context.h" | 28 #include "content/public/browser/browser_context.h" |
| 29 #include "content/public/browser/browser_thread.h" | 29 #include "content/public/browser/browser_thread.h" |
| 30 #include "content/public/browser/global_request_id.h" | 30 #include "content/public/browser/global_request_id.h" |
| 31 #include "content/public/browser/navigation_data.h" | 31 #include "content/public/browser/navigation_data.h" |
| 32 #include "content/public/browser/navigation_ui_data.h" | 32 #include "content/public/browser/navigation_ui_data.h" |
| 33 #include "content/public/browser/ssl_status.h" | 33 #include "content/public/browser/ssl_status.h" |
| 34 #include "content/public/browser/stream_handle.h" | 34 #include "content/public/browser/stream_handle.h" |
| 35 #include "content/public/common/referrer.h" | 35 #include "content/public/common/referrer.h" |
| 36 #include "content/public/common/url_constants.h" | 36 #include "content/public/common/url_constants.h" |
| 37 #include "mojo/public/cpp/bindings/strong_binding.h" |
| 37 #include "net/base/load_flags.h" | 38 #include "net/base/load_flags.h" |
| 38 #include "net/url_request/url_request_context.h" | 39 #include "net/url_request/url_request_context.h" |
| 39 | 40 |
| 40 namespace content { | 41 namespace content { |
| 41 | 42 |
| 42 namespace { | 43 namespace { |
| 43 | 44 |
| 44 // Request ID for browser initiated requests. We start at -2 on the same lines | 45 // Request ID for browser initiated requests. We start at -2 on the same lines |
| 45 // as ResourceDispatcherHostImpl. | 46 // as ResourceDispatcherHostImpl. |
| 46 int g_next_request_id = -2; | 47 int g_next_request_id = -2; |
| 47 | 48 |
| 48 WebContents* GetWebContentsFromFrameTreeNodeID(int frame_tree_node_id) { | 49 WebContents* GetWebContentsFromFrameTreeNodeID(int frame_tree_node_id) { |
| 49 DCHECK_CURRENTLY_ON(BrowserThread::UI); | 50 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 50 FrameTreeNode* frame_tree_node = | 51 FrameTreeNode* frame_tree_node = |
| 51 FrameTreeNode::GloballyFindByID(frame_tree_node_id); | 52 FrameTreeNode::GloballyFindByID(frame_tree_node_id); |
| 52 if (!frame_tree_node) | 53 if (!frame_tree_node) |
| 53 return nullptr; | 54 return nullptr; |
| 54 | 55 |
| 55 return WebContentsImpl::FromFrameTreeNode(frame_tree_node); | 56 return WebContentsImpl::FromFrameTreeNode(frame_tree_node); |
| 56 } | 57 } |
| 57 | 58 |
| 59 class AssociatedURLLoaderWrapper final : public mojom::URLLoader { |
| 60 public: |
| 61 static void CreateLoaderAndStart(mojom::URLLoaderFactory* factory, |
| 62 mojom::URLLoaderRequest request, |
| 63 uint32_t options, |
| 64 const ResourceRequest& resource_request, |
| 65 mojom::URLLoaderClientPtr client) { |
| 66 mojom::URLLoaderAssociatedPtr associated_ptr; |
| 67 mojom::URLLoaderAssociatedRequest associated_request = |
| 68 mojo::MakeRequest(&associated_ptr); |
| 69 factory->CreateLoaderAndStart(std::move(associated_request), |
| 70 0 /* routing_id */, 0 /* request_id */, |
| 71 options, resource_request, std::move(client)); |
| 72 mojo::MakeStrongBinding( |
| 73 base::MakeUnique<AssociatedURLLoaderWrapper>(std::move(associated_ptr)), |
| 74 std::move(request)); |
| 75 } |
| 76 |
| 77 explicit AssociatedURLLoaderWrapper( |
| 78 mojom::URLLoaderAssociatedPtr associated_ptr) |
| 79 : associated_ptr_(std::move(associated_ptr)) {} |
| 80 ~AssociatedURLLoaderWrapper() override {} |
| 81 |
| 82 void FollowRedirect() override { associated_ptr_->FollowRedirect(); } |
| 83 |
| 84 void SetPriority(net::RequestPriority priority, |
| 85 int intra_priority_value) override { |
| 86 associated_ptr_->SetPriority(priority, intra_priority_value); |
| 87 } |
| 88 |
| 89 private: |
| 90 mojom::URLLoaderAssociatedPtr associated_ptr_; |
| 91 DISALLOW_COPY_AND_ASSIGN(AssociatedURLLoaderWrapper); |
| 92 }; |
| 93 |
| 58 } // namespace | 94 } // namespace |
| 59 | 95 |
| 60 // Kept around during the lifetime of the navigation request, and is | 96 // Kept around during the lifetime of the navigation request, and is |
| 61 // responsible for dispatching a ResourceRequest to the appropriate | 97 // responsible for dispatching a ResourceRequest to the appropriate |
| 62 // URLLoader. In order to get the right URLLoader it builds a vector | 98 // URLLoader. In order to get the right URLLoader it builds a vector |
| 63 // of URLLoaderRequestHandler's and successively calls MaybeCreateLoader | 99 // of URLLoaderRequestHandler's and successively calls MaybeCreateLoader |
| 64 // on each until the request is successfully handled. The same sequence | 100 // on each until the request is successfully handled. The same sequence |
| 65 // may be performed multiple times when redirects happen. | 101 // may be performed multiple times when redirects happen. |
| 66 class NavigationURLLoaderNetworkService::URLLoaderRequestController { | 102 class NavigationURLLoaderNetworkService::URLLoaderRequestController { |
| 67 public: | 103 public: |
| 68 URLLoaderRequestController( | 104 URLLoaderRequestController( |
| 69 std::unique_ptr<ResourceRequest> resource_request, | 105 std::unique_ptr<ResourceRequest> resource_request, |
| 70 ResourceContext* resource_context, | 106 ResourceContext* resource_context, |
| 71 scoped_refptr<URLLoaderFactoryGetter> url_loader_factory_getter) | 107 scoped_refptr<URLLoaderFactoryGetter> url_loader_factory_getter) |
| 72 : resource_request_(std::move(resource_request)), | 108 : resource_request_(std::move(resource_request)), |
| 73 resource_context_(resource_context), | 109 resource_context_(resource_context), |
| 74 url_loader_factory_getter_(url_loader_factory_getter) {} | 110 url_loader_factory_getter_(url_loader_factory_getter) {} |
| 75 | 111 |
| 76 virtual ~URLLoaderRequestController() { | 112 virtual ~URLLoaderRequestController() { |
| 77 DCHECK_CURRENTLY_ON(BrowserThread::IO); | 113 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| 78 } | 114 } |
| 79 | 115 |
| 80 void Start( | 116 void Start( |
| 81 ServiceWorkerNavigationHandleCore* service_worker_navigation_handle_core, | 117 ServiceWorkerNavigationHandleCore* service_worker_navigation_handle_core, |
| 82 AppCacheNavigationHandleCore* appcache_handle_core, | 118 AppCacheNavigationHandleCore* appcache_handle_core, |
| 83 std::unique_ptr<NavigationRequestInfo> request_info, | 119 std::unique_ptr<NavigationRequestInfo> request_info, |
| 84 mojom::URLLoaderFactoryPtrInfo factory_for_webui, | 120 mojom::URLLoaderFactoryPtrInfo factory_for_webui, |
| 85 const base::Callback<WebContents*(void)>& web_contents_getter, | 121 const base::Callback<WebContents*(void)>& web_contents_getter, |
| 86 mojom::URLLoaderAssociatedRequest url_loader_request, | 122 mojom::URLLoaderRequest url_loader_request, |
| 87 mojom::URLLoaderClientPtrInfo url_loader_client_ptr_info, | 123 mojom::URLLoaderClientPtrInfo url_loader_client_ptr_info, |
| 88 std::unique_ptr<service_manager::Connector> connector) { | 124 std::unique_ptr<service_manager::Connector> connector) { |
| 89 DCHECK_CURRENTLY_ON(BrowserThread::IO); | 125 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| 90 url_loader_client_ptr_.Bind(std::move(url_loader_client_ptr_info)); | 126 url_loader_client_ptr_.Bind(std::move(url_loader_client_ptr_info)); |
| 91 const ResourceType resource_type = request_info->is_main_frame | 127 const ResourceType resource_type = request_info->is_main_frame |
| 92 ? RESOURCE_TYPE_MAIN_FRAME | 128 ? RESOURCE_TYPE_MAIN_FRAME |
| 93 : RESOURCE_TYPE_SUB_FRAME; | 129 : RESOURCE_TYPE_SUB_FRAME; |
| 94 | 130 |
| 95 if (resource_request_->request_body) { | 131 if (resource_request_->request_body) { |
| 96 AttachRequestBodyBlobDataHandles(resource_request_->request_body.get(), | 132 AttachRequestBodyBlobDataHandles(resource_request_->request_body.get(), |
| 97 resource_context_); | 133 resource_context_); |
| 98 } | 134 } |
| 99 | 135 |
| 100 // Requests to WebUI scheme won't get redirected to/from other schemes | 136 // Requests to WebUI scheme won't get redirected to/from other schemes |
| 101 // or be intercepted, so we just let it go here. | 137 // or be intercepted, so we just let it go here. |
| 102 if (factory_for_webui.is_valid()) { | 138 if (factory_for_webui.is_valid()) { |
| 103 mojom::URLLoaderFactoryPtr factory_ptr; | 139 mojom::URLLoaderFactoryPtr factory_ptr; |
| 104 factory_ptr.Bind(std::move(factory_for_webui)); | 140 factory_ptr.Bind(std::move(factory_for_webui)); |
| 105 factory_ptr->CreateLoaderAndStart( | 141 AssociatedURLLoaderWrapper::CreateLoaderAndStart( |
| 106 std::move(url_loader_request), 0 /* routing_id? */, | 142 factory_ptr.get(), std::move(url_loader_request), |
| 107 0 /* request_id? */, mojom::kURLLoadOptionSendSSLInfo, | 143 mojom::kURLLoadOptionSendSSLInfo, *resource_request_, |
| 108 *resource_request_, std::move(url_loader_client_ptr_)); | 144 std::move(url_loader_client_ptr_)); |
| 109 return; | 145 return; |
| 110 } | 146 } |
| 111 | 147 |
| 112 DCHECK(handlers_.empty()); | 148 DCHECK(handlers_.empty()); |
| 113 if (service_worker_navigation_handle_core) { | 149 if (service_worker_navigation_handle_core) { |
| 114 RequestContextFrameType frame_type = | 150 RequestContextFrameType frame_type = |
| 115 request_info->is_main_frame ? REQUEST_CONTEXT_FRAME_TYPE_TOP_LEVEL | 151 request_info->is_main_frame ? REQUEST_CONTEXT_FRAME_TYPE_TOP_LEVEL |
| 116 : REQUEST_CONTEXT_FRAME_TYPE_NESTED; | 152 : REQUEST_CONTEXT_FRAME_TYPE_NESTED; |
| 117 | 153 |
| 118 storage::BlobStorageContext* blob_storage_context = GetBlobStorageContext( | 154 storage::BlobStorageContext* blob_storage_context = GetBlobStorageContext( |
| (...skipping 11 matching lines...) Expand all Loading... |
| 130 } | 166 } |
| 131 | 167 |
| 132 if (appcache_handle_core) { | 168 if (appcache_handle_core) { |
| 133 // TODO: add appcache code here. | 169 // TODO: add appcache code here. |
| 134 } | 170 } |
| 135 | 171 |
| 136 Restart(std::move(url_loader_request), std::move(url_loader_client_ptr_)); | 172 Restart(std::move(url_loader_request), std::move(url_loader_client_ptr_)); |
| 137 } | 173 } |
| 138 | 174 |
| 139 // This could be called multiple times. | 175 // This could be called multiple times. |
| 140 void Restart(mojom::URLLoaderAssociatedRequest url_loader_request, | 176 void Restart(mojom::URLLoaderRequest url_loader_request, |
| 141 mojom::URLLoaderClientPtr url_loader_client_ptr) { | 177 mojom::URLLoaderClientPtr url_loader_client_ptr) { |
| 142 url_loader_request_ = std::move(url_loader_request); | 178 url_loader_request_ = std::move(url_loader_request); |
| 143 url_loader_client_ptr_ = std::move(url_loader_client_ptr); | 179 url_loader_client_ptr_ = std::move(url_loader_client_ptr); |
| 144 handler_index_ = 0; | 180 handler_index_ = 0; |
| 145 MaybeStartLoader(StartLoaderCallback()); | 181 MaybeStartLoader(StartLoaderCallback()); |
| 146 } | 182 } |
| 147 | 183 |
| 148 void MaybeStartLoader(StartLoaderCallback start_loader_callback) { | 184 void MaybeStartLoader(StartLoaderCallback start_loader_callback) { |
| 149 DCHECK(url_loader_client_ptr_.is_bound()); | 185 DCHECK(url_loader_client_ptr_.is_bound()); |
| 150 | 186 |
| (...skipping 12 matching lines...) Expand all Loading... |
| 163 return; | 199 return; |
| 164 } | 200 } |
| 165 | 201 |
| 166 mojom::URLLoaderFactory* factory = nullptr; | 202 mojom::URLLoaderFactory* factory = nullptr; |
| 167 DCHECK_EQ(handlers_.size(), handler_index_); | 203 DCHECK_EQ(handlers_.size(), handler_index_); |
| 168 if (resource_request_->url.SchemeIs(url::kBlobScheme)) { | 204 if (resource_request_->url.SchemeIs(url::kBlobScheme)) { |
| 169 factory = url_loader_factory_getter_->GetBlobFactory()->get(); | 205 factory = url_loader_factory_getter_->GetBlobFactory()->get(); |
| 170 } else { | 206 } else { |
| 171 factory = url_loader_factory_getter_->GetNetworkFactory()->get(); | 207 factory = url_loader_factory_getter_->GetNetworkFactory()->get(); |
| 172 } | 208 } |
| 173 factory->CreateLoaderAndStart( | 209 AssociatedURLLoaderWrapper::CreateLoaderAndStart( |
| 174 std::move(url_loader_request_), 0 /* routing_id? */, | 210 factory, std::move(url_loader_request_), |
| 175 0 /* request_id? */, mojom::kURLLoadOptionSendSSLInfo, | 211 mojom::kURLLoadOptionSendSSLInfo, *resource_request_, |
| 176 *resource_request_, std::move(url_loader_client_ptr_)); | 212 std::move(url_loader_client_ptr_)); |
| 177 } | 213 } |
| 178 | 214 |
| 179 private: | 215 private: |
| 180 std::vector<std::unique_ptr<URLLoaderRequestHandler>> handlers_; | 216 std::vector<std::unique_ptr<URLLoaderRequestHandler>> handlers_; |
| 181 size_t handler_index_ = 0; | 217 size_t handler_index_ = 0; |
| 182 | 218 |
| 183 std::unique_ptr<ResourceRequest> resource_request_; | 219 std::unique_ptr<ResourceRequest> resource_request_; |
| 184 ResourceContext* resource_context_; | 220 ResourceContext* resource_context_; |
| 185 | 221 |
| 186 scoped_refptr<URLLoaderFactoryGetter> url_loader_factory_getter_; | 222 scoped_refptr<URLLoaderFactoryGetter> url_loader_factory_getter_; |
| 187 | 223 |
| 188 // Kept around until we create a loader. | 224 // Kept around until we create a loader. |
| 189 mojom::URLLoaderAssociatedRequest url_loader_request_; | 225 mojom::URLLoaderRequest url_loader_request_; |
| 190 mojom::URLLoaderClientPtr url_loader_client_ptr_; | 226 mojom::URLLoaderClientPtr url_loader_client_ptr_; |
| 191 | 227 |
| 192 DISALLOW_COPY_AND_ASSIGN(URLLoaderRequestController); | 228 DISALLOW_COPY_AND_ASSIGN(URLLoaderRequestController); |
| 193 }; | 229 }; |
| 194 | 230 |
| 195 NavigationURLLoaderNetworkService::NavigationURLLoaderNetworkService( | 231 NavigationURLLoaderNetworkService::NavigationURLLoaderNetworkService( |
| 196 ResourceContext* resource_context, | 232 ResourceContext* resource_context, |
| 197 StoragePartition* storage_partition, | 233 StoragePartition* storage_partition, |
| 198 std::unique_ptr<NavigationRequestInfo> request_info, | 234 std::unique_ptr<NavigationRequestInfo> request_info, |
| 199 std::unique_ptr<NavigationUIData> navigation_ui_data, | 235 std::unique_ptr<NavigationUIData> navigation_ui_data, |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 233 // Sync loads should have maximum priority and should be the only | 269 // Sync loads should have maximum priority and should be the only |
| 234 // requests that have the ignore limits flag set. | 270 // requests that have the ignore limits flag set. |
| 235 DCHECK(!(load_flags & net::LOAD_IGNORE_LIMITS)); | 271 DCHECK(!(load_flags & net::LOAD_IGNORE_LIMITS)); |
| 236 | 272 |
| 237 new_request->load_flags = load_flags; | 273 new_request->load_flags = load_flags; |
| 238 | 274 |
| 239 new_request->request_body = request_info->common_params.post_data.get(); | 275 new_request->request_body = request_info->common_params.post_data.get(); |
| 240 | 276 |
| 241 int frame_tree_node_id = request_info->frame_tree_node_id; | 277 int frame_tree_node_id = request_info->frame_tree_node_id; |
| 242 | 278 |
| 243 mojom::URLLoaderAssociatedRequest loader_associated_request = | 279 mojom::URLLoaderRequest loader_request = mojo::MakeRequest(&url_loader_ptr_); |
| 244 mojo::MakeRequest(&url_loader_associated_ptr_); | |
| 245 mojom::URLLoaderClientPtr url_loader_client_ptr_to_pass; | 280 mojom::URLLoaderClientPtr url_loader_client_ptr_to_pass; |
| 246 binding_.Bind(mojo::MakeRequest(&url_loader_client_ptr_to_pass)); | 281 binding_.Bind(mojo::MakeRequest(&url_loader_client_ptr_to_pass)); |
| 247 | 282 |
| 248 // Check if a web UI scheme wants to handle this request. | 283 // Check if a web UI scheme wants to handle this request. |
| 249 mojom::URLLoaderFactoryPtrInfo factory_for_webui; | 284 mojom::URLLoaderFactoryPtrInfo factory_for_webui; |
| 250 const auto& schemes = URLDataManagerBackend::GetWebUISchemes(); | 285 const auto& schemes = URLDataManagerBackend::GetWebUISchemes(); |
| 251 if (std::find(schemes.begin(), schemes.end(), new_request->url.scheme()) != | 286 if (std::find(schemes.begin(), schemes.end(), new_request->url.scheme()) != |
| 252 schemes.end()) { | 287 schemes.end()) { |
| 253 FrameTreeNode* frame_tree_node = | 288 FrameTreeNode* frame_tree_node = |
| 254 FrameTreeNode::GloballyFindByID(frame_tree_node_id); | 289 FrameTreeNode::GloballyFindByID(frame_tree_node_id); |
| (...skipping 12 matching lines...) Expand all Loading... |
| 267 base::Bind( | 302 base::Bind( |
| 268 &URLLoaderRequestController::Start, | 303 &URLLoaderRequestController::Start, |
| 269 base::Unretained(request_controller_.get()), | 304 base::Unretained(request_controller_.get()), |
| 270 service_worker_navigation_handle | 305 service_worker_navigation_handle |
| 271 ? service_worker_navigation_handle->core() | 306 ? service_worker_navigation_handle->core() |
| 272 : nullptr, | 307 : nullptr, |
| 273 appcache_handle ? appcache_handle->core() : nullptr, | 308 appcache_handle ? appcache_handle->core() : nullptr, |
| 274 base::Passed(std::move(request_info)), | 309 base::Passed(std::move(request_info)), |
| 275 base::Passed(std::move(factory_for_webui)), | 310 base::Passed(std::move(factory_for_webui)), |
| 276 base::Bind(&GetWebContentsFromFrameTreeNodeID, frame_tree_node_id), | 311 base::Bind(&GetWebContentsFromFrameTreeNodeID, frame_tree_node_id), |
| 277 base::Passed(std::move(loader_associated_request)), | 312 base::Passed(std::move(loader_request)), |
| 278 base::Passed(url_loader_client_ptr_to_pass.PassInterface()), | 313 base::Passed(url_loader_client_ptr_to_pass.PassInterface()), |
| 279 base::Passed(ServiceManagerConnection::GetForProcess() | 314 base::Passed(ServiceManagerConnection::GetForProcess() |
| 280 ->GetConnector() | 315 ->GetConnector() |
| 281 ->Clone()))); | 316 ->Clone()))); |
| 282 } | 317 } |
| 283 | 318 |
| 284 NavigationURLLoaderNetworkService::~NavigationURLLoaderNetworkService() { | 319 NavigationURLLoaderNetworkService::~NavigationURLLoaderNetworkService() { |
| 285 BrowserThread::DeleteSoon(BrowserThread::IO, FROM_HERE, | 320 BrowserThread::DeleteSoon(BrowserThread::IO, FROM_HERE, |
| 286 request_controller_.release()); | 321 request_controller_.release()); |
| 287 } | 322 } |
| 288 | 323 |
| 289 void NavigationURLLoaderNetworkService::FollowRedirect() { | 324 void NavigationURLLoaderNetworkService::FollowRedirect() { |
| 290 url_loader_associated_ptr_->FollowRedirect(); | 325 url_loader_ptr_->FollowRedirect(); |
| 291 } | 326 } |
| 292 | 327 |
| 293 void NavigationURLLoaderNetworkService::ProceedWithResponse() {} | 328 void NavigationURLLoaderNetworkService::ProceedWithResponse() {} |
| 294 | 329 |
| 295 void NavigationURLLoaderNetworkService::OnReceiveResponse( | 330 void NavigationURLLoaderNetworkService::OnReceiveResponse( |
| 296 const ResourceResponseHead& head, | 331 const ResourceResponseHead& head, |
| 297 const base::Optional<net::SSLInfo>& ssl_info, | 332 const base::Optional<net::SSLInfo>& ssl_info, |
| 298 mojom::DownloadedTempFilePtr downloaded_file) { | 333 mojom::DownloadedTempFilePtr downloaded_file) { |
| 299 // TODO(scottmg): This needs to do more of what | 334 // TODO(scottmg): This needs to do more of what |
| 300 // NavigationResourceHandler::OnReponseStarted() does. Or maybe in | 335 // NavigationResourceHandler::OnReponseStarted() does. Or maybe in |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 354 TRACE_EVENT_ASYNC_END2("navigation", "Navigation timeToResponseStarted", | 389 TRACE_EVENT_ASYNC_END2("navigation", "Navigation timeToResponseStarted", |
| 355 this, "&NavigationURLLoaderNetworkService", this, | 390 this, "&NavigationURLLoaderNetworkService", this, |
| 356 "success", false); | 391 "success", false); |
| 357 | 392 |
| 358 delegate_->OnRequestFailed(completion_status.exists_in_cache, | 393 delegate_->OnRequestFailed(completion_status.exists_in_cache, |
| 359 completion_status.error_code); | 394 completion_status.error_code); |
| 360 } | 395 } |
| 361 } | 396 } |
| 362 | 397 |
| 363 } // namespace content | 398 } // namespace content |
| OLD | NEW |