OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include <string> |
| 6 #include <iostream> |
| 7 #include <fstream> |
| 8 |
| 9 #include "base/bind.h" |
| 10 #include "base/callback.h" |
| 11 #include "base/command_line.h" |
| 12 #include "base/files/file_path.h" |
| 13 #include "base/json/json_writer.h" |
| 14 #include "base/location.h" |
| 15 #include "base/memory/ref_counted.h" |
| 16 #include "base/single_thread_task_runner.h" |
| 17 #include "base/stl_util.h" |
| 18 #include "base/synchronization/waitable_event.h" |
| 19 #include "base/threading/thread.h" |
| 20 #include "base/trace_event/trace_config.h" |
| 21 #include "base/values.h" |
| 22 #include "cc/output/copy_output_request.h" |
| 23 #include "cc/output/copy_output_result.h" |
| 24 #include "content/public/browser/browser_thread.h" |
| 25 #include "content/public/common/content_switches.h" |
| 26 #include "headless/public/headless_browser.h" |
| 27 #include "headless/public/web_contents.h" |
| 28 #include "headless/public/web_document.h" |
| 29 #include "headless/public/web_element.h" |
| 30 #include "headless/public/web_frame.h" |
| 31 #include "headless/public/web_node.h" |
| 32 #include "third_party/WebKit/public/platform/WebRect.h" |
| 33 #include "third_party/skia/include/core/SkBitmap.h" |
| 34 #include "ui/gfx/codec/png_codec.h" |
| 35 |
| 36 using headless::HeadlessBrowser; |
| 37 using headless::WebContents; |
| 38 using headless::WebElement; |
| 39 using headless::WebNode; |
| 40 |
| 41 void DoNothing() {} |
| 42 |
| 43 void DumpElementNode(const WebElement& element, |
| 44 base::DictionaryValue* output) { |
| 45 output->SetString("tag", element.TagName()); |
| 46 |
| 47 if (!element.Attributes().empty()) { |
| 48 base::DictionaryValue* attributes = new base::DictionaryValue(); |
| 49 output->Set("attributes", attributes); |
| 50 |
| 51 for (const auto& attribute : element.Attributes()) { |
| 52 attributes->SetString(attribute.first, attribute.second); |
| 53 } |
| 54 } |
| 55 } |
| 56 |
| 57 scoped_ptr<base::DictionaryValue> DumpNode(const WebNode& node) { |
| 58 scoped_ptr<base::DictionaryValue> result(new base::DictionaryValue()); |
| 59 result->SetString("value", node.Value()); |
| 60 |
| 61 if (node.IsElement()) { |
| 62 DumpElementNode(node.AsElement(), result.get()); |
| 63 } |
| 64 |
| 65 if (node.HasChildNodes()) { |
| 66 result->Set("children", new base::ListValue()); |
| 67 |
| 68 base::ListValue* children; |
| 69 result->GetList("children", &children); |
| 70 |
| 71 for (WebNode child = node.FirstChild();; |
| 72 child = child.NextSibling()) { |
| 73 children->Append(DumpNode(child)); |
| 74 |
| 75 if (child == node.LastChild()) { |
| 76 break; |
| 77 } |
| 78 } |
| 79 } |
| 80 |
| 81 return result; |
| 82 } |
| 83 |
| 84 class Observer : public WebContents::Observer { |
| 85 public: |
| 86 Observer(HeadlessBrowser* browser, |
| 87 WebContents* web_contents) |
| 88 : WebContents::Observer(web_contents) |
| 89 , browser_(browser) |
| 90 , web_contents_(web_contents) |
| 91 , screenshot_captured_(false, false) |
| 92 , javascript_executed_(false, false) |
| 93 , dom_dumped_(false, false) |
| 94 , trace_recorded_(false, false) {} |
| 95 |
| 96 ~Observer() override {} |
| 97 |
| 98 |
| 99 void DumpDOMTree() { |
| 100 scoped_ptr<headless::WebFrame> main_frame = web_contents_->main_frame(); |
| 101 |
| 102 scoped_ptr<base::DictionaryValue> dumped_tree = DumpNode(main_frame->documen
t()); |
| 103 |
| 104 content::BrowserThread::PostTask( |
| 105 content::BrowserThread::FILE, |
| 106 FROM_HERE, |
| 107 base::Bind(&Observer::WriteDOMToFile, |
| 108 base::Unretained(this), |
| 109 std::string("dom.json"), |
| 110 base::Owned(dumped_tree.release()))); |
| 111 } |
| 112 |
| 113 std::string JSONToString(base::Value* value) { |
| 114 std::string string; |
| 115 base::JSONWriter::WriteWithOptions( |
| 116 *value, base::JSONWriter::OPTIONS_PRETTY_PRINT, &string); |
| 117 return string; |
| 118 } |
| 119 |
| 120 void WriteDOMToFile( |
| 121 const std::string& path, |
| 122 base::Value* dumped_tree) { |
| 123 std::string dumped_string = JSONToString(dumped_tree); |
| 124 |
| 125 base::WriteFile(base::FilePath(path), |
| 126 dumped_string.c_str(), |
| 127 dumped_string.size()); |
| 128 |
| 129 dom_dumped_.Signal(); |
| 130 } |
| 131 |
| 132 void GetScreenshot(scoped_ptr<SkBitmap> bitmap) { |
| 133 content::BrowserThread::PostTask( |
| 134 content::BrowserThread::FILE, |
| 135 FROM_HERE, |
| 136 base::Bind(&Observer::WriteScreenshot, |
| 137 base::Unretained(this), |
| 138 base::Owned(bitmap.release()))); |
| 139 } |
| 140 |
| 141 void WriteScreenshot(SkBitmap* bitmap) { |
| 142 std::vector<unsigned char> png_data; |
| 143 gfx::PNGCodec::FastEncodeBGRASkBitmap(*bitmap, true, &png_data); |
| 144 base::WriteFile(base::FilePath("screenshot.png"), |
| 145 reinterpret_cast<const char*>(vector_as_array(&png_data)), |
| 146 png_data.size()); |
| 147 screenshot_captured_.Signal(); |
| 148 } |
| 149 |
| 150 void ProcessJavaScriptOutput(const std::vector<scoped_ptr<base::Value>>& value
s) { |
| 151 std::cerr << "Got " << values.size() << " values from javascript" << std::en
dl; |
| 152 for (auto& value : values) { |
| 153 std::cerr << "JavaScript value: " |
| 154 << (value ? JSONToString(value.get()) : "empty value") << std::e
ndl; |
| 155 } |
| 156 javascript_executed_.Signal(); |
| 157 } |
| 158 |
| 159 void ExecuteJavaScript() { |
| 160 web_contents_->main_frame()->ExecuteScript("console.log('Written from JavaSc
ript')"); |
| 161 web_contents_->main_frame()->ExecuteScriptAndReturnValue( |
| 162 "[1, 2, {'a': 3, 'b': 'c'}]", |
| 163 base::Bind(&Observer::ProcessJavaScriptOutput, base::Unretained(this))); |
| 164 } |
| 165 |
| 166 void MaybeShutdown() { |
| 167 const base::CommandLine& command_line = |
| 168 *base::CommandLine::ForCurrentProcess(); |
| 169 if (!command_line.HasSwitch(switches::kRemoteDebuggingPort)) |
| 170 browser_->Stop(); |
| 171 } |
| 172 |
| 173 void StopShutdownThread() { |
| 174 stop_browser_thread_->Stop(); |
| 175 } |
| 176 |
| 177 void DidFinishTracing() { |
| 178 trace_recorded_.Signal(); |
| 179 } |
| 180 |
| 181 void WaitForTasksToComplete() { |
| 182 screenshot_captured_.Wait(); |
| 183 javascript_executed_.Wait(); |
| 184 dom_dumped_.Wait(); |
| 185 trace_recorded_.Wait(); |
| 186 |
| 187 browser_->browser_main_thread()->PostTask( |
| 188 FROM_HERE, |
| 189 base::Bind(&Observer::MaybeShutdown, base::Unretained(this))); |
| 190 |
| 191 content::BrowserThread::PostTask( |
| 192 content::BrowserThread::PROCESS_LAUNCHER, |
| 193 FROM_HERE, |
| 194 base::Bind(&Observer::StopShutdownThread, base::Unretained(this))); |
| 195 } |
| 196 |
| 197 |
| 198 void DocumentOnLoadCompletedInMainFrame() override { |
| 199 fprintf(stderr, "Loaded\n"); |
| 200 web_contents_->GetScreenshot( |
| 201 base::Bind(&Observer::GetScreenshot, base::Unretained(this))); |
| 202 |
| 203 browser_->renderer_main_thread()->PostTask( |
| 204 FROM_HERE, |
| 205 base::Bind(&Observer::ExecuteJavaScript, base::Unretained(this))); |
| 206 |
| 207 browser_->renderer_main_thread()->PostTask( |
| 208 FROM_HERE, |
| 209 base::Bind(&Observer::DumpDOMTree, base::Unretained(this))); |
| 210 |
| 211 browser_->StopTracing("chrometrace.log", |
| 212 base::Bind(&Observer::DidFinishTracing, |
| 213 base::Unretained(this))); |
| 214 |
| 215 stop_browser_thread_.reset(new base::Thread("WaitForTasksAndStopBrowserThrea
d")); |
| 216 stop_browser_thread_->Start(); |
| 217 |
| 218 stop_browser_thread_->task_runner()->PostTask( |
| 219 FROM_HERE, |
| 220 base::Bind(&Observer::WaitForTasksToComplete, base::Unretained(this))); |
| 221 } |
| 222 |
| 223 private: |
| 224 HeadlessBrowser* browser_; |
| 225 WebContents* web_contents_; |
| 226 |
| 227 scoped_ptr<base::Thread> stop_browser_thread_; |
| 228 |
| 229 base::WaitableEvent screenshot_captured_; |
| 230 base::WaitableEvent javascript_executed_; |
| 231 base::WaitableEvent dom_dumped_; |
| 232 base::WaitableEvent trace_recorded_; |
| 233 }; |
| 234 |
| 235 void Start() { |
| 236 HeadlessBrowser* browser = HeadlessBrowser::Get(); |
| 237 |
| 238 browser->StartTracing( |
| 239 base::trace_event::TraceConfig("", ""), |
| 240 base::Bind(&DoNothing)); |
| 241 |
| 242 static scoped_ptr<WebContents> web_contents = browser->CreateWebContents({800,
600}); |
| 243 static scoped_ptr<Observer> observer( |
| 244 new Observer(browser, web_contents.get())); |
| 245 |
| 246 base::CommandLine::StringVector args = |
| 247 base::CommandLine::ForCurrentProcess()->GetArgs(); |
| 248 |
| 249 const std::string DEFAULT_URL = "https://google.com"; |
| 250 |
| 251 std::string url; |
| 252 if (args.empty() || args[0].empty()) { |
| 253 LOG(ERROR) << "No url provided, opening default one"; |
| 254 url = DEFAULT_URL; |
| 255 } else { |
| 256 url = args[0]; |
| 257 } |
| 258 web_contents->OpenURL(GURL(url)); |
| 259 } |
| 260 |
| 261 int main(int argc, const char** argv) { |
| 262 HeadlessBrowser* browser = HeadlessBrowser::Get(); |
| 263 |
| 264 return browser->Run( |
| 265 HeadlessBrowser::Options::Builder(argc, argv).Build(), |
| 266 base::Bind(Start)); |
| 267 } |
OLD | NEW |