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 { |
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 Loading... |
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 |
OLD | NEW |