| // Copyright 2017 The Chromium Authors |
| // 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/renderer_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/android/content_jni_headers/DialogOverlayImpl_jni.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/browser/web_contents_delegate.h" |
| #include "content/public/common/content_client.h" |
| #include "gpu/ipc/common/gpu_surface_tracker.h" |
| #include "media/mojo/mojom/android_overlay.mojom.h" |
| #include "mojo/public/cpp/bindings/sync_call_restrictions.h" |
| #include "third_party/abseil-cpp/absl/types/variant.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); |
| |
| std::optional<base::UnguessableToken> token = |
| base::UnguessableToken::Deserialize(high, low); |
| if (!token.has_value()) { |
| return 0; |
| } |
| |
| RenderFrameHostImpl* rfhi = |
| content::RenderFrameHostImpl::FromOverlayRoutingToken(token.value()); |
| |
| if (!rfhi) |
| return 0; |
| |
| // If the RenderFrameHost does not have a live RenderFrame, immediately bail |
| // out: not only is there nothing to do, the `RenderFrameDeleted()` |
| // notification to clean up the overlay would never be called. |
| if (!rfhi->IsRenderFrameLive()) |
| 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->IsActive() || !web_contents_impl || web_contents_impl->IsHidden()) |
| return 0; |
| |
| // Dialog-based overlays are not supported for persistent video. |
| if (web_contents_impl->has_persistent_video()) |
| 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; |
| |
| bool observe_container_view = |
| GetContentClient() |
| ->browser() |
| ->ShouldObserveContainerViewLocationForDialogOverlays(); |
| |
| return reinterpret_cast<jlong>(new DialogOverlayImpl( |
| obj, rfhi, web_contents_impl, power_efficient, observe_container_view)); |
| } |
| |
| DialogOverlayImpl::DialogOverlayImpl(const JavaParamRef<jobject>& obj, |
| RenderFrameHostImpl* rfhi, |
| WebContents* web_contents, |
| bool power_efficient, |
| bool observe_container_view) |
| : WebContentsObserver(web_contents), |
| rfhi_(rfhi), |
| power_efficient_(power_efficient), |
| observed_window_android_(false), |
| observe_container_view_(observe_container_view) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK(rfhi_); |
| |
| JNIEnv* env = AttachCurrentThread(); |
| obj_ = JavaObjectWeakGlobalRef(env, obj); |
| |
| // Make sure RenderFrameDeleted will be called on RFH and thus we will clean |
| // up. |
| CHECK(rfhi_->IsRenderFrameLive()); |
| 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); |
| |
| Java_DialogOverlayImpl_onWebContents(env, obj, |
| web_contents()->GetJavaWebContents()); |
| |
| // Send the initial token, if there is one. The observer will notify us about |
| // changes only. |
| if (auto* window = web_contents()->GetNativeView()->GetWindowAndroid()) { |
| RegisterWindowObserverIfNeeded(window); |
| Java_DialogOverlayImpl_onWindowAndroid(env, obj, window->GetJavaObject()); |
| } |
| |
| // Pass up a reference to the container view so we can observe its location. |
| // The observer will notify us if there is none yet. |
| StartObservingContainerView(); |
| } |
| |
| DialogOverlayImpl::~DialogOverlayImpl() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| } |
| |
| void DialogOverlayImpl::Stop() { |
| UnregisterCallbacksIfNeeded(); |
| |
| 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); |
| UnregisterCallbacksIfNeeded(); |
| // We delete soon since this might be part of an onDismissed callback. |
| GetUIThreadTaskRunner({})->DeleteSoon(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::UnregisterCallbacksIfNeeded() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (!rfhi_) |
| return; |
| |
| // No need to track the container view location anymore. |
| StopObservingContainerView(); |
| |
| // 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); |
| if (observed_window_android_) { |
| auto* window_android = web_contents()->GetNativeView()->GetWindowAndroid(); |
| if (window_android) |
| window_android->RemoveObserver(this); |
| observed_window_android_ = 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::OnVisibilityChanged(content::Visibility visibility) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (visibility == content::Visibility::HIDDEN) |
| Stop(); |
| } |
| |
| void DialogOverlayImpl::OnRootWindowVisibilityChanged(bool visible) { |
| if (!visible) |
| 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(); |
| |
| auto* window = web_contents()->GetNativeView()->GetWindowAndroid(); |
| if (window) |
| RegisterWindowObserverIfNeeded(window); |
| |
| ScopedJavaLocalRef<jobject> obj = obj_.get(env); |
| if (!obj.is_null()) |
| Java_DialogOverlayImpl_onWindowAndroid(env, obj, window->GetJavaObject()); |
| |
| StartObservingContainerView(); |
| } |
| |
| void DialogOverlayImpl::OnDetachedFromWindow() { |
| JNIEnv* env = AttachCurrentThread(); |
| ScopedJavaLocalRef<jobject> obj = obj_.get(env); |
| if (!obj.is_null()) |
| Java_DialogOverlayImpl_onWindowAndroid(env, obj, nullptr); |
| Stop(); |
| } |
| |
| void DialogOverlayImpl::RegisterWindowObserverIfNeeded( |
| ui::WindowAndroid* window) { |
| if (!observed_window_android_) { |
| observed_window_android_ = true; |
| window->AddObserver(this); |
| } |
| } |
| |
| void DialogOverlayImpl::StartObservingContainerView() { |
| ObserveContainerViewIfNeeded( |
| web_contents()->GetNativeView()->GetContainerView()); |
| } |
| |
| void DialogOverlayImpl::StopObservingContainerView() { |
| ObserveContainerViewIfNeeded(/*container_view=*/nullptr); |
| } |
| |
| void DialogOverlayImpl::ObserveContainerViewIfNeeded( |
| const ScopedJavaLocalRef<jobject>& container_view) { |
| if (!observe_container_view_) |
| return; |
| |
| JNIEnv* env = AttachCurrentThread(); |
| ScopedJavaLocalRef<jobject> obj = obj_.get(env); |
| if (!obj.is_null()) |
| Java_DialogOverlayImpl_observeContainerView(env, obj, container_view); |
| } |
| |
| // Helper class that has permission to talk to SyncCallRestrictions. Rather |
| // than friend the function directly, which has an odd signature, friend a class |
| // that knows how to do the work. |
| class AndroidOverlaySyncHelper { |
| public: |
| static void MakeSyncCall(media::mojom::AndroidOverlayClient* remote) { |
| mojo::SyncCallRestrictions::ScopedAllowSyncCall scoped_allow; |
| remote->OnSynchronouslyDestroyed(); |
| } |
| }; |
| |
| static void JNI_DialogOverlayImpl_NotifyDestroyedSynchronously( |
| JNIEnv* env, |
| jlong message_pipe_handle) { |
| mojo::MessagePipeHandle handle(message_pipe_handle); |
| mojo::ScopedMessagePipeHandle scoped_handle(handle); |
| mojo::Remote<media::mojom::AndroidOverlayClient> remote( |
| mojo::PendingRemote<media::mojom::AndroidOverlayClient>( |
| std::move(scoped_handle), |
| media::mojom::AndroidOverlayClient::Version_)); |
| // This prevents crashes, though it's unclear how we'd have a null remote. |
| // https://crbug.com/1155313 . |
| if (!remote.is_bound()) |
| return; |
| AndroidOverlaySyncHelper::MakeSyncCall(remote.get()); |
| // Note that we don't take back the mojo message pipe. We let it close when |
| // `remote` goes out of scope. |
| } |
| |
| static jint JNI_DialogOverlayImpl_RegisterSurface( |
| JNIEnv* env, |
| const JavaParamRef<jobject>& surface) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| return gpu::GpuSurfaceTracker::Get()->AddSurfaceForNativeWidget( |
| gpu::GpuSurfaceTracker::SurfaceRecord( |
| gl::ScopedJavaSurface(surface, /*auto_release=*/false), |
| /*can_be_used_with_surface_control=*/false)); |
| } |
| |
| static void JNI_DialogOverlayImpl_UnregisterSurface( |
| JNIEnv* env, |
| jint surface_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| gpu::GpuSurfaceTracker::Get()->RemoveSurface(surface_id); |
| } |
| |
| static ScopedJavaLocalRef<jobject> |
| JNI_DialogOverlayImpl_LookupSurfaceForTesting( |
| JNIEnv* env, |
| jint surfaceId) { |
| bool can_be_used_with_surface_control = false; |
| auto surface_variant = gpu::GpuSurfaceTracker::Get()->AcquireJavaSurface( |
| surfaceId, &can_be_used_with_surface_control); |
| if (!absl::holds_alternative<gl::ScopedJavaSurface>(surface_variant)) { |
| return nullptr; |
| } |
| return ScopedJavaLocalRef<jobject>( |
| absl::get<gl::ScopedJavaSurface>(surface_variant).j_surface()); |
| } |
| |
| } // namespace content |