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