| 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));
|
| +}
|
|
|