| // 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/callback_helpers.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 "base/strings/string_split.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/input_injector.mojom.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/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 "mojo/public/cpp/bindings/remote.h" |
| #include "third_party/blink/public/common/browser_interface_broker_proxy.h" |
| #include "third_party/blink/public/common/input/web_mouse_event.h" |
| #include "third_party/blink/public/mojom/page/page_visibility_state.mojom.h" |
| #include "third_party/blink/public/web/blink.h" |
| #include "third_party/blink/public/web/web_frame_widget.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 |
| |
| namespace blink { |
| |
| // This class allows us to access the LayerTreeHost on WebFrameWidget. It is |
| // hidden from the public interface. It also extracts some commonly |
| // used objects from RenderFrameImpl. |
| class GpuBenchmarkingContext { |
| public: |
| explicit GpuBenchmarkingContext(content::RenderFrameImpl* frame) |
| : web_frame_(frame->GetWebFrame()), |
| web_view_(web_frame_->View()), |
| frame_widget_(frame->GetLocalRootWebFrameWidget()), |
| layer_tree_host_(frame_widget_->LayerTreeHost()) {} |
| |
| WebLocalFrame* web_frame() const { |
| DCHECK(web_frame_ != nullptr); |
| return web_frame_; |
| } |
| WebView* web_view() const { |
| DCHECK(web_view_ != nullptr); |
| return web_view_; |
| } |
| WebFrameWidget* frame_widget() const { return frame_widget_; } |
| cc::LayerTreeHost* layer_tree_host() const { |
| DCHECK(layer_tree_host_ != nullptr); |
| return layer_tree_host_; |
| } |
| |
| private: |
| WebLocalFrame* web_frame_; |
| WebView* web_view_; |
| WebFrameWidget* frame_widget_; |
| cc::LayerTreeHost* layer_tree_host_; |
| |
| DISALLOW_COPY_AND_ASSIGN(GpuBenchmarkingContext); |
| }; |
| |
| } // namespace blink |
| |
| using blink::GpuBenchmarkingContext; |
| using blink::WebImageCache; |
| using blink::WebLocalFrame; |
| using blink::WebPrivatePtr; |
| using blink::WebView; |
| |
| namespace content { |
| |
| namespace { |
| |
| int GestureSourceTypeAsInt(content::mojom::GestureSourceType type) { |
| switch (type) { |
| case content::mojom::GestureSourceType::kDefaultInput: |
| return 0; |
| case content::mojom::GestureSourceType::kTouchInput: |
| return 1; |
| case content::mojom::GestureSourceType::kMouseInput: |
| return 2; |
| case content::mojom::GestureSourceType::kPenInput: |
| return 3; |
| } |
| NOTREACHED(); |
| return 0; |
| } |
| |
| 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::NumberToString(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); |
| }; |
| |
| 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 ThrowIfPointOutOfBounds(GpuBenchmarkingContext* context, |
| gin::Arguments* args, |
| const gfx::Point& point, |
| const std::string& message) { |
| gfx::Rect rect = context->frame_widget()->ViewRect(); |
| rect -= rect.OffsetFromOrigin(); |
| |
| // If the bounds are not available here, as is the case with an OOPIF, |
| // for now, we will forgo the renderer-side bounds check. |
| if (rect.IsEmpty()) |
| return false; |
| |
| if (!rect.Contains(point)) { |
| args->ThrowTypeError(message); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| base::Optional<gfx::Vector2dF> ToVector(const std::string& direction, |
| float distance) { |
| if (direction == "down") { |
| return gfx::Vector2dF(0, distance); |
| } else if (direction == "up") { |
| return gfx::Vector2dF(0, -distance); |
| } else if (direction == "right") { |
| return gfx::Vector2dF(distance, 0); |
| } else if (direction == "left") { |
| return gfx::Vector2dF(-distance, 0); |
| } else if (direction == "upleft") { |
| return gfx::Vector2dF(-distance, -distance); |
| } else if (direction == "upright") { |
| return gfx::Vector2dF(distance, -distance); |
| } else if (direction == "downleft") { |
| return gfx::Vector2dF(-distance, distance); |
| } else if (direction == "downright") { |
| return gfx::Vector2dF(distance, distance); |
| } |
| return base::nullopt; |
| } |
| |
| int ToKeyModifiers(const base::StringPiece& key) { |
| if (key == "Alt") |
| return blink::WebInputEvent::kAltKey; |
| if (key == "Control") |
| return blink::WebInputEvent::kControlKey; |
| if (key == "Meta") |
| return blink::WebInputEvent::kMetaKey; |
| if (key == "Shift") |
| return blink::WebInputEvent::kShiftKey; |
| if (key == "CapsLock") |
| return blink::WebInputEvent::kCapsLockOn; |
| if (key == "NumLock") |
| return blink::WebInputEvent::kNumLockOn; |
| if (key == "AltGraph") |
| return blink::WebInputEvent::kAltGrKey; |
| NOTREACHED() << "invalid key modifier"; |
| return 0; |
| } |
| |
| int ToButtonModifiers(const base::StringPiece& button) { |
| if (button == "Left") |
| return blink::WebMouseEvent::kLeftButtonDown; |
| if (button == "Middle") |
| return blink::WebMouseEvent::kMiddleButtonDown; |
| if (button == "Right") |
| return blink::WebMouseEvent::kRightButtonDown; |
| if (button == "Back") |
| return blink::WebMouseEvent::kBackButtonDown; |
| if (button == "Forward") |
| return blink::WebMouseEvent::kForwardButtonDown; |
| NOTREACHED() << "invalid button modifier"; |
| return 0; |
| } |
| |
| // BeginSmoothScroll takes pixels_to_scroll_x and pixels_to_scroll_y, positive |
| // pixels_to_scroll_y means scroll down, positive pixels_to_scroll_x means |
| // scroll right. |
| bool BeginSmoothScroll(GpuBenchmarkingContext* context, |
| gin::Arguments* args, |
| const mojo::Remote<mojom::InputInjector>& injector, |
| const gfx::Vector2dF& pixels_to_scroll, |
| v8::Local<v8::Function> callback, |
| int gesture_source_type, |
| float speed_in_pixels_s, |
| bool prevent_fling, |
| float start_x, |
| float start_y, |
| const gfx::Vector2dF& fling_velocity, |
| bool precise_scrolling_deltas, |
| bool scroll_by_page, |
| bool cursor_visible, |
| bool scroll_by_percentage, |
| int modifiers) { |
| DCHECK(!(precise_scrolling_deltas && scroll_by_page)); |
| DCHECK(!(precise_scrolling_deltas && scroll_by_percentage)); |
| DCHECK(!(scroll_by_page && scroll_by_percentage)); |
| if (ThrowIfPointOutOfBounds(context, args, gfx::Point(start_x, start_y), |
| "Start point not in bounds")) { |
| return false; |
| } |
| |
| if (gesture_source_type == |
| GestureSourceTypeAsInt(content::mojom::GestureSourceType::kMouseInput)) { |
| // 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::Type::kMouseMove, |
| modifiers, ui::EventTimeForNow()); |
| mouseMove.SetPositionInWidget(start_x, start_y); |
| CHECK(context->web_view()->MainFrameWidget()); |
| context->web_view()->MainFrameWidget()->HandleInputEvent( |
| blink::WebCoalescedInputEvent(mouseMove, ui::LatencyInfo())); |
| 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 > |
| GestureSourceTypeAsInt( |
| content::mojom::GestureSourceType::kGestureSourceTypeMax)) { |
| return false; |
| } |
| gesture_params.gesture_source_type = |
| static_cast<content::mojom::GestureSourceType>(gesture_source_type); |
| |
| gesture_params.speed_in_pixels_s = speed_in_pixels_s; |
| gesture_params.prevent_fling = prevent_fling; |
| |
| if (scroll_by_page) |
| gesture_params.granularity = ui::ScrollGranularity::kScrollByPage; |
| else if (precise_scrolling_deltas) |
| gesture_params.granularity = ui::ScrollGranularity::kScrollByPrecisePixel; |
| else if (scroll_by_percentage) |
| gesture_params.granularity = ui::ScrollGranularity::kScrollByPercentage; |
| else |
| gesture_params.granularity = ui::ScrollGranularity::kScrollByPixel; |
| |
| gesture_params.anchor.SetPoint(start_x, start_y); |
| |
| DCHECK(gesture_source_type != |
| GestureSourceTypeAsInt( |
| content::mojom::GestureSourceType::kTouchInput) || |
| fling_velocity.IsZero()); |
| // Positive pixels_to_scroll_y means scroll down, positive pixels_to_scroll_x |
| // means scroll right, but SyntheticSmoothScrollGestureParams requests |
| // Positive X/Y to scroll left/up, which is opposite. Positive |
| // fling_velocity_x and fling_velocity_y means scroll left and up, which is |
| // the same direction with SyntheticSmoothScrollGestureParams. |
| gesture_params.fling_velocity_x = fling_velocity.x(); |
| gesture_params.fling_velocity_y = fling_velocity.y(); |
| gesture_params.distances.push_back(-pixels_to_scroll); |
| |
| gesture_params.modifiers = modifiers; |
| |
| injector->QueueSyntheticSmoothScroll( |
| gesture_params, base::BindOnce(&OnSyntheticGestureCompleted, |
| base::RetainedRef(callback_and_context))); |
| |
| return true; |
| } |
| |
| bool BeginSmoothDrag(GpuBenchmarkingContext* context, |
| gin::Arguments* args, |
| const mojo::Remote<mojom::InputInjector>& 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) { |
| if (ThrowIfPointOutOfBounds(context, args, gfx::Point(start_x, start_y), |
| "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<content::mojom::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(gfx::Size(kContentWidth, kContentHeight)); |
| params.printer_dpi = 300; |
| uint32_t page_count = frame->PrintBegin(params, blink::WebNode()); |
| for (uint32_t 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_MAC) |
| 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*), |
| RenderFrameImpl* render_frame) { |
| GpuBenchmarkingContext context(render_frame); |
| |
| 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::WebSwapResult, |
| 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(base::WeakPtr<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(context, gin::StringToV8(isolate, "gpuBenchmarking"), |
| controller.ToV8()) |
| .Check(); |
| } |
| |
| GpuBenchmarking::GpuBenchmarking(base::WeakPtr<RenderFrameImpl> frame) |
| : render_frame_(std::move(frame)) {} |
| |
| GpuBenchmarking::~GpuBenchmarking() {} |
| |
| void GpuBenchmarking::EnsureRemoteInterface() { |
| if (!input_injector_) { |
| render_frame_->GetBrowserInterfaceBroker()->GetInterface( |
| input_injector_.BindNewPipeAndPassReceiver( |
| 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", |
| GestureSourceTypeAsInt( |
| content::mojom::GestureSourceType::kDefaultInput)) |
| .SetValue("TOUCH_INPUT", |
| GestureSourceTypeAsInt( |
| content::mojom::GestureSourceType::kTouchInput)) |
| .SetValue("MOUSE_INPUT", |
| GestureSourceTypeAsInt( |
| content::mojom::GestureSourceType::kMouseInput)) |
| .SetValue("TOUCHPAD_INPUT", |
| GestureSourceTypeAsInt( |
| content::mojom::GestureSourceType::kTouchpadInput)) |
| .SetValue("PEN_INPUT", GestureSourceTypeAsInt( |
| content::mojom::GestureSourceType::kPenInput)) |
| .SetMethod("gestureSourceTypeSupported", |
| &GpuBenchmarking::GestureSourceTypeSupported) |
| .SetMethod("smoothScrollBy", &GpuBenchmarking::SmoothScrollBy) |
| .SetMethod("smoothScrollByXY", &GpuBenchmarking::SmoothScrollByXY) |
| .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("terminateGpuProcessNormally", |
| &GpuBenchmarking::TerminateGpuProcessNormally) |
| .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(render_frame_.get()); |
| context.layer_tree_host()->SetNeedsDisplayOnAllLayers(); |
| } |
| |
| void GpuBenchmarking::SetRasterizeOnlyVisibleContent() { |
| GpuBenchmarkingContext context(render_frame_.get()); |
| cc::LayerTreeDebugState current = context.layer_tree_host()->GetDebugState(); |
| current.rasterize_only_visible_content = true; |
| context.layer_tree_host()->SetDebugState(current); |
| } |
| |
| 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, |
| render_frame_.get()); |
| } |
| |
| void GpuBenchmarking::PrintPagesToXPS(v8::Isolate* isolate, |
| const std::string& filename) { |
| #if defined(OS_WIN) && !defined(NDEBUG) |
| PrintDocumentTofile(isolate, filename, &MakeXPSDocument, render_frame_.get()); |
| #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(render_frame_.get()); |
| |
| const cc::Layer* root_layer = context.layer_tree_host()->root_layer(); |
| 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 > |
| GestureSourceTypeAsInt( |
| content::mojom::GestureSourceType::kGestureSourceTypeMax)) { |
| return false; |
| } |
| |
| return SyntheticGestureParams::IsGestureSourceTypeSupported( |
| static_cast<content::mojom::GestureSourceType>(gesture_source_type)); |
| } |
| |
| // TODO(lanwei): this is will be removed after this is replaced by |
| // SmoothScrollByXY in telemetry/internal/actions/scroll.js. |
| bool GpuBenchmarking::SmoothScrollBy(gin::Arguments* args) { |
| GpuBenchmarkingContext context(render_frame_.get()); |
| gfx::Rect rect = context.frame_widget()->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 = |
| GestureSourceTypeAsInt(content::mojom::GestureSourceType::kDefaultInput); |
| std::string direction = "down"; |
| float speed_in_pixels_s = 800; |
| bool precise_scrolling_deltas = true; |
| bool scroll_by_page = false; |
| bool cursor_visible = true; |
| bool scroll_by_percentage = false; |
| std::string keys_value; |
| |
| 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) || |
| !GetOptionalArg(args, &scroll_by_percentage) || |
| !GetOptionalArg(args, &keys_value)) { |
| return false; |
| } |
| |
| // For all touch inputs, always scroll by precise deltas. |
| DCHECK(gesture_source_type != |
| GestureSourceTypeAsInt( |
| content::mojom::GestureSourceType::kTouchInput) || |
| precise_scrolling_deltas); |
| // Scroll by page only for mouse inputs. |
| DCHECK(!scroll_by_page || |
| gesture_source_type == |
| GestureSourceTypeAsInt( |
| content::mojom::GestureSourceType::kMouseInput)); |
| // Scroll by percentage only for mouse inputs. |
| DCHECK(!scroll_by_percentage || |
| gesture_source_type == |
| GestureSourceTypeAsInt( |
| content::mojom::GestureSourceType::kMouseInput)); |
| // Scroll by percentage does not require speed in pixels |
| DCHECK(!scroll_by_percentage || (speed_in_pixels_s == 800)); |
| |
| base::Optional<gfx::Vector2dF> pixels_to_scrol_vector = |
| ToVector(direction, pixels_to_scroll); |
| if (!pixels_to_scrol_vector.has_value()) |
| return false; |
| gfx::Vector2dF fling_velocity(0, 0); |
| int modifiers = 0; |
| std::vector<base::StringPiece> key_list = base::SplitStringPiece( |
| keys_value, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); |
| for (const base::StringPiece& key : key_list) { |
| int key_modifier = ToKeyModifiers(key); |
| if (key_modifier == 0) { |
| return false; |
| } |
| modifiers |= key_modifier; |
| } |
| |
| EnsureRemoteInterface(); |
| return BeginSmoothScroll( |
| &context, args, input_injector_, pixels_to_scrol_vector.value(), callback, |
| gesture_source_type, speed_in_pixels_s, true /* prevent_fling */, start_x, |
| start_y, fling_velocity, precise_scrolling_deltas, scroll_by_page, |
| cursor_visible, scroll_by_percentage, modifiers); |
| } |
| |
| // SmoothScrollByXY does not take direction as one of the arguments, and |
| // instead we pass two scroll delta values for both x and y directions, when |
| // pixels_to_scroll_y is positive, it will scroll down, otherwise scroll up. |
| // When pixels_to_scroll_x is positive, it will scroll right, otherwise |
| // scroll left. |
| bool GpuBenchmarking::SmoothScrollByXY(gin::Arguments* args) { |
| GpuBenchmarkingContext context(render_frame_.get()); |
| gfx::Rect rect = context.frame_widget()->ViewRect(); |
| |
| float pixels_to_scroll_x = 0; |
| float pixels_to_scroll_y = 0; |
| v8::Local<v8::Function> callback; |
| float start_x = rect.width() / 2; |
| float start_y = rect.height() / 2; |
| int gesture_source_type = |
| GestureSourceTypeAsInt(content::mojom::GestureSourceType::kDefaultInput); |
| float speed_in_pixels_s = 800; |
| bool precise_scrolling_deltas = true; |
| bool scroll_by_page = false; |
| bool cursor_visible = true; |
| bool scroll_by_percentage = false; |
| // It should be one or multiple values in the |Modifiers| in the function |
| // ToKeyModifiers, multiple values are expressed as a string |
| // separated by comma. |
| std::string keys_value; |
| // It should be one or multiple values in the |Buttons| in the function |
| // ToButtonModifiers, multiple values are expressed as a string separated by |
| // comma. |
| std::string buttons_value; |
| |
| if (!GetOptionalArg(args, &pixels_to_scroll_x) || |
| !GetOptionalArg(args, &pixels_to_scroll_y) || |
| !GetOptionalArg(args, &callback) || !GetOptionalArg(args, &start_x) || |
| !GetOptionalArg(args, &start_y) || |
| !GetOptionalArg(args, &gesture_source_type) || |
| !GetOptionalArg(args, &speed_in_pixels_s) || |
| !GetOptionalArg(args, &precise_scrolling_deltas) || |
| !GetOptionalArg(args, &scroll_by_page) || |
| !GetOptionalArg(args, &cursor_visible) || |
| !GetOptionalArg(args, &scroll_by_percentage) || |
| !GetOptionalArg(args, &keys_value) || |
| !GetOptionalArg(args, &buttons_value)) { |
| return false; |
| } |
| |
| // For all touch inputs, always scroll by precise deltas. |
| DCHECK(gesture_source_type != |
| GestureSourceTypeAsInt( |
| content::mojom::GestureSourceType::kTouchInput) || |
| precise_scrolling_deltas); |
| // Scroll by page only for mouse inputs. |
| DCHECK(!scroll_by_page || |
| gesture_source_type == |
| GestureSourceTypeAsInt( |
| content::mojom::GestureSourceType::kMouseInput)); |
| // Scroll by percentage only for mouse inputs. |
| DCHECK(!scroll_by_percentage || |
| gesture_source_type == |
| GestureSourceTypeAsInt( |
| content::mojom::GestureSourceType::kMouseInput)); |
| |
| gfx::Vector2dF distances(pixels_to_scroll_x, pixels_to_scroll_y); |
| gfx::Vector2dF fling_velocity(0, 0); |
| int modifiers = 0; |
| std::vector<base::StringPiece> key_list = base::SplitStringPiece( |
| keys_value, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); |
| for (const base::StringPiece& key : key_list) { |
| int key_modifier = ToKeyModifiers(key); |
| if (key_modifier == 0) { |
| return false; |
| } |
| modifiers |= key_modifier; |
| } |
| |
| std::vector<base::StringPiece> button_list = base::SplitStringPiece( |
| buttons_value, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); |
| for (const base::StringPiece& button : button_list) { |
| int button_modifier = ToButtonModifiers(button); |
| if (button_modifier == 0) { |
| return false; |
| } |
| modifiers |= button_modifier; |
| } |
| |
| EnsureRemoteInterface(); |
| return BeginSmoothScroll( |
| &context, args, input_injector_, distances, callback, gesture_source_type, |
| speed_in_pixels_s, true /* prevent_fling */, start_x, start_y, |
| fling_velocity, precise_scrolling_deltas, scroll_by_page, cursor_visible, |
| scroll_by_percentage, modifiers); |
| } |
| |
| bool GpuBenchmarking::SmoothDrag(gin::Arguments* args) { |
| GpuBenchmarkingContext context(render_frame_.get()); |
| float start_x; |
| float start_y; |
| float end_x; |
| float end_y; |
| v8::Local<v8::Function> callback; |
| int gesture_source_type = |
| GestureSourceTypeAsInt(content::mojom::GestureSourceType::kDefaultInput); |
| 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); |
| } |
| |
| // TODO(lanwei): Swipe takes pixels_to_scroll and direction. When the |
| // pixels_to_scroll is positive and direction is up, it means the finger moves |
| // up, but the page scrolls down, which is opposite to SmoothScrollBy. We |
| // should change this to match with SmoothScrollBy or SmoothScrollByXY. |
| bool GpuBenchmarking::Swipe(gin::Arguments* args) { |
| GpuBenchmarkingContext context(render_frame_.get()); |
| gfx::Rect rect = context.frame_widget()->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 = |
| GestureSourceTypeAsInt(content::mojom::GestureSourceType::kTouchInput); |
| |
| 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 == |
| GestureSourceTypeAsInt( |
| content::mojom::GestureSourceType::kTouchpadInput) && |
| fling_velocity == 0) { |
| fling_velocity = 1000; |
| } |
| |
| base::Optional<gfx::Vector2dF> pixels_to_scrol_vector = |
| ToVector(direction, pixels_to_scroll); |
| base::Optional<gfx::Vector2dF> fling_velocity_vector = |
| ToVector(direction, fling_velocity); |
| if (!pixels_to_scrol_vector.has_value() || |
| !fling_velocity_vector.has_value()) { |
| return false; |
| } |
| |
| EnsureRemoteInterface(); |
| return BeginSmoothScroll( |
| &context, args, input_injector_, -pixels_to_scrol_vector.value(), |
| callback, gesture_source_type, speed_in_pixels_s, |
| false /* prevent_fling */, start_x, start_y, |
| fling_velocity_vector.value(), true /* precise_scrolling_deltas */, |
| false /* scroll_by_page */, true /* cursor_visible */, |
| false /* scroll_by_percentage */, 0 /* modifiers */); |
| } |
| |
| bool GpuBenchmarking::ScrollBounce(gin::Arguments* args) { |
| GpuBenchmarkingContext context(render_frame_.get()); |
| gfx::Rect content_rect = context.frame_widget()->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; |
| } |
| |
| if (ThrowIfPointOutOfBounds(&context, args, gfx::Point(start_x, start_y), |
| "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(render_frame_.get()); |
| |
| 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 = |
| GestureSourceTypeAsInt(content::mojom::GestureSourceType::kDefaultInput); |
| |
| 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; |
| } |
| |
| if (ThrowIfPointOutOfBounds(&context, args, gfx::Point(anchor_x, anchor_y), |
| "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 > |
| GestureSourceTypeAsInt( |
| content::mojom::GestureSourceType::kGestureSourceTypeMax)) { |
| args->ThrowTypeError("Unknown gesture source type"); |
| return false; |
| } |
| |
| gesture_params.gesture_source_type = |
| static_cast<content::mojom::GestureSourceType>(gesture_source_type); |
| |
| switch (gesture_params.gesture_source_type) { |
| case content::mojom::GestureSourceType::kDefaultInput: |
| case content::mojom::GestureSourceType::kTouchInput: |
| case content::mojom::GestureSourceType::kMouseInput: |
| break; |
| case content::mojom::GestureSourceType::kPenInput: |
| 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(render_frame_.get()); |
| return context.web_view()->PageScaleFactor(); |
| } |
| |
| void GpuBenchmarking::SetPageScaleFactor(float scale) { |
| GpuBenchmarkingContext context(render_frame_.get()); |
| context.web_view()->SetPageScaleFactor(scale); |
| } |
| |
| void GpuBenchmarking::SetBrowserControlsShown(bool show) { |
| GpuBenchmarkingContext context(render_frame_.get()); |
| context.layer_tree_host()->UpdateBrowserControlsState( |
| cc::BrowserControlsState::kBoth, |
| show ? cc::BrowserControlsState::kShown |
| : cc::BrowserControlsState::kHidden, |
| false); |
| } |
| |
| float GpuBenchmarking::VisualViewportY() { |
| GpuBenchmarkingContext context(render_frame_.get()); |
| float y = context.web_view()->VisualViewportOffset().y(); |
| gfx::RectF rect_in_dips = |
| context.frame_widget()->BlinkSpaceToDIPs(gfx::RectF(0, y, 0, 0)); |
| return rect_in_dips.y(); |
| } |
| |
| float GpuBenchmarking::VisualViewportX() { |
| GpuBenchmarkingContext context(render_frame_.get()); |
| float x = context.web_view()->VisualViewportOffset().x(); |
| gfx::RectF rect_in_dips = |
| context.frame_widget()->BlinkSpaceToDIPs(gfx::RectF(x, 0, 0, 0)); |
| return rect_in_dips.x(); |
| } |
| |
| float GpuBenchmarking::VisualViewportHeight() { |
| GpuBenchmarkingContext context(render_frame_.get()); |
| float height = context.web_view()->VisualViewportSize().height(); |
| gfx::RectF rect_in_dips = |
| context.frame_widget()->BlinkSpaceToDIPs(gfx::RectF(0, 0, 0, height)); |
| return rect_in_dips.height(); |
| } |
| |
| float GpuBenchmarking::VisualViewportWidth() { |
| GpuBenchmarkingContext context(render_frame_.get()); |
| float width = context.web_view()->VisualViewportSize().width(); |
| gfx::RectF rect_in_dips = |
| context.frame_widget()->BlinkSpaceToDIPs(gfx::RectF(0, 0, width, 0)); |
| return rect_in_dips.width(); |
| } |
| |
| bool GpuBenchmarking::Tap(gin::Arguments* args) { |
| GpuBenchmarkingContext context(render_frame_.get()); |
| |
| float position_x; |
| float position_y; |
| v8::Local<v8::Function> callback; |
| int duration_ms = 50; |
| int gesture_source_type = |
| GestureSourceTypeAsInt(content::mojom::GestureSourceType::kDefaultInput); |
| |
| if (!GetArg(args, &position_x) || !GetArg(args, &position_y) || |
| !GetOptionalArg(args, &callback) || !GetOptionalArg(args, &duration_ms) || |
| !GetOptionalArg(args, &gesture_source_type)) { |
| return false; |
| } |
| |
| if (ThrowIfPointOutOfBounds(&context, args, |
| gfx::Point(position_x, position_y), |
| "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 > |
| GestureSourceTypeAsInt( |
| content::mojom::GestureSourceType::kGestureSourceTypeMax)) { |
| return false; |
| } |
| gesture_params.gesture_source_type = |
| static_cast<content::mojom::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(render_frame_.get()); |
| |
| 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); |
| if (!value.get()) { |
| // TODO(dtapuska): Throw an error here, some web tests start |
| // failing when this is done though. |
| // args->ThrowTypeError(actions_parser.error_message()); |
| return false; |
| } |
| |
| // Get all the pointer actions from the user input and wrap them into a |
| // SyntheticPointerActionListParams object. |
| ActionsParser actions_parser( |
| base::Value::FromUniquePtrValue(std::move(value))); |
| if (!actions_parser.Parse()) { |
| // 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(); |
| if (actions_parser.gesture_params().GetGestureType() == |
| SyntheticGestureParams::SMOOTH_SCROLL_GESTURE) { |
| input_injector_->QueueSyntheticSmoothScroll( |
| static_cast<const SyntheticSmoothScrollGestureParams&>( |
| actions_parser.gesture_params()), |
| base::BindOnce(&OnSyntheticGestureCompleted, |
| base::RetainedRef(callback_and_context))); |
| } else { |
| DCHECK(actions_parser.gesture_params().GetGestureType() == |
| SyntheticGestureParams::POINTER_ACTION_LIST); |
| input_injector_->QueueSyntheticPointerAction( |
| static_cast<const SyntheticPointerActionListParams&>( |
| 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(render_frame_.get()); |
| |
| 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_host()->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(render_frame_.get()); |
| |
| 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_host()->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(); |
| } |
| |
| // Terminates the GPU process with an exit code of 0. |
| void GpuBenchmarking::TerminateGpuProcessNormally() { |
| gpu::GpuChannelHost* gpu_channel = |
| RenderThreadImpl::current()->GetGpuChannel(); |
| if (!gpu_channel) |
| return; |
| gpu_channel->TerminateGpuProcessForTesting(); |
| } |
| |
| 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(render_frame_.get()); |
| // TODO(fmeawad): Instead of forcing a visibility change, only allow |
| // freezing a page if it was already hidden. |
| context.web_view()->SetVisibilityState(PageVisibilityState::kHidden, |
| /*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; |
| GpuBenchmarkingContext context(render_frame_.get()); |
| |
| auto callback_and_context = base::MakeRefCounted<CallbackAndContext>( |
| args->isolate(), callback, context.web_frame()->MainWorldScriptContext()); |
| context.web_frame()->FrameWidget()->NotifySwapAndPresentationTime( |
| base::NullCallback(), |
| base::BindOnce(&OnSwapCompletedHelper, |
| base::RetainedRef(callback_and_context))); |
| // Request a begin frame explicitly, as the test-api expects a 'swap' to |
| // happen for the above queued swap promise even if there is no actual update. |
| context.layer_tree_host()->SetNeedsAnimateIfNotInsideMainFrame(); |
| return true; |
| } |
| |
| } // namespace content |