[M108 Migration] Enable snapshot feature for EFL port 69/286769/7
authorChandan Padhi <c.padhi@samsung.com>
Thu, 12 Jan 2023 13:04:51 +0000 (18:34 +0530)
committerChandan Padhi <c.padhi@samsung.com>
Thu, 19 Jan 2023 09:47:46 +0000 (15:17 +0530)
This commit provides support for both asynchronous and
synchronous snapshot APIs. This also adds sample code
to test async and sync snapshot APIs in ubrowser and
efl_webview_app respectively.

Legacy IPCs have also been replaced with Mojo calls for
software capture.

Reference: https://review.tizen.org/gerrit/281757/

Change-Id: I2e03fcffb5cfcf6377a09e7dec97d9533881265c
Signed-off-by: Chandan Padhi <c.padhi@samsung.com>
24 files changed:
content/browser/renderer_host/render_widget_host_impl.cc
content/browser/renderer_host/render_widget_host_impl.h
content/browser/renderer_host/render_widget_host_view_aura.cc
content/browser/renderer_host/render_widget_host_view_aura.h
content/browser/renderer_host/render_widget_host_view_base.h
third_party/blink/public/mojom/BUILD.gn
third_party/blink/public/mojom/widget/platform_widget.mojom
third_party/blink/public/web/web_view.h
third_party/blink/renderer/core/exported/web_view_impl.cc
third_party/blink/renderer/core/exported/web_view_impl.h
third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
third_party/blink/renderer/core/frame/web_frame_widget_impl.h
third_party/blink/renderer/platform/widget/widget_base.cc
third_party/blink/renderer/platform/widget/widget_base.h
third_party/blink/renderer/platform/widget/widget_base_client.h
tizen_src/chromium_impl/content/browser/renderer_host/rwhv_aura_offscreen_helper_efl.cc
tizen_src/chromium_impl/content/browser/renderer_host/rwhv_aura_offscreen_helper_efl.h
tizen_src/ewk/efl_integration/eweb_view.cc
tizen_src/ewk/efl_integration/eweb_view.h
tizen_src/ewk/efl_integration/public/ewk_view.cc
tizen_src/ewk/ubrowser/window.cc
tizen_src/ewk/ubrowser/window.h
tizen_src/ewk/ubrowser/window_ui.cc
tizen_src/ewk/ubrowser/window_ui.h

index db4f1c9..e12f71f 100644 (file)
@@ -971,6 +971,25 @@ void RenderWidgetHostImpl::RemoveImeInputEventObserver(
 }
 #endif
 
+#if BUILDFLAG(IS_EFL)
+void RenderWidgetHostImpl::RequestContentSnapshot(
+    const gfx::Rect& snapshot_rect,
+    float page_scale_factor,
+    int request_id) {
+  blink_widget_->GetContentSnapshot(
+      snapshot_rect, page_scale_factor,
+      base::BindOnce(&RenderWidgetHostImpl::OnGetContentSnapshot,
+                     weak_factory_.GetWeakPtr(), request_id));
+}
+
+void RenderWidgetHostImpl::OnGetContentSnapshot(int request_id,
+                                                const SkBitmap& bitmap) {
+  if (!view_)
+    return;
+  view_->DidGetContentSnapshot(bitmap, request_id);
+}
+#endif
+
 blink::VisualProperties RenderWidgetHostImpl::GetInitialVisualProperties() {
   blink::VisualProperties initial_props = GetVisualProperties();
 
index b2df2f8..aa77594 100644 (file)
@@ -439,6 +439,13 @@ class CONTENT_EXPORT RenderWidgetHostImpl
       RenderWidgetHost::InputEventObserver* observer) override;
 #endif
 
+#if BUILDFLAG(IS_EFL)
+  void RequestContentSnapshot(const gfx::Rect& src_subrect,
+                              float page_scale_factor,
+                              int request_id);
+  void OnGetContentSnapshot(int request_id, const SkBitmap& bitmap);
+#endif
+
   // Returns true if the RenderWidget is hidden.
   bool is_hidden() const { return is_hidden_; }
 
index fcee03c..366c8a7 100644 (file)
@@ -563,6 +563,12 @@ void RenderWidgetHostViewAura::NotifySwap(const uint32_t texture_id) {
     offscreen_helper_->NotifySwap(texture_id);
 }
 
+void RenderWidgetHostViewAura::DidGetContentSnapshot(const SkBitmap& bitmap,
+                                                     int request_id) {
+  if (offscreen_helper_)
+    offscreen_helper_->DidGetContentSnapshot(bitmap, request_id);
+}
+
 void RenderWidgetHostViewAura::DidHandleKeyEvent(
     blink::WebInputEvent::Type input_event_type,
     bool processed) {
index fa5af30..cacda97 100644 (file)
@@ -418,6 +418,7 @@ class CONTENT_EXPORT RenderWidgetHostViewAura
     return offscreen_helper_.get();
   }
   void NotifySwap(const uint32_t texture_id);
