Index: headless/app/headless_shell.cc |
diff --git a/headless/app/headless_shell.cc b/headless/app/headless_shell.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..76566b78f8aef39053422ee5e9919f9fa6c83ee8 |
--- /dev/null |
+++ b/headless/app/headless_shell.cc |
@@ -0,0 +1,267 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include <string> |
+#include <iostream> |
+#include <fstream> |
+ |
+#include "base/bind.h" |
+#include "base/callback.h" |
+#include "base/command_line.h" |
+#include "base/files/file_path.h" |
+#include "base/json/json_writer.h" |
+#include "base/location.h" |
+#include "base/memory/ref_counted.h" |
+#include "base/single_thread_task_runner.h" |
+#include "base/stl_util.h" |
+#include "base/synchronization/waitable_event.h" |
+#include "base/threading/thread.h" |
+#include "base/trace_event/trace_config.h" |
+#include "base/values.h" |
+#include "cc/output/copy_output_request.h" |
+#include "cc/output/copy_output_result.h" |
+#include "content/public/browser/browser_thread.h" |
+#include "content/public/common/content_switches.h" |
+#include "headless/public/headless_browser.h" |
+#include "headless/public/web_contents.h" |
+#include "headless/public/web_document.h" |
+#include "headless/public/web_element.h" |
+#include "headless/public/web_frame.h" |
+#include "headless/public/web_node.h" |
+#include "third_party/WebKit/public/platform/WebRect.h" |
+#include "third_party/skia/include/core/SkBitmap.h" |
+#include "ui/gfx/codec/png_codec.h" |
+ |
+using headless::HeadlessBrowser; |
+using headless::WebContents; |
+using headless::WebElement; |
+using headless::WebNode; |
+ |
+void DoNothing() {} |
+ |
+void DumpElementNode(const WebElement& element, |
+ base::DictionaryValue* output) { |
+ output->SetString("tag", element.TagName()); |
+ |
+ if (!element.Attributes().empty()) { |
+ base::DictionaryValue* attributes = new base::DictionaryValue(); |
+ output->Set("attributes", attributes); |
+ |
+ for (const auto& attribute : element.Attributes()) { |
+ attributes->SetString(attribute.first, attribute.second); |
+ } |
+ } |
+} |
+ |
+scoped_ptr<base::DictionaryValue> DumpNode(const WebNode& node) { |
+ scoped_ptr<base::DictionaryValue> result(new base::DictionaryValue()); |
+ result->SetString("value", node.Value()); |
+ |
+ if (node.IsElement()) { |
+ DumpElementNode(node.AsElement(), result.get()); |
+ } |
+ |
+ if (node.HasChildNodes()) { |
+ result->Set("children", new base::ListValue()); |
+ |
+ base::ListValue* children; |
+ result->GetList("children", &children); |
+ |
+ for (WebNode child = node.FirstChild();; |
+ child = child.NextSibling()) { |
+ children->Append(DumpNode(child)); |
+ |
+ if (child == node.LastChild()) { |
+ break; |
+ } |
+ } |
+ } |
+ |
+ return result; |
+} |
+ |
+class Observer : public WebContents::Observer { |
+ public: |
+ Observer(HeadlessBrowser* browser, |
+ WebContents* web_contents) |
+ : WebContents::Observer(web_contents) |
+ , browser_(browser) |
+ , web_contents_(web_contents) |
+ , screenshot_captured_(false, false) |
+ , javascript_executed_(false, false) |
+ , dom_dumped_(false, false) |
+ , trace_recorded_(false, false) {} |
+ |
+ ~Observer() override {} |
+ |
+ |
+ void DumpDOMTree() { |
+ scoped_ptr<headless::WebFrame> main_frame = web_contents_->main_frame(); |
+ |
+ scoped_ptr<base::DictionaryValue> dumped_tree = DumpNode(main_frame->document()); |
+ |
+ content::BrowserThread::PostTask( |
+ content::BrowserThread::FILE, |
+ FROM_HERE, |
+ base::Bind(&Observer::WriteDOMToFile, |
+ base::Unretained(this), |
+ std::string("dom.json"), |
+ base::Owned(dumped_tree.release()))); |
+ } |
+ |
+ std::string JSONToString(base::Value* value) { |
+ std::string string; |
+ base::JSONWriter::WriteWithOptions( |
+ *value, base::JSONWriter::OPTIONS_PRETTY_PRINT, &string); |
+ return string; |
+ } |
+ |
+ void WriteDOMToFile( |
+ const std::string& path, |
+ base::Value* dumped_tree) { |
+ std::string dumped_string = JSONToString(dumped_tree); |
+ |
+ base::WriteFile(base::FilePath(path), |
+ dumped_string.c_str(), |
+ dumped_string.size()); |
+ |
+ dom_dumped_.Signal(); |
+ } |
+ |
+ void GetScreenshot(scoped_ptr<SkBitmap> bitmap) { |
+ content::BrowserThread::PostTask( |
+ content::BrowserThread::FILE, |
+ FROM_HERE, |
+ base::Bind(&Observer::WriteScreenshot, |
+ base::Unretained(this), |
+ base::Owned(bitmap.release()))); |
+ } |
+ |
+ void WriteScreenshot(SkBitmap* bitmap) { |
+ std::vector<unsigned char> png_data; |
+ gfx::PNGCodec::FastEncodeBGRASkBitmap(*bitmap, true, &png_data); |
+ base::WriteFile(base::FilePath("screenshot.png"), |
+ reinterpret_cast<const char*>(vector_as_array(&png_data)), |
+ png_data.size()); |
+ screenshot_captured_.Signal(); |
+ } |
+ |
+ void ProcessJavaScriptOutput(const std::vector<scoped_ptr<base::Value>>& values) { |
+ std::cerr << "Got " << values.size() << " values from javascript" << std::endl; |
+ for (auto& value : values) { |
+ std::cerr << "JavaScript value: " |
+ << (value ? JSONToString(value.get()) : "empty value") << std::endl; |
+ } |
+ javascript_executed_.Signal(); |
+ } |
+ |
+ void ExecuteJavaScript() { |
+ web_contents_->main_frame()->ExecuteScript("console.log('Written from JavaScript')"); |
+ web_contents_->main_frame()->ExecuteScriptAndReturnValue( |
+ "[1, 2, {'a': 3, 'b': 'c'}]", |
+ base::Bind(&Observer::ProcessJavaScriptOutput, base::Unretained(this))); |
+ } |
+ |
+ void MaybeShutdown() { |
+ const base::CommandLine& command_line = |
+ *base::CommandLine::ForCurrentProcess(); |
+ if (!command_line.HasSwitch(switches::kRemoteDebuggingPort)) |
+ browser_->Stop(); |
+ } |
+ |
+ void StopShutdownThread() { |
+ stop_browser_thread_->Stop(); |
+ } |
+ |
+ void DidFinishTracing() { |
+ trace_recorded_.Signal(); |
+ } |
+ |
+ void WaitForTasksToComplete() { |
+ screenshot_captured_.Wait(); |
+ javascript_executed_.Wait(); |
+ dom_dumped_.Wait(); |
+ trace_recorded_.Wait(); |
+ |
+ browser_->browser_main_thread()->PostTask( |
+ FROM_HERE, |
+ base::Bind(&Observer::MaybeShutdown, base::Unretained(this))); |
+ |
+ content::BrowserThread::PostTask( |
+ content::BrowserThread::PROCESS_LAUNCHER, |
+ FROM_HERE, |
+ base::Bind(&Observer::StopShutdownThread, base::Unretained(this))); |
+ } |
+ |
+ |
+ void DocumentOnLoadCompletedInMainFrame() override { |
+ fprintf(stderr, "Loaded\n"); |
+ web_contents_->GetScreenshot( |
+ base::Bind(&Observer::GetScreenshot, base::Unretained(this))); |
+ |
+ browser_->renderer_main_thread()->PostTask( |
+ FROM_HERE, |
+ base::Bind(&Observer::ExecuteJavaScript, base::Unretained(this))); |
+ |
+ browser_->renderer_main_thread()->PostTask( |
+ FROM_HERE, |
+ base::Bind(&Observer::DumpDOMTree, base::Unretained(this))); |
+ |
+ browser_->StopTracing("chrometrace.log", |
+ base::Bind(&Observer::DidFinishTracing, |
+ base::Unretained(this))); |
+ |
+ stop_browser_thread_.reset(new base::Thread("WaitForTasksAndStopBrowserThread")); |
+ stop_browser_thread_->Start(); |
+ |
+ stop_browser_thread_->task_runner()->PostTask( |
+ FROM_HERE, |
+ base::Bind(&Observer::WaitForTasksToComplete, base::Unretained(this))); |
+ } |
+ |
+ private: |
+ HeadlessBrowser* browser_; |
+ WebContents* web_contents_; |
+ |
+ scoped_ptr<base::Thread> stop_browser_thread_; |
+ |
+ base::WaitableEvent screenshot_captured_; |
+ base::WaitableEvent javascript_executed_; |
+ base::WaitableEvent dom_dumped_; |
+ base::WaitableEvent trace_recorded_; |
+}; |
+ |
+void Start() { |
+ HeadlessBrowser* browser = HeadlessBrowser::Get(); |
+ |
+ browser->StartTracing( |
+ base::trace_event::TraceConfig("", ""), |
+ base::Bind(&DoNothing)); |
+ |
+ static scoped_ptr<WebContents> web_contents = browser->CreateWebContents({800, 600}); |
+ static scoped_ptr<Observer> observer( |
+ new Observer(browser, web_contents.get())); |
+ |
+ base::CommandLine::StringVector args = |
+ base::CommandLine::ForCurrentProcess()->GetArgs(); |
+ |
+ const std::string DEFAULT_URL = "https://google.com"; |
+ |
+ std::string url; |
+ if (args.empty() || args[0].empty()) { |
+ LOG(ERROR) << "No url provided, opening default one"; |
+ url = DEFAULT_URL; |
+ } else { |
+ url = args[0]; |
+ } |
+ web_contents->OpenURL(GURL(url)); |
+} |
+ |
+int main(int argc, const char** argv) { |
+ HeadlessBrowser* browser = HeadlessBrowser::Get(); |
+ |
+ return browser->Run( |
+ HeadlessBrowser::Options::Builder(argc, argv).Build(), |
+ base::Bind(Start)); |
+} |