| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/android/dialog_overlay_impl.h" |
| |
| #include "content/browser/frame_host/render_frame_host_impl.h" |
| #include "content/browser/renderer_host/render_widget_host_view_base.h" |
| #include "content/browser/web_contents/web_contents_impl.h" |
| #include "content/public/browser/web_contents_delegate.h" |
| #include "gpu/ipc/common/gpu_surface_tracker.h" |
| #include "jni/DialogOverlayImpl_jni.h" |
| #include "ui/android/view_android_observer.h" |
| #include "ui/android/window_android.h" |
| |
| using base::android::AttachCurrentThread; |
| using base::android::JavaParamRef; |
| using base::android::ScopedJavaLocalRef; |
| |
| namespace content { |
| |
| static jlong JNI_DialogOverlayImpl_Init(JNIEnv* env, |
| const JavaParamRef<jobject>& obj, |
| jlong high, |
| jlong low, |
| jboolean power_efficient) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| RenderFrameHostImpl* rfhi = |
| content::RenderFrameHostImpl::FromOverlayRoutingToken( |
| base::UnguessableToken::Deserialize(high, low)); |
| |
| if (!rfhi) |
| return 0; |
| |
| // TODO(http://crbug.com/673886): Support overlay surfaces in VR using GVR |
| // reprojection video surface. |
| RenderWidgetHostViewBase* rwhvb = |
| static_cast<RenderWidgetHostViewBase*>(rfhi->GetView()); |
| if (rwhvb->IsInVR()) |
| return 0; |
| |
| WebContentsImpl* web_contents_impl = static_cast<WebContentsImpl*>( |
| content::WebContents::FromRenderFrameHost(rfhi)); |
| |
| // If the overlay would not be immediately used, fail the request. |
| if (!rfhi->IsCurrent() || web_contents_impl->IsHidden()) |
| return 0; |
| |
| // Dialog-based overlays are not supported for persistent video. |
| if (web_contents_impl->HasPersistentVideo()) |
| return 0; |
| |
| // If we require a power-efficient overlay, then approximate that with "is |
| // fullscreen". The reason is that we want to be somewhat sure that we don't |
| // have more layers than HWC can support, else SurfaceFlinger will fall back |
| // to GLES composition. In fullscreen mode, the android status bar is hidden, |
| // as is the nav bar (if present). The chrome activity surface also gets |
| // hidden when possible. |
| if (power_efficient && !web_contents_impl->IsFullscreen()) |
| return 0; |
| |
| return reinterpret_cast<jlong>( |
| new DialogOverlayImpl(obj, rfhi, web_contents_impl, power_efficient)); |
| } |
| |
| DialogOverlayImpl::DialogOverlayImpl(const JavaParamRef<jobject>& obj, |
| RenderFrameHostImpl* rfhi, |
| WebContents* web_contents, |
| bool power_efficient) |
| : WebContentsObserver(web_contents), |
| rfhi_(rfhi), |
| power_efficient_(power_efficient) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK(rfhi_); |
| |
| JNIEnv* env = AttachCurrentThread(); |
| obj_ = JavaObjectWeakGlobalRef(env, obj); |
| |
| web_contents->GetNativeView()->AddObserver(this); |
| |
| // Note that we're not allowed to call back into |obj| before it calls |
| // CompleteInit. However, the observer won't actually call us back until the |
| // token changes. As long as the java side calls us from the ui thread before |
| // returning, we won't send a callback before then. |
| } |
| |
| void DialogOverlayImpl::CompleteInit(JNIEnv* env, |
| const JavaParamRef<jobject>& obj) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| WebContentsDelegate* delegate = web_contents()->GetDelegate(); |
| |
| if (!delegate) { |
| Stop(); |
| return; |
| } |
| |
| // Note: It's ok to call SetOverlayMode() directly here, because there can be |
| // at most one overlay alive at the time. This logic needs to be updated if |
| // ever AndroidOverlayProviderImpl.MAX_OVERLAYS > 1. |
| delegate->SetOverlayMode(true); |
| |
| // Send the initial token, if there is one. The observer will notify us about |
| // changes only. |
| if (auto* window = web_contents()->GetNativeView()->GetWindowAndroid()) { |
| ScopedJavaLocalRef<jobject> token = window->GetWindowToken(); |
| if (!token.is_null()) |
| Java_DialogOverlayImpl_onWindowToken(env, obj, token); |
| // else we will send one if we get a callback from ViewAndroid. |
| } |
| } |
| |
| DialogOverlayImpl::~DialogOverlayImpl() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| } |
| |
| void DialogOverlayImpl::Stop() { |
| UnregisterForTokensIfNeeded(); |
| |
| JNIEnv* env = AttachCurrentThread(); |
| ScopedJavaLocalRef<jobject> obj = obj_.get(env); |
| if (!obj.is_null()) |
| Java_DialogOverlayImpl_onDismissed(env, obj); |
| |
| obj_.reset(); |
| } |
| |
| void DialogOverlayImpl::Destroy(JNIEnv* env, const JavaParamRef<jobject>& obj) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| UnregisterForTokensIfNeeded(); |
| // We delete soon since this might be part of an onDismissed callback. |
| BrowserThread::DeleteSoon(BrowserThread::UI, FROM_HERE, this); |
| } |
| |
| void DialogOverlayImpl::GetCompositorOffset( |
| JNIEnv* env, |
| const base::android::JavaParamRef<jobject>& obj, |
| const base::android::JavaParamRef<jobject>& rect) { |
| gfx::Point point = |
| web_contents()->GetNativeView()->GetLocationOfContainerViewInWindow(); |
| |
| Java_DialogOverlayImpl_receiveCompositorOffset(env, rect, point.x(), |
| point.y()); |
| } |
| |
| void DialogOverlayImpl::UnregisterForTokensIfNeeded() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (!rfhi_) |
| return; |
| |
| // We clear overlay mode here rather than in Destroy(), because we may have |
| // been called via a WebContentsDestroyed() event, and this might be the last |
| // opportunity we have to access web_contents(). |
| WebContentsDelegate* delegate = web_contents()->GetDelegate(); |
| if (delegate) |
| delegate->SetOverlayMode(false); |
| |
| web_contents()->GetNativeView()->RemoveObserver(this); |
| rfhi_ = nullptr; |
| } |
| |
| void DialogOverlayImpl::RenderFrameDeleted(RenderFrameHost* render_frame_host) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (render_frame_host == rfhi_) |
| Stop(); |
| } |
| |
| void DialogOverlayImpl::RenderFrameHostChanged(RenderFrameHost* old_host, |
| RenderFrameHost* new_host) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (old_host == rfhi_) |
| Stop(); |
| } |
| |
| void DialogOverlayImpl::FrameDeleted(RenderFrameHost* render_frame_host) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (render_frame_host == rfhi_) |
| Stop(); |
| } |
| |
| void DialogOverlayImpl::WasHidden() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| Stop(); |
| } |
| |
| void DialogOverlayImpl::WebContentsDestroyed() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| Stop(); |
| } |
| |
| void DialogOverlayImpl::DidToggleFullscreenModeForTab(bool entered_fullscreen, |
| bool will_cause_resize) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| // If the caller doesn't care about power-efficient overlays, then don't send |
| // any callbacks about state change. |
| if (!power_efficient_) |
| return; |
| |
| JNIEnv* env = AttachCurrentThread(); |
| ScopedJavaLocalRef<jobject> obj = obj_.get(env); |
| if (!obj.is_null()) |
| Java_DialogOverlayImpl_onPowerEfficientState(env, obj, entered_fullscreen); |
| } |
| |
| void DialogOverlayImpl::OnAttachedToWindow() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| JNIEnv* env = AttachCurrentThread(); |
| |
| ScopedJavaLocalRef<jobject> token; |
| |
| if (auto* window = web_contents()->GetNativeView()->GetWindowAndroid()) |
| token = window->GetWindowToken(); |
| |
| ScopedJavaLocalRef<jobject> obj = obj_.get(env); |
| if (!obj.is_null()) |
| Java_DialogOverlayImpl_onWindowToken(env, obj, token); |
| } |
| |
| void DialogOverlayImpl::OnDetachedFromWindow() { |
| JNIEnv* env = AttachCurrentThread(); |
| ScopedJavaLocalRef<jobject> obj = obj_.get(env); |
| if (!obj.is_null()) |
| Java_DialogOverlayImpl_onWindowToken(env, obj, nullptr); |
| } |
| |
| static jint JNI_DialogOverlayImpl_RegisterSurface( |
| JNIEnv* env, |
| const base::android::JavaParamRef<jclass>& jcaller, |
| const JavaParamRef<jobject>& surface) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| return gpu::GpuSurfaceTracker::Get()->AddSurfaceForNativeWidget( |
| gpu::GpuSurfaceTracker::SurfaceRecord(gfx::kNullAcceleratedWidget, |
| surface.obj())); |
| } |
| |
| static void JNI_DialogOverlayImpl_UnregisterSurface( |
| JNIEnv* env, |
| const base::android::JavaParamRef<jclass>& jcaller, |
| jint surface_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| gpu::GpuSurfaceTracker::Get()->RemoveSurface(surface_id); |
| } |
| |
| static ScopedJavaLocalRef<jobject> |
| JNI_DialogOverlayImpl_LookupSurfaceForTesting( |
| JNIEnv* env, |
| const base::android::JavaParamRef<jclass>& jcaller, |
| jint surfaceId) { |
| gl::ScopedJavaSurface surface = |
| gpu::GpuSurfaceTracker::Get()->AcquireJavaSurface(surfaceId); |
| return ScopedJavaLocalRef<jobject>(surface.j_surface()); |
| } |
| |
| } // namespace content |