1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "content/renderer/gpu/gpu_benchmarking_extension.h"
6
7#include <string>
8
9#include "base/base64.h"
10#include "base/files/file_path.h"
11#include "base/files/file_util.h"
12#include "base/memory/scoped_vector.h"
13#include "base/strings/string_number_conversions.h"
14#include "cc/layers/layer.h"
15#include "content/common/input/synthetic_gesture_params.h"
16#include "content/common/input/synthetic_pinch_gesture_params.h"
17#include "content/common/input/synthetic_smooth_scroll_gesture_params.h"
18#include "content/common/input/synthetic_tap_gesture_params.h"
19#include "content/public/renderer/render_thread.h"
20#include "content/public/renderer/v8_value_converter.h"
21#include "content/renderer/gpu/render_widget_compositor.h"
22#include "content/renderer/render_thread_impl.h"
23#include "content/renderer/render_view_impl.h"
24#include "content/renderer/skia_benchmarking_extension.h"
25#include "third_party/WebKit/public/web/WebImageCache.h"
26#include "third_party/WebKit/public/web/WebLocalFrame.h"
27#include "third_party/WebKit/public/web/WebView.h"
28#include "third_party/skia/include/core/SkData.h"
29#include "third_party/skia/include/core/SkGraphics.h"
30#include "third_party/skia/include/core/SkPicture.h"
31#include "third_party/skia/include/core/SkPixelRef.h"
32#include "third_party/skia/include/core/SkStream.h"
33#include "ui/gfx/codec/png_codec.h"
34#include "v8/include/v8.h"
35
36using blink::WebCanvas;
37using blink::WebLocalFrame;
38using blink::WebImageCache;
39using blink::WebPrivatePtr;
40using blink::WebSize;
41using blink::WebView;
42
43const char kGpuBenchmarkingExtensionName[] = "v8/GpuBenchmarking";
44
45// offset parameter is deprecated/ignored, and will be remove from the
46// signature in a future skia release. <reed@google.com>
47static SkData* EncodeBitmapToData(size_t* offset, const SkBitmap& bm) {
48    SkPixelRef* pr = bm.pixelRef();
49    if (pr != NULL) {
50        SkData* data = pr->refEncodedData();
51        if (data != NULL)
52            return data;
53    }
54    std::vector<unsigned char> vector;
55    if (gfx::PNGCodec::EncodeBGRASkBitmap(bm, false, &vector)) {
56        return SkData::NewWithCopy(&vector.front() , vector.size());
57    }
58    return NULL;
59}
60
61namespace {
62
63class SkPictureSerializer {
64 public:
65  explicit SkPictureSerializer(const base::FilePath& dirpath)
66      : dirpath_(dirpath),
67        layer_id_(0) {
68    // Let skia register known effect subclasses. This basically enables
69    // reflection on those subclasses required for picture serialization.
70    content::SkiaBenchmarking::Initialize();
71  }
72
73  // Recursively serializes the layer tree.
74  // Each layer in the tree is serialized into a separate skp file
75  // in the given directory.
76  void Serialize(const cc::Layer* layer) {
77    const cc::LayerList& children = layer->children();
78    for (size_t i = 0; i < children.size(); ++i) {
79      Serialize(children[i].get());
80    }
81
82    skia::RefPtr<SkPicture> picture = layer->GetPicture();
83    if (!picture)
84      return;
85
86    // Serialize picture to file.
87    // TODO(alokp): Note that for this to work Chrome needs to be launched with
88    // --no-sandbox command-line flag. Get rid of this limitation.
89    // CRBUG: 139640.
90    std::string filename = "layer_" + base::IntToString(layer_id_++) + ".skp";
91    std::string filepath = dirpath_.AppendASCII(filename).MaybeAsASCII();
92    DCHECK(!filepath.empty());
93    SkFILEWStream file(filepath.c_str());
94    DCHECK(file.isValid());
95    picture->serialize(&file, &EncodeBitmapToData);
96  }
97
98 private:
99  base::FilePath dirpath_;
100  int layer_id_;
101};
102
103}  // namespace
104
105namespace content {
106
107namespace {
108
109class CallbackAndContext : public base::RefCounted<CallbackAndContext> {
110 public:
111  CallbackAndContext(v8::Isolate* isolate,
112                     v8::Handle<v8::Function> callback,
113                     v8::Handle<v8::Context> context)
114      : isolate_(isolate) {
115    callback_.Reset(isolate_, callback);
116    context_.Reset(isolate_, context);
117  }
118
119  v8::Isolate* isolate() {
120    return isolate_;
121  }
122
123  v8::Handle<v8::Function> GetCallback() {
124    return v8::Local<v8::Function>::New(isolate_, callback_);
125  }
126
127  v8::Handle<v8::Context> GetContext() {
128    return v8::Local<v8::Context>::New(isolate_, context_);
129  }
130
131 private:
132  friend class base::RefCounted<CallbackAndContext>;
133
134  virtual ~CallbackAndContext() {
135    callback_.Reset();
136    context_.Reset();
137  }
138
139  v8::Isolate* isolate_;
140  v8::Persistent<v8::Function> callback_;
141  v8::Persistent<v8::Context> context_;
142  DISALLOW_COPY_AND_ASSIGN(CallbackAndContext);
143};
144
145class GpuBenchmarkingContext {
146 public:
147  GpuBenchmarkingContext()
148      : web_frame_(NULL),
149        web_view_(NULL),
150        render_view_impl_(NULL),
151        compositor_(NULL) {}
152
153  bool Init(bool init_compositor) {
154    web_frame_ = WebLocalFrame::frameForCurrentContext();
155    if (!web_frame_)
156      return false;
157
158    web_view_ = web_frame_->view();
159    if (!web_view_) {
160      web_frame_ = NULL;
161      return false;
162    }
163
164    render_view_impl_ = RenderViewImpl::FromWebView(web_view_);
165    if (!render_view_impl_) {
166      web_frame_ = NULL;
167      web_view_ = NULL;
168      return false;
169    }
170
171    if (!init_compositor)
172      return true;
173
174    compositor_ = render_view_impl_->compositor();
175    if (!compositor_) {
176      web_frame_ = NULL;
177      web_view_ = NULL;
178      render_view_impl_ = NULL;
179      return false;
180    }
181
182    return true;
183  }
184
185  WebLocalFrame* web_frame() const {
186    DCHECK(web_frame_ != NULL);
187    return web_frame_;
188  }
189  WebView* web_view() const {
190    DCHECK(web_view_ != NULL);
191    return web_view_;
192  }
193  RenderViewImpl* render_view_impl() const {
194    DCHECK(render_view_impl_ != NULL);
195    return render_view_impl_;
196  }
197  RenderWidgetCompositor* compositor() const {
198    DCHECK(compositor_ != NULL);
199    return compositor_;
200  }
201
202 private:
203  WebLocalFrame* web_frame_;
204  WebView* web_view_;
205  RenderViewImpl* render_view_impl_;
206  RenderWidgetCompositor* compositor_;
207
208  DISALLOW_COPY_AND_ASSIGN(GpuBenchmarkingContext);
209};
210
211}  // namespace
212
213class GpuBenchmarkingWrapper : public v8::Extension {
214 public:
215  GpuBenchmarkingWrapper() :
216      v8::Extension(kGpuBenchmarkingExtensionName,
217            "if (typeof(chrome) == 'undefined') {"
218            "  chrome = {};"
219            "};"
220            "if (typeof(chrome.gpuBenchmarking) == 'undefined') {"
221            "  chrome.gpuBenchmarking = {};"
222            "};"
223            "chrome.gpuBenchmarking.setNeedsDisplayOnAllLayers = function() {"
224            "  native function SetNeedsDisplayOnAllLayers();"
225            "  return SetNeedsDisplayOnAllLayers();"
226            "};"
227            "chrome.gpuBenchmarking.setRasterizeOnlyVisibleContent = "
228            "function() {"
229            "  native function SetRasterizeOnlyVisibleContent();"
230            "  return SetRasterizeOnlyVisibleContent();"
231            "};"
232            "chrome.gpuBenchmarking.printToSkPicture = function(dirname) {"
233            "  native function PrintToSkPicture();"
234            "  return PrintToSkPicture(dirname);"
235            "};"
236            "chrome.gpuBenchmarking.DEFAULT_INPUT = 0;"
237            "chrome.gpuBenchmarking.TOUCH_INPUT = 1;"
238            "chrome.gpuBenchmarking.MOUSE_INPUT = 2;"
239            "chrome.gpuBenchmarking.gestureSourceTypeSupported = "
240            "    function(gesture_source_type) {"
241            "  native function GestureSourceTypeSupported();"
242            "  return GestureSourceTypeSupported(gesture_source_type);"
243            "};"
244            "chrome.gpuBenchmarking.smoothScrollBy = "
245            "    function(pixels_to_scroll, opt_callback, opt_start_x,"
246            "             opt_start_y, opt_gesture_source_type,"
247            "             opt_direction, opt_speed_in_pixels_s) {"
248            "  pixels_to_scroll = pixels_to_scroll || 0;"
249            "  callback = opt_callback || function() { };"
250            "  gesture_source_type = opt_gesture_source_type ||"
251            "      chrome.gpuBenchmarking.DEFAULT_INPUT;"
252            "  direction = opt_direction || 'down';"
253            "  speed_in_pixels_s = opt_speed_in_pixels_s || 800;"
254            "  native function BeginSmoothScroll();"
255            "  return BeginSmoothScroll(pixels_to_scroll, callback,"
256            "                           gesture_source_type, direction,"
257            "                           speed_in_pixels_s, true,"
258            "                           opt_start_x, opt_start_y);"
259            "};"
260            "chrome.gpuBenchmarking.swipe = "
261            "    function(direction, distance, opt_callback,"
262            "             opt_start_x, opt_start_y,"
263            "             opt_speed_in_pixels_s) {"
264            "  direction = direction || 'up';"
265            "  distance = distance || 0;"
266            "  callback = opt_callback || function() { };"
267            "  speed_in_pixels_s = opt_speed_in_pixels_s || 800;"
268            "  native function BeginSmoothScroll();"
269            "  return BeginSmoothScroll(-distance, callback,"
270            "                           chrome.gpuBenchmarking.TOUCH_INPUT,"
271            "                           direction, speed_in_pixels_s, false,"
272            "                           opt_start_x, opt_start_y);"
273            "};"
274            "chrome.gpuBenchmarking.scrollBounce = "
275            "    function(direction, distance, overscroll, opt_repeat_count,"
276            "             opt_callback, opt_start_x, opt_start_y,"
277            "             opt_speed_in_pixels_s) {"
278            "  direction = direction || 'down';"
279            "  distance = distance || 0;"
280            "  overscroll = overscroll || 0;"
281            "  repeat_count = opt_repeat_count || 1;"
282            "  callback = opt_callback || function() { };"
283            "  speed_in_pixels_s = opt_speed_in_pixels_s || 800;"
284            "  native function BeginScrollBounce();"
285            "  return BeginScrollBounce(direction, distance, overscroll,"
286            "                           repeat_count, callback,"
287            "                           speed_in_pixels_s,"
288            "                           opt_start_x, opt_start_y);"
289            "};"
290            // TODO(dominikg): Remove once JS interface changes have rolled into
291            //                 stable.
292            "chrome.gpuBenchmarking.newPinchInterface = true;"
293            "chrome.gpuBenchmarking.pinchBy = "
294            "    function(scale_factor, anchor_x, anchor_y,"
295            "             opt_callback, "
296            "opt_relative_pointer_speed_in_pixels_s) {"
297            "  callback = opt_callback || function() { };"
298            "  relative_pointer_speed_in_pixels_s ="
299            "      opt_relative_pointer_speed_in_pixels_s || 800;"
300            "  native function BeginPinch();"
301            "  return BeginPinch(scale_factor, anchor_x, anchor_y, callback,"
302            "                    relative_pointer_speed_in_pixels_s);"
303            "};"
304            "chrome.gpuBenchmarking.tap = "
305            "    function(position_x, position_y, opt_callback, "
306            "opt_duration_ms,"
307            "             opt_gesture_source_type) {"
308            "  callback = opt_callback || function() { };"
309            "  duration_ms = opt_duration_ms || 50;"
310            "  gesture_source_type = opt_gesture_source_type ||"
311            "      chrome.gpuBenchmarking.DEFAULT_INPUT;"
312            "  native function BeginTap();"
313            "  return BeginTap(position_x, position_y, callback, duration_ms,"
314            "                  gesture_source_type);"
315            "};"
316            "chrome.gpuBenchmarking.beginWindowSnapshotPNG = "
317            "function(callback) {"
318            "  native function BeginWindowSnapshotPNG();"
319            "  BeginWindowSnapshotPNG(callback);"
320            "};"
321            "chrome.gpuBenchmarking.clearImageCache = function() {"
322            "  native function ClearImageCache();"
323            "  ClearImageCache();"
324            "};"
325            "chrome.gpuBenchmarking.runMicroBenchmark ="
326            "    function(name, callback, opt_arguments) {"
327            "  arguments = opt_arguments || {};"
328            "  native function RunMicroBenchmark();"
329            "  return RunMicroBenchmark(name, callback, arguments);"
330            "};"
331            "chrome.gpuBenchmarking.sendMessageToMicroBenchmark ="
332            "    function(id, arguments) {"
333            "  native function SendMessageToMicroBenchmark();"
334            "  return SendMessageToMicroBenchmark(id, arguments);"
335            "};"
336            "chrome.gpuBenchmarking.hasGpuProcess = function() {"
337            "  native function HasGpuProcess();"
338            "  return HasGpuProcess();"
339            "};") {}
340
341  virtual v8::Handle<v8::FunctionTemplate> GetNativeFunctionTemplate(
342      v8::Isolate* isolate,
343      v8::Handle<v8::String> name) OVERRIDE {
344    if (name->Equals(
345            v8::String::NewFromUtf8(isolate, "SetNeedsDisplayOnAllLayers")))
346      return v8::FunctionTemplate::New(isolate, SetNeedsDisplayOnAllLayers);
347    if (name->Equals(
348            v8::String::NewFromUtf8(isolate, "SetRasterizeOnlyVisibleContent")))
349      return v8::FunctionTemplate::New(isolate, SetRasterizeOnlyVisibleContent);
350    if (name->Equals(v8::String::NewFromUtf8(isolate, "PrintToSkPicture")))
351      return v8::FunctionTemplate::New(isolate, PrintToSkPicture);
352    if (name->Equals(
353            v8::String::NewFromUtf8(isolate, "GestureSourceTypeSupported")))
354      return v8::FunctionTemplate::New(isolate, GestureSourceTypeSupported);
355    if (name->Equals(v8::String::NewFromUtf8(isolate, "BeginSmoothScroll")))
356      return v8::FunctionTemplate::New(isolate, BeginSmoothScroll);
357    if (name->Equals(v8::String::NewFromUtf8(isolate, "BeginScrollBounce")))
358      return v8::FunctionTemplate::New(isolate, BeginScrollBounce);
359    if (name->Equals(v8::String::NewFromUtf8(isolate, "BeginPinch")))
360      return v8::FunctionTemplate::New(isolate, BeginPinch);
361    if (name->Equals(v8::String::NewFromUtf8(isolate, "BeginTap")))
362      return v8::FunctionTemplate::New(isolate, BeginTap);
363    if (name->Equals(
364            v8::String::NewFromUtf8(isolate, "BeginWindowSnapshotPNG")))
365      return v8::FunctionTemplate::New(isolate, BeginWindowSnapshotPNG);
366    if (name->Equals(v8::String::NewFromUtf8(isolate, "ClearImageCache")))
367      return v8::FunctionTemplate::New(isolate, ClearImageCache);
368    if (name->Equals(v8::String::NewFromUtf8(isolate, "RunMicroBenchmark")))
369      return v8::FunctionTemplate::New(isolate, RunMicroBenchmark);
370    if (name->Equals(
371            v8::String::NewFromUtf8(isolate, "SendMessageToMicroBenchmark")))
372      return v8::FunctionTemplate::New(isolate, SendMessageToMicroBenchmark);
373    if (name->Equals(v8::String::NewFromUtf8(isolate, "HasGpuProcess")))
374      return v8::FunctionTemplate::New(isolate, HasGpuProcess);
375
376    return v8::Handle<v8::FunctionTemplate>();
377  }
378
379  static void SetNeedsDisplayOnAllLayers(
380      const v8::FunctionCallbackInfo<v8::Value>& args) {
381    GpuBenchmarkingContext context;
382    if (!context.Init(true))
383      return;
384
385    context.compositor()->SetNeedsDisplayOnAllLayers();
386  }
387
388  static void SetRasterizeOnlyVisibleContent(
389      const v8::FunctionCallbackInfo<v8::Value>& args) {
390    GpuBenchmarkingContext context;
391    if (!context.Init(true))
392      return;
393
394    context.compositor()->SetRasterizeOnlyVisibleContent();
395  }
396
397  static void PrintToSkPicture(
398      const v8::FunctionCallbackInfo<v8::Value>& args) {
399    if (args.Length() != 1)
400      return;
401
402    v8::String::Utf8Value dirname(args[0]);
403    if (dirname.length() == 0)
404      return;
405
406    GpuBenchmarkingContext context;
407    if (!context.Init(true))
408      return;
409
410    const cc::Layer* root_layer = context.compositor()->GetRootLayer();
411    if (!root_layer)
412      return;
413
414    base::FilePath dirpath(
415        base::FilePath::StringType(*dirname, *dirname + dirname.length()));
416    if (!base::CreateDirectory(dirpath) ||
417        !base::PathIsWritable(dirpath)) {
418      std::string msg("Path is not writable: ");
419      msg.append(dirpath.MaybeAsASCII());
420      v8::Isolate* isolate = args.GetIsolate();
421      isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(
422          isolate, msg.c_str(), v8::String::kNormalString, msg.length())));
423      return;
424    }
425
426    SkPictureSerializer serializer(dirpath);
427    serializer.Serialize(root_layer);
428  }
429
430  static void OnSyntheticGestureCompleted(
431      CallbackAndContext* callback_and_context) {
432    v8::Isolate* isolate = callback_and_context->isolate();
433    v8::HandleScope scope(isolate);
434    v8::Handle<v8::Context> context = callback_and_context->GetContext();
435    v8::Context::Scope context_scope(context);
436    WebLocalFrame* frame = WebLocalFrame::frameForContext(context);
437    if (frame) {
438      frame->callFunctionEvenIfScriptDisabled(
439          callback_and_context->GetCallback(),
440          v8::Object::New(isolate),
441          0,
442          NULL);
443    }
444  }
445
446  static void GestureSourceTypeSupported(
447      const v8::FunctionCallbackInfo<v8::Value>& args) {
448    if (args.Length() != 1 || !args[0]->IsNumber()) {
449      args.GetReturnValue().Set(false);
450      return;
451    }
452
453    int gesture_source_type = args[0]->IntegerValue();
454    if (gesture_source_type < 0 ||
455        gesture_source_type > SyntheticGestureParams::GESTURE_SOURCE_TYPE_MAX) {
456      args.GetReturnValue().Set(false);
457      return;
458    }
459
460    bool is_supported = SyntheticGestureParams::IsGestureSourceTypeSupported(
461        static_cast<SyntheticGestureParams::GestureSourceType>(
462            gesture_source_type));
463    args.GetReturnValue().Set(is_supported);
464  }
465
466  static void BeginSmoothScroll(
467      const v8::FunctionCallbackInfo<v8::Value>& args) {
468    GpuBenchmarkingContext context;
469    if (!context.Init(false))
470      return;
471
472    // The last two arguments can be undefined. We check their validity later.
473    int arglen = args.Length();
474    if (arglen < 8 ||
475        !args[0]->IsNumber() ||
476        !args[1]->IsFunction() ||
477        !args[2]->IsNumber() ||
478        !args[3]->IsString() ||
479        !args[4]->IsNumber() ||
480        !args[5]->IsBoolean()) {
481      args.GetReturnValue().Set(false);
482      return;
483    }
484
485    v8::Local<v8::Function> callback_local =
486        v8::Local<v8::Function>::Cast(args[1]);
487
488    scoped_refptr<CallbackAndContext> callback_and_context =
489        new CallbackAndContext(args.GetIsolate(),
490                               callback_local,
491                               context.web_frame()->mainWorldScriptContext());
492
493    scoped_ptr<SyntheticSmoothScrollGestureParams> gesture_params(
494        new SyntheticSmoothScrollGestureParams);
495
496    // Convert coordinates from CSS pixels to density independent pixels (DIPs).
497    float page_scale_factor = context.web_view()->pageScaleFactor();
498
499    int gesture_source_type = args[2]->IntegerValue();
500    if (gesture_source_type < 0 ||
501        gesture_source_type > SyntheticGestureParams::GESTURE_SOURCE_TYPE_MAX) {
502      args.GetReturnValue().Set(false);
503      return;
504    }
505    gesture_params->gesture_source_type =
506        static_cast<SyntheticGestureParams::GestureSourceType>(
507            gesture_source_type);
508
509    gesture_params->speed_in_pixels_s = args[4]->IntegerValue();
510    gesture_params->prevent_fling = args[5]->BooleanValue();
511
512    // Account for the 2 optional arguments, start_x and start_y.
513    gfx::Point anchor;
514    if (args[6]->IsUndefined() || args[7]->IsUndefined()) {
515      blink::WebRect rect = context.render_view_impl()->windowRect();
516      anchor.SetPoint(rect.width / 2, rect.height / 2);
517    } else if (args[6]->IsNumber() && args[7]->IsNumber()) {
518      anchor.SetPoint(args[6]->IntegerValue() * page_scale_factor,
519                      args[7]->IntegerValue() * page_scale_factor);
520    } else {
521      args.GetReturnValue().Set(false);
522      return;
523    }
524    gesture_params->anchor = anchor;
525
526    int distance_length = args[0]->IntegerValue() * page_scale_factor;
527    gfx::Vector2d distance;
528    v8::String::Utf8Value direction(args[3]);
529    DCHECK(*direction);
530    std::string direction_str(*direction);
531    if (direction_str == "down")
532      distance.set_y(-distance_length);
533    else if (direction_str == "up")
534      distance.set_y(distance_length);
535    else if (direction_str == "right")
536      distance.set_x(-distance_length);
537    else if (direction_str == "left")
538      distance.set_x(distance_length);
539    else {
540      args.GetReturnValue().Set(false);
541      return;
542    }
543    gesture_params->distances.push_back(distance);
544
545    // TODO(nduca): If the render_view_impl is destroyed while the gesture is in
546    // progress, we will leak the callback and context. This needs to be fixed,
547    // somehow.
548    context.render_view_impl()->QueueSyntheticGesture(
549        gesture_params.PassAs<SyntheticGestureParams>(),
550        base::Bind(&OnSyntheticGestureCompleted,
551                   callback_and_context));
552
553    args.GetReturnValue().Set(true);
554  }
555
556  static void BeginScrollBounce(
557      const v8::FunctionCallbackInfo<v8::Value>& args) {
558    GpuBenchmarkingContext context;
559    if (!context.Init(false))
560      return;
561
562    // The last two arguments can be undefined. We check their validity later.
563    int arglen = args.Length();
564    if (arglen < 8 ||
565        !args[0]->IsString() ||
566        !args[1]->IsNumber() ||
567        !args[2]->IsNumber() ||
568        !args[3]->IsNumber() ||
569        !args[4]->IsFunction() ||
570        !args[5]->IsNumber()) {
571      args.GetReturnValue().Set(false);
572      return;
573    }
574
575    v8::Local<v8::Function> callback_local =
576        v8::Local<v8::Function>::Cast(args[4]);
577
578    scoped_refptr<CallbackAndContext> callback_and_context =
579        new CallbackAndContext(args.GetIsolate(),
580                               callback_local,
581                               context.web_frame()->mainWorldScriptContext());
582
583    scoped_ptr<SyntheticSmoothScrollGestureParams> gesture_params(
584        new SyntheticSmoothScrollGestureParams);
585
586    // Convert coordinates from CSS pixels to density independent pixels (DIPs).
587    float page_scale_factor = context.web_view()->pageScaleFactor();
588
589    gesture_params->speed_in_pixels_s = args[5]->IntegerValue();
590
591    // Account for the 2 optional arguments, start_x and start_y.
592    gfx::Point start;
593    if (args[6]->IsUndefined() || args[7]->IsUndefined()) {
594      blink::WebRect rect = context.render_view_impl()->windowRect();
595      start.SetPoint(rect.width / 2, rect.height / 2);
596    } else if (args[6]->IsNumber() && args[7]->IsNumber()) {
597      start.SetPoint(args[6]->IntegerValue() * page_scale_factor,
598                     args[7]->IntegerValue() * page_scale_factor);
599    } else {
600      args.GetReturnValue().Set(false);
601      return;
602    }
603
604    int distance_length = args[1]->IntegerValue() * page_scale_factor;
605    int overscroll_length = args[2]->IntegerValue() * page_scale_factor;
606    gfx::Vector2d distance;
607    gfx::Vector2d overscroll;
608    v8::String::Utf8Value direction(args[0]);
609    DCHECK(*direction);
610    std::string direction_str(*direction);
611    if (direction_str == "down") {
612      distance.set_y(-distance_length);
613      overscroll.set_y(overscroll_length);
614    }
615    else if (direction_str == "up") {
616      distance.set_y(distance_length);
617      overscroll.set_y(-overscroll_length);
618    }
619    else if (direction_str == "right") {
620      distance.set_x(-distance_length);
621      overscroll.set_x(overscroll_length);
622    }
623    else if (direction_str == "left") {
624      distance.set_x(distance_length);
625      overscroll.set_x(-overscroll_length);
626    }
627    else {
628      args.GetReturnValue().Set(false);
629      return;
630    }
631
632    int repeat_count = args[3]->IntegerValue();
633    gesture_params->anchor = start;
634    for (int i = 0; i < repeat_count; i++) {
635      gesture_params->distances.push_back(distance);
636      gesture_params->distances.push_back(-distance + overscroll);
637    }
638
639    // TODO(nduca): If the render_view_impl is destroyed while the gesture is in
640    // progress, we will leak the callback and context. This needs to be fixed,
641    // somehow.
642    context.render_view_impl()->QueueSyntheticGesture(
643        gesture_params.PassAs<SyntheticGestureParams>(),
644        base::Bind(&OnSyntheticGestureCompleted,
645                   callback_and_context));
646
647    args.GetReturnValue().Set(true);
648  }
649
650  static void BeginPinch(
651      const v8::FunctionCallbackInfo<v8::Value>& args) {
652    GpuBenchmarkingContext context;
653    if (!context.Init(false))
654      return;
655
656    int arglen = args.Length();
657    if (arglen < 5 ||
658        !args[0]->IsNumber() ||
659        !args[1]->IsNumber() ||
660        !args[2]->IsNumber() ||
661        !args[3]->IsFunction() ||
662        !args[4]->IsNumber()) {
663      args.GetReturnValue().Set(false);
664      return;
665    }
666
667    scoped_ptr<SyntheticPinchGestureParams> gesture_params(
668        new SyntheticPinchGestureParams);
669
670    // Convert coordinates from CSS pixels to density independent pixels (DIPs).
671    float page_scale_factor = context.web_view()->pageScaleFactor();
672
673    gesture_params->scale_factor = args[0]->NumberValue();
674    gesture_params->anchor.SetPoint(
675        args[1]->IntegerValue() * page_scale_factor,
676        args[2]->IntegerValue() * page_scale_factor);
677    gesture_params->relative_pointer_speed_in_pixels_s =
678        args[4]->IntegerValue();
679
680    v8::Local<v8::Function> callback_local =
681        v8::Local<v8::Function>::Cast(args[3]);
682
683    scoped_refptr<CallbackAndContext> callback_and_context =
684        new CallbackAndContext(args.GetIsolate(),
685                               callback_local,
686                               context.web_frame()->mainWorldScriptContext());
687
688
689    // TODO(nduca): If the render_view_impl is destroyed while the gesture is in
690    // progress, we will leak the callback and context. This needs to be fixed,
691    // somehow.
692    context.render_view_impl()->QueueSyntheticGesture(
693        gesture_params.PassAs<SyntheticGestureParams>(),
694        base::Bind(&OnSyntheticGestureCompleted,
695                   callback_and_context));
696
697    args.GetReturnValue().Set(true);
698  }
699
700  static void BeginTap(
701      const v8::FunctionCallbackInfo<v8::Value>& args) {
702    GpuBenchmarkingContext context;
703    if (!context.Init(false))
704      return;
705
706    int arglen = args.Length();
707    if (arglen < 5 ||
708        !args[0]->IsNumber() ||
709        !args[1]->IsNumber() ||
710        !args[2]->IsFunction() ||
711        !args[3]->IsNumber() ||
712        !args[4]->IsNumber()) {
713      args.GetReturnValue().Set(false);
714      return;
715    }
716
717    scoped_ptr<SyntheticTapGestureParams> gesture_params(
718        new SyntheticTapGestureParams);
719
720    // Convert coordinates from CSS pixels to density independent pixels (DIPs).
721    float page_scale_factor = context.web_view()->pageScaleFactor();
722
723    gesture_params->position.SetPoint(
724        args[0]->IntegerValue() * page_scale_factor,
725        args[1]->IntegerValue() * page_scale_factor);
726    gesture_params->duration_ms = args[3]->IntegerValue();
727
728    int gesture_source_type = args[4]->IntegerValue();
729    if (gesture_source_type < 0 ||
730        gesture_source_type > SyntheticGestureParams::GESTURE_SOURCE_TYPE_MAX) {
731      args.GetReturnValue().Set(false);
732      return;
733    }
734    gesture_params->gesture_source_type =
735        static_cast<SyntheticGestureParams::GestureSourceType>(
736            gesture_source_type);
737
738    v8::Local<v8::Function> callback_local =
739        v8::Local<v8::Function>::Cast(args[2]);
740
741    scoped_refptr<CallbackAndContext> callback_and_context =
742        new CallbackAndContext(args.GetIsolate(),
743                               callback_local,
744                               context.web_frame()->mainWorldScriptContext());
745
746
747    // TODO(nduca): If the render_view_impl is destroyed while the gesture is in
748    // progress, we will leak the callback and context. This needs to be fixed,
749    // somehow.
750    context.render_view_impl()->QueueSyntheticGesture(
751        gesture_params.PassAs<SyntheticGestureParams>(),
752        base::Bind(&OnSyntheticGestureCompleted,
753                   callback_and_context));
754
755    args.GetReturnValue().Set(true);
756  }
757
758  static void OnSnapshotCompleted(CallbackAndContext* callback_and_context,
759                                  const gfx::Size& size,
760                                  const std::vector<unsigned char>& png) {
761    v8::Isolate* isolate = callback_and_context->isolate();
762    v8::HandleScope scope(isolate);
763    v8::Handle<v8::Context> context = callback_and_context->GetContext();
764    v8::Context::Scope context_scope(context);
765    WebLocalFrame* frame = WebLocalFrame::frameForContext(context);
766    if (frame) {
767
768      v8::Handle<v8::Value> result;
769
770      if(!size.IsEmpty()) {
771        v8::Handle<v8::Object> result_object;
772        result_object = v8::Object::New(isolate);
773
774        result_object->Set(v8::String::NewFromUtf8(isolate, "width"),
775                           v8::Number::New(isolate, size.width()));
776        result_object->Set(v8::String::NewFromUtf8(isolate, "height"),
777                           v8::Number::New(isolate, size.height()));
778
779        std::string base64_png;
780        base::Base64Encode(base::StringPiece(
781            reinterpret_cast<const char*>(&*png.begin()), png.size()),
782            &base64_png);
783
784        result_object->Set(v8::String::NewFromUtf8(isolate, "data"),
785                           v8::String::NewFromUtf8(isolate,
786                                                   base64_png.c_str(),
787                                                   v8::String::kNormalString,
788                                                   base64_png.size()));
789
790        result = result_object;
791      } else {
792        result = v8::Null(isolate);
793      }
794
795      v8::Handle<v8::Value> argv[] = { result };
796
797      frame->callFunctionEvenIfScriptDisabled(
798          callback_and_context->GetCallback(),
799          v8::Object::New(isolate),
800          1,
801          argv);
802    }
803  }
804
805  static void BeginWindowSnapshotPNG(
806      const v8::FunctionCallbackInfo<v8::Value>& args) {
807    GpuBenchmarkingContext context;
808    if (!context.Init(false))
809      return;
810
811    if (!args[0]->IsFunction())
812      return;
813
814    v8::Local<v8::Function> callback_local =
815        v8::Local<v8::Function>::Cast(args[0]);
816
817    scoped_refptr<CallbackAndContext> callback_and_context =
818        new CallbackAndContext(args.GetIsolate(),
819                               callback_local,
820                               context.web_frame()->mainWorldScriptContext());
821
822    context.render_view_impl()->GetWindowSnapshot(
823        base::Bind(&OnSnapshotCompleted, callback_and_context));
824  }
825
826  static void ClearImageCache(
827      const v8::FunctionCallbackInfo<v8::Value>& args) {
828    WebImageCache::clear();
829  }
830
831  static void OnMicroBenchmarkCompleted(
832      CallbackAndContext* callback_and_context,
833      scoped_ptr<base::Value> result) {
834    v8::Isolate* isolate = callback_and_context->isolate();
835    v8::HandleScope scope(isolate);
836    v8::Handle<v8::Context> context = callback_and_context->GetContext();
837    v8::Context::Scope context_scope(context);
838    WebLocalFrame* frame = WebLocalFrame::frameForContext(context);
839    if (frame) {
840      scoped_ptr<V8ValueConverter> converter =
841          make_scoped_ptr(V8ValueConverter::create());
842      v8::Handle<v8::Value> value = converter->ToV8Value(result.get(), context);
843      v8::Handle<v8::Value> argv[] = { value };
844
845      frame->callFunctionEvenIfScriptDisabled(
846          callback_and_context->GetCallback(),
847          v8::Object::New(isolate),
848          1,
849          argv);
850    }
851  }
852
853  static void RunMicroBenchmark(
854      const v8::FunctionCallbackInfo<v8::Value>& args) {
855    GpuBenchmarkingContext context;
856    if (!context.Init(true)) {
857      args.GetReturnValue().Set(0);
858      return;
859    }
860
861    if (args.Length() != 3 ||
862        !args[0]->IsString() ||
863        !args[1]->IsFunction() ||
864        !args[2]->IsObject()) {
865      args.GetReturnValue().Set(0);
866      return;
867    }
868
869    v8::Local<v8::Function> callback_local =
870        v8::Local<v8::Function>::Cast(args[1]);
871
872    scoped_refptr<CallbackAndContext> callback_and_context =
873        new CallbackAndContext(args.GetIsolate(),
874                               callback_local,
875                               context.web_frame()->mainWorldScriptContext());
876
877    scoped_ptr<V8ValueConverter> converter =
878        make_scoped_ptr(V8ValueConverter::create());
879    v8::Handle<v8::Context> v8_context = callback_and_context->GetContext();
880    scoped_ptr<base::Value> value =
881        make_scoped_ptr(converter->FromV8Value(args[2], v8_context));
882
883    v8::String::Utf8Value benchmark(args[0]);
884    DCHECK(*benchmark);
885    args.GetReturnValue().Set(context.compositor()->ScheduleMicroBenchmark(
886        std::string(*benchmark),
887        value.Pass(),
888        base::Bind(&OnMicroBenchmarkCompleted, callback_and_context)));
889  }
890
891  static void SendMessageToMicroBenchmark(
892      const v8::FunctionCallbackInfo<v8::Value>& args) {
893    GpuBenchmarkingContext context;
894    if (!context.Init(true)) {
895      args.GetReturnValue().Set(0);
896      return;
897    }
898
899    if (args.Length() != 2 || !args[0]->IsNumber() || !args[1]->IsObject()) {
900      args.GetReturnValue().Set(0);
901      return;
902    }
903
904    scoped_ptr<V8ValueConverter> converter =
905        make_scoped_ptr(V8ValueConverter::create());
906    v8::Handle<v8::Context> v8_context =
907        context.web_frame()->mainWorldScriptContext();
908    scoped_ptr<base::Value> value =
909        make_scoped_ptr(converter->FromV8Value(args[1], v8_context));
910
911    int id = 0;
912    converter->FromV8Value(args[0], v8_context)->GetAsInteger(&id);
913    args.GetReturnValue().Set(
914        context.compositor()->SendMessageToMicroBenchmark(id, value.Pass()));
915  }
916
917  static void HasGpuProcess(const v8::FunctionCallbackInfo<v8::Value>& args) {
918    GpuChannelHost* gpu_channel = RenderThreadImpl::current()->GetGpuChannel();
919    args.GetReturnValue().Set(!!gpu_channel);
920  }
921};
922
923v8::Extension* GpuBenchmarkingExtension::Get() {
924  return new GpuBenchmarkingWrapper();
925}
926
927}  // namespace content
928