blob: 16042ecacac159244de7f6d98f6ef0b30e514038 [file] [log] [blame]
// Copyright (c) 2012 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 "content/renderer/gpu_benchmarking_extension.h"
#include <stddef.h>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/base64.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/debug/profiler.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/macros.h"
#include "base/strings/string_number_conversions.h"
#include "build/build_config.h"
#include "cc/layers/layer.h"
#include "cc/paint/skia_paint_canvas.h"
#include "cc/trees/layer_tree_host.h"
#include "content/common/input/actions_parser.h"
#include "content/common/input/synthetic_gesture_params.h"
#include "content/common/input/synthetic_pinch_gesture_params.h"
#include "content/common/input/synthetic_pointer_action_list_params.h"
#include "content/common/input/synthetic_pointer_action_params.h"
#include "content/common/input/synthetic_smooth_drag_gesture_params.h"
#include "content/common/input/synthetic_smooth_scroll_gesture_params.h"
#include "content/common/input/synthetic_tap_gesture_params.h"
#include "content/public/common/content_switches.h"
#include "content/public/renderer/chrome_object_extensions_utils.h"
#include "content/public/renderer/render_thread.h"
#include "content/public/renderer/v8_value_converter.h"
#include "content/renderer/compositor/layer_tree_view.h"
#include "content/renderer/render_thread_impl.h"
#include "content/renderer/render_view_impl.h"
#include "content/renderer/skia_benchmarking_extension.h"
#include "gin/arguments.h"
#include "gin/handle.h"
#include "gin/object_template_builder.h"
#include "gpu/config/gpu_driver_bug_workaround_type.h"
#include "gpu/ipc/common/gpu_messages.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#include "third_party/blink/public/platform/web_layer_tree_view.h"
#include "third_party/blink/public/platform/web_mouse_event.h"
#include "third_party/blink/public/web/blink.h"
#include "third_party/blink/public/web/web_image_cache.h"
#include "third_party/blink/public/web/web_local_frame.h"
#include "third_party/blink/public/web/web_print_params.h"
#include "third_party/blink/public/web/web_settings.h"
#include "third_party/blink/public/web/web_view.h"
#include "third_party/skia/include/core/SkData.h"
#include "third_party/skia/include/core/SkGraphics.h"
#include "third_party/skia/include/core/SkPicture.h"
#include "third_party/skia/include/core/SkPictureRecorder.h"
#include "third_party/skia/include/docs/SkXPSDocument.h"
// Note that headers in third_party/skia/src are fragile. This is
// an experimental, fragile, and diagnostic-only document type.
#include "third_party/skia/src/utils/SkMultiPictureDocument.h"
#include "ui/events/base_event_utils.h"
#include "v8/include/v8.h"
#if defined(OS_WIN) && !defined(NDEBUG)
// XpsObjectModel.h indirectly includes <wincrypt.h> which is
// incompatible with Chromium's OpenSSL. By including wincrypt_shim.h
// first, problems are avoided.
#include "base/win/wincrypt_shim.h"
#include <XpsObjectModel.h>
#include <objbase.h>
#include <wrl/client.h>
#endif
using blink::WebImageCache;
using blink::WebLocalFrame;
using blink::WebPrivatePtr;
using blink::WebSize;
using blink::WebView;
namespace content {
namespace {
class SkPictureSerializer {
public:
explicit SkPictureSerializer(const base::FilePath& dirpath)
: dirpath_(dirpath), layer_id_(0) {
// Let skia register known effect subclasses. This basically enables
// reflection on those subclasses required for picture serialization.
SkiaBenchmarking::Initialize();
}
// Recursively serializes the layer tree.
// Each layer in the tree is serialized into a separate skp file
// in the given directory.
void Serialize(const cc::Layer* root_layer) {
for (auto* layer : *root_layer->layer_tree_host()) {
sk_sp<SkPicture> picture = layer->GetPicture();
if (!picture)
continue;
// Serialize picture to file.
// TODO(alokp): Note that for this to work Chrome needs to be launched
// with
// --no-sandbox command-line flag. Get rid of this limitation.
// CRBUG: 139640.
std::string filename = "layer_" + base::IntToString(layer_id_++) + ".skp";
std::string filepath = dirpath_.AppendASCII(filename).MaybeAsASCII();
DCHECK(!filepath.empty());
SkFILEWStream file(filepath.c_str());
DCHECK(file.isValid());
auto data = picture->serialize();
file.write(data->data(), data->size());
file.fsync();
}
}
private:
base::FilePath dirpath_;
int layer_id_;
};
template <typename T>
bool GetArg(gin::Arguments* args, T* value) {
if (!args->GetNext(value)) {
args->ThrowError();
return false;
}
return true;
}
template <>
bool GetArg(gin::Arguments* args, int* value) {
float number;
bool ret = GetArg(args, &number);
*value = number;
return ret;
}
template <typename T>
bool GetOptionalArg(gin::Arguments* args, T* value) {
if (args->PeekNext().IsEmpty())
return true;
if (args->PeekNext()->IsUndefined()) {
args->Skip();
return true;
}
return GetArg(args, value);
}
class CallbackAndContext : public base::RefCounted<CallbackAndContext> {
public:
CallbackAndContext(v8::Isolate* isolate,
v8::Local<v8::Function> callback,
v8::Local<v8::Context> context)
: isolate_(isolate) {
callback_.Reset(isolate_, callback);
context_.Reset(isolate_, context);
}
v8::Isolate* isolate() { return isolate_; }
v8::Local<v8::Function> GetCallback() {
return v8::Local<v8::Function>::New(isolate_, callback_);
}
v8::Local<v8::Context> GetContext() {
return v8::Local<v8::Context>::New(isolate_, context_);
}
private:
friend class base::RefCounted<CallbackAndContext>;
virtual ~CallbackAndContext() {
callback_.Reset();
context_.Reset();
}
v8::Isolate* isolate_;
v8::Persistent<v8::Function> callback_;
v8::Persistent<v8::Context> context_;
DISALLOW_COPY_AND_ASSIGN(CallbackAndContext);
};
class GpuBenchmarkingContext {
public:
GpuBenchmarkingContext() = default;
bool Init(bool init_compositor) {
web_frame_ = WebLocalFrame::FrameForCurrentContext();
if (!web_frame_)
return false;
web_view_ = web_frame_->View();
if (!web_view_) {
web_frame_ = nullptr;
return false;
}
render_view_impl_ = RenderViewImpl::FromWebView(web_view_);
if (!render_view_impl_) {
web_frame_ = nullptr;
web_view_ = nullptr;
return false;
}
if (!init_compositor)
return true;
layer_tree_view_ = render_view_impl_->GetWidget()->layer_tree_view();
if (!layer_tree_view_) {
web_frame_ = nullptr;
web_view_ = nullptr;
render_view_impl_ = nullptr;
return false;
}
return true;
}
WebLocalFrame* web_frame() const {
DCHECK(web_frame_ != nullptr);
return web_frame_;
}
WebView* web_view() const {
DCHECK(web_view_ != nullptr);
return web_view_;
}
RenderViewImpl* render_view_impl() const {
DCHECK(render_view_impl_ != nullptr);
return render_view_impl_;
}
LayerTreeView* layer_tree_view() const {
DCHECK(layer_tree_view_ != nullptr);
return layer_tree_view_;
}
private:
WebLocalFrame* web_frame_ = nullptr;
WebView* web_view_ = nullptr;
RenderViewImpl* render_view_impl_ = nullptr;
LayerTreeView* layer_tree_view_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(GpuBenchmarkingContext);
};
void OnMicroBenchmarkCompleted(CallbackAndContext* callback_and_context,
std::unique_ptr<base::Value> result) {
v8::Isolate* isolate = callback_and_context->isolate();
v8::HandleScope scope(isolate);
v8::Local<v8::Context> context = callback_and_context->GetContext();
v8::Context::Scope context_scope(context);
WebLocalFrame* frame = WebLocalFrame::FrameForContext(context);
if (frame) {
v8::Local<v8::Value> value =
V8ValueConverter::Create()->ToV8Value(result.get(), context);
v8::Local<v8::Value> argv[] = {value};
frame->CallFunctionEvenIfScriptDisabled(callback_and_context->GetCallback(),
v8::Object::New(isolate), 1, argv);
}
}
void RunCallbackHelper(CallbackAndContext* callback_and_context) {
v8::Isolate* isolate = callback_and_context->isolate();
v8::HandleScope scope(isolate);
v8::Local<v8::Context> context = callback_and_context->GetContext();
v8::Context::Scope context_scope(context);
v8::Local<v8::Function> callback = callback_and_context->GetCallback();
WebLocalFrame* frame = WebLocalFrame::FrameForContext(context);
if (frame && !callback.IsEmpty()) {
frame->CallFunctionEvenIfScriptDisabled(callback, v8::Object::New(isolate),
0, nullptr);
}
}
void OnSyntheticGestureCompleted(CallbackAndContext* callback_and_context) {
RunCallbackHelper(callback_and_context);
}
bool BeginSmoothScroll(GpuBenchmarkingContext* context,
gin::Arguments* args,
mojom::InputInjectorPtr& injector,
float pixels_to_scroll,
v8::Local<v8::Function> callback,
int gesture_source_type,
const std::string& direction,
float speed_in_pixels_s,
bool prevent_fling,
float start_x,
float start_y,
float fling_velocity,
bool precise_scrolling_deltas,
bool scroll_by_page,
bool cursor_visible) {
gfx::Rect rect = context->render_view_impl()->GetWidget()->ViewRect();
rect -= rect.OffsetFromOrigin();
if (!rect.Contains(start_x, start_y)) {
args->ThrowTypeError("Start point not in bounds");
return false;
}
if (gesture_source_type == SyntheticGestureParams::MOUSE_INPUT) {
// Ensure the mouse is visible and move to start position, in case it will
// trigger any hover or mousemove effects.
context->web_view()->SetIsActive(true);
blink::WebMouseEvent mouseMove(blink::WebInputEvent::kMouseMove,
blink::WebInputEvent::kNoModifiers,
ui::EventTimeForNow());
mouseMove.SetPositionInWidget(start_x, start_y);
context->web_view()->MainFrameWidget()->HandleInputEvent(
blink::WebCoalescedInputEvent(mouseMove));
context->web_view()->MainFrameWidget()->SetCursorVisibilityState(
cursor_visible);
}
scoped_refptr<CallbackAndContext> callback_and_context =
new CallbackAndContext(args->isolate(), callback,
context->web_frame()->MainWorldScriptContext());
SyntheticSmoothScrollGestureParams gesture_params;
if (gesture_source_type < 0 ||
gesture_source_type > SyntheticGestureParams::GESTURE_SOURCE_TYPE_MAX) {
return false;
}
gesture_params.gesture_source_type =
static_cast<SyntheticGestureParams::GestureSourceType>(
gesture_source_type);
gesture_params.speed_in_pixels_s = speed_in_pixels_s;
gesture_params.prevent_fling = prevent_fling;
gesture_params.precise_scrolling_deltas = precise_scrolling_deltas;
gesture_params.scroll_by_page = scroll_by_page;
gesture_params.anchor.SetPoint(start_x, start_y);
DCHECK(gesture_source_type != SyntheticGestureParams::TOUCH_INPUT ||
fling_velocity == 0);
float distance_length = pixels_to_scroll;
gfx::Vector2dF distance;
if (direction == "down") {
distance.set_y(-distance_length);
gesture_params.fling_velocity_y = fling_velocity;
} else if (direction == "up") {
distance.set_y(distance_length);
gesture_params.fling_velocity_y = -fling_velocity;
} else if (direction == "right") {
distance.set_x(-distance_length);
gesture_params.fling_velocity_x = fling_velocity;
} else if (direction == "left") {
distance.set_x(distance_length);
gesture_params.fling_velocity_x = -fling_velocity;
} else if (direction == "upleft") {
distance.set_y(distance_length);
distance.set_x(distance_length);
gesture_params.fling_velocity_x = -fling_velocity;
gesture_params.fling_velocity_y = -fling_velocity;
} else if (direction == "upright") {
distance.set_y(distance_length);
distance.set_x(-distance_length);
gesture_params.fling_velocity_x = fling_velocity;
gesture_params.fling_velocity_y = -fling_velocity;
} else if (direction == "downleft") {
distance.set_y(-distance_length);
distance.set_x(distance_length);
gesture_params.fling_velocity_x = -fling_velocity;
gesture_params.fling_velocity_y = fling_velocity;
} else if (direction == "downright") {
distance.set_y(-distance_length);
distance.set_x(-distance_length);
gesture_params.fling_velocity_x = fling_velocity;
gesture_params.fling_velocity_y = fling_velocity;
} else {
return false;
}
gesture_params.distances.push_back(distance);
injector->QueueSyntheticSmoothScroll(
gesture_params, base::BindOnce(&OnSyntheticGestureCompleted,
base::RetainedRef(callback_and_context)));
return true;
}
bool BeginSmoothDrag(GpuBenchmarkingContext* context,
gin::Arguments* args,
mojom::InputInjectorPtr& injector,
float start_x,
float start_y,
float end_x,
float end_y,
v8::Local<v8::Function> callback,
int gesture_source_type,
float speed_in_pixels_s) {
gfx::Rect rect = context->render_view_impl()->GetWidget()->ViewRect();
rect -= rect.OffsetFromOrigin();
if (!rect.Contains(start_x, start_y)) {
args->ThrowTypeError("Start point not in bounds");
return false;
}
scoped_refptr<CallbackAndContext> callback_and_context =
new CallbackAndContext(args->isolate(), callback,
context->web_frame()->MainWorldScriptContext());
SyntheticSmoothDragGestureParams gesture_params;
gesture_params.start_point.SetPoint(start_x, start_y);
gfx::PointF end_point(end_x, end_y);
gfx::Vector2dF distance = end_point - gesture_params.start_point;
gesture_params.distances.push_back(distance);
gesture_params.speed_in_pixels_s = speed_in_pixels_s;
gesture_params.gesture_source_type =
static_cast<SyntheticGestureParams::GestureSourceType>(
gesture_source_type);
injector->QueueSyntheticSmoothDrag(
gesture_params, base::BindOnce(&OnSyntheticGestureCompleted,
base::RetainedRef(callback_and_context)));
return true;
}
static void PrintDocument(blink::WebLocalFrame* frame, SkDocument* doc) {
const float kPageWidth = 612.0f; // 8.5 inch
const float kPageHeight = 792.0f; // 11 inch
const float kMarginTop = 29.0f; // 0.40 inch
const float kMarginLeft = 29.0f; // 0.40 inch
const int kContentWidth = 555; // 7.71 inch
const int kContentHeight = 735; // 10.21 inch
blink::WebPrintParams params(blink::WebSize(kContentWidth, kContentHeight));
params.printer_dpi = 300;
int page_count = frame->PrintBegin(params);
for (int i = 0; i < page_count; ++i) {
SkCanvas* sk_canvas = doc->beginPage(kPageWidth, kPageHeight);
cc::SkiaPaintCanvas canvas(sk_canvas);
cc::PaintCanvasAutoRestore auto_restore(&canvas, true);
canvas.translate(kMarginLeft, kMarginTop);
#if defined(OS_WIN) || defined(OS_MACOSX)
float page_shrink = frame->GetPrintPageShrink(i);
DCHECK_GT(page_shrink, 0);
canvas.scale(page_shrink, page_shrink);
#endif
frame->PrintPage(i, &canvas);
}
frame->PrintEnd();
}
static void PrintDocumentTofile(v8::Isolate* isolate,
const std::string& filename,
sk_sp<SkDocument> (*make_doc)(SkWStream*)) {
GpuBenchmarkingContext context;
if (!context.Init(true))
return;
base::FilePath path = base::FilePath::FromUTF8Unsafe(filename);
if (!base::PathIsWritable(path.DirName())) {
std::string msg("Path is not writable: ");
msg.append(path.DirName().MaybeAsASCII());
isolate->ThrowException(v8::Exception::Error(
v8::String::NewFromUtf8(isolate, msg.c_str(),
v8::NewStringType::kNormal, msg.length())
.ToLocalChecked()));
return;
}
SkFILEWStream wStream(path.MaybeAsASCII().c_str());
sk_sp<SkDocument> doc = make_doc(&wStream);
if (doc) {
context.web_frame()->View()->GetSettings()->SetShouldPrintBackgrounds(true);
PrintDocument(context.web_frame(), doc.get());
doc->close();
}
}
void OnSwapCompletedHelper(CallbackAndContext* callback_and_context,
blink::WebLayerTreeView::SwapResult,
base::TimeTicks) {
RunCallbackHelper(callback_and_context);
}
// This function is only used for correctness testing of this experimental
// feature; no need for it in release builds.
// Also note: You must execute Chrome with `--no-sandbox` and
// `--enable-gpu-benchmarking` for this to work.
#if defined(OS_WIN) && !defined(NDEBUG)
static sk_sp<SkDocument> MakeXPSDocument(SkWStream* s) {
// I am not sure why this hasn't been initialized yet.
(void)CoInitializeEx(nullptr,
COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
// In non-sandboxed mode, we will need to create and hold on to the
// factory before entering the sandbox.
Microsoft::WRL::ComPtr<IXpsOMObjectFactory> factory;
HRESULT hr = ::CoCreateInstance(CLSID_XpsOMObjectFactory, nullptr,
CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&factory));
if (FAILED(hr) || !factory) {
LOG(ERROR) << "CoCreateInstance(CLSID_XpsOMObjectFactory, ...) failed:"
<< logging::SystemErrorCodeToString(hr);
}
return SkXPS::MakeDocument(s, factory.Get());
}
#endif
} // namespace
gin::WrapperInfo GpuBenchmarking::kWrapperInfo = {gin::kEmbedderNativeGin};
// static
void GpuBenchmarking::Install(RenderFrameImpl* frame) {
v8::Isolate* isolate = blink::MainThreadIsolate();
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Context> context =
frame->GetWebFrame()->MainWorldScriptContext();
if (context.IsEmpty())
return;
v8::Context::Scope context_scope(context);
gin::Handle<GpuBenchmarking> controller =
gin::CreateHandle(isolate, new GpuBenchmarking(frame));
if (controller.IsEmpty())
return;
v8::Local<v8::Object> chrome = GetOrCreateChromeObject(isolate, context);
chrome->Set(gin::StringToV8(isolate, "gpuBenchmarking"), controller.ToV8());
}
GpuBenchmarking::GpuBenchmarking(RenderFrameImpl* frame)
: render_frame_(frame) {}
GpuBenchmarking::~GpuBenchmarking() {}
void GpuBenchmarking::EnsureRemoteInterface() {
if (!input_injector_) {
render_frame_->GetRemoteInterfaces()->GetInterface(mojo::MakeRequest(
&input_injector_,
render_frame_->GetTaskRunner(blink::TaskType::kInternalDefault)));
}
}
gin::ObjectTemplateBuilder GpuBenchmarking::GetObjectTemplateBuilder(
v8::Isolate* isolate) {
return gin::Wrappable<GpuBenchmarking>::GetObjectTemplateBuilder(isolate)
.SetMethod("setNeedsDisplayOnAllLayers",
&GpuBenchmarking::SetNeedsDisplayOnAllLayers)
.SetMethod("setRasterizeOnlyVisibleContent",
&GpuBenchmarking::SetRasterizeOnlyVisibleContent)
.SetMethod("printToSkPicture", &GpuBenchmarking::PrintToSkPicture)
.SetMethod("printPagesToSkPictures",
&GpuBenchmarking::PrintPagesToSkPictures)
.SetMethod("printPagesToXPS", &GpuBenchmarking::PrintPagesToXPS)
.SetValue("DEFAULT_INPUT", 0)
.SetValue("TOUCH_INPUT", 1)
.SetValue("MOUSE_INPUT", 2)
.SetMethod("gestureSourceTypeSupported",
&GpuBenchmarking::GestureSourceTypeSupported)
.SetMethod("smoothScrollBy", &GpuBenchmarking::SmoothScrollBy)
.SetMethod("smoothDrag", &GpuBenchmarking::SmoothDrag)
.SetMethod("swipe", &GpuBenchmarking::Swipe)
.SetMethod("scrollBounce", &GpuBenchmarking::ScrollBounce)
.SetMethod("pinchBy", &GpuBenchmarking::PinchBy)
.SetMethod("pageScaleFactor", &GpuBenchmarking::PageScaleFactor)
.SetMethod("setPageScaleFactor", &GpuBenchmarking::SetPageScaleFactor)
.SetMethod("setBrowserControlsShown",
&GpuBenchmarking::SetBrowserControlsShown)
.SetMethod("tap", &GpuBenchmarking::Tap)
.SetMethod("pointerActionSequence",
&GpuBenchmarking::PointerActionSequence)
.SetMethod("visualViewportX", &GpuBenchmarking::VisualViewportX)
.SetMethod("visualViewportY", &GpuBenchmarking::VisualViewportY)
.SetMethod("visualViewportHeight", &GpuBenchmarking::VisualViewportHeight)
.SetMethod("visualViewportWidth", &GpuBenchmarking::VisualViewportWidth)
.SetMethod("clearImageCache", &GpuBenchmarking::ClearImageCache)
.SetMethod("runMicroBenchmark", &GpuBenchmarking::RunMicroBenchmark)
.SetMethod("sendMessageToMicroBenchmark",
&GpuBenchmarking::SendMessageToMicroBenchmark)
.SetMethod("hasGpuChannel", &GpuBenchmarking::HasGpuChannel)
.SetMethod("hasGpuProcess", &GpuBenchmarking::HasGpuProcess)
.SetMethod("crashGpuProcess", &GpuBenchmarking::CrashGpuProcess)
.SetMethod("getGpuDriverBugWorkarounds",
&GpuBenchmarking::GetGpuDriverBugWorkarounds)
.SetMethod("startProfiling", &GpuBenchmarking::StartProfiling)
.SetMethod("stopProfiling", &GpuBenchmarking::StopProfiling)
.SetMethod("freeze", &GpuBenchmarking::Freeze)
.SetMethod("addSwapCompletionEventListener",
&GpuBenchmarking::AddSwapCompletionEventListener);
}
void GpuBenchmarking::SetNeedsDisplayOnAllLayers() {
GpuBenchmarkingContext context;
if (!context.Init(true))
return;
context.layer_tree_view()->SetNeedsDisplayOnAllLayers();
}
void GpuBenchmarking::SetRasterizeOnlyVisibleContent() {
GpuBenchmarkingContext context;
if (!context.Init(true))
return;
context.layer_tree_view()->SetRasterizeOnlyVisibleContent();
}
namespace {
sk_sp<SkDocument> make_multipicturedocument(SkWStream* stream) {
return SkMakeMultiPictureDocument(stream);
}
} // namespace
void GpuBenchmarking::PrintPagesToSkPictures(v8::Isolate* isolate,
const std::string& filename) {
PrintDocumentTofile(isolate, filename, &make_multipicturedocument);
}
void GpuBenchmarking::PrintPagesToXPS(v8::Isolate* isolate,
const std::string& filename) {
#if defined(OS_WIN) && !defined(NDEBUG)
PrintDocumentTofile(isolate, filename, &MakeXPSDocument);
#else
std::string msg("PrintPagesToXPS is unsupported.");
isolate->ThrowException(v8::Exception::Error(
v8::String::NewFromUtf8(isolate, msg.c_str(), v8::NewStringType::kNormal,
msg.length())
.ToLocalChecked()));
#endif
}
void GpuBenchmarking::PrintToSkPicture(v8::Isolate* isolate,
const std::string& dirname) {
GpuBenchmarkingContext context;
if (!context.Init(true))
return;
const cc::Layer* root_layer = context.layer_tree_view()->GetRootLayer();
if (!root_layer)
return;
base::FilePath dirpath = base::FilePath::FromUTF8Unsafe(dirname);
if (!base::CreateDirectory(dirpath) || !base::PathIsWritable(dirpath)) {
std::string msg("Path is not writable: ");
msg.append(dirpath.MaybeAsASCII());
isolate->ThrowException(v8::Exception::Error(
v8::String::NewFromUtf8(isolate, msg.c_str(),
v8::NewStringType::kNormal, msg.length())
.ToLocalChecked()));
return;
}
SkPictureSerializer serializer(dirpath);
serializer.Serialize(root_layer);
}
bool GpuBenchmarking::GestureSourceTypeSupported(int gesture_source_type) {
if (gesture_source_type < 0 ||
gesture_source_type > SyntheticGestureParams::GESTURE_SOURCE_TYPE_MAX) {
return false;
}
return SyntheticGestureParams::IsGestureSourceTypeSupported(
static_cast<SyntheticGestureParams::GestureSourceType>(
gesture_source_type));
}
bool GpuBenchmarking::SmoothScrollBy(gin::Arguments* args) {
GpuBenchmarkingContext context;
if (!context.Init(true))
return false;
blink::WebRect rect = context.render_view_impl()->GetWidget()->ViewRect();
float pixels_to_scroll = 0;
v8::Local<v8::Function> callback;
float start_x = rect.width / 2;
float start_y = rect.height / 2;
int gesture_source_type = SyntheticGestureParams::DEFAULT_INPUT;
std::string direction = "down";
float speed_in_pixels_s = 800;
bool precise_scrolling_deltas = true;
bool scroll_by_page = false;
bool cursor_visible = true;
if (!GetOptionalArg(args, &pixels_to_scroll) ||
!GetOptionalArg(args, &callback) || !GetOptionalArg(args, &start_x) ||
!GetOptionalArg(args, &start_y) ||
!GetOptionalArg(args, &gesture_source_type) ||
!GetOptionalArg(args, &direction) ||
!GetOptionalArg(args, &speed_in_pixels_s) ||
!GetOptionalArg(args, &precise_scrolling_deltas) ||
!GetOptionalArg(args, &scroll_by_page) ||
!GetOptionalArg(args, &cursor_visible)) {
return false;
}
// For all touch inputs, always scroll by precise deltas.
DCHECK(gesture_source_type != SyntheticGestureParams::TOUCH_INPUT ||
precise_scrolling_deltas);
// Scroll by page only for mouse inputs.
DCHECK(!scroll_by_page ||
gesture_source_type == SyntheticGestureParams::MOUSE_INPUT);
EnsureRemoteInterface();
return BeginSmoothScroll(
&context, args, input_injector_, pixels_to_scroll, callback,
gesture_source_type, direction, speed_in_pixels_s, true, start_x, start_y,
0, precise_scrolling_deltas, scroll_by_page, cursor_visible);
}
bool GpuBenchmarking::SmoothDrag(gin::Arguments* args) {
GpuBenchmarkingContext context;
if (!context.Init(true))
return false;
float start_x;
float start_y;
float end_x;
float end_y;
v8::Local<v8::Function> callback;
int gesture_source_type = SyntheticGestureParams::DEFAULT_INPUT;
float speed_in_pixels_s = 800;
if (!GetArg(args, &start_x) || !GetArg(args, &start_y) ||
!GetArg(args, &end_x) || !GetArg(args, &end_y) ||
!GetOptionalArg(args, &callback) ||
!GetOptionalArg(args, &gesture_source_type) ||
!GetOptionalArg(args, &speed_in_pixels_s)) {
return false;
}
EnsureRemoteInterface();
return BeginSmoothDrag(&context, args, input_injector_, start_x, start_y,
end_x, end_y, callback, gesture_source_type,
speed_in_pixels_s);
}
bool GpuBenchmarking::Swipe(gin::Arguments* args) {
GpuBenchmarkingContext context;
if (!context.Init(true))
return false;
blink::WebRect rect = context.render_view_impl()->GetWidget()->ViewRect();
std::string direction = "up";
float pixels_to_scroll = 0;
v8::Local<v8::Function> callback;
float start_x = rect.width / 2;
float start_y = rect.height / 2;
float speed_in_pixels_s = 800;
float fling_velocity = 0;
int gesture_source_type = SyntheticGestureParams::TOUCH_INPUT;
if (!GetOptionalArg(args, &direction) ||
!GetOptionalArg(args, &pixels_to_scroll) ||
!GetOptionalArg(args, &callback) || !GetOptionalArg(args, &start_x) ||
!GetOptionalArg(args, &start_y) ||
!GetOptionalArg(args, &speed_in_pixels_s) ||
!GetOptionalArg(args, &fling_velocity) ||
!GetOptionalArg(args, &gesture_source_type)) {
return false;
}
// For touchpad swipe, we should be given a fling velocity, but it is not
// needed for touchscreen swipe, because we will calculate the velocity in
// our code.
if (gesture_source_type == SyntheticGestureParams::TOUCHPAD_INPUT &&
fling_velocity == 0)
fling_velocity = 1000;
EnsureRemoteInterface();
return BeginSmoothScroll(
&context, args, input_injector_, -pixels_to_scroll, callback,
gesture_source_type, direction, speed_in_pixels_s, false, start_x,
start_y, fling_velocity, true /* precise_scrolling_deltas */,
false /* scroll_by_page */, true /* cursor_visible */);
}
bool GpuBenchmarking::ScrollBounce(gin::Arguments* args) {
GpuBenchmarkingContext context;
if (!context.Init(false))
return false;
blink::WebRect content_rect =
context.render_view_impl()->GetWidget()->ViewRect();
std::string direction = "down";
float distance_length = 0;
float overscroll_length = 0;
int repeat_count = 1;
v8::Local<v8::Function> callback;
float start_x = content_rect.width / 2;
float start_y = content_rect.height / 2;
float speed_in_pixels_s = 800;
if (!GetOptionalArg(args, &direction) ||
!GetOptionalArg(args, &distance_length) ||
!GetOptionalArg(args, &overscroll_length) ||
!GetOptionalArg(args, &repeat_count) ||
!GetOptionalArg(args, &callback) || !GetOptionalArg(args, &start_x) ||
!GetOptionalArg(args, &start_y) ||
!GetOptionalArg(args, &speed_in_pixels_s)) {
return false;
}
gfx::Rect rect = context.render_view_impl()->GetWidget()->ViewRect();
rect -= rect.OffsetFromOrigin();
if (!rect.Contains(start_x, start_y)) {
args->ThrowTypeError("Start point not in bounds");
return false;
}
scoped_refptr<CallbackAndContext> callback_and_context =
new CallbackAndContext(args->isolate(), callback,
context.web_frame()->MainWorldScriptContext());
SyntheticSmoothScrollGestureParams gesture_params;
gesture_params.speed_in_pixels_s = speed_in_pixels_s;
gesture_params.anchor.SetPoint(start_x, start_y);
gfx::Vector2dF distance;
gfx::Vector2dF overscroll;
if (direction == "down") {
distance.set_y(-distance_length);
overscroll.set_y(overscroll_length);
} else if (direction == "up") {
distance.set_y(distance_length);
overscroll.set_y(-overscroll_length);
} else if (direction == "right") {
distance.set_x(-distance_length);
overscroll.set_x(overscroll_length);
} else if (direction == "left") {
distance.set_x(distance_length);
overscroll.set_x(-overscroll_length);
} else {
return false;
}
for (int i = 0; i < repeat_count; i++) {
gesture_params.distances.push_back(distance);
gesture_params.distances.push_back(-distance + overscroll);
}
EnsureRemoteInterface();
input_injector_->QueueSyntheticSmoothScroll(
gesture_params, base::BindOnce(&OnSyntheticGestureCompleted,
base::RetainedRef(callback_and_context)));
return true;
}
bool GpuBenchmarking::PinchBy(gin::Arguments* args) {
GpuBenchmarkingContext context;
if (!context.Init(false))
return false;
float scale_factor;
float anchor_x;
float anchor_y;
v8::Local<v8::Function> callback;
float relative_pointer_speed_in_pixels_s = 800;
int gesture_source_type = SyntheticGestureParams::DEFAULT_INPUT;
if (!GetArg(args, &scale_factor) || !GetArg(args, &anchor_x) ||
!GetArg(args, &anchor_y) || !GetOptionalArg(args, &callback) ||
!GetOptionalArg(args, &relative_pointer_speed_in_pixels_s) ||
!GetOptionalArg(args, &gesture_source_type)) {
return false;
}
gfx::Rect rect = context.render_view_impl()->GetWidget()->ViewRect();
rect -= rect.OffsetFromOrigin();
if (!rect.Contains(anchor_x, anchor_y)) {
args->ThrowTypeError("Anchor point not in bounds");
return false;
}
SyntheticPinchGestureParams gesture_params;
gesture_params.scale_factor = scale_factor;
gesture_params.anchor.SetPoint(anchor_x, anchor_y);
gesture_params.relative_pointer_speed_in_pixels_s =
relative_pointer_speed_in_pixels_s;
if (gesture_source_type < 0 ||
gesture_source_type > SyntheticGestureParams::GESTURE_SOURCE_TYPE_MAX) {
args->ThrowTypeError("Unknown gesture source type");
return false;
}
gesture_params.gesture_source_type =
static_cast<SyntheticGestureParams::GestureSourceType>(
gesture_source_type);
switch (gesture_params.gesture_source_type) {
case SyntheticGestureParams::DEFAULT_INPUT:
case SyntheticGestureParams::TOUCH_INPUT:
case SyntheticGestureParams::MOUSE_INPUT:
break;
case SyntheticGestureParams::PEN_INPUT:
args->ThrowTypeError(
"Gesture is not implemented for the given source type");
return false;
}
scoped_refptr<CallbackAndContext> callback_and_context =
new CallbackAndContext(args->isolate(), callback,
context.web_frame()->MainWorldScriptContext());
EnsureRemoteInterface();
input_injector_->QueueSyntheticPinch(
gesture_params, base::BindOnce(&OnSyntheticGestureCompleted,
base::RetainedRef(callback_and_context)));
return true;
}
float GpuBenchmarking::PageScaleFactor() {
GpuBenchmarkingContext context;
if (!context.Init(false))
return 0.0;
return context.web_view()->PageScaleFactor();
}
void GpuBenchmarking::SetPageScaleFactor(float scale) {
GpuBenchmarkingContext context;
if (!context.Init(false))
return;
context.web_view()->SetPageScaleFactor(scale);
}
void GpuBenchmarking::SetBrowserControlsShown(bool show) {
GpuBenchmarkingContext context;
if (!context.Init(true))
return;
context.layer_tree_view()->UpdateBrowserControlsState(
cc::BrowserControlsState::kBoth,
show ? cc::BrowserControlsState::kShown
: cc::BrowserControlsState::kHidden,
false);
}
float GpuBenchmarking::VisualViewportY() {
GpuBenchmarkingContext context;
if (!context.Init(false))
return 0.0;
float y = context.web_view()->VisualViewportOffset().y;
blink::WebRect rect(0, y, 0, 0);
context.render_view_impl()->WidgetClient()->ConvertViewportToWindow(&rect);
return rect.y;
}
float GpuBenchmarking::VisualViewportX() {
GpuBenchmarkingContext context;
if (!context.Init(false))
return 0.0;
float x = context.web_view()->VisualViewportOffset().x;
blink::WebRect rect(x, 0, 0, 0);
context.render_view_impl()->WidgetClient()->ConvertViewportToWindow(&rect);
return rect.x;
}
float GpuBenchmarking::VisualViewportHeight() {
GpuBenchmarkingContext context;
if (!context.Init(false))
return 0.0;
float height = context.web_view()->VisualViewportSize().height;
blink::WebRect rect(0, 0, 0, height);
context.render_view_impl()->WidgetClient()->ConvertViewportToWindow(&rect);
return rect.height;
}
float GpuBenchmarking::VisualViewportWidth() {
GpuBenchmarkingContext context;
if (!context.Init(false))
return 0.0;
float width = context.web_view()->VisualViewportSize().width;
blink::WebRect rect(0, 0, width, 0);
context.render_view_impl()->WidgetClient()->ConvertViewportToWindow(&rect);
return rect.width;
}
bool GpuBenchmarking::Tap(gin::Arguments* args) {
GpuBenchmarkingContext context;
if (!context.Init(false))
return false;
float position_x;
float position_y;
v8::Local<v8::Function> callback;
int duration_ms = 50;
int gesture_source_type = SyntheticGestureParams::DEFAULT_INPUT;
if (!GetArg(args, &position_x) || !GetArg(args, &position_y) ||
!GetOptionalArg(args, &callback) || !GetOptionalArg(args, &duration_ms) ||
!GetOptionalArg(args, &gesture_source_type)) {
return false;
}
gfx::Rect rect = context.render_view_impl()->GetWidget()->ViewRect();
rect -= rect.OffsetFromOrigin();
if (!rect.Contains(position_x, position_y)) {
args->ThrowTypeError("Start point not in bounds");
return false;
}
SyntheticTapGestureParams gesture_params;
gesture_params.position.SetPoint(position_x, position_y);
gesture_params.duration_ms = duration_ms;
if (gesture_source_type < 0 ||
gesture_source_type > SyntheticGestureParams::GESTURE_SOURCE_TYPE_MAX) {
return false;
}
gesture_params.gesture_source_type =
static_cast<SyntheticGestureParams::GestureSourceType>(
gesture_source_type);
scoped_refptr<CallbackAndContext> callback_and_context =
new CallbackAndContext(args->isolate(), callback,
context.web_frame()->MainWorldScriptContext());
EnsureRemoteInterface();
input_injector_->QueueSyntheticTap(
gesture_params, base::BindOnce(&OnSyntheticGestureCompleted,
base::RetainedRef(callback_and_context)));
return true;
}
bool GpuBenchmarking::PointerActionSequence(gin::Arguments* args) {
GpuBenchmarkingContext context;
if (!context.Init(false))
return false;
v8::Local<v8::Function> callback;
v8::Local<v8::Object> obj;
if (!args->GetNext(&obj)) {
args->ThrowError();
return false;
}
v8::Local<v8::Context> v8_context =
context.web_frame()->MainWorldScriptContext();
std::unique_ptr<base::Value> value =
V8ValueConverter::Create()->FromV8Value(obj, v8_context);
// Get all the pointer actions from the user input and wrap them into a
// SyntheticPointerActionListParams object.
ActionsParser actions_parser(value.get());
if (!actions_parser.ParsePointerActionSequence()) {
// TODO(dtapuska): Throw an error here, some web tests start
// failing when this is done though.
// args->ThrowTypeError(actions_parser.error_message());
return false;
}
if (!GetOptionalArg(args, &callback)) {
args->ThrowError();
return false;
}
// At the end, we will send a 'FINISH' action and need a callback.
scoped_refptr<CallbackAndContext> callback_and_context =
new CallbackAndContext(args->isolate(), callback,
context.web_frame()->MainWorldScriptContext());
EnsureRemoteInterface();
input_injector_->QueueSyntheticPointerAction(
actions_parser.gesture_params(),
base::BindOnce(&OnSyntheticGestureCompleted,
base::RetainedRef(callback_and_context)));
return true;
}
void GpuBenchmarking::ClearImageCache() {
WebImageCache::Clear();
}
int GpuBenchmarking::RunMicroBenchmark(gin::Arguments* args) {
GpuBenchmarkingContext context;
if (!context.Init(true))
return 0;
std::string name;
v8::Local<v8::Function> callback;
v8::Local<v8::Object> arguments;
if (!GetArg(args, &name) || !GetArg(args, &callback) ||
!GetOptionalArg(args, &arguments)) {
return 0;
}
scoped_refptr<CallbackAndContext> callback_and_context =
new CallbackAndContext(args->isolate(), callback,
context.web_frame()->MainWorldScriptContext());
v8::Local<v8::Context> v8_context = callback_and_context->GetContext();
std::unique_ptr<base::Value> value =
V8ValueConverter::Create()->FromV8Value(arguments, v8_context);
return context.layer_tree_view()->ScheduleMicroBenchmark(
name, std::move(value),
base::BindOnce(&OnMicroBenchmarkCompleted,
base::RetainedRef(callback_and_context)));
}
bool GpuBenchmarking::SendMessageToMicroBenchmark(
int id,
v8::Local<v8::Object> message) {
GpuBenchmarkingContext context;
if (!context.Init(true))
return false;
v8::Local<v8::Context> v8_context =
context.web_frame()->MainWorldScriptContext();
std::unique_ptr<base::Value> value =
V8ValueConverter::Create()->FromV8Value(message, v8_context);
return context.layer_tree_view()->SendMessageToMicroBenchmark(
id, std::move(value));
}
bool GpuBenchmarking::HasGpuChannel() {
gpu::GpuChannelHost* gpu_channel =
RenderThreadImpl::current()->GetGpuChannel();
return !!gpu_channel;
}
bool GpuBenchmarking::HasGpuProcess() {
bool has_gpu_process = false;
if (!RenderThreadImpl::current()->render_message_filter()->HasGpuProcess(
&has_gpu_process)) {
return false;
}
return has_gpu_process;
}
void GpuBenchmarking::CrashGpuProcess() {
gpu::GpuChannelHost* gpu_channel =
RenderThreadImpl::current()->GetGpuChannel();
if (!gpu_channel)
return;
gpu_channel->CrashGpuProcessForTesting();
}
void GpuBenchmarking::GetGpuDriverBugWorkarounds(gin::Arguments* args) {
std::vector<std::string> gpu_driver_bug_workarounds;
gpu::GpuChannelHost* gpu_channel =
RenderThreadImpl::current()->GetGpuChannel();
if (!gpu_channel)
return;
const gpu::GpuFeatureInfo& gpu_feature_info = gpu_channel->gpu_feature_info();
const std::vector<int32_t>& workarounds =
gpu_feature_info.enabled_gpu_driver_bug_workarounds;
for (int32_t workaround : workarounds) {
gpu_driver_bug_workarounds.push_back(
gpu::GpuDriverBugWorkaroundTypeToString(
static_cast<gpu::GpuDriverBugWorkaroundType>(workaround)));
}
// This code must be kept in sync with compositor_util's
// GetDriverBugWorkaroundsImpl.
for (auto ext :
base::SplitString(gpu_feature_info.disabled_extensions, " ",
base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) {
gpu_driver_bug_workarounds.push_back("disabled_extension_" + ext);
}
for (auto ext :
base::SplitString(gpu_feature_info.disabled_webgl_extensions, " ",
base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) {
gpu_driver_bug_workarounds.push_back("disabled_webgl_extension_" + ext);
}
v8::Local<v8::Value> result;
if (gin::TryConvertToV8(args->isolate(), gpu_driver_bug_workarounds, &result))
args->Return(result);
}
void GpuBenchmarking::StartProfiling(gin::Arguments* args) {
if (base::debug::BeingProfiled())
return;
std::string file_name;
if (!GetOptionalArg(args, &file_name))
return;
if (!file_name.length())
file_name = "profile.pb";
base::debug::StartProfiling(file_name);
base::debug::RestartProfilingAfterFork();
}
void GpuBenchmarking::StopProfiling() {
if (base::debug::BeingProfiled())
base::debug::StopProfiling();
}
void GpuBenchmarking::Freeze() {
GpuBenchmarkingContext context;
if (!context.Init(true))
return;
// TODO(fmeawad): Instead of forcing a visibility change, only allow
// freezing a page if it was already hidden.
context.web_view()->SetIsHidden(/*hidden=*/true,
/*is_initial_state=*/false);
context.web_view()->SetPageFrozen(true);
}
bool GpuBenchmarking::AddSwapCompletionEventListener(gin::Arguments* args) {
v8::Local<v8::Function> callback;
if (!GetArg(args, &callback))
return false;
if (!render_frame_)
return false;
LayerTreeView* layer_tree_view =
render_frame_->GetLocalRootRenderWidget()->layer_tree_view();
if (!layer_tree_view)
return false;
GpuBenchmarkingContext context;
if (!context.Init(true))
return false;
auto callback_and_context = base::MakeRefCounted<CallbackAndContext>(
args->isolate(), callback, context.web_frame()->MainWorldScriptContext());
layer_tree_view->NotifySwapTime(base::BindOnce(
&OnSwapCompletedHelper, base::RetainedRef(callback_and_context)));
return true;
}
} // namespace content