+  void DidGetContentSnapshot(const SkBitmap& bitmap, int request_id) override;
   void DidHandleKeyEvent(blink::WebInputEvent::Type input_event,
                          bool processed) override;
   void SelectionChanged(const std::u16string& text,
index ee2ad3c..0207afa 100644 (file)
@@ -275,6 +275,13 @@ class CONTENT_EXPORT RenderWidgetHostViewBase : public RenderWidgetHostView {
   // This method will clear any cached fallback surface. For use in response to
   // a CommitPending where there is no content for TakeFallbackContentFrom.
   virtual void ClearFallbackSurfaceForCommitPending() {}
+
+#if BUILDFLAG(IS_EFL)
+  virtual void DidHandleKeyEvent(blink::WebInputEvent::Type input_event,
+                                 bool processed) {}
+  virtual void DidGetContentSnapshot(const SkBitmap& bitmap, int request_id) {}
+#endif
+
   // This method will reset the fallback to the first surface after navigation.
   virtual void ResetFallbackToFirstNavigationSurface() = 0;
 
@@ -572,11 +579,6 @@ class CONTENT_EXPORT RenderWidgetHostViewBase : public RenderWidgetHostView {
   // RenderWidgetHostView.
   VisibleTimeRequestTrigger* GetVisibleTimeRequestTrigger();
 
-#if BUILDFLAG(IS_EFL)
-  virtual void DidHandleKeyEvent(blink::WebInputEvent::Type input_event,
-                                 bool processed) {}
-#endif
-
  protected:
   explicit RenderWidgetHostViewBase(RenderWidgetHost* host);
   ~RenderWidgetHostViewBase() override;
index 40822a7..4fe3988 100644 (file)
@@ -332,6 +332,9 @@ mojom("mojom_platform") {
   if (tizen_product_tv) {
     enabled_features += [ "is_tizen_tv" ]
   }
+  if (use_efl) {
+    enabled_features += [ "is_efl" ]
+  }
 
   shared_cpp_typemaps = [
     {
index aeb55d1..4dfb64b 100644 (file)
@@ -16,6 +16,9 @@ import "third_party/blink/public/mojom/widget/visual_properties.mojom";
 import "third_party/blink/public/mojom/widget/record_content_to_visible_time_request.mojom";
 import "ui/base/ime/mojom/text_input_state.mojom";
 
+[EnableIf=is_efl]
+import "skia/public/mojom/bitmap.mojom";
+
 // This interface is bound on the compositor thread.
 interface WidgetCompositor {
   // Requests that the RenderWidget sends back a response after the next main
@@ -107,6 +110,10 @@ interface Widget {
                     gfx.mojom.Rect window_screen_rect) => ();
 
 
+  [EnableIf=is_efl]
+  GetContentSnapshot(gfx.mojom.Rect snapshot_rect, float page_scale_factor)
+      => (skia.mojom.BitmapN32? bitmap);
+
   // Informs the widget that it was hidden. This allows it to reduce its
   // resource utilization, and will cancel any pending
   // RecordContentToVisibleTimeRequest that was set with WasShown or
index f3b681e..454da39 100644 (file)
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/display/mojom/screen_orientation.mojom-shared.h"
 
+#if BUILDFLAG(IS_EFL)
+#include "third_party/skia/include/core/SkCanvas.h"
+#endif
+
 namespace base {
 class TimeDelta;
 }
@@ -470,6 +474,13 @@ class BLINK_EXPORT WebView {
   // Returns the number of live WebView instances in this process.
   static size_t GetWebViewCount();
 
+#if BUILDFLAG(IS_EFL)
+  // Paints the rectangular region within the WebWidget
+  // onto the specified canvas, when the page is not having any 3D content.
+  virtual bool PaintSoftBitmap(SkCanvas*, const gfx::Rect&) = 0;
+  virtual bool HasAcceleratedCanvasWithinViewport() const = 0;
+#endif
+
  protected:
   ~WebView() = default;
 };
index 604c0e0..00f7e7f 100644 (file)
 #include "third_party/blink/public/web/win/web_font_rendering.h"
 #endif
 
+#if BUILDFLAG(IS_EFL)
+#include "third_party/blink/renderer/core/dom/container_node.h"
+#include "third_party/blink/renderer/core/dom/static_node_list.h"
+#endif
+
 // Get rid of WTF's pow define so we can use std::pow.
 #undef pow
 #include <cmath>  // for std::pow
@@ -3922,4 +3927,71 @@ void WebViewImpl::CreateRemoteMainFrame(
       std::move(remote_main_frame_interfaces->main_frame));
 }
 
+#if BUILDFLAG(IS_EFL)
+bool WebViewImpl::PaintSoftBitmap(SkCanvas* canvas, const gfx::Rect& rect) {
+  if (rect.IsEmpty())
+    return false;
+
+  if (!MainFrameImpl() || !MainFrameImpl()->GetFrameView())
+    return false;
+
+  LocalFrameView* view = MainFrameImpl()->GetFrameView();
+  view->UpdateAllLifecyclePhases(DocumentUpdateReason::kOverlay);
+
+  gfx::PointF offset = MainFrame()->ToWebLocalFrame()->GetScrollOffset();
+  gfx::Rect dirty_rect(rect.x() + offset.x(), rect.y() + offset.y(),
+                       rect.width(), rect.height());
+  PaintRecordBuilder builder;
+  {
+    // FIXME: scaleFactor should be ideally pagescalefactor * devicescale
+    // here we are assuming pagescalefactor as 1 always, this needs to
+    // be taken care with a proper fix for capturing zoomed contents.
+    LocalFrame& frame = view->GetFrame();
+    ChromeClient& chrome_client = frame.GetChromeClient();
+    float scale_factor = chrome_client.GetScreenInfo(frame).device_scale_factor;
+
+    GraphicsContext& context = builder.Context();
+    context.SetDeviceScaleFactor(scale_factor);
+
+    AffineTransform transform;
+    transform.Scale(scale_factor);
+    transform.Translate(static_cast<float>(-dirty_rect.x()),
+                        static_cast<float>(-dirty_rect.y()));
+
+    // FIXME: Use the page-background color to clear instead for half
+    // loaded pages.
+    SkColor color = SkColorSetARGB(0xff, 0xff, 0xff, 0xff);
+    canvas->clear(color);
+    view->PaintOutsideOfLifecycle(context, PaintFlag::kOmitCompositingInfo,
+                                  CullRect(dirty_rect));
+  }
+  builder.EndRecording()->Playback(canvas);
+  return true;
+}
+
+bool WebViewImpl::HasAcceleratedCanvasWithinViewport() const {
+  if (!MainFrameImpl() || !MainFrameImpl()->GetFrameView())
+    return false;
+
+  Document* document = MainFrameImpl()->GetFrame()->GetDocument();
+  if (!document)
+    return false;
+
+  StaticElementList* canvas_elements =
+      document->QuerySelectorAll("canvas", ASSERT_NO_EXCEPTION);
+  if (!canvas_elements)
+    return false;
+
+  gfx::Rect visible_content_rect =
+      MainFrameImpl()->GetFrameView()->LayoutViewport()->VisibleContentRect();
+  for (unsigned i = 0; i < canvas_elements->length(); ++i) {
+    if (visible_content_rect.Intersects(
+            canvas_elements->item(i)->BoundsInWidget())) {
+      return true;
+    }
+  }
+  return false;
+}
+#endif
+
 }  // namespace blink
index ac38111..ebf4292 100644 (file)
@@ -222,6 +222,11 @@ class CORE_EXPORT WebViewImpl final : public WebView,
   const SessionStorageNamespaceId& GetSessionStorageNamespaceId() override;
   bool IsFencedFrameRoot() const override;
 
+#if BUILDFLAG(IS_EFL)
+  bool PaintSoftBitmap(SkCanvas*, const gfx::Rect&) override;
+  bool HasAcceleratedCanvasWithinViewport() const override;
+#endif
+
   // Functions to add and remove observers for this object.
   void AddObserver(WebViewObserver* observer);
   void RemoveObserver(WebViewObserver* observer);
index 18c4feb..db9d088 100644 (file)
 #include "ui/gfx/geometry/point.h"
 #endif
 
+#if BUILDFLAG(IS_EFL)
+#include "skia/ext/image_operations.h"
+#endif
+
 namespace WTF {
 
 template <>
@@ -4115,6 +4119,42 @@ bool WebFrameWidgetImpl::UpdateScreenRects(
   return true;
 }
 
+#if BUILDFLAG(IS_EFL)
+void WebFrameWidgetImpl::GetContentSnapshot(const gfx::Rect& snapshot_rect,
+                                            float page_scale_factor,
+                                            SkBitmap* snapshot) {
+  WebView* view = View();
+  if (!view || !view->MainFrame()) {
+    LOG(ERROR) << "SoftBitmap:Capture Invalid view";
+    return;
+  }
+
+  if (view->HasAcceleratedCanvasWithinViewport()) {
+    LOG(ERROR)
+        << "SoftBitmap:Capture avoided for WebGl/Cavas2D content on viewport";
+    return;
+  }
+
+  if (view->MainFrameWidget()->Size().IsEmpty()) {
+    LOG(ERROR) << "SoftBitmap:Capture Invalid size";
+    return;
+  }
+
+  snapshot->allocPixels(SkImageInfo::MakeN32Premul(snapshot_rect.width(),
+                                                   snapshot_rect.height()));
+  SkCanvas canvas(*snapshot);
+  if (!view->PaintSoftBitmap(&canvas, snapshot_rect)) {
+    LOG(ERROR) << "SoftBitmap:Capture PaintSoftBitmap failed";
+    return;
+  }
+
+  *snapshot = skia::ImageOperations::Resize(
+      *snapshot, skia::ImageOperations::RESIZE_GOOD,
+      page_scale_factor * snapshot->width(),
+      page_scale_factor * snapshot->height());
+}
+#endif
+
 void WebFrameWidgetImpl::OrientationChanged() {
   local_root_->SendOrientationChangeEvent();
 }
index 72528ee..57c0357 100644 (file)
@@ -702,6 +702,11 @@ class CORE_EXPORT WebFrameWidgetImpl
       const VisualProperties& visual_properties) override;
   bool UpdateScreenRects(const gfx::Rect& widget_screen_rect,
                          const gfx::Rect& window_screen_rect) override;
+#if BUILDFLAG(IS_EFL)
+  void GetContentSnapshot(const gfx::Rect& snapshot_rect,
+                          float page_scale_factor,
+                          SkBitmap* snapshot) override;
+#endif
   void OrientationChanged() override;
   void DidUpdateSurfaceAndScreen(
       const display::ScreenInfos& previous_original_screen_infos) override;
index 78bb164..d2fca1a 100644 (file)
 #include "third_party/blink/renderer/platform/widget/compositing/android_webview/synchronous_layer_tree_frame_sink.h"
 #endif
 
+#if BUILDFLAG(IS_EFL)
+#include "third_party/skia/include/core/SkBitmap.h"
+#endif
+
 namespace blink {
 
 namespace {
@@ -450,6 +454,17 @@ void WidgetBase::UpdateScreenRects(const gfx::Rect& widget_screen_rect,
   std::move(callback).Run();
 }
 
+#if BUILDFLAG(IS_EFL)
+void WidgetBase::GetContentSnapshot(const gfx::Rect& snapshot_rect,
+                                    float page_scale_factor,
+                                    GetContentSnapshotCallback callback) {
+  SkBitmap content_snapshot;
+  client_->GetContentSnapshot(snapshot_rect, page_scale_factor,
+                              &content_snapshot);
+  std::move(callback).Run(content_snapshot);
+}
+#endif
+
 void WidgetBase::WasHidden() {
   // A provisional frame widget will never be hidden since that would require it
   // to be shown first. A frame must be attached to the frame tree before
index a26cd3b..1ba4b68 100644 (file)
@@ -136,6 +136,11 @@ class PLATFORM_EXPORT WidgetBase : public mojom::blink::Widget,
   void UpdateScreenRects(const gfx::Rect& widget_screen_rect,
                          const gfx::Rect& window_screen_rect,
                          UpdateScreenRectsCallback callback) override;
+#if BUILDFLAG(IS_EFL)
+  void GetContentSnapshot(const gfx::Rect& snapshot_rect,
+                          float page_scale_factor,
+                          GetContentSnapshotCallback callback) override;
+#endif
   void WasHidden() override;
   void WasShown(bool was_evicted,
                 mojom::blink::RecordContentToVisibleTimeRequestPtr
index 9f8a391..d952389 100644 (file)
@@ -169,6 +169,12 @@ class WidgetBaseClient {
     return false;
   }
 
+#if BUILDFLAG(IS_EFL)
+  virtual void GetContentSnapshot(const gfx::Rect& snapshot_rect,
+                                  float page_scale_factor,
+                                  SkBitmap* snapshot) {}
+#endif
+
   // Convert screen coordinates to device emulated coordinates (scaled
   // coordinates when devtools is used). This occurs for popups where their
   // window bounds are emulated.
index 749b0d0..30492b0 100644 (file)
 
 namespace content {
 
+// If the first frame is not prepared after load is finished,
+// delay getting the snapshot by 100ms.
+static const int kSnapshotProcessDelay = 100;
+
+class ScreenshotCapturedCallback {
+ public:
+  ScreenshotCapturedCallback(Screenshot_Captured_Callback func, void* user_data)
+      : func_(func), user_data_(user_data) {}
+  void Run(Evas_Object* image) {
+    if (func_ != NULL)
+      (func_)(image, user_data_);
+  }
+
+ private:
+  Screenshot_Captured_Callback func_;
+  void* user_data_;
+};
+
 class RenderWidgetHostHelperAura : public RenderWidgetHostHelper {
  public:
   explicit RenderWidgetHostHelperAura(RWHVAuraOffscreenHelperEfl* rwhv_helper)
@@ -156,6 +174,8 @@ RWHVAuraOffscreenHelperEfl::~RWHVAuraOffscreenHelperEfl() {
                                  OnFocusIn);
   evas_object_event_callback_del(content_image_, EVAS_CALLBACK_FOCUS_OUT,
                                  OnFocusOut);
+  evas_event_callback_del_full(evas_, EVAS_CALLBACK_RENDER_FLUSH_PRE,
+                               OnEvasRenderFlushPre, this);
   evas_object_del(content_image_elm_host_);
   evas_object_del(content_image_);
 
@@ -177,6 +197,8 @@ void RWHVAuraOffscreenHelperEfl::Initialize() {
 
   device_scale_factor_ =
       display::Screen::GetScreen()->GetPrimaryDisplay().device_scale_factor();
+
+  snapshot_timer_.reset(new base::OneShotTimer);
 }
 
 void RWHVAuraOffscreenHelperEfl::SetAuraParentWindow(
@@ -390,6 +412,9 @@ void RWHVAuraOffscreenHelperEfl::PaintTextureToSurface(GLuint texture_id) {
 
   evas_gl_api_->glBindTexture(GL_TEXTURE_2D, 0);
   evas_gl_make_current(evas_gl_, 0, 0);
+
+  // for snapshot
+  frame_rendered_ = true;
 }
 
 void RWHVAuraOffscreenHelperEfl::InitEvasGL() {
@@ -594,6 +619,286 @@ void RWHVAuraOffscreenHelperEfl::EvasToBlinkCords(int x,
   }
 }
 
+void RWHVAuraOffscreenHelperEfl::OnEvasRenderFlushPre(void* data,
+                                                      Evas* evas,
+                                                      void* event_info) {
+  RWHVAuraOffscreenHelperEfl* rwhv_helper =
+      static_cast<RWHVAuraOffscreenHelperEfl*>(data);
+  if (rwhv_helper && rwhv_helper->snapshot_task_list_.size() > 0)
+    rwhv_helper->ProcessSnapshotRequest();
+}
+
+void RWHVAuraOffscreenHelperEfl::InvalidateForSnapshot() {
+  evas_object_image_pixels_dirty_set(content_image_, true);
+}
+
+void RWHVAuraOffscreenHelperEfl::ProcessSnapshotRequest() {
+  if (!frame_rendered_)
+    return;
+
+  // Process all snapshot requests pending now.
+  for (auto& task : snapshot_task_list_) {
+    if (!task.is_null())
+      std::move(task).Run();
+  }
+
+  if (snapshot_timer_->IsRunning())
+    snapshot_timer_->Stop();
+  // Stop now and clear task list
+  snapshot_task_list_.clear();
+  // Unregister render post event
+  evas_event_callback_del_full(evas_, EVAS_CALLBACK_RENDER_FLUSH_PRE,
+                               OnEvasRenderFlushPre, this);
+}
+
+void RWHVAuraOffscreenHelperEfl::RunGetSnapshotOnMainThread(
+    const gfx::Rect snapshot_area,
+    int request_id,
+    float scale_factor) {
+#if defined(USE_TTRACE)
+  TTRACE(TTRACE_TAG_WEB,
+         "RWHVAuraOffscreenHelperEfl::RunGetSnapshotOnMainThread");
+#endif
+  ScreenshotCapturedCallback* callback =
+      screen_capture_cb_map_.Lookup(request_id);
+
+  if (!callback)
+    return;
+
+  if (rwhv_aura_->IsShowing() && evas_focus_get(evas_))
+    callback->Run(GetSnapshot(snapshot_area, scale_factor));
+  screen_capture_cb_map_.Remove(request_id);
+}
+
+Evas_Object* RWHVAuraOffscreenHelperEfl::GetSnapshot(
+    const gfx::Rect& snapshot_area,
+    float scale_factor,
+    bool is_magnifier) {
+#if defined(USE_TTRACE)
+  TTRACE(TTRACE_TAG_WEB, "RWHVAuraOffscreenHelperEfl::GetSnapshot");
+#endif
+  if (scale_factor == 0.0)
+    return nullptr;
+
+  int rotation =
+      display::Screen::GetScreen()->GetPrimaryDisplay().RotationAsDegree();
+
+  bool is_evasgl_direct_landscape = false;
+  // For EvasGL Direct Rendering in landscape mode
+  if (!is_magnifier) {
+    is_evasgl_direct_landscape = ((rotation == 90 || rotation == 270) &&
+                                  !evas_gl_rotation_get(evas_gl_));
+  }
+
+  const gfx::Rect window_rect = gfx::ToEnclosingRect(gfx::ConvertRectToPixels(
+      rwhv_aura_->GetBoundsInRootWindow(), device_scale_factor_));
+  // |view_rect| is absolute coordinate of webview.
+  gfx::Rect view_rect = GetViewBoundsInPix();
+
+  // the gl coordinate system is based on screen rect for direct rendering.
+  // if direct rendering is disabled, the coordinate is based on webview.
+  // TODO(is46.kim) : Even though evas direct rendering flag is set, direct
+  // rendering may be disabled in the runtime depend on environment.
+  // There is no way to get current rendering mode. |is_direct_rendering|
+  // flag should be changed by current rendering mode.
+  bool is_direct_rendering = !is_magnifier;
+  if (!is_direct_rendering) {
+    view_rect.set_x(0);
+    view_rect.set_y(0);
+  }
+  const gfx::Size surface_size =
+      (is_direct_rendering) ? window_rect.size() : view_rect.size();
+
+  // |snapshot_area| is relative coordinate in webview.
+  int x = snapshot_area.x();
+  int y = snapshot_area.y();
+  int width = snapshot_area.width();
+  int height = snapshot_area.height();
+
+  // Convert |snapshot_area| to absolute coordinate.
+  x += view_rect.x();
+  y += view_rect.y();
+
+  // Limit snapshot rect by webview size
+  if (x + width > view_rect.right())
+    width = view_rect.right() - x;
+  if (y + height > view_rect.bottom())
+    height = view_rect.bottom() - y;
+
+  // Convert coordinate system for OpenGL.
+  // (0,0) is top left corner in webview coordinate system.
+  if (is_evasgl_direct_landscape) {
+    if (rotation == 270) {
+      x = surface_size.width() - x - width;
+      y = surface_size.height() - y - height;
+    }
+    std::swap(x, y);
+    std::swap(width, height);
+  } else {
+    // (0,0) is bottom left corner in opengl coordinate system.
+    y = surface_size.height() - y - height;
+  }
+
+  int size = width * height * sizeof(GLuint);
+  GLuint* data = (GLuint*)malloc(size);
+  if (!data) {
+    LOG(ERROR) << "Failed to allocate memory for snapshot";
+    return nullptr;
+  }
+
+  {
+#if defined(USE_TTRACE)
+    TTRACE(TTRACE_TAG_WEB,
+           "RWHVAuraOffscreenHelperEfl::GetSnapshot(ReadPixels)");
+#endif
+    if (!MakeCurrent()) {
+      LOG(ERROR) << "MakeCurrent() failed.";
+      free(data);
+      return nullptr;
+    }
+
+    evas_gl_api_->glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE,
+                               data);
+    ClearCurrent();
+  }
+
+  if (!is_evasgl_direct_landscape && (rotation == 90 || rotation == 270))
+    std::swap(width, height);
+
+#if _DUMP_SNAPSHOT_
+  static int frame_number = 0;
+  frame_number++;
+  {
+    char filename[150];
+    sprintf(filename, "/opt/share/dump/snapshot%02d-%d_%dx%d_rgba8888.raw",
+            frame_number, rotation, width, height);
+    FILE* fp = fopen(filename, "w");
+    if (fp) {
+      fwrite(data, width * height * 4, 1, fp);
+      fflush(fp);
+      fclose(fp);
+    } else {
+      LOG(ERROR) << "Unable to open file";
+    }
+  }
+#endif
+
+  // flip the Y axis and change color format from RGBA to BGRA
+  for (int j = 0; j < height / 2; j++) {
+    for (int i = 0; i < width; i++) {
+      GLuint& px1 = data[(j * width) + i];
+      GLuint& px2 = data[((height - 1) - j) * width + i];
+      px1 = ((px1 & 0xff) << 16) | ((px1 >> 16) & 0xff) | (px1 & 0xff00ff00) |
+            0xff000000;
+      px2 = ((px2 & 0xff) << 16) | ((px2 >> 16) & 0xff) | (px2 & 0xff00ff00) |
+            0xff000000;
+      std::swap(px1, px2);
+    }
+  }
+
+  SkBitmap bitmap;
+  if (scale_factor != 1.0 || rotation) {
+    bitmap.setInfo(SkImageInfo::MakeN32Premul(width, height));
+    bitmap.setPixels(data);
+
+    // scaling bitmap
+    if (scale_factor != 1.0) {
+      width = scale_factor * width;
+      height = scale_factor * height;
+      bitmap = skia::ImageOperations::Resize(
+          bitmap, skia::ImageOperations::RESIZE_GOOD, width, height);
+    }
+
+    if (rotation == 90) {
+      bitmap = SkBitmapOperations::Rotate(bitmap,
+                                          SkBitmapOperations::ROTATION_90_CW);
+    } else if (rotation == 180) {
+      bitmap = SkBitmapOperations::Rotate(bitmap,
+                                          SkBitmapOperations::ROTATION_180_CW);
+    } else if (rotation == 270) {
+      bitmap = SkBitmapOperations::Rotate(bitmap,
+                                          SkBitmapOperations::ROTATION_270_CW);
+    }
+  }
+
+  if (rotation == 90 || rotation == 270)
+    std::swap(width, height);
+
+  Evas_Object* image = evas_object_image_filled_add(evas_);
+  if (image) {
+    evas_object_image_size_set(image, width, height);
+    evas_object_image_alpha_set(image, EINA_TRUE);
+    evas_object_image_data_copy_set(image,
+                                    bitmap.empty() ? data : bitmap.getPixels());
+    evas_object_resize(image, width, height);
+#if _DUMP_SNAPSHOT_
+    char filename[150];
+    sprintf(filename, "/opt/share/dump/snapshot%02d-%d_%dx%d.png", frame_number,
+            rotation, width, height);
+    evas_object_image_save(image, filename, NULL, "quality=100");
+#endif
+  }
+  free(data);
+
+  return image;
+}
+
+void RWHVAuraOffscreenHelperEfl::RequestSnapshotAsync(
+    const gfx::Rect& snapshot_area,
+    Screenshot_Captured_Callback callback,
+    void* user_data,
+    float scale_factor) {
+#if defined(USE_TTRACE)
+  TTRACE(TTRACE_TAG_WEB, "RWHVAuraOffscreenHelperEfl::RequestSnapshotAsync");
+#endif
+  ScreenshotCapturedCallback* cb =
+      new ScreenshotCapturedCallback(callback, user_data);
+
+  int request_id = screen_capture_cb_map_.Add(base::WrapUnique(cb));
+
+  if (rwhv_aura_->IsShowing() && evas_focus_get(evas_)) {
+    // Create a snapshot task that will be executed after frame
+    // is generated and drawn to surface.
+    snapshot_task_list_.push_back(base::BindOnce(
+        &RWHVAuraOffscreenHelperEfl::RunGetSnapshotOnMainThread,
+        base::Unretained(this), snapshot_area, request_id, scale_factor));
+
+    // Delete OnEvasRenderPost callback to prevent registration twice.
+    evas_event_callback_del_full(evas_, EVAS_CALLBACK_RENDER_FLUSH_PRE,
+                                 OnEvasRenderFlushPre, this);
+    evas_event_callback_add(evas_, EVAS_CALLBACK_RENDER_FLUSH_PRE,
+                            OnEvasRenderFlushPre, this);
+
+    // If there is no EVAS_CALLBACK_RENDER_FLUSH_PRE event sent out in 400ms,
+    // we trigger one by ourself
+    snapshot_timer_->Start(
+        FROM_HERE, base::Milliseconds(4 * kSnapshotProcessDelay),
+        base::BindOnce(&RWHVAuraOffscreenHelperEfl::InvalidateForSnapshot,
+                       base::Unretained(this)));
+  } else {
+    // Sends message to renderer to capture the content snapshot of the view.
+    rwhv_aura_->host()->RequestContentSnapshot(snapshot_area, scale_factor,
+                                               request_id);
+  }
+}
+
+void RWHVAuraOffscreenHelperEfl::DidGetContentSnapshot(const SkBitmap& bitmap,
+                                                       const int request_id) {
+  ScreenshotCapturedCallback* callback =
+      screen_capture_cb_map_.Lookup(request_id);
+  if (!callback)
+    return;
+
+  Evas_Object* image = nullptr;
+  if (!bitmap.empty()) {
+    image = evas_object_image_filled_add(evas_);
+    evas_object_image_size_set(image, bitmap.width(), bitmap.height());
+    evas_object_image_data_copy_set(image, bitmap.getPixels());
+  }
+  callback->Run(image);
+  screen_capture_cb_map_.Remove(request_id);
+}
+
 Evas_Object* RWHVAuraOffscreenHelperEfl::ewk_view() const {
   auto wci = static_cast<WebContentsImpl*>(web_contents_);
   if (!wci)
index c4c91f6..a4264d1 100644 (file)
@@ -38,9 +38,14 @@ class IMContextEfl;
 
 namespace content {
 
+typedef void (*Screenshot_Captured_Callback)(Evas_Object* image,
+                                             void* user_data);
+typedef base::OnceCallback<void()> SnapshotTask;
+
 class RenderWidgetHostHelper;
 class RenderWidgetHostImpl;
 class RenderWidgetHostViewAura;
+class ScreenshotCapturedCallback;
 class WebContents;
 class WebContentsDelegate;
 
@@ -80,6 +85,17 @@ class CONTENT_EXPORT RWHVAuraOffscreenHelperEfl {
 #endif
   );
 
+  // |snapshot_area| is relative coordinate system based on Webview.
+  // (0,0) is top left corner.
+  Evas_Object* GetSnapshot(const gfx::Rect& snapshot_area,
+                           float scale_factor,
+                           bool is_magnifier = false);
+  void RequestSnapshotAsync(const gfx::Rect& snapshot_area,
+                            Screenshot_Captured_Callback callback,
+                            void* user_data,
+                            float scale_factor = 1.0);
+  void DidGetContentSnapshot(const SkBitmap& bitmap, int request_id);
+
   RenderWidgetHostViewAura* rwhva() { return rwhv_aura_; }
   void OnMouseOrTouchEvent(ui::Event* event);
   void DidHandleKeyEvent(blink::WebInputEvent::Type input_event_type,
@@ -106,6 +122,7 @@ class CONTENT_EXPORT RWHVAuraOffscreenHelperEfl {
   static void OnFocusOut(void* data, Evas*, Evas_Object*, void*);
   static void OnHostFocusIn(void* data, Evas_Object*, void*);
   static void OnHostFocusOut(void* data, Evas_Object*, void*);
+  static void OnEvasRenderFlushPre(void* data, Evas* evas, void* event_info);
 
   void Initialize();
   void InitializeProgram();
@@ -118,6 +135,12 @@ class CONTENT_EXPORT RWHVAuraOffscreenHelperEfl {
   bool MakeCurrent();
   void ClearBrowserFrame();
 
+  void InvalidateForSnapshot();
+  void ProcessSnapshotRequest();
+  void RunGetSnapshotOnMainThread(const gfx::Rect snapshot_area,
+                                  int request_id,
+                                  float scale_factor);
+
   ui::EflEventHandler* GetEventHandler();
   ui::IMContextEfl* GetIMContextEfl();
 
@@ -159,6 +182,12 @@ class CONTENT_EXPORT RWHVAuraOffscreenHelperEfl {
   WebContents* web_contents_ = nullptr;
 
   std::unique_ptr<RenderWidgetHostHelper> rwh_helper_;
+
+  bool frame_rendered_ = false;
+  std::list<SnapshotTask> snapshot_task_list_;
+  std::unique_ptr<base::OneShotTimer> snapshot_timer_;
+  base::IDMap<std::unique_ptr<ScreenshotCapturedCallback>>
+      screen_capture_cb_map_;
 };
 
 }  // namespace content
index ec146b6..11e805b 100644 (file)
@@ -1619,75 +1619,23 @@ void EWebView::SetLinkMagnifierEnabled(bool enabled) {
 bool EWebView::GetSnapshotAsync(
     Eina_Rectangle rect,
     Ewk_Web_App_Screenshot_Captured_Callback callback,
-    void* user_data) {
-#if !defined(USE_AURA)
-  if (!rwhv())
+    void* user_data,
+    float scale_factor) {
+  if (!rwhva() || !rwhva()->offscreen_helper())
     return false;
 
-  return rwhv()->RequestSnapshotAsync(rect, callback, user_data);
-#else
-  return false;
-#endif
+  rwhva()->offscreen_helper()->RequestSnapshotAsync(
+      gfx::Rect(rect.x, rect.y, rect.w, rect.h), callback, user_data,
+      scale_factor);
+  return true;
 }
 
-Evas_Object* EWebView::GetSnapshot(Eina_Rectangle rect) {
-  Evas_Object* image = NULL;
-#if !defined(USE_AURA)
-#if BUILDFLAG(IS_TIZEN)
-  if (!rwhv() || !rwhv()->MakeCurrent())
-    return NULL;
-
-  int width = rect.w;
-  int height = rect.h;
-
-  if (width > rwhv()->GetViewBoundsInPix().width() - rect.x)
-    width = rwhv()->GetViewBoundsInPix().width() - rect.x;
-  if (height > rwhv()->GetViewBoundsInPix().height() - rect.y)
-    height = rwhv()->GetViewBoundsInPix().height() - rect.y;
-
-  int x = rect.x;
-  int y = rwhv()->GetViewBoundsInPix().height() - height + rect.y;
+Evas_Object* EWebView::GetSnapshot(Eina_Rectangle rect, float scale_factor) {
+  if (!rwhva() || !rwhva()->offscreen_helper())
+    return nullptr;
 
-  Evas_GL_API* gl_api = rwhv()->evasGlApi();
-  DCHECK(gl_api);
-  int size = width * height * sizeof(GLuint);
-
-  GLuint* tmp = (GLuint*)malloc(size);
-  if (!tmp)
-    return NULL;
-
-  GLuint* bits = (GLuint*)malloc(size);
-  if (!bits) {
-    free(tmp);
-    return NULL;
-  }
-
-  gl_api->glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE,
-                       (GLubyte*)bits);
-
-  // flip the Y axis and change color format from RGBA to BGRA
-  int i, j, idx1, idx2;
-  GLuint d;
-  for (j = 0; j < height; j++) {
-    for (i = 0; i < width; i++) {
-      idx1 = (j * width) + i;
-      idx2 = ((height - 1) - j) * width + i;
-      d = bits[idx1];
-      tmp[idx2] = ((d & 0x000000ff) << 16) + ((d & 0x00ff0000) >> 16) +
-                  ((d & 0xff00ff00));
-    }
-  }
-
-  image = evas_object_image_filled_add(rwhv()->evas());
-  if (image) {
-    evas_object_image_size_set(image, width, height);
-    evas_object_image_alpha_set(image, EINA_TRUE);
-    evas_object_image_data_copy_set(image, tmp);
-    evas_object_resize(image, width, height);
-  }
-#endif
-#endif
-  return image;
+  return rwhva()->offscreen_helper()->GetSnapshot(
+      gfx::Rect(rect.x, rect.y, rect.w, rect.h), scale_factor);
 }
 
 void EWebView::BackForwardListClear() {
index b2d0037..f76d97b 100755 (executable)
@@ -320,15 +320,16 @@ class EWebView {
    * Creates a snapshot of given rectangle from EWebView
    *
    * @param rect rectangle of EWebView which will be taken into snapshot
-   *
+   * @param scale_factor scale factor
    * @return created snapshot or NULL if error occured.
    * @note ownership of snapshot is passed to caller
-  */
-  Evas_Object* GetSnapshot(Eina_Rectangle rect);
+   */
+  Evas_Object* GetSnapshot(Eina_Rectangle rect, float scale_factor);
 
   bool GetSnapshotAsync(Eina_Rectangle rect,
                         Ewk_Web_App_Screenshot_Captured_Callback callback,
-                        void* user_data);
+                        void* user_data,
+                        float scale_factor);
   void InvokePolicyResponseCallback(_Ewk_Policy_Decision* policy_decision,
                                     bool* defer);
   void InvokePolicyNavigationCallback(const NavigationPolicyParams& params,
index 05864b8..40d8e79 100644 (file)
@@ -440,13 +440,7 @@ Evas_Object* ewk_view_screenshot_contents_get(const Evas_Object* view, Eina_Rect
 {
   EINA_SAFETY_ON_NULL_RETURN_VAL(canvas, NULL);
   EWK_VIEW_IMPL_GET_OR_RETURN(view, impl, NULL);
-  // Zoom level equality (<=0.001) is sufficient compared to high precision std::numeric_value::epsilon.
-  if (!blink::PageZoomValuesEqual(scale_factor, 1.0)) {
-    LOG(ERROR) << "We only support scale factor of 1.0."
-               << "Scaling option will be supported after hardware acceleration is enabled.";
-    return NULL;
-  }
-  return impl->GetSnapshot(view_area);
+  return impl->GetSnapshot(view_area, scale_factor);
 }
 
 Eina_Bool ewk_view_screenshot_contents_get_async(const Evas_Object* view, Eina_Rectangle view_area,
@@ -454,13 +448,9 @@ Eina_Bool ewk_view_screenshot_contents_get_async(const Evas_Object* view, Eina_R
 {
   EINA_SAFETY_ON_NULL_RETURN_VAL(callback, EINA_FALSE);
   EWK_VIEW_IMPL_GET_OR_RETURN(view, impl, EINA_FALSE);
-  // Zoom level equality (<=0.001) is sufficient compared to high precision std::numeric_value::epsilon.
-  if (!blink::PageZoomValuesEqual(scale_factor, 1.0)) {
-    LOG(ERROR) << "We only support scale factor of 1.0."
-               << "Scaling option will be supported after hardware acceleration is enabled.";
-    return EINA_FALSE;
-  }
-  return impl->GetSnapshotAsync(view_area, callback, user_data) ? EINA_TRUE : EINA_FALSE;
+  return impl->GetSnapshotAsync(view_area, callback, user_data, scale_factor)
+             ? EINA_TRUE
+             : EINA_FALSE;
 }
 
 unsigned int ewk_view_inspector_server_start(Evas_Object* ewkView, unsigned int port)
index 8e17761..f34e706 100644 (file)
@@ -408,6 +408,15 @@ void Window::SetAutoRotate() {
                                               (sizeof(rots) / sizeof(int)));
 }
 
+void Window::TakeScreenshotAsync() {
+  log_trace("%s", __PRETTY_FUNCTION__);
+  Eina_Rectangle rect;
+  EINA_RECTANGLE_SET(&rect, 0, 0, 400, 400);
+  ewk_view_screenshot_contents_get_async(web_view_, rect, 1.0,
+                                         evas_object_evas_get(web_view_),
+                                         &Window::OnScreenshotCaptured, NULL);
+}
+
 #if BUILDFLAG(IS_TIZEN_TV)
 void Window::On_Video_Playback_Load(void* data,
                                     Evas_Object*,
@@ -824,6 +833,17 @@ void Window::SetGoogleDataProxyHeaders() const {
   ewk_view_custom_header_add(web_view_, "accept", "image/webp,*/*;q=0.8");
 }
 
+void Window::OnScreenshotCaptured(Evas_Object* image, void* user_data) {
+  log_trace("%s", __PRETTY_FUNCTION__);
+  static int c = 1;
+  char buf[250];
+  sprintf(buf, "screenshot%d.png", c++);
+  if (evas_object_image_save(image, buf, 0, 0))
+    log_info("Screenshot image saved in %s", buf);
+  else
+    log_info("Screenshot image could not be saved");
+}
+
 void Window::Exit() const {
   browser_.Exit();
 }
index eaff40a..9289c70 100644 (file)
@@ -22,7 +22,7 @@ class Window {
   Window(Browser&, int width, int height, bool incognito);
   ~Window();
 
-  Evas_Object* GetEvasObject() const { return window_; };
+  Evas_Object* GetEvasObject() const { return window_; }
   Ewk_Settings* GetEwkSettings() const;
 
   void LoadURL(std::string url);
@@ -51,6 +51,7 @@ class Window {
   void ShowInspectorURL(const char* url);
   void SetGoogleDataProxyHeaders() const;
   void SetAutoRotate();
+  void TakeScreenshotAsync();
   void Exit() const;
 
   IdType Id() const;
@@ -67,6 +68,7 @@ class Window {
   static void OnNewWindowPolicyDecide(void*, Evas_Object*, void*);
   static void OnBackForwardListChanged(void*, Evas_Object*, void*);
   static void OnQuotaPermissionRequest(Evas_Object*, const Ewk_Quota_Permission_Request*, void*);
+  static void OnScreenshotCaptured(Evas_Object*, void*);
   static void OnUserMediaPermissionRequest(void* data, Evas_Object*, void* event_info);
   static void OnUserMediaPermissionDecisionTaken(bool decision, void* data);
   static void OnEnterFullScreenRequest(void*, Evas_Object*, void*);
index d2145f7..dd30408 100644 (file)
@@ -312,6 +312,9 @@ Evas_Object* WindowUI::CreateExtraActionsMenu(Evas_Object* parent) {
   elm_ctxpopup_item_append(menu_, "Change page zoom level", NULL,
                            &WindowUI::OnShowZoomPopup, this);
 
+  elm_ctxpopup_item_append(menu_, "Take screenshot async", NULL,
+                           &WindowUI::OnTakeScreenshotAsync, this);
+
   elm_ctxpopup_item_append(menu_, "Quit", NULL, &WindowUI::Exit, this);
 
   return menu_;
@@ -669,6 +672,13 @@ void WindowUI::OnShowZoomPopup(void* data, Evas_Object* obj, void*) {
   thiz->CloseMenu();
 }
 
+void WindowUI::OnTakeScreenshotAsync(void* data, Evas_Object* obj, void*) {
+  log_trace("%s", __PRETTY_FUNCTION__);
+  WindowUI* thiz = static_cast<WindowUI*>(data);
+  thiz->window_.TakeScreenshotAsync();
+  thiz->CloseMenu();
+}
+
 void WindowUI::OnZoomChanged(void* data, Evas_Object* obj, void*) {
   log_trace("%s", __PRETTY_FUNCTION__);
   WindowUI* thiz = static_cast<WindowUI*>(data);
index 0a05495..b7a4ebf 100644 (file)
@@ -89,6 +89,7 @@ class WindowUI {
 
   static void ClosePopup(void* data, Evas_Object*, void*);
   static void OnShowZoomPopup(void* data, Evas_Object*, void*);
+  static void OnTakeScreenshotAsync(void* data, Evas_Object*, void*);
   static void OnZoomChanged(void* data, Evas_Object*, void*);
   static void OnRememberFormDataChange(void* data, Evas_Object*, void*);
   static void OnRememberPasswordChange(void* data, Evas_Object*, void*);