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