Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(299)

Side by Side Diff: headless/app/headless_shell.cc

Issue 2687083002: Headless: make URLRequestDispatcher aware of navigations (Closed)
Patch Set: Address nits Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « headless/app/headless_shell.h ('k') | headless/app/shell_navigation_request.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2016 The Chromium Authors. All rights reserved. 1 // Copyright 2016 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 <memory> 5 #include <memory>
6 #include <sstream> 6 #include <sstream>
7 #include <string> 7 #include <string>
8 8
9 #include "base/base64.h" 9 #include "base/base64.h"
10 #include "base/bind.h" 10 #include "base/bind.h"
11 #include "base/callback.h" 11 #include "base/callback.h"
12 #include "base/command_line.h" 12 #include "base/command_line.h"
13 #include "base/files/file_path.h" 13 #include "base/files/file_path.h"
14 #include "base/json/json_writer.h" 14 #include "base/json/json_writer.h"
15 #include "base/location.h" 15 #include "base/location.h"
16 #include "base/memory/ptr_util.h"
17 #include "base/memory/ref_counted.h"
18 #include "base/memory/weak_ptr.h" 16 #include "base/memory/weak_ptr.h"
19 #include "base/numerics/safe_conversions.h" 17 #include "base/numerics/safe_conversions.h"
20 #include "base/strings/string_number_conversions.h" 18 #include "base/strings/string_number_conversions.h"
19 #include "headless/app/headless_shell.h"
21 #include "headless/app/headless_shell_switches.h" 20 #include "headless/app/headless_shell_switches.h"
22 #include "headless/public/devtools/domains/emulation.h"
23 #include "headless/public/devtools/domains/inspector.h"
24 #include "headless/public/devtools/domains/page.h"
25 #include "headless/public/devtools/domains/runtime.h"
26 #include "headless/public/headless_browser.h"
27 #include "headless/public/headless_devtools_client.h"
28 #include "headless/public/headless_devtools_target.h" 21 #include "headless/public/headless_devtools_target.h"
29 #include "headless/public/headless_web_contents.h"
30 #include "headless/public/util/deterministic_dispatcher.h"
31 #include "headless/public/util/deterministic_http_protocol_handler.h" 22 #include "headless/public/util/deterministic_http_protocol_handler.h"
32 #include "net/base/file_stream.h"
33 #include "net/base/io_buffer.h" 23 #include "net/base/io_buffer.h"
34 #include "net/base/ip_address.h" 24 #include "net/base/ip_address.h"
35 #include "net/base/net_errors.h" 25 #include "net/base/net_errors.h"
36 #include "ui/gfx/geometry/size.h" 26 #include "ui/gfx/geometry/size.h"
37 27
38 namespace headless { 28 namespace headless {
39 namespace { 29 namespace {
40 // Address where to listen to incoming DevTools connections. 30 // Address where to listen to incoming DevTools connections.
41 const char kDevToolsHttpServerAddress[] = "127.0.0.1"; 31 const char kDevToolsHttpServerAddress[] = "127.0.0.1";
42 // Default file name for screenshot. Can be overriden by "--screenshot" switch. 32 // Default file name for screenshot. Can be overriden by "--screenshot" switch.
43 const char kDefaultScreenshotFileName[] = "screenshot.png"; 33 const char kDefaultScreenshotFileName[] = "screenshot.png";
44 34
45 bool ParseWindowSize(std::string window_size, gfx::Size* parsed_window_size) { 35 bool ParseWindowSize(std::string window_size, gfx::Size* parsed_window_size) {
46 int width, height = 0; 36 int width, height = 0;
47 if (sscanf(window_size.c_str(), "%d%*[x,]%d", &width, &height) >= 2 && 37 if (sscanf(window_size.c_str(), "%d%*[x,]%d", &width, &height) >= 2 &&
48 width >= 0 && height >= 0) { 38 width >= 0 && height >= 0) {
49 parsed_window_size->set_width(width); 39 parsed_window_size->set_width(width);
50 parsed_window_size->set_height(height); 40 parsed_window_size->set_height(height);
51 return true; 41 return true;
52 } 42 }
53 return false; 43 return false;
54 } 44 }
55 } // namespace 45 } // namespace
56 46
57 // An application which implements a simple headless browser. 47 HeadlessShell::HeadlessShell()
58 class HeadlessShell : public HeadlessWebContents::Observer, 48 : browser_(nullptr),
59 emulation::ExperimentalObserver, 49 devtools_client_(HeadlessDevToolsClient::Create()),
60 inspector::ExperimentalObserver, 50 web_contents_(nullptr),
61 page::Observer { 51 processed_page_ready_(false),
62 public: 52 browser_context_(nullptr),
63 HeadlessShell() 53 weak_factory_(this) {}
64 : browser_(nullptr), 54
65 devtools_client_(HeadlessDevToolsClient::Create()), 55 HeadlessShell::~HeadlessShell() {}
66 web_contents_(nullptr), 56
67 processed_page_ready_(false), 57 void HeadlessShell::OnStart(HeadlessBrowser* browser) {
68 browser_context_(nullptr), 58 browser_ = browser;
69 weak_factory_(this) {} 59
70 ~HeadlessShell() override {} 60 HeadlessBrowserContext::Builder context_builder =
71 61 browser_->CreateBrowserContextBuilder();
72 void OnStart(HeadlessBrowser* browser) { 62 // TODO(eseckler): These switches should also affect BrowserContexts that
73 browser_ = browser; 63 // are created via DevTools later.
74 64 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
75 HeadlessBrowserContext::Builder context_builder = 65 switches::kDeterministicFetch)) {
76 browser_->CreateBrowserContextBuilder(); 66 deterministic_dispatcher_.reset(
77 // TODO(eseckler): These switches should also affect BrowserContexts that 67 new DeterministicDispatcher(browser_->BrowserIOThread()));
78 // are created via DevTools later. 68
79 if (base::CommandLine::ForCurrentProcess()->HasSwitch( 69 ProtocolHandlerMap protocol_handlers;
80 switches::kDeterministicFetch)) { 70 protocol_handlers[url::kHttpScheme] =
81 deterministic_dispatcher_.reset( 71 base::MakeUnique<DeterministicHttpProtocolHandler>(
82 new DeterministicDispatcher(browser_->BrowserIOThread())); 72 deterministic_dispatcher_.get(), browser->BrowserIOThread());
83 73 protocol_handlers[url::kHttpsScheme] =
84 ProtocolHandlerMap protocol_handlers; 74 base::MakeUnique<DeterministicHttpProtocolHandler>(
85 protocol_handlers[url::kHttpScheme] = 75 deterministic_dispatcher_.get(), browser->BrowserIOThread());
86 base::MakeUnique<DeterministicHttpProtocolHandler>( 76
87 deterministic_dispatcher_.get(), browser->BrowserIOThread()); 77 context_builder.SetProtocolHandlers(std::move(protocol_handlers));
88 protocol_handlers[url::kHttpsScheme] = 78 }
89 base::MakeUnique<DeterministicHttpProtocolHandler>( 79 browser_context_ = context_builder.Build();
90 deterministic_dispatcher_.get(), browser->BrowserIOThread()); 80 browser_->SetDefaultBrowserContext(browser_context_);
91 81
92 context_builder.SetProtocolHandlers(std::move(protocol_handlers)); 82 HeadlessWebContents::Builder builder(
93 } 83 browser_context_->CreateWebContentsBuilder());
94 browser_context_ = context_builder.Build(); 84 base::CommandLine::StringVector args =
95 browser_->SetDefaultBrowserContext(browser_context_); 85 base::CommandLine::ForCurrentProcess()->GetArgs();
96 86
97 HeadlessWebContents::Builder builder( 87 // TODO(alexclarke): Should we navigate to about:blank first if using
98 browser_context_->CreateWebContentsBuilder()); 88 // virtual time?
99 base::CommandLine::StringVector args = 89 if (args.empty())
100 base::CommandLine::ForCurrentProcess()->GetArgs(); 90 args.push_back("about:blank");
101 91 for (auto it = args.rbegin(); it != args.rend(); ++it) {
102 // TODO(alexclarke): Should we navigate to about:blank first if using 92 GURL url(*it);
103 // virtual time? 93 HeadlessWebContents* web_contents = builder.SetInitialURL(url).Build();
104 if (args.empty()) 94 if (!web_contents) {
105 args.push_back("about:blank"); 95 LOG(ERROR) << "Navigation to " << url << " failed";
106 for (auto it = args.rbegin(); it != args.rend(); ++it) { 96 browser_->Shutdown();
107 GURL url(*it);
108 HeadlessWebContents* web_contents = builder.SetInitialURL(url).Build();
109 if (!web_contents) {
110 LOG(ERROR) << "Navigation to " << url << " failed";
111 browser_->Shutdown();
112 return;
113 }
114 if (!web_contents_ && !RemoteDebuggingEnabled()) {
115 // TODO(jzfeng): Support observing multiple targets.
116 url_ = url;
117 web_contents_ = web_contents;
118 web_contents_->AddObserver(this);
119 }
120 }
121 }
122
123 void Shutdown() {
124 if (!web_contents_)
125 return; 97 return;
126 if (!RemoteDebuggingEnabled()) { 98 }
127 devtools_client_->GetEmulation()->GetExperimental()->RemoveObserver(this); 99 if (!web_contents_ && !RemoteDebuggingEnabled()) {
128 devtools_client_->GetInspector()->GetExperimental()->RemoveObserver(this); 100 // TODO(jzfeng): Support observing multiple targets.
129 devtools_client_->GetPage()->RemoveObserver(this); 101 url_ = url;
130 if (web_contents_->GetDevToolsTarget()) { 102 web_contents_ = web_contents;
131 web_contents_->GetDevToolsTarget()->DetachClient( 103 web_contents_->AddObserver(this);
132 devtools_client_.get()); 104 }
133 } 105 }
134 } 106 }
135 web_contents_->RemoveObserver(this); 107
136 web_contents_ = nullptr; 108 void HeadlessShell::Shutdown() {
137 browser_context_->Close(); 109 if (!web_contents_)
138 browser_->Shutdown(); 110 return;
139 } 111 if (!RemoteDebuggingEnabled()) {
140 112 devtools_client_->GetEmulation()->GetExperimental()->RemoveObserver(this);
141 // HeadlessWebContents::Observer implementation: 113 devtools_client_->GetInspector()->GetExperimental()->RemoveObserver(this);
142 void DevToolsTargetReady() override { 114 devtools_client_->GetPage()->GetExperimental()->RemoveObserver(this);
143 web_contents_->GetDevToolsTarget()->AttachClient(devtools_client_.get()); 115 if (web_contents_->GetDevToolsTarget()) {
144 devtools_client_->GetInspector()->GetExperimental()->AddObserver(this); 116 web_contents_->GetDevToolsTarget()->DetachClient(devtools_client_.get());
145 devtools_client_->GetPage()->AddObserver(this); 117 }
146 devtools_client_->GetPage()->Enable(); 118 }
147 // Check if the document had already finished loading by the time we 119 web_contents_->RemoveObserver(this);
148 // attached. 120 web_contents_ = nullptr;
149 121 browser_context_->Close();
150 devtools_client_->GetEmulation()->GetExperimental()->AddObserver(this); 122 browser_->Shutdown();
151 123 }
152 if (base::CommandLine::ForCurrentProcess()->HasSwitch( 124
153 switches::kVirtualTimeBudget)) { 125 void HeadlessShell::DevToolsTargetReady() {
154 std::string budget_ms_ascii = 126 web_contents_->GetDevToolsTarget()->AttachClient(devtools_client_.get());
155 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( 127 devtools_client_->GetInspector()->GetExperimental()->AddObserver(this);
156 switches::kVirtualTimeBudget); 128 devtools_client_->GetPage()->GetExperimental()->AddObserver(this);
157 int budget_ms; 129 devtools_client_->GetPage()->Enable();
158 CHECK(base::StringToInt(budget_ms_ascii, &budget_ms)) 130 // Check if the document had already finished loading by the time we
159 << "Expected an integer value for --virtual-time-budget="; 131 // attached.
160 devtools_client_->GetEmulation()->GetExperimental()->SetVirtualTimePolicy( 132
161 emulation::SetVirtualTimePolicyParams::Builder() 133 devtools_client_->GetEmulation()->GetExperimental()->AddObserver(this);
162 .SetPolicy(emulation::VirtualTimePolicy:: 134
163 PAUSE_IF_NETWORK_FETCHES_PENDING) 135 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
164 .SetBudget(budget_ms) 136 switches::kDeterministicFetch)) {
165 .Build()); 137 devtools_client_->GetPage()->GetExperimental()->SetControlNavigations(
166 } else { 138 headless::page::SetControlNavigationsParams::Builder()
167 PollReadyState(); 139 .SetEnabled(true)
168 } 140 .Build());
169 141 }
170 if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kTimeout)) { 142
171 std::string timeout_ms_ascii = 143 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
172 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( 144 switches::kVirtualTimeBudget)) {
173 switches::kTimeout); 145 std::string budget_ms_ascii =
174 int timeout_ms; 146 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
175 CHECK(base::StringToInt(timeout_ms_ascii, &timeout_ms)) 147 switches::kVirtualTimeBudget);
176 << "Expected an integer value for --timeout="; 148 int budget_ms;
177 browser_->BrowserMainThread()->PostDelayedTask( 149 CHECK(base::StringToInt(budget_ms_ascii, &budget_ms))
178 FROM_HERE, 150 << "Expected an integer value for --virtual-time-budget=";
179 base::Bind(&HeadlessShell::FetchTimeout, weak_factory_.GetWeakPtr()), 151 devtools_client_->GetEmulation()->GetExperimental()->SetVirtualTimePolicy(
180 base::TimeDelta::FromMilliseconds(timeout_ms)); 152 emulation::SetVirtualTimePolicyParams::Builder()
181 } 153 .SetPolicy(
182 154 emulation::VirtualTimePolicy::PAUSE_IF_NETWORK_FETCHES_PENDING)
183 // TODO(skyostil): Implement more features to demonstrate the devtools API. 155 .SetBudget(budget_ms)
184 } 156 .Build());
185 157 } else {
186 void FetchTimeout() { 158 PollReadyState();
187 LOG(INFO) << "Timeout."; 159 }
188 devtools_client_->GetPage()->GetExperimental()->StopLoading( 160
189 page::StopLoadingParams::Builder().Build()); 161 if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kTimeout)) {
190 } 162 std::string timeout_ms_ascii =
191 163 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
192 void OnTargetCrashed(const inspector::TargetCrashedParams& params) override { 164 switches::kTimeout);
193 LOG(ERROR) << "Abnormal renderer termination."; 165 int timeout_ms;
194 // NB this never gets called if remote debugging is enabled. 166 CHECK(base::StringToInt(timeout_ms_ascii, &timeout_ms))
167 << "Expected an integer value for --timeout=";
168 browser_->BrowserMainThread()->PostDelayedTask(
169 FROM_HERE,
170 base::Bind(&HeadlessShell::FetchTimeout, weak_factory_.GetWeakPtr()),
171 base::TimeDelta::FromMilliseconds(timeout_ms));
172 }
173
174 // TODO(skyostil): Implement more features to demonstrate the devtools API.
175 }
176
177 void HeadlessShell::FetchTimeout() {
178 LOG(INFO) << "Timeout.";
179 devtools_client_->GetPage()->GetExperimental()->StopLoading(
180 page::StopLoadingParams::Builder().Build());
181 }
182
183 void HeadlessShell::OnTargetCrashed(
184 const inspector::TargetCrashedParams& params) {
185 LOG(ERROR) << "Abnormal renderer termination.";
186 // NB this never gets called if remote debugging is enabled.
187 Shutdown();
188 }
189
190 void HeadlessShell::PollReadyState() {
191 // We need to check the current location in addition to the ready state to
192 // be sure the expected page is ready.
193 devtools_client_->GetRuntime()->Evaluate(
194 "document.readyState + ' ' + document.location.href",
195 base::Bind(&HeadlessShell::OnReadyState, weak_factory_.GetWeakPtr()));
196 }
197
198 void HeadlessShell::OnReadyState(
199 std::unique_ptr<runtime::EvaluateResult> result) {
200 std::string ready_state_and_url;
201 if (result->GetResult()->GetValue()->GetAsString(&ready_state_and_url)) {
202 std::stringstream stream(ready_state_and_url);
203 std::string ready_state;
204 std::string url;
205 stream >> ready_state;
206 stream >> url;
207
208 if (ready_state == "complete" &&
209 (url_.spec() == url || url != "about:blank")) {
210 OnPageReady();
211 return;
212 }
213 }
214 }
215
216 // emulation::Observer implementation:
217 void HeadlessShell::OnVirtualTimeBudgetExpired(
218 const emulation::VirtualTimeBudgetExpiredParams& params) {
219 OnPageReady();
220 }
221
222 // page::Observer implementation:
223 void HeadlessShell::OnLoadEventFired(const page::LoadEventFiredParams& params) {
224 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
225 switches::kVirtualTimeBudget)) {
226 return;
227 }
228 OnPageReady();
229 }
230
231 void HeadlessShell::OnNavigationRequested(
232 const headless::page::NavigationRequestedParams& params) {
233 deterministic_dispatcher_->NavigationRequested(
234 base::MakeUnique<ShellNavigationRequest>(weak_factory_.GetWeakPtr(),
235 params));
236 }
237
238 void HeadlessShell::OnPageReady() {
239 if (processed_page_ready_)
240 return;
241 processed_page_ready_ = true;
242
243 if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kDumpDom)) {
244 FetchDom();
245 } else if (base::CommandLine::ForCurrentProcess()->HasSwitch(
246 switches::kRepl)) {
247 LOG(INFO)
248 << "Type a Javascript expression to evaluate or \"quit\" to exit.";
249 InputExpression();
250 } else if (base::CommandLine::ForCurrentProcess()->HasSwitch(
251 switches::kScreenshot)) {
252 CaptureScreenshot();
253 } else {
195 Shutdown(); 254 Shutdown();
196 } 255 }
197 256 }
198 void PollReadyState() { 257
199 // We need to check the current location in addition to the ready state to 258 void HeadlessShell::FetchDom() {
200 // be sure the expected page is ready. 259 devtools_client_->GetRuntime()->Evaluate(
201 devtools_client_->GetRuntime()->Evaluate( 260 "document.body.innerHTML",
202 "document.readyState + ' ' + document.location.href", 261 base::Bind(&HeadlessShell::OnDomFetched, weak_factory_.GetWeakPtr()));
203 base::Bind(&HeadlessShell::OnReadyState, weak_factory_.GetWeakPtr())); 262 }
204 } 263
205 264 void HeadlessShell::OnDomFetched(
206 void OnReadyState(std::unique_ptr<runtime::EvaluateResult> result) { 265 std::unique_ptr<runtime::EvaluateResult> result) {
207 std::string ready_state_and_url; 266 if (result->HasExceptionDetails()) {
208 if (result->GetResult()->GetValue()->GetAsString(&ready_state_and_url)) { 267 LOG(ERROR) << "Failed to evaluate document.body.innerHTML: "
209 std::stringstream stream(ready_state_and_url); 268 << result->GetExceptionDetails()->GetText();
210 std::string ready_state; 269 } else {
211 std::string url; 270 std::string dom;
212 stream >> ready_state; 271 if (result->GetResult()->GetValue()->GetAsString(&dom)) {
213 stream >> url; 272 printf("%s\n", dom.c_str());
214 273 }
215 if (ready_state == "complete" && 274 }
216 (url_.spec() == url || url != "about:blank")) { 275 Shutdown();
217 OnPageReady(); 276 }
218 return; 277
219 } 278 void HeadlessShell::InputExpression() {
220 } 279 // Note that a real system should read user input asynchronously, because
221 } 280 // otherwise all other browser activity is suspended (e.g., page loading).
222 281 printf(">>> ");
223 // emulation::Observer implementation: 282 std::stringstream expression;
224 void OnVirtualTimeBudgetExpired( 283 while (true) {
225 const emulation::VirtualTimeBudgetExpiredParams& params) override { 284 int c = fgetc(stdin);
226 OnPageReady(); 285 if (c == EOF || c == '\n') {
227 } 286 break;
228 287 }
229 // page::Observer implementation: 288 expression << static_cast<char>(c);
230 void OnLoadEventFired(const page::LoadEventFiredParams& params) override { 289 }
231 if (base::CommandLine::ForCurrentProcess()->HasSwitch( 290 if (expression.str() == "quit") {
232 switches::kVirtualTimeBudget)) {
233 return;
234 }
235 OnPageReady();
236 }
237
238 void OnPageReady() {
239 if (processed_page_ready_)
240 return;
241 processed_page_ready_ = true;
242
243 if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kDumpDom)) {
244 FetchDom();
245 } else if (base::CommandLine::ForCurrentProcess()->HasSwitch(
246 switches::kRepl)) {
247 LOG(INFO)
248 << "Type a Javascript expression to evaluate or \"quit\" to exit.";
249 InputExpression();
250 } else if (base::CommandLine::ForCurrentProcess()->HasSwitch(
251 switches::kScreenshot)) {
252 CaptureScreenshot();
253 } else {
254 Shutdown();
255 }
256 }
257
258 void FetchDom() {
259 devtools_client_->GetRuntime()->Evaluate(
260 "document.body.innerHTML",
261 base::Bind(&HeadlessShell::OnDomFetched, weak_factory_.GetWeakPtr()));
262 }
263
264 void OnDomFetched(std::unique_ptr<runtime::EvaluateResult> result) {
265 if (result->HasExceptionDetails()) {
266 LOG(ERROR) << "Failed to evaluate document.body.innerHTML: "
267 << result->GetExceptionDetails()->GetText();
268 } else {
269 std::string dom;
270 if (result->GetResult()->GetValue()->GetAsString(&dom)) {
271 printf("%s\n", dom.c_str());
272 }
273 }
274 Shutdown(); 291 Shutdown();
275 } 292 return;
276 293 }
277 void InputExpression() { 294 devtools_client_->GetRuntime()->Evaluate(
278 // Note that a real system should read user input asynchronously, because 295 expression.str(), base::Bind(&HeadlessShell::OnExpressionResult,
279 // otherwise all other browser activity is suspended (e.g., page loading). 296 weak_factory_.GetWeakPtr()));
280 printf(">>> "); 297 }
281 std::stringstream expression; 298
282 while (true) { 299 void HeadlessShell::OnExpressionResult(
283 int c = fgetc(stdin); 300 std::unique_ptr<runtime::EvaluateResult> result) {
284 if (c == EOF || c == '\n') { 301 std::unique_ptr<base::Value> value = result->Serialize();
285 break; 302 std::string result_json;
286 } 303 base::JSONWriter::Write(*value, &result_json);
287 expression << static_cast<char>(c); 304 printf("%s\n", result_json.c_str());
288 } 305 InputExpression();
289 if (expression.str() == "quit") { 306 }
290 Shutdown(); 307
291 return; 308 void HeadlessShell::CaptureScreenshot() {
292 } 309 devtools_client_->GetPage()->GetExperimental()->CaptureScreenshot(
293 devtools_client_->GetRuntime()->Evaluate( 310 page::CaptureScreenshotParams::Builder().Build(),
294 expression.str(), base::Bind(&HeadlessShell::OnExpressionResult, 311 base::Bind(&HeadlessShell::OnScreenshotCaptured,
295 weak_factory_.GetWeakPtr())); 312 weak_factory_.GetWeakPtr()));
296 } 313 }
297 314
298 void OnExpressionResult(std::unique_ptr<runtime::EvaluateResult> result) { 315 void HeadlessShell::OnScreenshotCaptured(
299 std::unique_ptr<base::Value> value = result->Serialize(); 316 std::unique_ptr<page::CaptureScreenshotResult> result) {
300 std::string result_json; 317 base::FilePath file_name =
301 base::JSONWriter::Write(*value, &result_json); 318 base::CommandLine::ForCurrentProcess()->GetSwitchValuePath(
302 printf("%s\n", result_json.c_str()); 319 switches::kScreenshot);
303 InputExpression(); 320 if (file_name.empty()) {
304 } 321 file_name = base::FilePath().AppendASCII(kDefaultScreenshotFileName);
305 322 }
306 void CaptureScreenshot() { 323
307 devtools_client_->GetPage()->GetExperimental()->CaptureScreenshot( 324 screenshot_file_stream_.reset(
308 page::CaptureScreenshotParams::Builder().Build(), 325 new net::FileStream(browser_->BrowserFileThread()));
309 base::Bind(&HeadlessShell::OnScreenshotCaptured, 326 const int open_result = screenshot_file_stream_->Open(
310 weak_factory_.GetWeakPtr())); 327 file_name, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE |
311 } 328 base::File::FLAG_ASYNC,
312 329 base::Bind(&HeadlessShell::OnScreenshotFileOpened,
313 void OnScreenshotCaptured( 330 weak_factory_.GetWeakPtr(), base::Passed(std::move(result)),
314 std::unique_ptr<page::CaptureScreenshotResult> result) { 331 file_name));
315 base::FilePath file_name = 332 if (open_result != net::ERR_IO_PENDING) {
316 base::CommandLine::ForCurrentProcess()->GetSwitchValuePath( 333 // Operation could not be started.
317 switches::kScreenshot); 334 OnScreenshotFileOpened(nullptr, file_name, open_result);
318 if (file_name.empty()) { 335 }
319 file_name = base::FilePath().AppendASCII(kDefaultScreenshotFileName); 336 }
320 } 337
321 338 void HeadlessShell::OnScreenshotFileOpened(
322 screenshot_file_stream_.reset( 339 std::unique_ptr<page::CaptureScreenshotResult> result,
323 new net::FileStream(browser_->BrowserFileThread())); 340 const base::FilePath file_name,
324 const int open_result = screenshot_file_stream_->Open( 341 const int open_result) {
325 file_name, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE | 342 if (open_result != net::OK) {
326 base::File::FLAG_ASYNC, 343 LOG(ERROR) << "Writing screenshot to file " << file_name.value()
327 base::Bind(&HeadlessShell::OnScreenshotFileOpened, 344 << " was unsuccessful, could not open file: "
328 weak_factory_.GetWeakPtr(), base::Passed(std::move(result)), 345 << net::ErrorToString(open_result);
329 file_name)); 346 return;
330 if (open_result != net::ERR_IO_PENDING) { 347 }
331 // Operation could not be started. 348
332 OnScreenshotFileOpened(nullptr, file_name, open_result); 349 std::string decoded_png;
333 } 350 base::Base64Decode(result->GetData(), &decoded_png);
334 } 351 scoped_refptr<net::IOBufferWithSize> buf =
335 352 new net::IOBufferWithSize(decoded_png.size());
336 void OnScreenshotFileOpened( 353 memcpy(buf->data(), decoded_png.data(), decoded_png.size());
337 std::unique_ptr<page::CaptureScreenshotResult> result, 354 const int write_result = screenshot_file_stream_->Write(
338 const base::FilePath file_name, 355 buf.get(), buf->size(),
339 const int open_result) { 356 base::Bind(&HeadlessShell::OnScreenshotFileWritten,
340 if (open_result != net::OK) { 357 weak_factory_.GetWeakPtr(), file_name, buf->size()));
341 LOG(ERROR) << "Writing screenshot to file " << file_name.value() 358 if (write_result != net::ERR_IO_PENDING) {
342 << " was unsuccessful, could not open file: " 359 // Operation may have completed successfully or failed.
343 << net::ErrorToString(open_result); 360 OnScreenshotFileWritten(file_name, buf->size(), write_result);
344 return; 361 }
345 } 362 }
346 363
347 std::string decoded_png; 364 void HeadlessShell::OnScreenshotFileWritten(const base::FilePath file_name,
348 base::Base64Decode(result->GetData(), &decoded_png); 365 const int length,
349 scoped_refptr<net::IOBufferWithSize> buf = 366 const int write_result) {
350 new net::IOBufferWithSize(decoded_png.size()); 367 if (write_result < length) {
351 memcpy(buf->data(), decoded_png.data(), decoded_png.size()); 368 // TODO(eseckler): Support recovering from partial writes.
352 const int write_result = screenshot_file_stream_->Write( 369 LOG(ERROR) << "Writing screenshot to file " << file_name.value()
353 buf.get(), buf->size(), 370 << " was unsuccessful: " << net::ErrorToString(write_result);
354 base::Bind(&HeadlessShell::OnScreenshotFileWritten, 371 } else {
355 weak_factory_.GetWeakPtr(), file_name, buf->size())); 372 LOG(INFO) << "Screenshot written to file " << file_name.value() << "."
356 if (write_result != net::ERR_IO_PENDING) { 373 << std::endl;
357 // Operation may have completed successfully or failed. 374 }
358 OnScreenshotFileWritten(file_name, buf->size(), write_result); 375 int close_result = screenshot_file_stream_->Close(base::Bind(
359 } 376 &HeadlessShell::OnScreenshotFileClosed, weak_factory_.GetWeakPtr()));
360 } 377 if (close_result != net::ERR_IO_PENDING) {
361 378 // Operation could not be started.
362 void OnScreenshotFileWritten(const base::FilePath file_name, 379 OnScreenshotFileClosed(close_result);
363 const int length, 380 }
364 const int write_result) { 381 }
365 if (write_result < length) { 382
366 // TODO(eseckler): Support recovering from partial writes. 383 void HeadlessShell::OnScreenshotFileClosed(const int close_result) {
367 LOG(ERROR) << "Writing screenshot to file " << file_name.value() 384 Shutdown();
368 << " was unsuccessful: " << net::ErrorToString(write_result); 385 }
369 } else { 386
370 LOG(INFO) << "Screenshot written to file " << file_name.value() << "." 387 bool HeadlessShell::RemoteDebuggingEnabled() const {
371 << std::endl; 388 const base::CommandLine& command_line =
372 } 389 *base::CommandLine::ForCurrentProcess();
373 int close_result = screenshot_file_stream_->Close(base::Bind( 390 return command_line.HasSwitch(switches::kRemoteDebuggingPort);
374 &HeadlessShell::OnScreenshotFileClosed, weak_factory_.GetWeakPtr())); 391 }
375 if (close_result != net::ERR_IO_PENDING) {
376 // Operation could not be started.
377 OnScreenshotFileClosed(close_result);
378 }
379 }
380
381 void OnScreenshotFileClosed(const int close_result) { Shutdown(); }
382
383 bool RemoteDebuggingEnabled() const {
384 const base::CommandLine& command_line =
385 *base::CommandLine::ForCurrentProcess();
386 return command_line.HasSwitch(switches::kRemoteDebuggingPort);
387 }
388
389 private:
390 GURL url_;
391 HeadlessBrowser* browser_; // Not owned.
392 std::unique_ptr<HeadlessDevToolsClient> devtools_client_;
393 HeadlessWebContents* web_contents_;
394 bool processed_page_ready_;
395 std::unique_ptr<net::FileStream> screenshot_file_stream_;
396 HeadlessBrowserContext* browser_context_;
397 std::unique_ptr<DeterministicDispatcher> deterministic_dispatcher_;
398 base::WeakPtrFactory<HeadlessShell> weak_factory_;
399
400 DISALLOW_COPY_AND_ASSIGN(HeadlessShell);
401 };
402 392
403 bool ValidateCommandLine(const base::CommandLine& command_line) { 393 bool ValidateCommandLine(const base::CommandLine& command_line) {
404 if (!command_line.HasSwitch(switches::kRemoteDebuggingPort)) { 394 if (!command_line.HasSwitch(switches::kRemoteDebuggingPort)) {
405 if (command_line.GetArgs().size() <= 1) 395 if (command_line.GetArgs().size() <= 1)
406 return true; 396 return true;
407 LOG(ERROR) << "Open multiple tabs is only supported when the " 397 LOG(ERROR) << "Open multiple tabs is only supported when the "
408 << "remote debug port is set."; 398 << "remote debug port is set.";
409 return false; 399 return false;
410 } 400 }
411 if (command_line.HasSwitch(switches::kDumpDom)) { 401 if (command_line.HasSwitch(switches::kDumpDom)) {
(...skipping 104 matching lines...) Expand 10 before | Expand all | Expand 10 after
516 builder.SetOverrideWebPreferencesCallback(base::Bind([]( 506 builder.SetOverrideWebPreferencesCallback(base::Bind([](
517 WebPreferences* preferences) { preferences->hide_scrollbars = true; })); 507 WebPreferences* preferences) { preferences->hide_scrollbars = true; }));
518 } 508 }
519 509
520 return HeadlessBrowserMain( 510 return HeadlessBrowserMain(
521 builder.Build(), 511 builder.Build(),
522 base::Bind(&HeadlessShell::OnStart, base::Unretained(&shell))); 512 base::Bind(&HeadlessShell::OnStart, base::Unretained(&shell)));
523 } 513 }
524 514
525 } // namespace headless 515 } // namespace headless
OLDNEW
« no previous file with comments | « headless/app/headless_shell.h ('k') | headless/app/shell_navigation_request.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698