Chromium Code Reviews| 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 |