// Copyright 2013 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 "android_webview/browser/in_process_view_renderer.h" #include #include "android_webview/browser/aw_gl_surface.h" #include "android_webview/browser/scoped_app_gl_state_restore.h" #include "android_webview/common/aw_switches.h" #include "android_webview/public/browser/draw_gl.h" #include "android_webview/public/browser/draw_sw.h" #include "base/android/jni_android.h" #include "base/auto_reset.h" #include "base/command_line.h" #include "base/debug/trace_event.h" #include "base/lazy_instance.h" #include "base/logging.h" #include "base/strings/string_number_conversions.h" #include "base/strings/stringprintf.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/web_contents.h" #include "content/public/common/content_switches.h" #include "gpu/command_buffer/service/in_process_command_buffer.h" #include "third_party/skia/include/core/SkBitmap.h" #include "third_party/skia/include/core/SkBitmapDevice.h" #include "third_party/skia/include/core/SkCanvas.h" #include "third_party/skia/include/core/SkGraphics.h" #include "third_party/skia/include/core/SkPicture.h" #include "third_party/skia/include/utils/SkCanvasStateUtils.h" #include "ui/gfx/skia_util.h" #include "ui/gfx/transform.h" #include "ui/gfx/vector2d_conversions.h" #include "ui/gfx/vector2d_f.h" using base::android::AttachCurrentThread; using base::android::JavaRef; using base::android::ScopedJavaLocalRef; using content::BrowserThread; namespace android_webview { namespace { const void* kUserDataKey = &kUserDataKey; class UserData : public content::WebContents::Data { public: UserData(InProcessViewRenderer* ptr) : instance_(ptr) {} virtual ~UserData() { instance_->WebContentsGone(); } static InProcessViewRenderer* GetInstance(content::WebContents* contents) { if (!contents) return NULL; UserData* data = reinterpret_cast( contents->GetUserData(kUserDataKey)); return data ? data->instance_ : NULL; } private: InProcessViewRenderer* instance_; }; bool RasterizeIntoBitmap(JNIEnv* env, const JavaRef& jbitmap, int scroll_x, int scroll_y, const InProcessViewRenderer::RenderMethod& renderer) { DCHECK(jbitmap.obj()); AndroidBitmapInfo bitmap_info; if (AndroidBitmap_getInfo(env, jbitmap.obj(), &bitmap_info) < 0) { LOG(ERROR) << "Error getting java bitmap info."; return false; } void* pixels = NULL; if (AndroidBitmap_lockPixels(env, jbitmap.obj(), &pixels) < 0) { LOG(ERROR) << "Error locking java bitmap pixels."; return false; } bool succeeded; { SkBitmap bitmap; bitmap.setConfig(SkBitmap::kARGB_8888_Config, bitmap_info.width, bitmap_info.height, bitmap_info.stride); bitmap.setPixels(pixels); SkBitmapDevice device(bitmap); SkCanvas canvas(&device); canvas.translate(-scroll_x, -scroll_y); succeeded = renderer.Run(&canvas); } if (AndroidBitmap_unlockPixels(env, jbitmap.obj()) < 0) { LOG(ERROR) << "Error unlocking java bitmap pixels."; return false; } return succeeded; } class ScopedPixelAccess { public: ScopedPixelAccess(JNIEnv* env, jobject java_canvas) { AwDrawSWFunctionTable* sw_functions = BrowserViewRenderer::GetAwDrawSWFunctionTable(); pixels_ = sw_functions ? sw_functions->access_pixels(env, java_canvas) : NULL; } ~ScopedPixelAccess() { if (pixels_) BrowserViewRenderer::GetAwDrawSWFunctionTable()->release_pixels(pixels_); } AwPixelInfo* pixels() { return pixels_; } private: AwPixelInfo* pixels_; DISALLOW_IMPLICIT_CONSTRUCTORS(ScopedPixelAccess); }; bool HardwareEnabled() { static bool g_hw_enabled = !CommandLine::ForCurrentProcess()->HasSwitch( switches::kDisableWebViewGLMode); return g_hw_enabled; } // Provides software rendering functions from the Android glue layer. // Allows preventing extra copies of data when rendering. AwDrawSWFunctionTable* g_sw_draw_functions = NULL; const int64 kFallbackTickTimeoutInMilliseconds = 20; // Used to calculate memory and resource allocation. Determined experimentally. size_t g_memory_multiplier = 10; size_t g_num_gralloc_limit = 150; const size_t kBytesPerPixel = 4; const size_t kMemoryAllocationStep = 5 * 1024 * 1024; class ScopedAllowGL { public: ScopedAllowGL(); ~ScopedAllowGL(); static bool IsAllowed() { return BrowserThread::CurrentlyOn(BrowserThread::UI) && allow_gl; } private: static bool allow_gl; DISALLOW_COPY_AND_ASSIGN(ScopedAllowGL); }; ScopedAllowGL::ScopedAllowGL() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DCHECK(!allow_gl); allow_gl = true; } ScopedAllowGL::~ScopedAllowGL() { allow_gl = false; } bool ScopedAllowGL::allow_gl = false; base::LazyInstance::Leaky g_view_renderer_manager; void RequestProcessGLOnUIThread() { if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&RequestProcessGLOnUIThread)); return; } InProcessViewRenderer* renderer = static_cast( g_view_renderer_manager.Get().GetMostRecentlyDrawn()); if (!renderer || !renderer->RequestProcessGL()) { LOG(ERROR) << "Failed to request GL process. Deadlock likely: " << !!renderer; } } } // namespace // Called from different threads! static void ScheduleGpuWork() { if (ScopedAllowGL::IsAllowed()) { gpu::InProcessCommandBuffer::ProcessGpuWorkOnCurrentThread(); } else { RequestProcessGLOnUIThread(); } } // static void BrowserViewRenderer::SetAwDrawSWFunctionTable( AwDrawSWFunctionTable* table) { g_sw_draw_functions = table; gpu::InProcessCommandBuffer::SetScheduleCallback( base::Bind(&ScheduleGpuWork)); } // static AwDrawSWFunctionTable* BrowserViewRenderer::GetAwDrawSWFunctionTable() { return g_sw_draw_functions; } InProcessViewRenderer::InProcessViewRenderer( BrowserViewRenderer::Client* client, JavaHelper* java_helper, content::WebContents* web_contents) : client_(client), java_helper_(java_helper), web_contents_(web_contents), compositor_(NULL), is_paused_(false), view_visible_(false), window_visible_(false), attached_to_window_(false), dip_scale_(0.0), page_scale_factor_(1.0), on_new_picture_enable_(false), compositor_needs_continuous_invalidate_(false), block_invalidates_(false), width_(0), height_(0), hardware_initialized_(false), hardware_failed_(false), last_egl_context_(NULL), manager_key_(g_view_renderer_manager.Get().NullKey()) { CHECK(web_contents_); web_contents_->SetUserData(kUserDataKey, new UserData(this)); content::SynchronousCompositor::SetClientForWebContents(web_contents_, this); // Currently the logic in this class relies on |compositor_| remaining NULL // until the DidInitializeCompositor() call, hence it is not set here. } InProcessViewRenderer::~InProcessViewRenderer() { CHECK(web_contents_); content::SynchronousCompositor::SetClientForWebContents(web_contents_, NULL); web_contents_->SetUserData(kUserDataKey, NULL); NoLongerExpectsDrawGL(); DCHECK(web_contents_ == NULL); // WebContentsGone should have been called. } void InProcessViewRenderer::NoLongerExpectsDrawGL() { GLViewRendererManager& mru = g_view_renderer_manager.Get(); if (manager_key_ != mru.NullKey()) { mru.NoLongerExpectsDrawGL(manager_key_); manager_key_ = mru.NullKey(); } } // static InProcessViewRenderer* InProcessViewRenderer::FromWebContents( content::WebContents* contents) { return UserData::GetInstance(contents); } void InProcessViewRenderer::WebContentsGone() { web_contents_ = NULL; compositor_ = NULL; } // static void InProcessViewRenderer::CalculateTileMemoryPolicy() { CommandLine* cl = CommandLine::ForCurrentProcess(); if (cl->HasSwitch(switches::kTileMemoryMultiplier)) { std::string string_value = cl->GetSwitchValueASCII(switches::kTileMemoryMultiplier); int int_value = 0; if (base::StringToInt(string_value, &int_value) && int_value >= 2 && int_value <= 50) { g_memory_multiplier = int_value; } } if (cl->HasSwitch(switches::kNumGrallocBuffersPerWebview)) { std::string string_value = cl->GetSwitchValueASCII(switches::kNumGrallocBuffersPerWebview); int int_value = 0; if (base::StringToInt(string_value, &int_value) && int_value >= 50 && int_value <= 500) { g_num_gralloc_limit = int_value; } } const char kDefaultTileSize[] = "384"; if (!cl->HasSwitch(switches::kDefaultTileWidth)) cl->AppendSwitchASCII(switches::kDefaultTileWidth, kDefaultTileSize); if (!cl->HasSwitch(switches::kDefaultTileHeight)) cl->AppendSwitchASCII(switches::kDefaultTileHeight, kDefaultTileSize); } bool InProcessViewRenderer::RequestProcessGL() { return client_->RequestDrawGL(NULL); } void InProcessViewRenderer::TrimMemory(int level) { // Constants from Android ComponentCallbacks2. enum { TRIM_MEMORY_RUNNING_LOW = 10, TRIM_MEMORY_UI_HIDDEN = 20, TRIM_MEMORY_BACKGROUND = 40, }; // Not urgent enough. TRIM_MEMORY_UI_HIDDEN is treated specially because // it does not indicate memory pressure, but merely that the app is // backgrounded. if (level < TRIM_MEMORY_RUNNING_LOW || level == TRIM_MEMORY_UI_HIDDEN) return; // Nothing to drop. if (!attached_to_window_ || !hardware_initialized_ || !compositor_) return; // Do not release resources on view we expect to get DrawGL soon. if (level < TRIM_MEMORY_BACKGROUND) { client_->UpdateGlobalVisibleRect(); if (view_visible_ && window_visible_ && !cached_global_visible_rect_.IsEmpty()) { return; } } if (!eglGetCurrentContext()) { NOTREACHED(); return; } // Just set the memory limit to 0 and drop all tiles. This will be reset to // normal levels in the next DrawGL call. content::SynchronousCompositorMemoryPolicy policy; policy.bytes_limit = 0; policy.num_resources_limit = 0; if (memory_policy_ == policy) return; TRACE_EVENT0("android_webview", "InProcessViewRenderer::TrimMemory"); ScopedAppGLStateRestore state_restore( ScopedAppGLStateRestore::MODE_RESOURCE_MANAGEMENT); gpu::InProcessCommandBuffer::ProcessGpuWorkOnCurrentThread(); ScopedAllowGL allow_gl; SetMemoryPolicy(policy); ForceFakeCompositeSW(); } void InProcessViewRenderer::SetMemoryPolicy( content::SynchronousCompositorMemoryPolicy& new_policy) { if (memory_policy_ == new_policy) return; memory_policy_ = new_policy; compositor_->SetMemoryPolicy(memory_policy_); } void InProcessViewRenderer::UpdateCachedGlobalVisibleRect() { client_->UpdateGlobalVisibleRect(); } bool InProcessViewRenderer::OnDraw(jobject java_canvas, bool is_hardware_canvas, const gfx::Vector2d& scroll, const gfx::Rect& clip) { scroll_at_start_of_frame_ = scroll; if (is_hardware_canvas && attached_to_window_ && HardwareEnabled()) { // We should be performing a hardware draw here. If we don't have the // comositor yet or if RequestDrawGL fails, it means we failed this draw and // thus return false here to clear to background color for this draw. return compositor_ && client_->RequestDrawGL(java_canvas); } // Perform a software draw return DrawSWInternal(java_canvas, clip); } bool InProcessViewRenderer::InitializeHwDraw() { TRACE_EVENT0("android_webview", "InitializeHwDraw"); DCHECK(!gl_surface_); gl_surface_ = new AwGLSurface; hardware_failed_ = !compositor_->InitializeHwDraw(gl_surface_); hardware_initialized_ = true; if (hardware_failed_) gl_surface_ = NULL; return !hardware_failed_; } void InProcessViewRenderer::DrawGL(AwDrawGLInfo* draw_info) { TRACE_EVENT0("android_webview", "InProcessViewRenderer::DrawGL"); manager_key_ = g_view_renderer_manager.Get().DidDrawGL(manager_key_, this); // We need to watch if the current Android context has changed and enforce // a clean-up in the compositor. EGLContext current_context = eglGetCurrentContext(); if (!current_context) { TRACE_EVENT_INSTANT0( "android_webview", "EarlyOut_NullEGLContext", TRACE_EVENT_SCOPE_THREAD); return; } ScopedAppGLStateRestore state_restore(ScopedAppGLStateRestore::MODE_DRAW); gpu::InProcessCommandBuffer::ProcessGpuWorkOnCurrentThread(); ScopedAllowGL allow_gl; if (!attached_to_window_) { TRACE_EVENT_INSTANT0( "android_webview", "EarlyOut_NotAttached", TRACE_EVENT_SCOPE_THREAD); return; } if (draw_info->mode == AwDrawGLInfo::kModeProcess) { TRACE_EVENT_INSTANT0( "android_webview", "EarlyOut_ModeProcess", TRACE_EVENT_SCOPE_THREAD); return; } if (compositor_ && !hardware_initialized_) { if (InitializeHwDraw()) { last_egl_context_ = current_context; } else { TRACE_EVENT_INSTANT0( "android_webview", "EarlyOut_HwInitFail", TRACE_EVENT_SCOPE_THREAD); LOG(ERROR) << "WebView hardware initialization failed"; return; } } UpdateCachedGlobalVisibleRect(); if (cached_global_visible_rect_.IsEmpty()) { TRACE_EVENT_INSTANT0("android_webview", "EarlyOut_EmptyVisibleRect", TRACE_EVENT_SCOPE_THREAD); return; } if (last_egl_context_ != current_context) { // TODO(boliu): Handle context lost TRACE_EVENT_INSTANT0( "android_webview", "EGLContextChanged", TRACE_EVENT_SCOPE_THREAD); } if (!compositor_) { TRACE_EVENT_INSTANT0( "android_webview", "EarlyOut_NoCompositor", TRACE_EVENT_SCOPE_THREAD); return; } // DrawGL may be called without OnDraw, so cancel |fallback_tick_| here as // well just to be safe. fallback_tick_.Cancel(); // Update memory budget. This will no-op in compositor if the policy has not // changed since last draw. content::SynchronousCompositorMemoryPolicy policy; policy.bytes_limit = g_memory_multiplier * kBytesPerPixel * cached_global_visible_rect_.width() * cached_global_visible_rect_.height(); // Round up to a multiple of kMemoryAllocationStep. policy.bytes_limit = (policy.bytes_limit / kMemoryAllocationStep + 1) * kMemoryAllocationStep; policy.num_resources_limit = g_num_gralloc_limit; SetMemoryPolicy(policy); DCHECK(gl_surface_); gl_surface_->SetBackingFrameBufferObject( state_restore.framebuffer_binding_ext()); gfx::Transform transform; transform.matrix().setColMajorf(draw_info->transform); transform.Translate(scroll_at_start_of_frame_.x(), scroll_at_start_of_frame_.y()); gfx::Rect clip_rect(draw_info->clip_left, draw_info->clip_top, draw_info->clip_right - draw_info->clip_left, draw_info->clip_bottom - draw_info->clip_top); // Assume we always draw the full visible rect if we are drawing into a layer. bool drew_full_visible_rect = true; gfx::Rect viewport_rect; if (!draw_info->is_layer) { viewport_rect = cached_global_visible_rect_; clip_rect.Intersect(viewport_rect); drew_full_visible_rect = clip_rect.Contains(viewport_rect); } else { viewport_rect = clip_rect; } block_invalidates_ = true; // TODO(joth): Check return value. compositor_->DemandDrawHw(gfx::Size(draw_info->width, draw_info->height), transform, viewport_rect, clip_rect, state_restore.stencil_enabled()); block_invalidates_ = false; gl_surface_->ResetBackingFrameBufferObject(); EnsureContinuousInvalidation(draw_info, !drew_full_visible_rect); } void InProcessViewRenderer::SetGlobalVisibleRect( const gfx::Rect& visible_rect) { cached_global_visible_rect_ = visible_rect; } bool InProcessViewRenderer::DrawSWInternal(jobject java_canvas, const gfx::Rect& clip) { if (clip.IsEmpty()) { TRACE_EVENT_INSTANT0( "android_webview", "EarlyOut_EmptyClip", TRACE_EVENT_SCOPE_THREAD); return true; } if (!compositor_) { TRACE_EVENT_INSTANT0( "android_webview", "EarlyOut_NoCompositor", TRACE_EVENT_SCOPE_THREAD); return false; } return RenderViaAuxilaryBitmapIfNeeded( java_canvas, java_helper_, scroll_at_start_of_frame_, clip, base::Bind(&InProcessViewRenderer::CompositeSW, base::Unretained(this)), web_contents_); } // static bool InProcessViewRenderer::RenderViaAuxilaryBitmapIfNeeded( jobject java_canvas, BrowserViewRenderer::JavaHelper* java_helper, const gfx::Vector2d& scroll_correction, const gfx::Rect& clip, InProcessViewRenderer::RenderMethod render_source, void* owner_key) { TRACE_EVENT0("android_webview", "InProcessViewRenderer::RenderViaAuxilaryBitmapIfNeeded"); JNIEnv* env = AttachCurrentThread(); ScopedPixelAccess auto_release_pixels(env, java_canvas); AwPixelInfo* pixels = auto_release_pixels.pixels(); if (pixels && pixels->state) { skia::RefPtr canvas = skia::AdoptRef( SkCanvasStateUtils::CreateFromCanvasState(pixels->state)); // Workarounds for http://crbug.com/271096: SW draw only supports // translate & scale transforms, and a simple rectangular clip. if (canvas && (!canvas->getTotalClip().isRect() || (canvas->getTotalMatrix().getType() & ~(SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask)))) { canvas.clear(); } if (canvas) { canvas->translate(scroll_correction.x(), scroll_correction.y()); return render_source.Run(canvas.get()); } } // Render into an auxiliary bitmap if pixel info is not available. ScopedJavaLocalRef jcanvas(env, java_canvas); TRACE_EVENT0("android_webview", "RenderToAuxBitmap"); ScopedJavaLocalRef jbitmap(java_helper->CreateBitmap( env, clip.width(), clip.height(), jcanvas, owner_key)); if (!jbitmap.obj()) { TRACE_EVENT_INSTANT0("android_webview", "EarlyOut_BitmapAllocFail", TRACE_EVENT_SCOPE_THREAD); return false; } if (!RasterizeIntoBitmap(env, jbitmap, clip.x() - scroll_correction.x(), clip.y() - scroll_correction.y(), render_source)) { TRACE_EVENT_INSTANT0("android_webview", "EarlyOut_RasterizeFail", TRACE_EVENT_SCOPE_THREAD); return false; } java_helper->DrawBitmapIntoCanvas(env, jbitmap, jcanvas, clip.x(), clip.y()); return true; } skia::RefPtr InProcessViewRenderer::CapturePicture(int width, int height) { TRACE_EVENT0("android_webview", "InProcessViewRenderer::CapturePicture"); // Return empty Picture objects for empty SkPictures. skia::RefPtr picture = skia::AdoptRef(new SkPicture); if (width <= 0 || height <= 0) { return picture; } // Reset scroll back to the origin, will go back to the old // value when scroll_reset is out of scope. base::AutoReset scroll_reset(&scroll_offset_dip_, gfx::Vector2d()); SkCanvas* rec_canvas = picture->beginRecording(width, height, 0); if (compositor_) CompositeSW(rec_canvas); picture->endRecording(); return picture; } void InProcessViewRenderer::EnableOnNewPicture(bool enabled) { on_new_picture_enable_ = enabled; EnsureContinuousInvalidation(NULL, false); } void InProcessViewRenderer::SetIsPaused(bool paused) { TRACE_EVENT_INSTANT1("android_webview", "InProcessViewRenderer::SetIsPaused", TRACE_EVENT_SCOPE_THREAD, "paused", paused); is_paused_ = paused; EnsureContinuousInvalidation(NULL, false); } void InProcessViewRenderer::SetViewVisibility(bool view_visible) { TRACE_EVENT_INSTANT1("android_webview", "InProcessViewRenderer::SetViewVisibility", TRACE_EVENT_SCOPE_THREAD, "view_visible", view_visible); view_visible_ = view_visible; } void InProcessViewRenderer::SetWindowVisibility(bool window_visible) { TRACE_EVENT_INSTANT1("android_webview", "InProcessViewRenderer::SetWindowVisibility", TRACE_EVENT_SCOPE_THREAD, "window_visible", window_visible); window_visible_ = window_visible; EnsureContinuousInvalidation(NULL, false); } void InProcessViewRenderer::OnSizeChanged(int width, int height) { TRACE_EVENT_INSTANT2("android_webview", "InProcessViewRenderer::OnSizeChanged", TRACE_EVENT_SCOPE_THREAD, "width", width, "height", height); width_ = width; height_ = height; } void InProcessViewRenderer::OnAttachedToWindow(int width, int height) { TRACE_EVENT2("android_webview", "InProcessViewRenderer::OnAttachedToWindow", "width", width, "height", height); attached_to_window_ = true; width_ = width; height_ = height; } void InProcessViewRenderer::OnDetachedFromWindow() { TRACE_EVENT0("android_webview", "InProcessViewRenderer::OnDetachedFromWindow"); NoLongerExpectsDrawGL(); if (hardware_initialized_) { DCHECK(compositor_); ScopedAppGLStateRestore state_restore( ScopedAppGLStateRestore::MODE_RESOURCE_MANAGEMENT); gpu::InProcessCommandBuffer::ProcessGpuWorkOnCurrentThread(); ScopedAllowGL allow_gl; compositor_->ReleaseHwDraw(); hardware_initialized_ = false; } gl_surface_ = NULL; attached_to_window_ = false; } bool InProcessViewRenderer::IsAttachedToWindow() { return attached_to_window_; } bool InProcessViewRenderer::IsVisible() { // Ignore |window_visible_| if |attached_to_window_| is false. return view_visible_ && (!attached_to_window_ || window_visible_); } gfx::Rect InProcessViewRenderer::GetScreenRect() { return gfx::Rect(client_->GetLocationOnScreen(), gfx::Size(width_, height_)); } void InProcessViewRenderer::DidInitializeCompositor( content::SynchronousCompositor* compositor) { TRACE_EVENT0("android_webview", "InProcessViewRenderer::DidInitializeCompositor"); DCHECK(compositor && compositor_ == NULL); compositor_ = compositor; hardware_initialized_ = false; hardware_failed_ = false; } void InProcessViewRenderer::DidDestroyCompositor( content::SynchronousCompositor* compositor) { TRACE_EVENT0("android_webview", "InProcessViewRenderer::DidDestroyCompositor"); DCHECK(compositor_ == compositor); // This can fail if Apps call destroy while the webview is still attached // to the view tree. This is an illegal operation that will lead to leaks. // Log for now. Consider a proper fix if this becomes a problem. LOG_IF(ERROR, hardware_initialized_) << "Destroy called before OnDetachedFromWindow. May Leak GL resources"; compositor_ = NULL; } void InProcessViewRenderer::SetContinuousInvalidate(bool invalidate) { if (compositor_needs_continuous_invalidate_ == invalidate) return; TRACE_EVENT_INSTANT1("android_webview", "InProcessViewRenderer::SetContinuousInvalidate", TRACE_EVENT_SCOPE_THREAD, "invalidate", invalidate); compositor_needs_continuous_invalidate_ = invalidate; EnsureContinuousInvalidation(NULL, false); } void InProcessViewRenderer::SetDipScale(float dip_scale) { dip_scale_ = dip_scale; CHECK(dip_scale_ > 0); } gfx::Vector2d InProcessViewRenderer::max_scroll_offset() const { DCHECK_GT(dip_scale_, 0); return gfx::ToCeiledVector2d(gfx::ScaleVector2d( max_scroll_offset_dip_, dip_scale_ * page_scale_factor_)); } void InProcessViewRenderer::ScrollTo(gfx::Vector2d scroll_offset) { gfx::Vector2d max_offset = max_scroll_offset(); gfx::Vector2dF scroll_offset_dip; // To preserve the invariant that scrolling to the maximum physical pixel // value also scrolls to the maximum dip pixel value we transform the physical // offset into the dip offset by using a proportion (instead of dividing by // dip_scale * page_scale_factor). if (max_offset.x()) { scroll_offset_dip.set_x((scroll_offset.x() * max_scroll_offset_dip_.x()) / max_offset.x()); } if (max_offset.y()) { scroll_offset_dip.set_y((scroll_offset.y() * max_scroll_offset_dip_.y()) / max_offset.y()); } DCHECK_LE(0, scroll_offset_dip.x()); DCHECK_LE(0, scroll_offset_dip.y()); DCHECK_LE(scroll_offset_dip.x(), max_scroll_offset_dip_.x()); DCHECK_LE(scroll_offset_dip.y(), max_scroll_offset_dip_.y()); if (scroll_offset_dip_ == scroll_offset_dip) return; scroll_offset_dip_ = scroll_offset_dip; if (compositor_) compositor_->DidChangeRootLayerScrollOffset(); } void InProcessViewRenderer::DidUpdateContent() { if (on_new_picture_enable_) client_->OnNewPicture(); } void InProcessViewRenderer::SetMaxRootLayerScrollOffset( gfx::Vector2dF new_value_dip) { DCHECK_GT(dip_scale_, 0); max_scroll_offset_dip_ = new_value_dip; DCHECK_LE(0, max_scroll_offset_dip_.x()); DCHECK_LE(0, max_scroll_offset_dip_.y()); client_->SetMaxContainerViewScrollOffset(max_scroll_offset()); } void InProcessViewRenderer::SetTotalRootLayerScrollOffset( gfx::Vector2dF scroll_offset_dip) { // TOOD(mkosiba): Add a DCHECK to say that this does _not_ get called during // DrawGl when http://crbug.com/249972 is fixed. if (scroll_offset_dip_ == scroll_offset_dip) return; scroll_offset_dip_ = scroll_offset_dip; gfx::Vector2d max_offset = max_scroll_offset(); gfx::Vector2d scroll_offset; // For an explanation as to why this is done this way see the comment in // InProcessViewRenderer::ScrollTo. if (max_scroll_offset_dip_.x()) { scroll_offset.set_x((scroll_offset_dip.x() * max_offset.x()) / max_scroll_offset_dip_.x()); } if (max_scroll_offset_dip_.y()) { scroll_offset.set_y((scroll_offset_dip.y() * max_offset.y()) / max_scroll_offset_dip_.y()); } DCHECK(0 <= scroll_offset.x()); DCHECK(0 <= scroll_offset.y()); DCHECK(scroll_offset.x() <= max_offset.x()); DCHECK(scroll_offset.y() <= max_offset.y()); client_->ScrollContainerViewTo(scroll_offset); } gfx::Vector2dF InProcessViewRenderer::GetTotalRootLayerScrollOffset() { return scroll_offset_dip_; } bool InProcessViewRenderer::IsExternalFlingActive() const { return client_->IsFlingActive(); } void InProcessViewRenderer::SetRootLayerPageScaleFactor( float page_scale_factor) { page_scale_factor_ = page_scale_factor; DCHECK_GT(page_scale_factor_, 0); client_->SetPageScaleFactor(page_scale_factor); } void InProcessViewRenderer::SetRootLayerScrollableSize( gfx::SizeF scrollable_size) { client_->SetContentsSize(scrollable_size); } void InProcessViewRenderer::DidOverscroll( gfx::Vector2dF accumulated_overscroll, gfx::Vector2dF latest_overscroll_delta, gfx::Vector2dF current_fling_velocity) { DCHECK(current_fling_velocity.IsZero()); const float physical_pixel_scale = dip_scale_ * page_scale_factor_; if (accumulated_overscroll == latest_overscroll_delta) overscroll_rounding_error_ = gfx::Vector2dF(); gfx::Vector2dF scaled_overscroll_delta = gfx::ScaleVector2d(latest_overscroll_delta, physical_pixel_scale); gfx::Vector2d rounded_overscroll_delta = gfx::ToRoundedVector2d( scaled_overscroll_delta + overscroll_rounding_error_); overscroll_rounding_error_ = scaled_overscroll_delta - rounded_overscroll_delta; client_->DidOverscroll(rounded_overscroll_delta); } void InProcessViewRenderer::EnsureContinuousInvalidation( AwDrawGLInfo* draw_info, bool invalidate_ignore_compositor) { // This method should be called again when any of these conditions change. bool need_invalidate = compositor_needs_continuous_invalidate_ || invalidate_ignore_compositor; if (!need_invalidate || block_invalidates_) return; if (draw_info) { draw_info->dirty_left = cached_global_visible_rect_.x(); draw_info->dirty_top = cached_global_visible_rect_.y(); draw_info->dirty_right = cached_global_visible_rect_.right(); draw_info->dirty_bottom = cached_global_visible_rect_.bottom(); draw_info->status_mask |= AwDrawGLInfo::kStatusMaskDraw; } else { client_->PostInvalidate(); } bool throttle_fallback_tick = (is_paused_ && !on_new_picture_enable_) || (attached_to_window_ && !window_visible_); if (throttle_fallback_tick) return; block_invalidates_ = compositor_needs_continuous_invalidate_; // Unretained here is safe because the callback is cancelled when // |fallback_tick_| is destroyed. fallback_tick_.Reset(base::Bind(&InProcessViewRenderer::FallbackTickFired, base::Unretained(this))); // No need to reschedule fallback tick if compositor does not need to be // ticked. This can happen if this is reached because // invalidate_ignore_compositor is true. if (compositor_needs_continuous_invalidate_) { BrowserThread::PostDelayedTask( BrowserThread::UI, FROM_HERE, fallback_tick_.callback(), base::TimeDelta::FromMilliseconds( kFallbackTickTimeoutInMilliseconds)); } } void InProcessViewRenderer::FallbackTickFired() { TRACE_EVENT1("android_webview", "InProcessViewRenderer::FallbackTickFired", "compositor_needs_continuous_invalidate_", compositor_needs_continuous_invalidate_); // This should only be called if OnDraw or DrawGL did not come in time, which // means block_invalidates_ must still be true. DCHECK(block_invalidates_); if (compositor_needs_continuous_invalidate_ && compositor_) ForceFakeCompositeSW(); } void InProcessViewRenderer::ForceFakeCompositeSW() { DCHECK(compositor_); SkBitmapDevice device(SkBitmap::kARGB_8888_Config, 1, 1); SkCanvas canvas(&device); CompositeSW(&canvas); } bool InProcessViewRenderer::CompositeSW(SkCanvas* canvas) { DCHECK(compositor_); fallback_tick_.Cancel(); block_invalidates_ = true; bool result = compositor_->DemandDrawSw(canvas); block_invalidates_ = false; EnsureContinuousInvalidation(NULL, false); return result; } std::string InProcessViewRenderer::ToString(AwDrawGLInfo* draw_info) const { std::string str; base::StringAppendF(&str, "is_paused: %d ", is_paused_); base::StringAppendF(&str, "view_visible: %d ", view_visible_); base::StringAppendF(&str, "window_visible: %d ", window_visible_); base::StringAppendF(&str, "dip_scale: %f ", dip_scale_); base::StringAppendF(&str, "page_scale_factor: %f ", page_scale_factor_); base::StringAppendF(&str, "compositor_needs_continuous_invalidate: %d ", compositor_needs_continuous_invalidate_); base::StringAppendF(&str, "block_invalidates: %d ", block_invalidates_); base::StringAppendF(&str, "view width height: [%d %d] ", width_, height_); base::StringAppendF(&str, "attached_to_window: %d ", attached_to_window_); base::StringAppendF(&str, "hardware_initialized: %d ", hardware_initialized_); base::StringAppendF(&str, "hardware_failed: %d ", hardware_failed_); base::StringAppendF(&str, "global visible rect: %s ", cached_global_visible_rect_.ToString().c_str()); base::StringAppendF(&str, "scroll_at_start_of_frame: %s ", scroll_at_start_of_frame_.ToString().c_str()); base::StringAppendF( &str, "scroll_offset_dip: %s ", scroll_offset_dip_.ToString().c_str()); base::StringAppendF(&str, "overscroll_rounding_error_: %s ", overscroll_rounding_error_.ToString().c_str()); base::StringAppendF( &str, "on_new_picture_enable: %d ", on_new_picture_enable_); if (draw_info) { base::StringAppendF(&str, "clip left top right bottom: [%d %d %d %d] ", draw_info->clip_left, draw_info->clip_top, draw_info->clip_right, draw_info->clip_bottom); base::StringAppendF(&str, "surface width height: [%d %d] ", draw_info->width, draw_info->height); base::StringAppendF(&str, "is_layer: %d ", draw_info->is_layer); } return str; } } // namespace android_webview