1 // Copyright 2015 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
5 #include "headless/lib/browser/headless_web_contents_impl.h"
6
7 #include <memory>
8 #include <string>
9 #include <utility>
10 #include <vector>
11
12 #include "base/bind.h"
13 #include "base/memory/ptr_util.h"
14 #include "base/memory/weak_ptr.h"
15 #include "base/stl_util.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "base/threading/sequenced_task_runner_handle.h"
18 #include "base/trace_event/trace_event.h"
19 #include "base/values.h"
20 #include "components/security_state/content/content_utils.h"
21 #include "components/security_state/core/security_state.h"
22 #include "content/public/browser/browser_thread.h"
23 #include "content/public/browser/child_process_termination_info.h"
24 #include "content/public/browser/devtools_agent_host.h"
25 #include "content/public/browser/navigation_controller.h"
26 #include "content/public/browser/navigation_handle.h"
27 #include "content/public/browser/render_frame_host.h"
28 #include "content/public/browser/render_process_host.h"
29 #include "content/public/browser/render_view_host.h"
30 #include "content/public/browser/render_widget_host.h"
31 #include "content/public/browser/render_widget_host_view.h"
32 #include "content/public/browser/web_contents.h"
33 #include "content/public/browser/web_contents_delegate.h"
34 #include "content/public/common/bindings_policy.h"
35 #include "content/public/common/origin_util.h"
36 #include "headless/lib/browser/headless_browser_context_impl.h"
37 #include "headless/lib/browser/headless_browser_impl.h"
38 #include "headless/lib/browser/headless_browser_main_parts.h"
39 #include "headless/lib/browser/headless_devtools_agent_host_client.h"
40 #include "headless/lib/browser/protocol/headless_handler.h"
41 #include "headless/public/internal/headless_devtools_client_impl.h"
42 #include "printing/buildflags/buildflags.h"
43 #include "third_party/blink/public/common/renderer_preferences/renderer_preferences.h"
44 #include "third_party/skia/include/core/SkBitmap.h"
45 #include "ui/compositor/compositor.h"
46 #include "ui/gfx/switches.h"
47
48 #if BUILDFLAG(ENABLE_PRINTING)
49 #include "headless/lib/browser/headless_print_manager.h"
50 #endif
51
52 namespace headless {
53
54 // static
From(HeadlessWebContents * web_contents)55 HeadlessWebContentsImpl* HeadlessWebContentsImpl::From(
56 HeadlessWebContents* web_contents) {
57 // This downcast is safe because there is only one implementation of
58 // HeadlessWebContents.
59 return static_cast<HeadlessWebContentsImpl*>(web_contents);
60 }
61
62 // static
From(HeadlessBrowser * browser,content::WebContents * contents)63 HeadlessWebContentsImpl* HeadlessWebContentsImpl::From(
64 HeadlessBrowser* browser,
65 content::WebContents* contents) {
66 return HeadlessWebContentsImpl::From(
67 browser->GetWebContentsForDevToolsAgentHostId(
68 content::DevToolsAgentHost::GetOrCreateFor(contents)->GetId()));
69 }
70
71 class HeadlessWebContentsImpl::Delegate : public content::WebContentsDelegate {
72 public:
Delegate(HeadlessWebContentsImpl * headless_web_contents)73 explicit Delegate(HeadlessWebContentsImpl* headless_web_contents)
74 : headless_web_contents_(headless_web_contents) {}
75
76 // Return the security style of the given |web_contents|, populating
77 // |security_style_explanations| to explain why the SecurityStyle was chosen.
GetSecurityStyle(content::WebContents * web_contents,content::SecurityStyleExplanations * security_style_explanations)78 blink::SecurityStyle GetSecurityStyle(
79 content::WebContents* web_contents,
80 content::SecurityStyleExplanations* security_style_explanations)
81 override {
82 std::unique_ptr<security_state::VisibleSecurityState>
83 visible_security_state =
84 security_state::GetVisibleSecurityState(web_contents);
85 return security_state::GetSecurityStyle(
86 security_state::GetSecurityLevel(
87 *visible_security_state.get(),
88 false /* used_policy_installed_certificate */),
89 *visible_security_state.get(), security_style_explanations);
90 }
91
BeforeUnloadFired(content::WebContents * web_contents,bool proceed,bool * proceed_to_fire_unload)92 void BeforeUnloadFired(content::WebContents* web_contents,
93 bool proceed,
94 bool* proceed_to_fire_unload) override {
95 *proceed_to_fire_unload = proceed;
96 }
97
ActivateContents(content::WebContents * contents)98 void ActivateContents(content::WebContents* contents) override {
99 contents->GetMainFrame()->GetRenderViewHost()->GetWidget()->Focus();
100 }
101
CloseContents(content::WebContents * source)102 void CloseContents(content::WebContents* source) override {
103 auto* const headless_contents =
104 HeadlessWebContentsImpl::From(browser(), source);
105 DCHECK(headless_contents);
106 headless_contents->Close();
107 }
108
AddNewContents(content::WebContents * source,std::unique_ptr<content::WebContents> new_contents,const GURL & target_url,WindowOpenDisposition disposition,const gfx::Rect & initial_rect,bool user_gesture,bool * was_blocked)109 void AddNewContents(content::WebContents* source,
110 std::unique_ptr<content::WebContents> new_contents,
111 const GURL& target_url,
112 WindowOpenDisposition disposition,
113 const gfx::Rect& initial_rect,
114 bool user_gesture,
115 bool* was_blocked) override {
116 DCHECK(new_contents->GetBrowserContext() ==
117 headless_web_contents_->browser_context());
118
119 std::unique_ptr<HeadlessWebContentsImpl> child_contents =
120 HeadlessWebContentsImpl::CreateForChildContents(
121 headless_web_contents_, std::move(new_contents));
122 HeadlessWebContentsImpl* raw_child_contents = child_contents.get();
123 headless_web_contents_->browser_context()->RegisterWebContents(
124 std::move(child_contents));
125
126 const gfx::Rect default_rect(
127 headless_web_contents_->browser()->options()->window_size);
128 const gfx::Rect rect = initial_rect.IsEmpty() ? default_rect : initial_rect;
129 raw_child_contents->SetBounds(rect);
130 }
131
OpenURLFromTab(content::WebContents * source,const content::OpenURLParams & params)132 content::WebContents* OpenURLFromTab(
133 content::WebContents* source,
134 const content::OpenURLParams& params) override {
135 DCHECK_EQ(source, headless_web_contents_->web_contents());
136 content::WebContents* target = nullptr;
137 switch (params.disposition) {
138 case WindowOpenDisposition::CURRENT_TAB:
139 target = source;
140 break;
141
142 case WindowOpenDisposition::NEW_POPUP:
143 case WindowOpenDisposition::NEW_WINDOW:
144 case WindowOpenDisposition::NEW_BACKGROUND_TAB:
145 case WindowOpenDisposition::NEW_FOREGROUND_TAB: {
146 HeadlessWebContentsImpl* child_contents = HeadlessWebContentsImpl::From(
147 headless_web_contents_->browser_context()
148 ->CreateWebContentsBuilder()
149 .SetWindowSize(source->GetContainerBounds().size())
150 .Build());
151 target = child_contents->web_contents();
152 break;
153 }
154
155 // TODO(veluca): add support for other disposition types.
156 case WindowOpenDisposition::SINGLETON_TAB:
157 case WindowOpenDisposition::OFF_THE_RECORD:
158 case WindowOpenDisposition::SAVE_TO_DISK:
159 case WindowOpenDisposition::IGNORE_ACTION:
160 default:
161 return nullptr;
162 }
163
164 target->GetController().LoadURLWithParams(
165 content::NavigationController::LoadURLParams(params));
166 return target;
167 }
168
IsWebContentsCreationOverridden(content::SiteInstance * source_site_instance,content::mojom::WindowContainerType window_container_type,const GURL & opener_url,const std::string & frame_name,const GURL & target_url)169 bool IsWebContentsCreationOverridden(
170 content::SiteInstance* source_site_instance,
171 content::mojom::WindowContainerType window_container_type,
172 const GURL& opener_url,
173 const std::string& frame_name,
174 const GURL& target_url) override {
175 return headless_web_contents_->browser_context()
176 ->options()
177 ->block_new_web_contents();
178 }
179
180 private:
browser()181 HeadlessBrowserImpl* browser() { return headless_web_contents_->browser(); }
182
183 HeadlessWebContentsImpl* headless_web_contents_; // Not owned.
184 DISALLOW_COPY_AND_ASSIGN(Delegate);
185 };
186
187 namespace {
188 constexpr uint64_t kBeginFrameSourceId = viz::BeginFrameArgs::kManualSourceId;
189 }
190
191 class HeadlessWebContentsImpl::PendingFrame
192 : public base::RefCounted<HeadlessWebContentsImpl::PendingFrame>,
193 public base::SupportsWeakPtr<HeadlessWebContentsImpl::PendingFrame> {
194 public:
PendingFrame(uint64_t sequence_number,FrameFinishedCallback callback)195 PendingFrame(uint64_t sequence_number, FrameFinishedCallback callback)
196 : sequence_number_(sequence_number), callback_(std::move(callback)) {}
197
OnFrameComplete(const viz::BeginFrameAck & ack)198 void OnFrameComplete(const viz::BeginFrameAck& ack) {
199 DCHECK_EQ(kBeginFrameSourceId, ack.frame_id.source_id);
200 DCHECK_EQ(sequence_number_, ack.frame_id.sequence_number);
201 has_damage_ = ack.has_damage;
202 }
203
OnReadbackComplete(const SkBitmap & bitmap)204 void OnReadbackComplete(const SkBitmap& bitmap) {
205 TRACE_EVENT2(
206 "headless", "HeadlessWebContentsImpl::PendingFrame::OnReadbackComplete",
207 "sequence_number", sequence_number_, "success", !bitmap.drawsNothing());
208 if (bitmap.drawsNothing()) {
209 LOG(WARNING) << "Readback from surface failed.";
210 return;
211 }
212 bitmap_ = std::make_unique<SkBitmap>(bitmap);
213 }
214
215 private:
216 friend class base::RefCounted<PendingFrame>;
217
~PendingFrame()218 ~PendingFrame() {
219 std::move(callback_).Run(has_damage_, std::move(bitmap_), "");
220 }
221
222 const uint64_t sequence_number_;
223
224 FrameFinishedCallback callback_;
225 bool has_damage_ = false;
226 std::unique_ptr<SkBitmap> bitmap_;
227
228 DISALLOW_COPY_AND_ASSIGN(PendingFrame);
229 };
230
231 // static
Create(HeadlessWebContents::Builder * builder)232 std::unique_ptr<HeadlessWebContentsImpl> HeadlessWebContentsImpl::Create(
233 HeadlessWebContents::Builder* builder) {
234 content::WebContents::CreateParams create_params(builder->browser_context_,
235 nullptr);
236 auto headless_web_contents = base::WrapUnique(new HeadlessWebContentsImpl(
237 content::WebContents::Create(create_params), builder->browser_context_));
238
239 headless_web_contents->begin_frame_control_enabled_ =
240 builder->enable_begin_frame_control_ ||
241 headless_web_contents->browser()->options()->enable_begin_frame_control;
242 headless_web_contents->InitializeWindow(gfx::Rect(builder->window_size_));
243 if (!headless_web_contents->OpenURL(builder->initial_url_))
244 return nullptr;
245 return headless_web_contents;
246 }
247
248 // static
249 std::unique_ptr<HeadlessWebContentsImpl>
CreateForChildContents(HeadlessWebContentsImpl * parent,std::unique_ptr<content::WebContents> child_contents)250 HeadlessWebContentsImpl::CreateForChildContents(
251 HeadlessWebContentsImpl* parent,
252 std::unique_ptr<content::WebContents> child_contents) {
253 auto child = base::WrapUnique(new HeadlessWebContentsImpl(
254 std::move(child_contents), parent->browser_context()));
255
256 // Child contents have their own root window and inherit the BeginFrameControl
257 // setting.
258 child->begin_frame_control_enabled_ = parent->begin_frame_control_enabled_;
259 child->InitializeWindow(child->web_contents_->GetContainerBounds());
260
261 // There may already be frames, so make sure they also have our services.
262 for (content::RenderFrameHost* frame_host :
263 child->web_contents_->GetAllFrames()) {
264 child->RenderFrameCreated(frame_host);
265 }
266
267 return child;
268 }
269
InitializeWindow(const gfx::Rect & initial_bounds)270 void HeadlessWebContentsImpl::InitializeWindow(
271 const gfx::Rect& initial_bounds) {
272 static int window_id = 1;
273 window_id_ = window_id++;
274 window_state_ = "normal";
275
276 browser()->PlatformInitializeWebContents(this);
277 SetBounds(initial_bounds);
278 }
279
SetBounds(const gfx::Rect & bounds)280 void HeadlessWebContentsImpl::SetBounds(const gfx::Rect& bounds) {
281 browser()->PlatformSetWebContentsBounds(this, bounds);
282 }
283
HeadlessWebContentsImpl(std::unique_ptr<content::WebContents> web_contents,HeadlessBrowserContextImpl * browser_context)284 HeadlessWebContentsImpl::HeadlessWebContentsImpl(
285 std::unique_ptr<content::WebContents> web_contents,
286 HeadlessBrowserContextImpl* browser_context)
287 : content::WebContentsObserver(web_contents.get()),
288 web_contents_delegate_(new HeadlessWebContentsImpl::Delegate(this)),
289 web_contents_(std::move(web_contents)),
290 agent_host_(
291 content::DevToolsAgentHost::GetOrCreateFor(web_contents_.get())),
292 browser_context_(browser_context),
293 render_process_host_(web_contents_->GetMainFrame()->GetProcess()) {
294 #if BUILDFLAG(ENABLE_PRINTING)
295 HeadlessPrintManager::CreateForWebContents(web_contents_.get());
296 // TODO(weili): Add support for printing OOPIFs.
297 #endif
298 web_contents_->GetMutableRendererPrefs()->accept_languages =
299 browser_context->options()->accept_language();
300 web_contents_->GetMutableRendererPrefs()->hinting =
301 browser_context->options()->font_render_hinting();
302 web_contents_->SetDelegate(web_contents_delegate_.get());
303 render_process_host_->AddObserver(this);
304 agent_host_->AddObserver(this);
305 }
306
~HeadlessWebContentsImpl()307 HeadlessWebContentsImpl::~HeadlessWebContentsImpl() {
308 for (auto& observer : observers_)
309 observer.HeadlessWebContentsDestroyed();
310 agent_host_->RemoveObserver(this);
311 if (render_process_host_)
312 render_process_host_->RemoveObserver(this);
313 // Defer destruction of WindowTreeHost, as it does sync mojo calls
314 // in the destructor of ui::Compositor.
315 base::SequencedTaskRunnerHandle::Get()->DeleteSoon(
316 FROM_HERE, std::move(window_tree_host_));
317 }
318
RenderFrameCreated(content::RenderFrameHost * render_frame_host)319 void HeadlessWebContentsImpl::RenderFrameCreated(
320 content::RenderFrameHost* render_frame_host) {
321 browser_context_->SetDevToolsFrameToken(
322 render_frame_host->GetProcess()->GetID(),
323 render_frame_host->GetRoutingID(),
324 render_frame_host->GetDevToolsFrameToken(),
325 render_frame_host->GetFrameTreeNodeId());
326 }
327
RenderFrameDeleted(content::RenderFrameHost * render_frame_host)328 void HeadlessWebContentsImpl::RenderFrameDeleted(
329 content::RenderFrameHost* render_frame_host) {
330 browser_context_->RemoveDevToolsFrameToken(
331 render_frame_host->GetProcess()->GetID(),
332 render_frame_host->GetRoutingID(),
333 render_frame_host->GetFrameTreeNodeId());
334 }
335
RenderViewReady()336 void HeadlessWebContentsImpl::RenderViewReady() {
337 DCHECK(web_contents()->GetMainFrame()->IsRenderFrameLive());
338
339 if (devtools_target_ready_notification_sent_)
340 return;
341
342 for (auto& observer : observers_)
343 observer.DevToolsTargetReady();
344
345 devtools_target_ready_notification_sent_ = true;
346 }
347
GetMainFrameRenderProcessId() const348 int HeadlessWebContentsImpl::GetMainFrameRenderProcessId() const {
349 if (!web_contents() || !web_contents()->GetMainFrame())
350 return -1;
351 return web_contents()->GetMainFrame()->GetProcess()->GetID();
352 }
353
GetMainFrameTreeNodeId() const354 int HeadlessWebContentsImpl::GetMainFrameTreeNodeId() const {
355 if (!web_contents() || !web_contents()->GetMainFrame())
356 return -1;
357 return web_contents()->GetMainFrame()->GetFrameTreeNodeId();
358 }
359
GetMainFrameDevToolsId() const360 std::string HeadlessWebContentsImpl::GetMainFrameDevToolsId() const {
361 if (!web_contents() || !web_contents()->GetMainFrame())
362 return "";
363 return web_contents()->GetMainFrame()->GetDevToolsFrameToken().ToString();
364 }
365
OpenURL(const GURL & url)366 bool HeadlessWebContentsImpl::OpenURL(const GURL& url) {
367 if (!url.is_valid())
368 return false;
369 content::NavigationController::LoadURLParams params(url);
370 params.transition_type = ui::PageTransitionFromInt(
371 ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
372 web_contents_->GetController().LoadURLWithParams(params);
373 web_contents_delegate_->ActivateContents(web_contents_.get());
374 web_contents_->Focus();
375 return true;
376 }
377
Close()378 void HeadlessWebContentsImpl::Close() {
379 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
380 browser_context()->DestroyWebContents(this);
381 }
382
GetDevToolsAgentHostId()383 std::string HeadlessWebContentsImpl::GetDevToolsAgentHostId() {
384 return agent_host_->GetId();
385 }
386
AddObserver(Observer * observer)387 void HeadlessWebContentsImpl::AddObserver(Observer* observer) {
388 observers_.AddObserver(observer);
389 }
390
RemoveObserver(Observer * observer)391 void HeadlessWebContentsImpl::RemoveObserver(Observer* observer) {
392 observers_.RemoveObserver(observer);
393 }
394
DevToolsAgentHostAttached(content::DevToolsAgentHost * agent_host)395 void HeadlessWebContentsImpl::DevToolsAgentHostAttached(
396 content::DevToolsAgentHost* agent_host) {
397 for (auto& observer : observers_)
398 observer.DevToolsClientAttached();
399 }
400
DevToolsAgentHostDetached(content::DevToolsAgentHost * agent_host)401 void HeadlessWebContentsImpl::DevToolsAgentHostDetached(
402 content::DevToolsAgentHost* agent_host) {
403 for (auto& observer : observers_)
404 observer.DevToolsClientDetached();
405 }
406
RenderProcessExited(content::RenderProcessHost * host,const content::ChildProcessTerminationInfo & info)407 void HeadlessWebContentsImpl::RenderProcessExited(
408 content::RenderProcessHost* host,
409 const content::ChildProcessTerminationInfo& info) {
410 DCHECK_EQ(render_process_host_, host);
411 render_process_exited_ = true;
412 for (auto& observer : observers_)
413 observer.RenderProcessExited(info.status, info.exit_code);
414 }
415
RenderProcessHostDestroyed(content::RenderProcessHost * host)416 void HeadlessWebContentsImpl::RenderProcessHostDestroyed(
417 content::RenderProcessHost* host) {
418 DCHECK_EQ(render_process_host_, host);
419 render_process_host_ = nullptr;
420 }
421
GetDevToolsTarget()422 HeadlessDevToolsTarget* HeadlessWebContentsImpl::GetDevToolsTarget() {
423 return web_contents()->GetMainFrame()->IsRenderFrameLive() ? this : nullptr;
424 }
425
426 std::unique_ptr<HeadlessDevToolsChannel>
CreateDevToolsChannel()427 HeadlessWebContentsImpl::CreateDevToolsChannel() {
428 DCHECK(agent_host_);
429 return std::make_unique<HeadlessDevToolsAgentHostClient>(agent_host_);
430 }
431
AttachClient(HeadlessDevToolsClient * client)432 void HeadlessWebContentsImpl::AttachClient(HeadlessDevToolsClient* client) {
433 client->AttachToChannel(CreateDevToolsChannel());
434 }
435
DetachClient(HeadlessDevToolsClient * client)436 void HeadlessWebContentsImpl::DetachClient(HeadlessDevToolsClient* client) {
437 client->DetachFromChannel();
438 }
439
IsAttached()440 bool HeadlessWebContentsImpl::IsAttached() {
441 DCHECK(agent_host_);
442 return agent_host_->IsAttached();
443 }
444
web_contents() const445 content::WebContents* HeadlessWebContentsImpl::web_contents() const {
446 return web_contents_.get();
447 }
448
browser() const449 HeadlessBrowserImpl* HeadlessWebContentsImpl::browser() const {
450 return browser_context_->browser();
451 }
452
browser_context() const453 HeadlessBrowserContextImpl* HeadlessWebContentsImpl::browser_context() const {
454 return browser_context_;
455 }
456
BeginFrame(const base::TimeTicks & frame_timeticks,const base::TimeTicks & deadline,const base::TimeDelta & interval,bool animate_only,bool capture_screenshot,FrameFinishedCallback frame_finished_callback)457 void HeadlessWebContentsImpl::BeginFrame(
458 const base::TimeTicks& frame_timeticks,
459 const base::TimeTicks& deadline,
460 const base::TimeDelta& interval,
461 bool animate_only,
462 bool capture_screenshot,
463 FrameFinishedCallback frame_finished_callback) {
464 DCHECK(begin_frame_control_enabled_);
465 if (pending_frame_) {
466 std::move(frame_finished_callback)
467 .Run(false, nullptr, "Another frame is pending");
468 return;
469 }
470 TRACE_EVENT2("headless", "HeadlessWebContentsImpl::BeginFrame", "frame_time",
471 frame_timeticks, "capture_screenshot", capture_screenshot);
472
473 int64_t sequence_number = begin_frame_sequence_number_++;
474 auto pending_frame = base::MakeRefCounted<PendingFrame>(
475 sequence_number, std::move(frame_finished_callback));
476 pending_frame_ = pending_frame->AsWeakPtr();
477 if (capture_screenshot) {
478 content::RenderWidgetHostView* view =
479 web_contents()->GetRenderWidgetHostView();
480 if (view && view->IsSurfaceAvailableForCopy()) {
481 view->CopyFromSurface(
482 gfx::Rect(), gfx::Size(),
483 base::BindOnce(&PendingFrame::OnReadbackComplete, pending_frame));
484 } else {
485 LOG(WARNING) << "Surface not ready for screenshot.";
486 }
487 }
488
489 auto args = viz::BeginFrameArgs::Create(
490 BEGINFRAME_FROM_HERE, kBeginFrameSourceId, sequence_number,
491 frame_timeticks, deadline, interval, viz::BeginFrameArgs::NORMAL);
492 args.animate_only = animate_only;
493
494 ui::Compositor* compositor = browser()->PlatformGetCompositor(this);
495 CHECK(compositor);
496 compositor->IssueExternalBeginFrame(
497 args, /*force=*/true,
498 base::BindOnce(&PendingFrame::OnFrameComplete, pending_frame));
499 }
500
Builder(HeadlessBrowserContextImpl * browser_context)501 HeadlessWebContents::Builder::Builder(
502 HeadlessBrowserContextImpl* browser_context)
503 : browser_context_(browser_context),
504 window_size_(browser_context->options()->window_size()) {}
505
506 HeadlessWebContents::Builder::~Builder() = default;
507
508 HeadlessWebContents::Builder::Builder(Builder&&) = default;
509
SetInitialURL(const GURL & initial_url)510 HeadlessWebContents::Builder& HeadlessWebContents::Builder::SetInitialURL(
511 const GURL& initial_url) {
512 initial_url_ = initial_url;
513 return *this;
514 }
515
SetWindowSize(const gfx::Size & size)516 HeadlessWebContents::Builder& HeadlessWebContents::Builder::SetWindowSize(
517 const gfx::Size& size) {
518 window_size_ = size;
519 return *this;
520 }
521
522 HeadlessWebContents::Builder&
SetEnableBeginFrameControl(bool enable_begin_frame_control)523 HeadlessWebContents::Builder::SetEnableBeginFrameControl(
524 bool enable_begin_frame_control) {
525 enable_begin_frame_control_ = enable_begin_frame_control;
526 return *this;
527 }
528
Build()529 HeadlessWebContents* HeadlessWebContents::Builder::Build() {
530 return browser_context_->CreateWebContents(this);
531 }
532
533 } // namespace headless
534