OLD | NEW |
---|---|
1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 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/service_worker/service_worker_version.h" | 5 #include "content/browser/service_worker/service_worker_version.h" |
6 | 6 |
7 #include <map> | 7 #include <map> |
8 #include <string> | 8 #include <string> |
9 | 9 |
10 #include "base/command_line.h" | 10 #include "base/command_line.h" |
(...skipping 147 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
158 const std::vector<TransferredMessagePort>& sent_message_ports, | 158 const std::vector<TransferredMessagePort>& sent_message_ports, |
159 const ServiceWorkerVersion::StatusCallback& callback, | 159 const ServiceWorkerVersion::StatusCallback& callback, |
160 ServiceWorkerStatusCode status) { | 160 ServiceWorkerStatusCode status) { |
161 // Transfering the message ports failed, so destroy the ports. | 161 // Transfering the message ports failed, so destroy the ports. |
162 for (const TransferredMessagePort& port : sent_message_ports) { | 162 for (const TransferredMessagePort& port : sent_message_ports) { |
163 MessagePortService::GetInstance()->ClosePort(port.id); | 163 MessagePortService::GetInstance()->ClosePort(port.id); |
164 } | 164 } |
165 callback.Run(status); | 165 callback.Run(status); |
166 } | 166 } |
167 | 167 |
168 void RunErrorCrossOriginConnectCallback( | 168 void RunErrorServicePortConnectCallback( |
169 const ServiceWorkerVersion::CrossOriginConnectCallback& callback, | 169 const ServiceWorkerVersion::ServicePortConnectCallback& callback, |
170 ServiceWorkerStatusCode status) { | 170 ServiceWorkerStatusCode status) { |
171 callback.Run(status, false /* accept_connection */); | 171 callback.Run(status, false /* accept_connection */, base::string16(), |
172 base::string16()); | |
172 } | 173 } |
173 | 174 |
174 void RunErrorSendStashedPortsCallback( | 175 void RunErrorSendStashedPortsCallback( |
175 const ServiceWorkerVersion::SendStashedPortsCallback& callback, | 176 const ServiceWorkerVersion::SendStashedPortsCallback& callback, |
176 ServiceWorkerStatusCode status) { | 177 ServiceWorkerStatusCode status) { |
177 callback.Run(status, std::vector<int>()); | 178 callback.Run(status, std::vector<int>()); |
178 } | 179 } |
179 | 180 |
180 using WindowOpenedCallback = base::Callback<void(int, int)>; | 181 using WindowOpenedCallback = base::Callback<void(int, int)>; |
181 | 182 |
(...skipping 705 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
887 AddRequest(callback, &geofencing_callbacks_, REQUEST_GEOFENCING); | 888 AddRequest(callback, &geofencing_callbacks_, REQUEST_GEOFENCING); |
888 ServiceWorkerStatusCode status = | 889 ServiceWorkerStatusCode status = |
889 embedded_worker_->SendMessage(ServiceWorkerMsg_GeofencingEvent( | 890 embedded_worker_->SendMessage(ServiceWorkerMsg_GeofencingEvent( |
890 request_id, event_type, region_id, region)); | 891 request_id, event_type, region_id, region)); |
891 if (status != SERVICE_WORKER_OK) { | 892 if (status != SERVICE_WORKER_OK) { |
892 geofencing_callbacks_.Remove(request_id); | 893 geofencing_callbacks_.Remove(request_id); |
893 RunSoon(base::Bind(callback, status)); | 894 RunSoon(base::Bind(callback, status)); |
894 } | 895 } |
895 } | 896 } |
896 | 897 |
897 void ServiceWorkerVersion::DispatchCrossOriginConnectEvent( | 898 void ServiceWorkerVersion::DispatchServicePortConnectEvent( |
898 const CrossOriginConnectCallback& callback, | 899 const ServicePortConnectCallback& callback, |
899 const NavigatorConnectClient& client) { | 900 const GURL& target_url, |
901 const GURL& origin, | |
902 int port_id) { | |
900 DCHECK_EQ(ACTIVATED, status()) << status(); | 903 DCHECK_EQ(ACTIVATED, status()) << status(); |
901 | 904 |
902 if (!base::CommandLine::ForCurrentProcess()->HasSwitch( | 905 if (!base::CommandLine::ForCurrentProcess()->HasSwitch( |
903 switches::kEnableExperimentalWebPlatformFeatures)) { | 906 switches::kEnableExperimentalWebPlatformFeatures)) { |
904 callback.Run(SERVICE_WORKER_ERROR_ABORT, false); | 907 callback.Run(SERVICE_WORKER_ERROR_ABORT, false, base::string16(), |
908 base::string16()); | |
905 return; | 909 return; |
906 } | 910 } |
907 | 911 |
908 if (running_status() != RUNNING) { | 912 if (running_status() != RUNNING) { |
909 // Schedule calling this method after starting the worker. | 913 // Schedule calling this method after starting the worker. |
910 StartWorker( | 914 StartWorker( |
911 base::Bind(&RunTaskAfterStartWorker, weak_factory_.GetWeakPtr(), | 915 base::Bind(&RunTaskAfterStartWorker, weak_factory_.GetWeakPtr(), |
912 base::Bind(&RunErrorCrossOriginConnectCallback, callback), | 916 base::Bind(&RunErrorServicePortConnectCallback, callback), |
913 base::Bind(&self::DispatchCrossOriginConnectEvent, | 917 base::Bind(&self::DispatchServicePortConnectEvent, |
914 weak_factory_.GetWeakPtr(), callback, client))); | 918 weak_factory_.GetWeakPtr(), callback, target_url, |
919 origin, port_id))); | |
915 return; | 920 return; |
916 } | 921 } |
917 | 922 |
918 int request_id = AddRequest(callback, &cross_origin_connect_callbacks_, | 923 int request_id = AddRequest(callback, &service_port_connect_callbacks_, |
919 REQUEST_CROSS_ORIGIN_CONNECT); | 924 REQUEST_SERVICE_PORT_CONNECT); |
920 ServiceWorkerStatusCode status = embedded_worker_->SendMessage( | 925 if (!service_port_dispatcher_) { |
921 ServiceWorkerMsg_CrossOriginConnectEvent(request_id, client)); | 926 embedded_worker_->GetServiceRegistry()->ConnectToRemoteService( |
922 if (status != SERVICE_WORKER_OK) { | 927 mojo::GetProxy(&service_port_dispatcher_)); |
923 cross_origin_connect_callbacks_.Remove(request_id); | 928 service_port_dispatcher_.set_connection_error_handler( |
924 RunSoon(base::Bind(callback, status, false)); | 929 base::Bind(&ServiceWorkerVersion::OnMojoConnectionError< |
930 ServicePortDispatcher, ServicePortConnectCallback>, | |
931 weak_factory_.GetWeakPtr(), &service_port_dispatcher_, | |
932 &service_port_connect_callbacks_, | |
933 base::Bind(&RunErrorServicePortConnectCallback))); | |
925 } | 934 } |
935 service_port_dispatcher_->Connect( | |
michaeln
2015/07/15 02:47:25
More curiousity about where the "message pipe" fif
Marijn Kruisselbrink
2015/07/15 20:49:40
Yes, I'm pretty sure it's between each Service pai
michaeln
2015/07/16 22:23:46
Right, "there is one FIFO per interface"
| |
936 mojo::String::From(target_url), mojo::String::From(origin), port_id, | |
937 base::Bind(&ServiceWorkerVersion::OnServicePortConnectEventFinished, | |
938 weak_factory_.GetWeakPtr(), request_id)); | |
926 } | 939 } |
927 | 940 |
928 void ServiceWorkerVersion::DispatchCrossOriginMessageEvent( | 941 void ServiceWorkerVersion::DispatchCrossOriginMessageEvent( |
929 const NavigatorConnectClient& client, | 942 const NavigatorConnectClient& client, |
930 const base::string16& message, | 943 const base::string16& message, |
931 const std::vector<TransferredMessagePort>& sent_message_ports, | 944 const std::vector<TransferredMessagePort>& sent_message_ports, |
932 const StatusCallback& callback) { | 945 const StatusCallback& callback) { |
933 // Unlike in the case of DispatchMessageEvent, here the caller is assumed to | 946 // Unlike in the case of DispatchMessageEvent, here the caller is assumed to |
934 // have already put all the sent message ports on hold. So no need to do that | 947 // have already put all the sent message ports on hold. So no need to do that |
935 // here again. | 948 // here again. |
(...skipping 247 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1183 IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_FetchEventFinished, | 1196 IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_FetchEventFinished, |
1184 OnFetchEventFinished) | 1197 OnFetchEventFinished) |
1185 IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_SyncEventFinished, | 1198 IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_SyncEventFinished, |
1186 OnSyncEventFinished) | 1199 OnSyncEventFinished) |
1187 IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_NotificationClickEventFinished, | 1200 IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_NotificationClickEventFinished, |
1188 OnNotificationClickEventFinished) | 1201 OnNotificationClickEventFinished) |
1189 IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_PushEventFinished, | 1202 IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_PushEventFinished, |
1190 OnPushEventFinished) | 1203 OnPushEventFinished) |
1191 IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_GeofencingEventFinished, | 1204 IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_GeofencingEventFinished, |
1192 OnGeofencingEventFinished) | 1205 OnGeofencingEventFinished) |
1193 IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_CrossOriginConnectEventFinished, | |
1194 OnCrossOriginConnectEventFinished) | |
1195 IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_OpenWindow, | 1206 IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_OpenWindow, |
1196 OnOpenWindow) | 1207 OnOpenWindow) |
1197 IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_SetCachedMetadata, | 1208 IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_SetCachedMetadata, |
1198 OnSetCachedMetadata) | 1209 OnSetCachedMetadata) |
1199 IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_ClearCachedMetadata, | 1210 IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_ClearCachedMetadata, |
1200 OnClearCachedMetadata) | 1211 OnClearCachedMetadata) |
1201 IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_PostMessageToClient, | 1212 IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_PostMessageToClient, |
1202 OnPostMessageToClient) | 1213 OnPostMessageToClient) |
1203 IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_FocusClient, | 1214 IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_FocusClient, |
1204 OnFocusClient) | 1215 OnFocusClient) |
(...skipping 218 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1423 if (!callback) { | 1434 if (!callback) { |
1424 NOTREACHED() << "Got unexpected message: " << request_id; | 1435 NOTREACHED() << "Got unexpected message: " << request_id; |
1425 return; | 1436 return; |
1426 } | 1437 } |
1427 | 1438 |
1428 scoped_refptr<ServiceWorkerVersion> protect(this); | 1439 scoped_refptr<ServiceWorkerVersion> protect(this); |
1429 callback->Run(SERVICE_WORKER_OK); | 1440 callback->Run(SERVICE_WORKER_OK); |
1430 RemoveCallbackAndStopIfRedundant(&geofencing_callbacks_, request_id); | 1441 RemoveCallbackAndStopIfRedundant(&geofencing_callbacks_, request_id); |
1431 } | 1442 } |
1432 | 1443 |
1433 void ServiceWorkerVersion::OnCrossOriginConnectEventFinished( | 1444 void ServiceWorkerVersion::OnServicePortConnectEventFinished( |
1434 int request_id, | 1445 int request_id, |
1435 bool accept_connection) { | 1446 ServicePortConnectResult result, |
1447 const mojo::String& name, | |
1448 const mojo::String& data) { | |
1436 TRACE_EVENT1("ServiceWorker", | 1449 TRACE_EVENT1("ServiceWorker", |
1437 "ServiceWorkerVersion::OnCrossOriginConnectEventFinished", | 1450 "ServiceWorkerVersion::OnServicePortConnectEventFinished", |
1438 "Request id", request_id); | 1451 "Request id", request_id); |
1439 CrossOriginConnectCallback* callback = | 1452 ServicePortConnectCallback* callback = |
1440 cross_origin_connect_callbacks_.Lookup(request_id); | 1453 service_port_connect_callbacks_.Lookup(request_id); |
1441 if (!callback) { | 1454 if (!callback) { |
1442 NOTREACHED() << "Got unexpected message: " << request_id; | 1455 NOTREACHED() << "Got unexpected message: " << request_id; |
1443 return; | 1456 return; |
1444 } | 1457 } |
1445 | 1458 |
1446 scoped_refptr<ServiceWorkerVersion> protect(this); | 1459 scoped_refptr<ServiceWorkerVersion> protect(this); |
1447 callback->Run(SERVICE_WORKER_OK, accept_connection); | 1460 callback->Run(SERVICE_WORKER_OK, result == SERVICE_PORT_CONNECT_RESULT_ACCEPT, |
1448 RemoveCallbackAndStopIfRedundant(&cross_origin_connect_callbacks_, | 1461 name.To<base::string16>(), data.To<base::string16>()); |
1462 RemoveCallbackAndStopIfRedundant(&service_port_connect_callbacks_, | |
1449 request_id); | 1463 request_id); |
1450 } | 1464 } |
1451 | 1465 |
1452 void ServiceWorkerVersion::OnOpenWindow(int request_id, GURL url) { | 1466 void ServiceWorkerVersion::OnOpenWindow(int request_id, GURL url) { |
1453 // Just abort if we are shutting down. | 1467 // Just abort if we are shutting down. |
1454 if (!context_) | 1468 if (!context_) |
1455 return; | 1469 return; |
1456 | 1470 |
1457 if (!url.is_valid()) { | 1471 if (!url.is_valid()) { |
1458 DVLOG(1) << "Received unexpected invalid URL from renderer process."; | 1472 DVLOG(1) << "Received unexpected invalid URL from renderer process."; |
(...skipping 479 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1938 return; | 1952 return; |
1939 } | 1953 } |
1940 | 1954 |
1941 // TODO(falken): We may need to handle StopIfIdle failure and | 1955 // TODO(falken): We may need to handle StopIfIdle failure and |
1942 // forcibly fail pending callbacks so no one is stuck waiting | 1956 // forcibly fail pending callbacks so no one is stuck waiting |
1943 // for the worker. | 1957 // for the worker. |
1944 embedded_worker_->StopIfIdle(); | 1958 embedded_worker_->StopIfIdle(); |
1945 } | 1959 } |
1946 | 1960 |
1947 bool ServiceWorkerVersion::HasInflightRequests() const { | 1961 bool ServiceWorkerVersion::HasInflightRequests() const { |
1948 return | 1962 return !activate_callbacks_.IsEmpty() || !install_callbacks_.IsEmpty() || |
1949 !activate_callbacks_.IsEmpty() || | 1963 !fetch_callbacks_.IsEmpty() || !sync_callbacks_.IsEmpty() || |
1950 !install_callbacks_.IsEmpty() || | 1964 !notification_click_callbacks_.IsEmpty() || |
1951 !fetch_callbacks_.IsEmpty() || | 1965 !push_callbacks_.IsEmpty() || !geofencing_callbacks_.IsEmpty() || |
1952 !sync_callbacks_.IsEmpty() || | 1966 !service_port_connect_callbacks_.IsEmpty() || |
1953 !notification_click_callbacks_.IsEmpty() || | 1967 !streaming_url_request_jobs_.empty(); |
1954 !push_callbacks_.IsEmpty() || | |
1955 !geofencing_callbacks_.IsEmpty() || | |
1956 !cross_origin_connect_callbacks_.IsEmpty() || | |
1957 !streaming_url_request_jobs_.empty(); | |
1958 } | 1968 } |
1959 | 1969 |
1960 void ServiceWorkerVersion::RecordStartWorkerResult( | 1970 void ServiceWorkerVersion::RecordStartWorkerResult( |
1961 ServiceWorkerStatusCode status) { | 1971 ServiceWorkerStatusCode status) { |
1962 base::TimeTicks start_time = start_time_; | 1972 base::TimeTicks start_time = start_time_; |
1963 ClearTick(&start_time_); | 1973 ClearTick(&start_time_); |
1964 | 1974 |
1965 ServiceWorkerMetrics::RecordStartWorkerStatus(status, | 1975 ServiceWorkerMetrics::RecordStartWorkerStatus(status, |
1966 IsInstalled(prestart_status_)); | 1976 IsInstalled(prestart_status_)); |
1967 | 1977 |
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
2035 SERVICE_WORKER_ERROR_TIMEOUT); | 2045 SERVICE_WORKER_ERROR_TIMEOUT); |
2036 case REQUEST_NOTIFICATION_CLICK: | 2046 case REQUEST_NOTIFICATION_CLICK: |
2037 return RunIDMapCallback(¬ification_click_callbacks_, info.id, | 2047 return RunIDMapCallback(¬ification_click_callbacks_, info.id, |
2038 SERVICE_WORKER_ERROR_TIMEOUT); | 2048 SERVICE_WORKER_ERROR_TIMEOUT); |
2039 case REQUEST_PUSH: | 2049 case REQUEST_PUSH: |
2040 return RunIDMapCallback(&push_callbacks_, info.id, | 2050 return RunIDMapCallback(&push_callbacks_, info.id, |
2041 SERVICE_WORKER_ERROR_TIMEOUT); | 2051 SERVICE_WORKER_ERROR_TIMEOUT); |
2042 case REQUEST_GEOFENCING: | 2052 case REQUEST_GEOFENCING: |
2043 return RunIDMapCallback(&geofencing_callbacks_, info.id, | 2053 return RunIDMapCallback(&geofencing_callbacks_, info.id, |
2044 SERVICE_WORKER_ERROR_TIMEOUT); | 2054 SERVICE_WORKER_ERROR_TIMEOUT); |
2045 case REQUEST_CROSS_ORIGIN_CONNECT: | 2055 case REQUEST_SERVICE_PORT_CONNECT: |
2046 return RunIDMapCallback(&cross_origin_connect_callbacks_, info.id, | 2056 return RunIDMapCallback(&service_port_connect_callbacks_, info.id, |
2047 SERVICE_WORKER_ERROR_TIMEOUT, | 2057 SERVICE_WORKER_ERROR_TIMEOUT, |
2048 false /* accept_connection */); | 2058 false /* accept_connection */, base::string16(), |
2059 base::string16()); | |
2049 } | 2060 } |
2050 NOTREACHED() << "Got unexpected request type: " << info.type; | 2061 NOTREACHED() << "Got unexpected request type: " << info.type; |
2051 return false; | 2062 return false; |
2052 } | 2063 } |
2053 | 2064 |
2054 void ServiceWorkerVersion::SetAllRequestTimes(const base::TimeTicks& ticks) { | 2065 void ServiceWorkerVersion::SetAllRequestTimes(const base::TimeTicks& ticks) { |
2055 std::queue<RequestInfo> new_requests; | 2066 std::queue<RequestInfo> new_requests; |
2056 while (!requests_.empty()) { | 2067 while (!requests_.empty()) { |
2057 RequestInfo info = requests_.front(); | 2068 RequestInfo info = requests_.front(); |
2058 info.time = ticks; | 2069 info.time = ticks; |
(...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
2156 RunIDMapCallbacks(&install_callbacks_, | 2167 RunIDMapCallbacks(&install_callbacks_, |
2157 SERVICE_WORKER_ERROR_INSTALL_WORKER_FAILED); | 2168 SERVICE_WORKER_ERROR_INSTALL_WORKER_FAILED); |
2158 RunIDMapCallbacks(&fetch_callbacks_, SERVICE_WORKER_ERROR_FAILED, | 2169 RunIDMapCallbacks(&fetch_callbacks_, SERVICE_WORKER_ERROR_FAILED, |
2159 SERVICE_WORKER_FETCH_EVENT_RESULT_FALLBACK, | 2170 SERVICE_WORKER_FETCH_EVENT_RESULT_FALLBACK, |
2160 ServiceWorkerResponse()); | 2171 ServiceWorkerResponse()); |
2161 RunIDMapCallbacks(&sync_callbacks_, SERVICE_WORKER_ERROR_FAILED); | 2172 RunIDMapCallbacks(&sync_callbacks_, SERVICE_WORKER_ERROR_FAILED); |
2162 RunIDMapCallbacks(¬ification_click_callbacks_, | 2173 RunIDMapCallbacks(¬ification_click_callbacks_, |
2163 SERVICE_WORKER_ERROR_FAILED); | 2174 SERVICE_WORKER_ERROR_FAILED); |
2164 RunIDMapCallbacks(&push_callbacks_, SERVICE_WORKER_ERROR_FAILED); | 2175 RunIDMapCallbacks(&push_callbacks_, SERVICE_WORKER_ERROR_FAILED); |
2165 RunIDMapCallbacks(&geofencing_callbacks_, SERVICE_WORKER_ERROR_FAILED); | 2176 RunIDMapCallbacks(&geofencing_callbacks_, SERVICE_WORKER_ERROR_FAILED); |
2166 RunIDMapCallbacks(&cross_origin_connect_callbacks_, | 2177 |
2167 SERVICE_WORKER_ERROR_FAILED, false); | 2178 // Close all mojo services. This will also fire and clear all callbacks |
2179 // for messages that are still outstanding for those services. | |
michaeln
2015/07/15 02:47:26
I'm curious. Prior to getting here, the embeddedwo
Marijn Kruisselbrink
2015/07/15 20:49:40
The registry reset has no effect on dispatcher obj
michaeln
2015/07/16 22:23:46
Got it (or maybe starting to get it). In mojo term
| |
2180 service_port_dispatcher_.reset(); | |
2168 | 2181 |
2169 streaming_url_request_jobs_.clear(); | 2182 streaming_url_request_jobs_.clear(); |
2170 | 2183 |
2171 FOR_EACH_OBSERVER(Listener, listeners_, OnRunningStateChanged(this)); | 2184 FOR_EACH_OBSERVER(Listener, listeners_, OnRunningStateChanged(this)); |
2172 | 2185 |
2173 if (should_restart) | 2186 if (should_restart) |
2174 StartWorkerInternal(); | 2187 StartWorkerInternal(); |
2175 } | 2188 } |
2176 | 2189 |
2190 template <typename Interface, typename Callback> | |
2191 void ServiceWorkerVersion::OnMojoConnectionError( | |
2192 mojo::InterfacePtr<Interface>* interface, | |
2193 IDMap<Callback, IDMapOwnPointer>* callbacks, | |
2194 const base::Callback<void(const Callback&, ServiceWorkerStatusCode)> | |
2195 callback) { | |
2196 typename IDMap<Callback, IDMapOwnPointer>::iterator iter(callbacks); | |
2197 while (!iter.IsAtEnd()) { | |
2198 callback.Run(*iter.GetCurrentValue(), SERVICE_WORKER_ERROR_FAILED); | |
2199 iter.Advance(); | |
2200 } | |
2201 callbacks->Clear(); | |
2202 } | |
2203 | |
2177 } // namespace content | 2204 } // namespace content |
OLD | NEW |