blob: 73a5b483fa6fb2ad40a55fbac4f7ba01a632d6b3 [file] [log] [blame]
davidben6b77cd72014-10-29 21:13:451// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
dcheng40ce7b382016-04-08 23:46:135#include <memory>
dcheng36b6aec92015-12-26 06:16:366#include <utility>
7
Min Qin37db5102017-09-13 21:21:258#include "base/bind.h"
davidben6b77cd72014-10-29 21:13:459#include "base/command_line.h"
10#include "base/macros.h"
dcheng40ce7b382016-04-08 23:46:1311#include "base/memory/ptr_util.h"
davidben6b77cd72014-10-29 21:13:4512#include "base/memory/ref_counted.h"
davidben6b77cd72014-10-29 21:13:4513#include "base/run_loop.h"
14#include "content/browser/frame_host/navigation_request_info.h"
davidben6b77cd72014-10-29 21:13:4515#include "content/browser/loader/navigation_url_loader_impl.h"
16#include "content/browser/loader/resource_dispatcher_host_impl.h"
Min Qin37db5102017-09-13 21:21:2517#include "content/browser/loader/test_resource_handler.h"
scottmge22a5e52016-06-30 00:36:5118#include "content/browser/loader_delegate_impl.h"
davidben6b77cd72014-10-29 21:13:4519#include "content/browser/streams/stream.h"
20#include "content/browser/streams/stream_context.h"
21#include "content/browser/streams/stream_registry.h"
22#include "content/browser/streams/stream_url_request_job.h"
23#include "content/common/navigation_params.h"
24#include "content/public/browser/browser_context.h"
clamy1e5574e92016-09-29 16:48:4425#include "content/public/browser/navigation_ui_data.h"
davidben6b77cd72014-10-29 21:13:4526#include "content/public/browser/resource_context.h"
27#include "content/public/browser/resource_dispatcher_host_delegate.h"
28#include "content/public/browser/stream_handle.h"
29#include "content/public/common/content_switches.h"
davidben6b77cd72014-10-29 21:13:4530#include "content/public/test/test_browser_context.h"
31#include "content/public/test/test_browser_thread_bundle.h"
clamy9fb38cd2016-04-11 12:57:4832#include "content/test/test_navigation_url_loader_delegate.h"
clamy0ab288e2015-02-05 17:39:1433#include "net/base/load_flags.h"
davidben6b77cd72014-10-29 21:13:4534#include "net/base/net_errors.h"
Lucas Garron0efab6d2017-08-30 22:28:5135#include "net/cert/cert_status_flags.h"
davidben6b77cd72014-10-29 21:13:4536#include "net/http/http_response_headers.h"
Lucas Garron0efab6d2017-08-30 22:28:5137#include "net/test/embedded_test_server/embedded_test_server.h"
rhalavatia20efdbc2017-04-20 12:28:2738#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
davidben6b77cd72014-10-29 21:13:4539#include "net/url_request/redirect_info.h"
40#include "net/url_request/url_request.h"
41#include "net/url_request/url_request_context.h"
42#include "net/url_request/url_request_job_factory_impl.h"
43#include "net/url_request/url_request_test_job.h"
44#include "net/url_request/url_request_test_util.h"
45#include "testing/gtest/include/gtest/gtest.h"
xzhan96cf0a3c542017-11-17 04:33:0146#include "third_party/WebKit/common/page/page_visibility_state.mojom.h"
mkwst202534e32016-01-15 16:07:1547#include "url/origin.h"
davidben6b77cd72014-10-29 21:13:4548
49namespace content {
50
51namespace {
52
53class StreamProtocolHandler
54 : public net::URLRequestJobFactory::ProtocolHandler {
55 public:
56 StreamProtocolHandler(StreamRegistry* registry) : registry_(registry) {}
57
58 // net::URLRequestJobFactory::ProtocolHandler implementation.
59 net::URLRequestJob* MaybeCreateJob(
60 net::URLRequest* request,
61 net::NetworkDelegate* network_delegate) const override {
62 scoped_refptr<Stream> stream = registry_->GetStream(request->url());
63 if (stream.get())
64 return new StreamURLRequestJob(request, network_delegate, stream);
65 return nullptr;
66 }
67
68 private:
69 StreamRegistry* registry_;
70
71 DISALLOW_COPY_AND_ASSIGN(StreamProtocolHandler);
72};
73
davidben6b77cd72014-10-29 21:13:4574class RequestBlockingResourceDispatcherHostDelegate
75 : public ResourceDispatcherHostDelegate {
76 public:
77 // ResourceDispatcherHostDelegate implementation:
78 bool ShouldBeginRequest(const std::string& method,
79 const GURL& url,
80 ResourceType resource_type,
81 ResourceContext* resource_context) override {
82 return false;
83 }
84};
85
arthursonzognie37f76e52018-02-01 16:36:3786std::unique_ptr<ResourceHandler> CreateTestResourceHandler(
Min Qin37db5102017-09-13 21:21:2587 net::URLRequest* request) {
Jeremy Roman04f27c372017-10-27 15:20:5588 return std::make_unique<TestResourceHandler>();
Min Qin37db5102017-09-13 21:21:2589}
90
davidben6b77cd72014-10-29 21:13:4591} // namespace
92
93class NavigationURLLoaderTest : public testing::Test {
94 public:
95 NavigationURLLoaderTest()
96 : thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP),
Min Qin37db5102017-09-13 21:21:2597 browser_context_(new TestBrowserContext),
arthursonzognie37f76e52018-02-01 16:36:3798 host_(base::BindRepeating(&CreateTestResourceHandler),
Alex Clarkeea4ffdb2017-11-03 09:46:1399 base::ThreadTaskRunnerHandle::Get(),
100 /* enable_resource_scheduler */ true) {
scottmge22a5e52016-06-30 00:36:51101 host_.SetLoaderDelegate(&loader_delegate_);
davidben6b77cd72014-10-29 21:13:45102 BrowserContext::EnsureResourceContextInitialized(browser_context_.get());
103 base::RunLoop().RunUntilIdle();
104 net::URLRequestContext* request_context =
105 browser_context_->GetResourceContext()->GetRequestContext();
106 // Attach URLRequestTestJob and make streams work.
107 job_factory_.SetProtocolHandler(
108 "test", net::URLRequestTestJob::CreateProtocolHandler());
109 job_factory_.SetProtocolHandler(
Jeremy Roman04f27c372017-10-27 15:20:55110 "blob", std::make_unique<StreamProtocolHandler>(
ricea641bb022016-09-02 02:51:09111 StreamContext::GetFor(browser_context_.get())->registry()));
davidben6b77cd72014-10-29 21:13:45112 request_context->set_job_factory(&job_factory_);
davidben6b77cd72014-10-29 21:13:45113 }
114
dcheng40ce7b382016-04-08 23:46:13115 std::unique_ptr<NavigationURLLoader> MakeTestLoader(
davidben6b77cd72014-10-29 21:13:45116 const GURL& url,
117 NavigationURLLoaderDelegate* delegate) {
arthursonzognie37f76e52018-02-01 16:36:37118 return CreateTestLoader(url, delegate);
Min Qin37db5102017-09-13 21:21:25119 }
120
121 std::unique_ptr<NavigationURLLoader> CreateTestLoader(
122 const GURL& url,
arthursonzognie37f76e52018-02-01 16:36:37123 NavigationURLLoaderDelegate* delegate) {
Arthur Hemery7b67a972017-12-01 15:24:49124 mojom::BeginNavigationParamsPtr begin_params =
125 mojom::BeginNavigationParams::New(
126 std::string() /* headers */, net::LOAD_NORMAL,
127 false /* skip_service_worker */, REQUEST_CONTEXT_TYPE_LOCATION,
128 blink::WebMixedContentContextType::kBlockable,
129 false /* is_form_submission */, GURL() /* searchable_form_url */,
130 std::string() /* searchable_form_encoding */,
Jochen Eisinger3179df12018-01-23 12:58:29131 url::Origin::Create(url), GURL() /* client_side_redirect_url */);
davidben6b77cd72014-10-29 21:13:45132 CommonNavigationParams common_params;
davidben6b77cd72014-10-29 21:13:45133 common_params.url = url;
Lucas Garron0efab6d2017-08-30 22:28:51134
dcheng40ce7b382016-04-08 23:46:13135 std::unique_ptr<NavigationRequestInfo> request_info(
Arthur Hemery7b67a972017-12-01 15:24:49136 new NavigationRequestInfo(common_params, std::move(begin_params), url,
John Abd-El-Malek005ef202017-12-27 18:43:33137 true, false, false, -1, false, false, false));
jam0f396ee2017-03-01 01:34:11138 return NavigationURLLoader::Create(
139 browser_context_->GetResourceContext(),
140 BrowserContext::GetDefaultStoragePartition(browser_context_.get()),
141 std::move(request_info), nullptr, nullptr, nullptr, delegate);
davidben6b77cd72014-10-29 21:13:45142 }
143
144 // Helper function for fetching the body of a URL to a string.
145 std::string FetchURL(const GURL& url) {
146 net::TestDelegate delegate;
147 net::URLRequestContext* request_context =
148 browser_context_->GetResourceContext()->GetRequestContext();
rhalavatia20efdbc2017-04-20 12:28:27149 std::unique_ptr<net::URLRequest> request(request_context->CreateRequest(
150 url, net::DEFAULT_PRIORITY, &delegate, TRAFFIC_ANNOTATION_FOR_TESTS));
davidben6b77cd72014-10-29 21:13:45151 request->Start();
152 base::RunLoop().Run();
153
154 EXPECT_TRUE(request->status().is_success());
155 EXPECT_EQ(200, request->response_headers()->response_code());
156 return delegate.data_received();
157 }
158
159 protected:
160 TestBrowserThreadBundle thread_bundle_;
161 net::URLRequestJobFactoryImpl job_factory_;
dcheng40ce7b382016-04-08 23:46:13162 std::unique_ptr<TestBrowserContext> browser_context_;
scottmge22a5e52016-06-30 00:36:51163 LoaderDelegateImpl loader_delegate_;
davidben6b77cd72014-10-29 21:13:45164 ResourceDispatcherHostImpl host_;
165};
166
167// Tests that a basic request works.
168TEST_F(NavigationURLLoaderTest, Basic) {
169 TestNavigationURLLoaderDelegate delegate;
dcheng40ce7b382016-04-08 23:46:13170 std::unique_ptr<NavigationURLLoader> loader =
davidben6b77cd72014-10-29 21:13:45171 MakeTestLoader(net::URLRequestTestJob::test_url_1(), &delegate);
172
173 // Wait for the response to come back.
174 delegate.WaitForResponseStarted();
175
clamy5a3c3642016-03-24 17:46:56176 // Proceed with the response.
177 loader->ProceedWithResponse();
178
davidben6b77cd72014-10-29 21:13:45179 // Check the response is correct.
180 EXPECT_EQ("text/html", delegate.response()->head.mime_type);
181 EXPECT_EQ(200, delegate.response()->head.headers->response_code());
182
183 // Check the body is correct.
184 EXPECT_EQ(net::URLRequestTestJob::test_data_1(),
185 FetchURL(delegate.body()->GetURL()));
carlosk947ebfb62015-02-04 11:53:59186
187 EXPECT_EQ(1, delegate.on_request_handled_counter());
davidben6b77cd72014-10-29 21:13:45188}
189
190// Tests that request failures are propagated correctly.
Lucas Garron0efab6d2017-08-30 22:28:51191TEST_F(NavigationURLLoaderTest, RequestFailedNoCertError) {
davidben6b77cd72014-10-29 21:13:45192 TestNavigationURLLoaderDelegate delegate;
dcheng40ce7b382016-04-08 23:46:13193 std::unique_ptr<NavigationURLLoader> loader =
davidben6b77cd72014-10-29 21:13:45194 MakeTestLoader(GURL("bogus:bogus"), &delegate);
195
196 // Wait for the request to fail as expected.
197 delegate.WaitForRequestFailed();
198 EXPECT_EQ(net::ERR_UNKNOWN_URL_SCHEME, delegate.net_error());
John Abd-El-Malekf36e05f2017-11-30 16:17:52199 EXPECT_FALSE(delegate.ssl_info().is_valid());
Lucas Garron0efab6d2017-08-30 22:28:51200 EXPECT_EQ(1, delegate.on_request_handled_counter());
201}
202
203// Tests that request failures are propagated correctly for a (non-fatal) cert
204// error:
205// - |ssl_info| has the expected values.
Lucas Garron0efab6d2017-08-30 22:28:51206TEST_F(NavigationURLLoaderTest, RequestFailedCertError) {
207 net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
208 https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_MISMATCHED_NAME);
209 ASSERT_TRUE(https_server.Start());
210
211 TestNavigationURLLoaderDelegate delegate;
212 std::unique_ptr<NavigationURLLoader> loader =
213 MakeTestLoader(https_server.GetURL("/"), &delegate);
214
215 // Wait for the request to fail as expected.
216 delegate.WaitForRequestFailed();
217 ASSERT_EQ(net::ERR_ABORTED, delegate.net_error());
John Abd-El-Malekf36e05f2017-11-30 16:17:52218 net::SSLInfo ssl_info = delegate.ssl_info();
219 EXPECT_TRUE(ssl_info.is_valid());
Lucas Garron0efab6d2017-08-30 22:28:51220 EXPECT_TRUE(https_server.GetCertificate()->Equals(ssl_info.cert.get()));
221 EXPECT_EQ(net::ERR_CERT_COMMON_NAME_INVALID,
222 net::MapCertStatusToNetError(ssl_info.cert_status));
Carlos IL81133382017-12-06 17:18:45223 EXPECT_FALSE(ssl_info.is_fatal_cert_error);
Lucas Garron0efab6d2017-08-30 22:28:51224 EXPECT_EQ(1, delegate.on_request_handled_counter());
225}
226
227// Tests that request failures are propagated correctly for a fatal cert error:
228// - |ssl_info| has the expected values.
Lucas Garron0efab6d2017-08-30 22:28:51229TEST_F(NavigationURLLoaderTest, RequestFailedCertErrorFatal) {
230 net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
231 https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_MISMATCHED_NAME);
232 ASSERT_TRUE(https_server.Start());
233 GURL url = https_server.GetURL("/");
234
235 // Set HSTS for the test domain in order to make SSL errors fatal.
236 net::TransportSecurityState* transport_security_state =
237 browser_context_->GetResourceContext()
238 ->GetRequestContext()
239 ->transport_security_state();
240 base::Time expiry = base::Time::Now() + base::TimeDelta::FromDays(1000);
241 bool include_subdomains = false;
242 transport_security_state->AddHSTS(url.host(), expiry, include_subdomains);
243
244 TestNavigationURLLoaderDelegate delegate;
245 std::unique_ptr<NavigationURLLoader> loader = MakeTestLoader(url, &delegate);
246
247 // Wait for the request to fail as expected.
248 delegate.WaitForRequestFailed();
249 ASSERT_EQ(net::ERR_ABORTED, delegate.net_error());
John Abd-El-Malekf36e05f2017-11-30 16:17:52250 net::SSLInfo ssl_info = delegate.ssl_info();
251 EXPECT_TRUE(ssl_info.is_valid());
Lucas Garron0efab6d2017-08-30 22:28:51252 EXPECT_TRUE(https_server.GetCertificate()->Equals(ssl_info.cert.get()));
253 EXPECT_EQ(net::ERR_CERT_COMMON_NAME_INVALID,
254 net::MapCertStatusToNetError(ssl_info.cert_status));
Carlos IL81133382017-12-06 17:18:45255 EXPECT_TRUE(ssl_info.is_fatal_cert_error);
carlosk947ebfb62015-02-04 11:53:59256 EXPECT_EQ(1, delegate.on_request_handled_counter());
davidben6b77cd72014-10-29 21:13:45257}
258
259// Test that redirects are sent to the delegate.
260TEST_F(NavigationURLLoaderTest, RequestRedirected) {
261 // Fake a top-level request. Choose a URL which redirects so the request can
262 // be paused before the response comes in.
263 TestNavigationURLLoaderDelegate delegate;
dcheng40ce7b382016-04-08 23:46:13264 std::unique_ptr<NavigationURLLoader> loader = MakeTestLoader(
265 net::URLRequestTestJob::test_url_redirect_to_url_2(), &delegate);
davidben6b77cd72014-10-29 21:13:45266
267 // Wait for the request to redirect.
268 delegate.WaitForRequestRedirected();
269 EXPECT_EQ(net::URLRequestTestJob::test_url_2(),
270 delegate.redirect_info().new_url);
271 EXPECT_EQ("GET", delegate.redirect_info().new_method);
272 EXPECT_EQ(net::URLRequestTestJob::test_url_2(),
Mike Westb85da8ed2017-08-10 14:16:46273 delegate.redirect_info().new_site_for_cookies);
davidben6b77cd72014-10-29 21:13:45274 EXPECT_EQ(302, delegate.redirect_response()->head.headers->response_code());
carlosk947ebfb62015-02-04 11:53:59275 EXPECT_EQ(1, delegate.on_request_handled_counter());
davidben6b77cd72014-10-29 21:13:45276
277 // Wait for the response to complete.
278 loader->FollowRedirect();
279 delegate.WaitForResponseStarted();
280
clamy5a3c3642016-03-24 17:46:56281 // Proceed with the response.
282 loader->ProceedWithResponse();
283
davidben6b77cd72014-10-29 21:13:45284 // Check the response is correct.
285 EXPECT_EQ("text/html", delegate.response()->head.mime_type);
286 EXPECT_EQ(200, delegate.response()->head.headers->response_code());
287
288 // Release the body and check it is correct.
289 EXPECT_TRUE(net::URLRequestTestJob::ProcessOnePendingMessage());
290 EXPECT_EQ(net::URLRequestTestJob::test_data_2(),
291 FetchURL(delegate.body()->GetURL()));
carlosk947ebfb62015-02-04 11:53:59292
293 EXPECT_EQ(1, delegate.on_request_handled_counter());
davidben6b77cd72014-10-29 21:13:45294}
295
296// Tests that the destroying the loader cancels the request.
297TEST_F(NavigationURLLoaderTest, CancelOnDestruct) {
298 // Fake a top-level request. Choose a URL which redirects so the request can
299 // be paused before the response comes in.
300 TestNavigationURLLoaderDelegate delegate;
dcheng40ce7b382016-04-08 23:46:13301 std::unique_ptr<NavigationURLLoader> loader = MakeTestLoader(
302 net::URLRequestTestJob::test_url_redirect_to_url_2(), &delegate);
davidben6b77cd72014-10-29 21:13:45303
304 // Wait for the request to redirect.
305 delegate.WaitForRequestRedirected();
306
307 // Destroy the loader and verify that URLRequestTestJob no longer has anything
308 // paused.
309 loader.reset();
310 base::RunLoop().RunUntilIdle();
311 EXPECT_FALSE(net::URLRequestTestJob::ProcessOnePendingMessage());
312}
313
314// Test that the delegate is not called if OnResponseStarted and destroying the
315// loader race.
316TEST_F(NavigationURLLoaderTest, CancelResponseRace) {
317 TestNavigationURLLoaderDelegate delegate;
dcheng40ce7b382016-04-08 23:46:13318 std::unique_ptr<NavigationURLLoader> loader = MakeTestLoader(
319 net::URLRequestTestJob::test_url_redirect_to_url_2(), &delegate);
davidben6b77cd72014-10-29 21:13:45320
321 // Wait for the request to redirect.
322 delegate.WaitForRequestRedirected();
323
324 // In the same event loop iteration, follow the redirect (allowing the
325 // response to go through) and destroy the loader.
326 loader->FollowRedirect();
327 loader.reset();
328
329 // Verify the URLRequestTestJob no longer has anything paused and that no
330 // response body was received.
331 base::RunLoop().RunUntilIdle();
332 EXPECT_FALSE(net::URLRequestTestJob::ProcessOnePendingMessage());
333 EXPECT_FALSE(delegate.body());
334}
335
336// Tests that the loader may be canceled by context.
337TEST_F(NavigationURLLoaderTest, CancelByContext) {
338 TestNavigationURLLoaderDelegate delegate;
dcheng40ce7b382016-04-08 23:46:13339 std::unique_ptr<NavigationURLLoader> loader = MakeTestLoader(
340 net::URLRequestTestJob::test_url_redirect_to_url_2(), &delegate);
davidben6b77cd72014-10-29 21:13:45341
342 // Wait for the request to redirect.
343 delegate.WaitForRequestRedirected();
344
345 // Cancel all requests.
346 host_.CancelRequestsForContext(browser_context_->GetResourceContext());
347
348 // Wait for the request to now be aborted.
349 delegate.WaitForRequestFailed();
350 EXPECT_EQ(net::ERR_ABORTED, delegate.net_error());
carlosk947ebfb62015-02-04 11:53:59351 EXPECT_EQ(1, delegate.on_request_handled_counter());
davidben6b77cd72014-10-29 21:13:45352}
353
354// Tests that, if the request is blocked by the ResourceDispatcherHostDelegate,
355// the caller is informed appropriately.
356TEST_F(NavigationURLLoaderTest, RequestBlocked) {
357 RequestBlockingResourceDispatcherHostDelegate rdh_delegate;
358 host_.SetDelegate(&rdh_delegate);
359
360 TestNavigationURLLoaderDelegate delegate;
dcheng40ce7b382016-04-08 23:46:13361 std::unique_ptr<NavigationURLLoader> loader =
davidben6b77cd72014-10-29 21:13:45362 MakeTestLoader(net::URLRequestTestJob::test_url_1(), &delegate);
363
364 // Wait for the request to fail as expected.
365 delegate.WaitForRequestFailed();
366 EXPECT_EQ(net::ERR_ABORTED, delegate.net_error());
Charles Harrisonbbfe4e4d2017-11-20 15:39:54367
368 // Failing before start means OnRequestStarted is never called.
369 EXPECT_EQ(0, delegate.on_request_handled_counter());
davidben6b77cd72014-10-29 21:13:45370
371 host_.SetDelegate(nullptr);
372}
373
374// Tests that ownership leaves the loader once the response is received.
375TEST_F(NavigationURLLoaderTest, LoaderDetached) {
376 // Fake a top-level request to a URL whose body does not load immediately.
377 TestNavigationURLLoaderDelegate delegate;
dcheng40ce7b382016-04-08 23:46:13378 std::unique_ptr<NavigationURLLoader> loader =
davidben6b77cd72014-10-29 21:13:45379 MakeTestLoader(net::URLRequestTestJob::test_url_2(), &delegate);
380
381 // Wait for the response to come back.
382 delegate.WaitForResponseStarted();
383
clamy5a3c3642016-03-24 17:46:56384 // Proceed with the response.
385 loader->ProceedWithResponse();
386
davidben6b77cd72014-10-29 21:13:45387 // Check the response is correct.
388 EXPECT_EQ("text/html", delegate.response()->head.mime_type);
389 EXPECT_EQ(200, delegate.response()->head.headers->response_code());
390
391 // Destroy the loader.
392 loader.reset();
393 base::RunLoop().RunUntilIdle();
394
395 // Check the body can still be fetched through the StreamHandle.
396 EXPECT_TRUE(net::URLRequestTestJob::ProcessOnePendingMessage());
397 EXPECT_EQ(net::URLRequestTestJob::test_data_2(),
398 FetchURL(delegate.body()->GetURL()));
399}
400
401// Tests that the request is owned by the body StreamHandle.
402TEST_F(NavigationURLLoaderTest, OwnedByHandle) {
403 // Fake a top-level request to a URL whose body does not load immediately.
404 TestNavigationURLLoaderDelegate delegate;
dcheng40ce7b382016-04-08 23:46:13405 std::unique_ptr<NavigationURLLoader> loader =
davidben6b77cd72014-10-29 21:13:45406 MakeTestLoader(net::URLRequestTestJob::test_url_2(), &delegate);
407
408 // Wait for the response to come back.
409 delegate.WaitForResponseStarted();
410
clamy5a3c3642016-03-24 17:46:56411 // Proceed with the response.
412 loader->ProceedWithResponse();
413
davidben6b77cd72014-10-29 21:13:45414 // Release the body.
415 delegate.ReleaseBody();
416 base::RunLoop().RunUntilIdle();
417
418 // Verify that URLRequestTestJob no longer has anything paused.
419 EXPECT_FALSE(net::URLRequestTestJob::ProcessOnePendingMessage());
420}
421
davidben6b77cd72014-10-29 21:13:45422} // namespace content