ma1 pushed to branch tor-browser-153.1.0esr-16.0-1 at The Tor Project / Applications / Tor Browser Commits: bb66338d by giorga at 2026-08-16T00:41:44+02:00 Bug 1842361 - Download confirmation notification can be overlaid over other origins. r=android-reviewers,jdelorenzo Differential Revision: https://phabricator.services.mozilla.com/D309062 - - - - - 7655bbd2 by Marcin Koziński at 2026-08-16T00:41:44+02:00 Bug 1978587 - Forward onEnterAnimationComplete to interested fragments r=android-reviewers,twhite Differential Revision: https://phabricator.services.mozilla.com/D306954 - - - - - d13ffd02 by Marcin Koziński at 2026-08-16T00:56:58+02:00 Bug 2049034 - Add an initial delay to download button in Fenix download dialog a=pascalc Original Revision: https://phabricator.services.mozilla.com/D309334 Differential Revision: https://phabricator.services.mozilla.com/D310049 - - - - - 4e27f4b0 by Jamie Nicol at 2026-08-17T08:17:03+02:00 Bug 2049810 - Allocate HardwareBuffer for screen pixels request in parent process. r=gfx-reviewers,lsalzman Differential Revision: https://phabricator.services.mozilla.com/D308519 - - - - - a6ed37e7 by owlishDeveloper at 2026-08-17T10:29:07+02:00 Bug 2055683 - IPC improvement a=pascalc Original Revision: https://phabricator.services.mozilla.com/D314463 Differential Revision: https://phabricator.services.mozilla.com/D314798 - - - - - 24 changed files: - gfx/layers/ipc/PUiCompositorController.ipdl - gfx/layers/ipc/UiCompositorControllerChild.cpp - gfx/layers/ipc/UiCompositorControllerChild.h - gfx/layers/ipc/UiCompositorControllerParent.cpp - gfx/layers/ipc/UiCompositorControllerParent.h - gfx/layers/wr/WebRenderBridgeParent.cpp - gfx/layers/wr/WebRenderBridgeParent.h - gfx/webrender_bindings/RenderCompositor.h - gfx/webrender_bindings/RenderCompositorOGLSWGL.cpp - gfx/webrender_bindings/RenderCompositorOGLSWGL.h - gfx/webrender_bindings/RendererOGL.cpp - gfx/webrender_bindings/RendererOGL.h - gfx/webrender_bindings/WebRenderAPI.cpp - gfx/webrender_bindings/WebRenderAPI.h - mobile/android/android-components/components/feature/downloads/src/main/java/mozilla/components/feature/downloads/DownloadsFeature.kt - mobile/android/android-components/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadsFeatureTest.kt - mobile/android/android-components/components/feature/sitepermissions/src/main/java/mozilla/components/feature/sitepermissions/SitePermissionsDialogFragment.kt - + mobile/android/android-components/components/support/utils/src/main/java/mozilla/components/support/utils/OnEnterAnimationCompleteListener.kt - mobile/android/components/geckoview/GeckoViewContentChannelParent.cpp - mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/robots/DownloadRobot.kt - mobile/android/fenix/app/src/main/java/org/mozilla/fenix/addons/AddonPopupBaseFragment.kt - mobile/android/fenix/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt - mobile/android/fenix/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivity.kt - mobile/android/fenix/app/src/main/java/org/mozilla/fenix/downloads/RenameAndChangeLocationDialogFragment.kt Changes: ===================================== gfx/layers/ipc/PUiCompositorController.ipdl ===================================== @@ -3,9 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ using mozilla::gfx::IntRect from "mozilla/gfx/Rect.h"; -using mozilla::gfx::IntSize from "mozilla/gfx/Point.h"; using mozilla::layers::CompositorScrollUpdate from "mozilla/layers/CompositorScrollUpdate.h"; -using mozilla::void_t from "mozilla/ipc/IPCCore.h"; include "mozilla/GfxMessageUtils.h"; include "mozilla/layers/LayersMessageUtils.h"; @@ -34,15 +32,14 @@ parent: async MaxToolbarHeight(int32_t aHeight); async FixedBottomOffset(int32_t aOffset); async DefaultClearColor(uint32_t aColor); - async RequestScreenPixels(uint64_t aRequestId, IntRect aSourceRect, IntSize aDestSize); + async RequestScreenPixels(uint64_t aRequestId, IntRect aSourceRect, + FileDescriptor aHardwareBuffer); async EnableLayerUpdateNotifications(bool aEnable); child: async ToolbarAnimatorMessageFromCompositor(int32_t aMessage); async NotifyCompositorScrollUpdate(CompositorScrollUpdate aUpdate); - // Returns when the child side has finished using the HardwareBuffer, - // indicating that the parent side can now release it. - async ScreenPixels(uint64_t aRequestId, FileDescriptor? aHardwareBuffer, FileDescriptor? aAcquireFence) - returns (void_t ok); + async ScreenPixels(uint64_t aRequestId, bool aSuccess, + FileDescriptor? aAcquireFence); }; } // layers ===================================== gfx/layers/ipc/UiCompositorControllerChild.cpp ===================================== @@ -148,19 +148,36 @@ UiCompositorControllerChild::RequestScreenPixels(gfx::IntRect aSourceRect, // We only support one request at a time. If an old request is still // outstanding when a new request is made, just reject the old request. - if (mScreenPixelsPromise) { - mScreenPixelsPromise.extract().second->Reject(NS_ERROR_ABORT, __func__); + if (mScreenPixelsRequest) { + mScreenPixelsRequest.extract().mPromise->Reject(NS_ERROR_ABORT, __func__); + } + + RefPtr<layers::AndroidHardwareBuffer> hardwareBuffer = + layers::AndroidHardwareBuffer::Create(aDestSize, + gfx::SurfaceFormat::R8G8B8A8); + if (!hardwareBuffer) { + return ScreenPixelsPromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, + __func__); + } + + UniqueFileHandle bufferFd = hardwareBuffer->SerializeToFileDescriptor(); + if (!bufferFd) { + return ScreenPixelsPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); } static uint64_t nextRequestId = 0; const uint64_t requestId = nextRequestId++; auto promise = MakeRefPtr<ScreenPixelsPromise::Private>(__func__); - // Using synchronous dispatch ensures we are done using the hardware buffer - // prior to RecvScreenPixels calling aResolver which in turn will cause the - // hardware buffer on the parent side to be released. - promise->UseSynchronousTaskDispatch(__func__); - mScreenPixelsPromise.emplace(requestId, promise); - (void)SendRequestScreenPixels(requestId, aSourceRect, aDestSize); + mScreenPixelsRequest.emplace(ScreenPixelsRequest{ + .mRequestId = requestId, + .mHardwareBuffer = hardwareBuffer, + .mPromise = promise, + }); + if (!SendRequestScreenPixels(requestId, aSourceRect, + ipc::FileDescriptor(std::move(bufferFd)))) { + mScreenPixelsRequest.extract().mPromise->Reject(NS_ERROR_NOT_AVAILABLE, + __func__); + } return promise; } #endif @@ -213,8 +230,8 @@ void UiCompositorControllerChild::ActorDestroy(ActorDestroyReason aWhy) { mParent = nullptr; #ifdef MOZ_WIDGET_ANDROID - if (mScreenPixelsPromise) { - mScreenPixelsPromise->second->Reject(NS_ERROR_ABORT, __func__); + if (mScreenPixelsRequest) { + mScreenPixelsRequest->mPromise->Reject(NS_ERROR_ABORT, __func__); } #endif if (mProcessToken) { @@ -258,39 +275,28 @@ UiCompositorControllerChild::RecvNotifyCompositorScrollUpdate( } mozilla::ipc::IPCResult UiCompositorControllerChild::RecvScreenPixels( - uint64_t aRequestId, Maybe<ipc::FileDescriptor>&& aHardwareBuffer, - Maybe<ipc::FileDescriptor>&& aAcquireFence, - ScreenPixelsResolver&& aResolver) { + uint64_t aRequestId, bool aSuccess, + Maybe<ipc::FileDescriptor>&& aAcquireFence) { #if defined(MOZ_WIDGET_ANDROID) - if (!mScreenPixelsPromise || mScreenPixelsPromise->first != aRequestId) { + if (!mScreenPixelsRequest || mScreenPixelsRequest->mRequestId != aRequestId) { // Response is for an outdated request whose promise will have already been // rejected. Just ignore it. return IPC_OK(); } - RefPtr<layers::AndroidHardwareBuffer> hardwareBuffer; - if (aHardwareBuffer) { - hardwareBuffer = - layers::AndroidHardwareBuffer::DeserializeFromFileDescriptor( - aHardwareBuffer->TakePlatformHandle()); + auto request = mScreenPixelsRequest.extract(); + if (!aSuccess) { + request.mPromise->Reject(NS_ERROR_FAILURE, __func__); + return IPC_OK(); } - if (hardwareBuffer && aAcquireFence) { - hardwareBuffer->SetAcquireFence(aAcquireFence->TakePlatformHandle()); + + if (aAcquireFence) { + request.mHardwareBuffer->SetAcquireFence( + aAcquireFence->TakePlatformHandle()); } - // Note this is resolved synchronously, ensuring we have finished using the - // hardware buffer as soon as this call returns (and importantly before the - // aResolver call below). - mScreenPixelsPromise.extract().second->Resolve(std::move(hardwareBuffer), - __func__); + request.mPromise->Resolve(std::move(request.mHardwareBuffer), __func__); #endif // defined(MOZ_WIDGET_ANDROID) - // Notify the parent side that it can drop its reference to the hardware - // buffer. In theory this could be done as soon as we have called - // DeserializeFromFileDescriptor(). However, on certain Exynos devices we have - // seen that releasing the original hardware buffer frees the underlying - // resource even if a reference obtained via (de)serialization remains alive. - // See bug 2017901. - aResolver(void_t{}); return IPC_OK(); } ===================================== gfx/layers/ipc/UiCompositorControllerChild.h ===================================== @@ -84,9 +84,8 @@ class UiCompositorControllerChild final mozilla::ipc::IPCResult RecvNotifyCompositorScrollUpdate( const CompositorScrollUpdate& aUpdate); mozilla::ipc::IPCResult RecvScreenPixels( - uint64_t aRequestId, Maybe<ipc::FileDescriptor>&& aHardwareBuffer, - Maybe<ipc::FileDescriptor>&& aAcquireFence, - ScreenPixelsResolver&& aResolver); + uint64_t aRequestId, bool aSuccess, + Maybe<ipc::FileDescriptor>&& aAcquireFence); private: explicit UiCompositorControllerChild(const uint64_t& aProcessToken, @@ -118,8 +117,12 @@ class UiCompositorControllerChild final // RecvScreenPixels() altogether. Unfortunately, however, we cannot chain to a // promise returned from an IPDL function on the Android UI thread, as the // thread does not support direct task dispatch. - Maybe<std::pair<uint64_t, RefPtr<ScreenPixelsPromise::Private>>> - mScreenPixelsPromise; + struct ScreenPixelsRequest { + uint64_t mRequestId; + RefPtr<layers::AndroidHardwareBuffer> mHardwareBuffer; + RefPtr<ScreenPixelsPromise::Private> mPromise; + }; + Maybe<ScreenPixelsRequest> mScreenPixelsRequest; #endif // Should only be set when compositor is in process. ===================================== gfx/layers/ipc/UiCompositorControllerParent.cpp ===================================== @@ -139,39 +139,39 @@ mozilla::ipc::IPCResult UiCompositorControllerParent::RecvDefaultClearColor( } mozilla::ipc::IPCResult UiCompositorControllerParent::RecvRequestScreenPixels( - uint64_t aRequestId, gfx::IntRect aSourceRect, gfx::IntSize aDestSize) { + uint64_t aRequestId, gfx::IntRect aSourceRect, + ipc::FileDescriptor&& aHardwareBuffer) { #if defined(MOZ_WIDGET_ANDROID) + RefPtr<AndroidHardwareBuffer> hardwareBuffer = + AndroidHardwareBuffer::DeserializeFromFileDescriptor( + aHardwareBuffer.TakePlatformHandle()); + if (!hardwareBuffer) { + (void)SendScreenPixels(aRequestId, false, Nothing()); + return IPC_OK(); + } + LayerTreeState* state = CompositorBridgeParent::GetLayerTreeState(mRootLayerTreeId); if (state && state->mWrBridge) { - state->mWrBridge->RequestScreenPixels(aSourceRect, aDestSize) + state->mWrBridge->RequestScreenPixels(aSourceRect, hardwareBuffer) ->Then( GetCurrentSerialEventTarget(), __func__, - [target = RefPtr{this}, - aRequestId](RefPtr<AndroidHardwareBuffer> aHardwareBuffer) { - UniqueFileHandle bufferFd = - aHardwareBuffer->SerializeToFileDescriptor(); + [target = RefPtr{this}, aRequestId, + hardwareBuffer = std::move(hardwareBuffer)](Ok) { UniqueFileHandle fenceFd = - aHardwareBuffer->GetAndResetAcquireFence(); - target - ->SendScreenPixels( - aRequestId, - aHardwareBuffer - ? Some(ipc::FileDescriptor(std::move(bufferFd))) - : Nothing(), - fenceFd ? Some(ipc::FileDescriptor(std::move(fenceFd))) - : Nothing()) - // Ensure the hardware buffer remains alive until child side - // has finished using it. - ->Then(GetCurrentSerialEventTarget(), __func__, - [aHardwareBuffer]( - ScreenPixelsPromise::ResolveOrRejectValue&&) {}); + hardwareBuffer->GetAndResetAcquireFence(); + (void)target->SendScreenPixels( + aRequestId, true, + fenceFd ? Some(ipc::FileDescriptor(std::move(fenceFd))) + : Nothing()); }, [target = RefPtr{this}, aRequestId](nsresult aError) { - (void)target->SendScreenPixels(aRequestId, Nothing(), Nothing()); + (void)target->SendScreenPixels(aRequestId, false, Nothing()); }); state->mWrBridge->ScheduleForcedGenerateFrame(wr::RenderReasons::OTHER); + } else { + (void)SendScreenPixels(aRequestId, false, Nothing()); } #endif // defined(MOZ_WIDGET_ANDROID) ===================================== gfx/layers/ipc/UiCompositorControllerParent.h ===================================== @@ -41,9 +41,9 @@ class UiCompositorControllerParent final mozilla::ipc::IPCResult RecvMaxToolbarHeight(const int32_t& aHeight); mozilla::ipc::IPCResult RecvFixedBottomOffset(const int32_t& aOffset); mozilla::ipc::IPCResult RecvDefaultClearColor(const uint32_t& aColor); - mozilla::ipc::IPCResult RecvRequestScreenPixels(uint64_t aRequestId, - gfx::IntRect aSourceRect, - gfx::IntSize aDestSize); + mozilla::ipc::IPCResult RecvRequestScreenPixels( + uint64_t aRequestId, gfx::IntRect aSourceRect, + ipc::FileDescriptor&& aHardwareBuffer); mozilla::ipc::IPCResult RecvEnableLayerUpdateNotifications( const bool& aEnable); void ActorDestroy(ActorDestroyReason aWhy) override; ===================================== gfx/layers/wr/WebRenderBridgeParent.cpp ===================================== @@ -1945,8 +1945,8 @@ void WebRenderBridgeParent::UpdateBoolParameters() { #if defined(MOZ_WIDGET_ANDROID) RefPtr<WebRenderBridgeParent::ScreenPixelsPromise> -WebRenderBridgeParent::RequestScreenPixels(gfx::IntRect aSourceRect, - gfx::IntSize aDestSize) { +WebRenderBridgeParent::RequestScreenPixels( + gfx::IntRect aSourceRect, RefPtr<AndroidHardwareBuffer> aHardwareBuffer) { if (mDestroyed) { return ScreenPixelsPromise::CreateAndReject(NS_ERROR_ABORT, __func__); } @@ -1962,7 +1962,7 @@ WebRenderBridgeParent::RequestScreenPixels(gfx::IntRect aSourceRect, } mScreenPixelsRequest.emplace(ScreenPixelsRequest{ .mSourceRect = aSourceRect, - .mDestSize = aDestSize, + .mHardwareBuffer = std::move(aHardwareBuffer), .mPromise = new ScreenPixelsPromise::Private(__func__), }); return mScreenPixelsRequest->mPromise; @@ -1982,7 +1982,9 @@ void WebRenderBridgeParent::MaybeCaptureScreenPixels() { MOZ_ASSERT(cbp && !cbp->IsPaused()); # endif - mLateInit->mApi->RequestScreenPixels(request.mSourceRect, request.mDestSize) + mLateInit->mApi + ->RequestScreenPixels(request.mSourceRect, + std::move(request.mHardwareBuffer)) ->ChainTo(request.mPromise.forget(), __func__); } #endif ===================================== gfx/layers/wr/WebRenderBridgeParent.h ===================================== @@ -325,13 +325,13 @@ class WebRenderBridgeParent final : public PWebRenderBridgeParent, void BeginRecording(const TimeStamp& aRecordingStart); #if defined(MOZ_WIDGET_ANDROID) - using ScreenPixelsPromise = - MozPromise<RefPtr<layers::AndroidHardwareBuffer>, nsresult, true>; + using ScreenPixelsPromise = MozPromise<Ok, nsresult, true>; /** * Request a screengrab for android */ - RefPtr<ScreenPixelsPromise> RequestScreenPixels(gfx::IntRect aSourceRect, - gfx::IntSize aDestSize); + RefPtr<ScreenPixelsPromise> RequestScreenPixels( + gfx::IntRect aSourceRect, + RefPtr<layers::AndroidHardwareBuffer> aHardwareBuffer); #endif /** @@ -539,7 +539,7 @@ class WebRenderBridgeParent final : public PWebRenderBridgeParent, #if defined(MOZ_WIDGET_ANDROID) struct ScreenPixelsRequest { gfx::IntRect mSourceRect; - gfx::IntSize mDestSize; + RefPtr<layers::AndroidHardwareBuffer> mHardwareBuffer; RefPtr<ScreenPixelsPromise::Private> mPromise; }; Maybe<ScreenPixelsRequest> mScreenPixelsRequest; ===================================== gfx/webrender_bindings/RenderCompositor.h ===================================== @@ -235,7 +235,7 @@ class RenderCompositor { #ifdef MOZ_WIDGET_ANDROID virtual bool MaybeCaptureScreenPixels( const gfx::IntRect& aSourceRect, - RefPtr<layers::AndroidHardwareBuffer> aHardwareBuffer) { + layers::AndroidHardwareBuffer* aHardwareBuffer) { return false; } #endif ===================================== gfx/webrender_bindings/RenderCompositorOGLSWGL.cpp ===================================== @@ -315,7 +315,7 @@ bool RenderCompositorOGLSWGL::MaybeReadback( #ifdef MOZ_WIDGET_ANDROID bool RenderCompositorOGLSWGL::MaybeCaptureScreenPixels( const gfx::IntRect& aSourceRect, - RefPtr<layers::AndroidHardwareBuffer> aHardwareBuffer) { + layers::AndroidHardwareBuffer* aHardwareBuffer) { auto* const gl = GetGLContext(); gl::ScopedBindFramebuffer scopedBind(gl); ===================================== gfx/webrender_bindings/RenderCompositorOGLSWGL.h ===================================== @@ -59,7 +59,7 @@ class RenderCompositorOGLSWGL : public RenderCompositorLayersSWGL { #ifdef MOZ_WIDGET_ANDROID bool MaybeCaptureScreenPixels( const gfx::IntRect& aSourceRect, - RefPtr<layers::AndroidHardwareBuffer> aHardwareBuffer) override; + layers::AndroidHardwareBuffer* aHardwareBuffer) override; #endif private: ===================================== gfx/webrender_bindings/RendererOGL.cpp ===================================== @@ -469,7 +469,13 @@ Maybe<layers::FrameRecording> RendererOGL::EndRecording() { #ifdef MOZ_WIDGET_ANDROID RefPtr<RendererOGL::ScreenPixelsPromise> RendererOGL::RequestScreenPixels( - gfx::IntRect aSourceRect, gfx::IntSize aDestSize) { + gfx::IntRect aSourceRect, + RefPtr<layers::AndroidHardwareBuffer> aHardwareBuffer) { + if (!aHardwareBuffer) { + return ScreenPixelsPromise::CreateAndReject(NS_ERROR_ILLEGAL_VALUE, + __func__); + } + // If a new request is made we no longer care about the result of the previous // one, so just reject it if it exists. if (mPendingScreenPixelsRequest) { @@ -478,7 +484,7 @@ RefPtr<RendererOGL::ScreenPixelsPromise> RendererOGL::RequestScreenPixels( } mPendingScreenPixelsRequest.emplace(ScreenPixelsRequest{ .mSourceRect = aSourceRect, - .mDestSize = aDestSize, + .mHardwareBuffer = std::move(aHardwareBuffer), .mPromise = new ScreenPixelsPromise::Private(__func__), }); return mPendingScreenPixelsRequest->mPromise; @@ -491,19 +497,16 @@ void RendererOGL::MaybeCaptureScreenPixels() { auto request = mPendingScreenPixelsRequest.extract(); - const RefPtr<layers::AndroidHardwareBuffer> hardwareBuffer = - layers::AndroidHardwareBuffer::Create(request.mDestSize, - gfx::SurfaceFormat::R8G8B8A8); - if (mCompositor->MaybeCaptureScreenPixels(request.mSourceRect, - hardwareBuffer)) { - request.mPromise->Resolve(hardwareBuffer, __func__); + request.mHardwareBuffer)) { + request.mPromise->Resolve(Ok{}, __func__); return; } auto* const gle = gl::GLContextEGL::Cast(gl()); const auto& egl = gle->mEgl; - gl::ScopedEGLImageForAndroidHardwareBuffer eglImage(gle, hardwareBuffer); + gl::ScopedEGLImageForAndroidHardwareBuffer eglImage(gle, + request.mHardwareBuffer); gl::ScopedBindFramebuffer scopedBind(gl()); gl::ScopedRenderbuffer rb(gl()); gl()->fBindRenderbuffer(LOCAL_GL_RENDERBUFFER, rb); @@ -517,7 +520,7 @@ void RendererOGL::MaybeCaptureScreenPixels() { request.mSourceRect.x, mCompositor->GetBufferSize().height - request.mSourceRect.y, request.mSourceRect.width, -request.mSourceRect.height); - const auto destRect = gfx::IntRect({}, hardwareBuffer->mSize); + const auto destRect = gfx::IntRect({}, request.mHardwareBuffer->mSize); gl()->BindReadFB(0); gl()->BindDrawFB(fb.FB()); gl()->fBlitFramebuffer(srcRect.x, srcRect.y, srcRect.XMost(), srcRect.YMost(), @@ -529,12 +532,12 @@ void RendererOGL::MaybeCaptureScreenPixels() { egl->fCreateSync(LOCAL_EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr)) { auto fence = UniqueFileHandle(egl->fDupNativeFenceFDANDROID(sync)); if (fence) { - hardwareBuffer->SetAcquireFence(std::move(fence)); + request.mHardwareBuffer->SetAcquireFence(std::move(fence)); } egl->fDestroySync(sync); } - request.mPromise->Resolve(hardwareBuffer, __func__); + request.mPromise->Resolve(Ok{}, __func__); } #endif ===================================== gfx/webrender_bindings/RendererOGL.h ===================================== @@ -93,12 +93,12 @@ class RendererOGL { Maybe<layers::FrameRecording> EndRecording(); #ifdef MOZ_WIDGET_ANDROID - using ScreenPixelsPromise = - MozPromise<RefPtr<layers::AndroidHardwareBuffer>, nsresult, true>; + using ScreenPixelsPromise = MozPromise<Ok, nsresult, true>; // Captures the pixels for the next rendered frame. Returns a promise that // resolves once the pixels are captured. - RefPtr<ScreenPixelsPromise> RequestScreenPixels(gfx::IntRect aSourceRect, - gfx::IntSize aDestSize); + RefPtr<ScreenPixelsPromise> RequestScreenPixels( + gfx::IntRect aSourceRect, + RefPtr<layers::AndroidHardwareBuffer> aHardwareBuffer); #endif /// This can be called on the render thread only. @@ -174,7 +174,7 @@ class RendererOGL { #ifdef MOZ_WIDGET_ANDROID struct ScreenPixelsRequest { gfx::IntRect mSourceRect; - gfx::IntSize mDestSize; + RefPtr<layers::AndroidHardwareBuffer> mHardwareBuffer; RefPtr<ScreenPixelsPromise::Private> mPromise; }; Maybe<ScreenPixelsRequest> mPendingScreenPixelsRequest; ===================================== gfx/webrender_bindings/WebRenderAPI.cpp ===================================== @@ -962,12 +962,17 @@ RefPtr<WebRenderAPI::EndRecordingPromise> WebRenderAPI::EndRecording() { #ifdef MOZ_WIDGET_ANDROID RefPtr<WebRenderAPI::ScreenPixelsPromise> WebRenderAPI::RequestScreenPixels( - gfx::IntRect aSourceRect, gfx::IntSize aDestSize) { + gfx::IntRect aSourceRect, + RefPtr<layers::AndroidHardwareBuffer> aHardwareBuffer) { class ScreenshotEvent final : public RendererEvent { public: - explicit ScreenshotEvent(gfx::IntRect aSourceRect, gfx::IntSize aDestSize, - RefPtr<ScreenPixelsPromise::Private> aPromise) - : mSourceRect(aSourceRect), mDestSize(aDestSize), mPromise(aPromise) { + explicit ScreenshotEvent( + gfx::IntRect aSourceRect, + RefPtr<layers::AndroidHardwareBuffer> aHardwareBuffer, + RefPtr<ScreenPixelsPromise::Private> aPromise) + : mSourceRect(aSourceRect), + mHardwareBuffer(std::move(aHardwareBuffer)), + mPromise(aPromise) { MOZ_COUNT_CTOR(ScreenshotEvent); } @@ -977,8 +982,9 @@ RefPtr<WebRenderAPI::ScreenPixelsPromise> WebRenderAPI::RequestScreenPixels( RendererOGL* const renderer = aRenderThread.GetRenderer(aWindowId); if (!renderer) { mPromise->Reject(NS_ERROR_FAILURE, __func__); + return; } - renderer->RequestScreenPixels(mSourceRect, mDestSize) + renderer->RequestScreenPixels(mSourceRect, std::move(mHardwareBuffer)) ->ChainTo(mPromise.forget(), __func__); } @@ -986,12 +992,13 @@ RefPtr<WebRenderAPI::ScreenPixelsPromise> WebRenderAPI::RequestScreenPixels( private: const gfx::IntRect mSourceRect; - const gfx::IntSize mDestSize; + RefPtr<layers::AndroidHardwareBuffer> mHardwareBuffer; RefPtr<ScreenPixelsPromise::Private> mPromise; }; auto promise = MakeRefPtr<ScreenPixelsPromise::Private>(__func__); - auto event = MakeUnique<ScreenshotEvent>(aSourceRect, aDestSize, promise); + auto event = MakeUnique<ScreenshotEvent>(aSourceRect, + std::move(aHardwareBuffer), promise); RenderThread::Get()->PostEvent(mId, std::move(event)); return promise; ===================================== gfx/webrender_bindings/WebRenderAPI.h ===================================== @@ -322,13 +322,13 @@ class WebRenderAPI final { RefPtr<EndRecordingPromise> EndRecording(); #ifdef MOZ_WIDGET_ANDROID - using ScreenPixelsPromise = - MozPromise<RefPtr<layers::AndroidHardwareBuffer>, nsresult, true>; + using ScreenPixelsPromise = MozPromise<Ok, nsresult, true>; // Queues a task to the render thread to capture screen pixels for the next // rendered frame. Returns a promise that resolves once the pixels are // captured. - RefPtr<ScreenPixelsPromise> RequestScreenPixels(gfx::IntRect aSourceRect, - gfx::IntSize aDestSize); + RefPtr<ScreenPixelsPromise> RequestScreenPixels( + gfx::IntRect aSourceRect, + RefPtr<layers::AndroidHardwareBuffer> aHardwareBuffer); #endif layers::RemoteTextureInfoList* GetPendingRemoteTextureInfoList(); ===================================== mobile/android/android-components/components/feature/downloads/src/main/java/mozilla/components/feature/downloads/DownloadsFeature.kt ===================================== @@ -122,6 +122,8 @@ value class OpenFileCallback(val value: () -> Unit) * manager is provided, a dialog will be shown before every download. * @property promptsStyling styling properties for the dialog. * @property onDownloadStartedListener a callback invoked when a download is started. + * @property dismissCustomFirstPartyDownloadDialog A callback invoked when the custom first party + * download dialog should be dismissed. * @property shouldForwardToThirdParties Indicates if downloads should be forward to third party apps, * if there are multiple apps a chooser dialog will shown. * @property customFirstPartyDownloadDialog An optional delegate for showing a dialog for a download @@ -145,6 +147,7 @@ class DownloadsFeature( private val fragmentManager: FragmentManager? = null, private val promptsStyling: PromptsStyling? = null, private val onDownloadStartedListener: ((String) -> Unit) = {}, + private val dismissCustomFirstPartyDownloadDialog: () -> Unit = {}, private val shouldForwardToThirdParties: () -> Boolean = { false }, private val customFirstPartyDownloadDialog: ( ( @@ -555,6 +558,7 @@ class DownloadsFeature( internal fun dismissAllDownloadDialogs() { findPreviousDownloadDialogFragment()?.dismiss() findPreviousAppDownloaderDialogFragment()?.dismiss() + dismissCustomFirstPartyDownloadDialog.invoke() } private val ActivityInfo.identifier: String get() = packageName + name ===================================== mobile/android/android-components/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadsFeatureTest.kt ===================================== @@ -1513,6 +1513,64 @@ class DownloadsFeatureTest { verify(cancelDownloadRequestUseCase).invoke(anyString(), anyString()) } + @Test + fun `GIVEN a custom download dialog is used WHEN dismissAllDownloadDialogs is called THEN the dialog is dismissed`() = runTest(testDispatcher) { + val dismissCustomDialog = mock<() -> Unit>() + val feature = DownloadsFeature( + testContext, + store, + useCases = DownloadsUseCases(store, mock()), + downloadFileUtils = FakeDownloadFileUtils(), + downloadManager = mock(), + mainDispatcher = testDispatcher, + dismissCustomFirstPartyDownloadDialog = dismissCustomDialog, + ) + + feature.dismissAllDownloadDialogs() + + verify(dismissCustomDialog).invoke() + } + + @Test + fun `GIVEN a custom download dialog is used WHEN navigating to another website THEN the dialog is dismissed`() = runTest(testDispatcher) { + val dismissCustomDialog = mock<() -> Unit>() + val downloadsUseCases = spy(DownloadsUseCases(store, mock())) + val cancelDownloadRequestUseCase = mock<CancelDownloadRequestUseCase>() + val download = DownloadState(url = "https://www.mozilla.org", sessionId = "test-tab") + store.dispatch(ContentAction.UpdateDownloadAction("test-tab", download = download)) + + doReturn(cancelDownloadRequestUseCase).`when`(downloadsUseCases).cancelDownloadRequest + + val feature = spy( + DownloadsFeature( + testContext, + store, + useCases = downloadsUseCases, + downloadFileUtils = FakeDownloadFileUtils(), + downloadManager = mock(), + mainDispatcher = testDispatcher, + dismissCustomFirstPartyDownloadDialog = dismissCustomDialog, + ), + ) + + doReturn(true).`when`(feature).processDownload(any(), any()) + + feature.start() + testDispatcher.scheduler.advanceUntilIdle() + + store.dispatch(ContentAction.UpdateDownloadAction("test-tab", download = download)) + testDispatcher.scheduler.advanceUntilIdle() + + grantPermissions() + + val tab = createTab("https://www.firefox.com") + store.dispatch(TabListAction.AddTabAction(tab, select = true)) + testDispatcher.scheduler.advanceUntilIdle() + + verify(feature).dismissAllDownloadDialogs() + verify(dismissCustomDialog).invoke() + } + @Test fun `ResolveInfo to DownloaderApps`() = runTest(testDispatcher) { val spyContext = spy(testContext) ===================================== mobile/android/android-components/components/feature/sitepermissions/src/main/java/mozilla/components/feature/sitepermissions/SitePermissionsDialogFragment.kt ===================================== @@ -32,6 +32,7 @@ import mozilla.components.support.base.log.logger.Logger import mozilla.components.support.ktx.android.content.appName import mozilla.components.support.ktx.kotlin.ifNullOrEmpty import mozilla.components.support.ktx.util.PromptAbuserDetector +import mozilla.components.support.utils.OnEnterAnimationCompleteListener internal const val KEY_SESSION_ID = "KEY_SESSION_ID" internal const val KEY_TITLE = "KEY_TITLE" @@ -51,7 +52,9 @@ private const val KEY_IS_NOTIFICATION_REQUEST = "KEY_IS_NOTIFICATION_REQUEST" private const val DEFAULT_VALUE = Int.MAX_VALUE private const val KEY_PERMISSION_ID = "KEY_PERMISSION_ID" -internal open class SitePermissionsDialogFragment : NoObscuredTouchesDialogFragment() { +internal open class SitePermissionsDialogFragment : + NoObscuredTouchesDialogFragment(), + OnEnterAnimationCompleteListener { private val logger = Logger("SitePermissionsDialogFragment") @@ -134,6 +137,11 @@ internal open class SitePermissionsDialogFragment : NoObscuredTouchesDialogFragm feature?.onDismiss(permissionRequestId, sessionId) } + override fun onEnterAnimationComplete() { + // Extend the positive button click delay. + promptAbuserDetector.updateJSDialogAbusedState() + } + private fun Dialog.setContainerView(rootView: View) { if (dialogShouldWidthMatchParent) { setContentView(rootView) ===================================== mobile/android/android-components/components/support/utils/src/main/java/mozilla/components/support/utils/OnEnterAnimationCompleteListener.kt ===================================== @@ -0,0 +1,16 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.support.utils + +/** + * Allows forwarding [android.app.Activity.onEnterAnimationComplete] to other classes + * (e.g. fragments) that want to participate in handling it. + */ +interface OnEnterAnimationCompleteListener { + /** + * Called when the Activity's entering animation has completed. + */ + fun onEnterAnimationComplete() +} ===================================== mobile/android/components/geckoview/GeckoViewContentChannelParent.cpp ===================================== @@ -159,6 +159,11 @@ bool GeckoViewContentChannelParent::Init( nsCOMPtr<nsIURI> uri = ipc::DeserializeURI(aArgs.uri()); + if (!uri || !uri->SchemeIs("content")) { + rv = NS_ERROR_UNKNOWN_PROTOCOL; + return false; + } + nsAutoCString remoteType; rv = GetRemoteType(remoteType); if (MOZ_UNLIKELY(NS_FAILED(rv))) { ===================================== mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/robots/DownloadRobot.kt ===================================== @@ -29,6 +29,7 @@ import androidx.test.espresso.intent.matcher.IntentMatchers import androidx.test.uiautomator.By import androidx.test.uiautomator.UiSelector import androidx.test.uiautomator.Until +import mozilla.components.support.ktx.util.PromptAbuserDetector import org.hamcrest.CoreMatchers.allOf import org.mozilla.fenix.R import org.mozilla.fenix.compose.snackbar.SNACKBAR_TEST_TAG @@ -257,7 +258,9 @@ class DownloadRobot(private val composeTestRule: ComposeTestRule) { class Transition(private val composeTestRule: ComposeTestRule) { fun clickDownload(composeTestRule: ComposeTestRule, interact: DownloadRobot.() -> Unit): Transition { Log.i(TAG, "clickDownload: Trying to click the \"Download\" download prompt button") + PromptAbuserDetector.validationsEnabled = false composeTestRule.downloadButton().performClick() + PromptAbuserDetector.validationsEnabled = true Log.i(TAG, "clickDownload: Clicked the \"Download\" download prompt button") DownloadRobot(composeTestRule).interact() ===================================== mobile/android/fenix/app/src/main/java/org/mozilla/fenix/addons/AddonPopupBaseFragment.kt ===================================== @@ -161,6 +161,10 @@ abstract class AddonPopupBaseFragment : onNeedToRequestPermissions = { permissions -> requestPermissions(permissions, REQUEST_CODE_DOWNLOAD_PERMISSIONS) }, + dismissCustomFirstPartyDownloadDialog = { + dismissRenameDialog() + downloadDialog?.dismiss() + }, customFirstPartyDownloadDialog = { currentDownloadState, _, positiveAction, negativeAction, _ -> run { if (canShowDownloadDialog()) { @@ -394,6 +398,13 @@ abstract class AddonPopupBaseFragment : return downloadDialog == null && !isRenameFragmentShowing } + private fun dismissRenameDialog() { + val renameDialog = childFragmentManager.findFragmentByTag( + RenameAndChangeLocationDialogFragment.RENAME_AND_CHANGE_LOCATION_DIALOG_TAG, + ) as? RenameAndChangeLocationDialogFragment + renameDialog?.dismissAllowingStateLoss() + } + /** * Forwards activity results to the [ActivityResultHandler] features. */ ===================================== mobile/android/fenix/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt ===================================== @@ -742,6 +742,10 @@ abstract class BaseBrowserFragment : onNeedToRequestPermissions = { permissions -> requestPermissions(permissions, REQUEST_CODE_DOWNLOAD_PERMISSIONS) }, + dismissCustomFirstPartyDownloadDialog = { + dismissRenameDialog() + dismissDownloadDialogs() + }, customFirstPartyDownloadDialog = { currentDownloadState, fileNameIfAlreadyDownloaded, ===================================== mobile/android/fenix/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivity.kt ===================================== @@ -11,6 +11,7 @@ import androidx.annotation.VisibleForTesting import androidx.core.net.toUri import mozilla.components.browser.state.selector.findCustomTab import mozilla.components.browser.state.state.SessionState +import mozilla.components.support.utils.OnEnterAnimationCompleteListener import mozilla.components.support.utils.SafeIntent import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.ext.components @@ -92,5 +93,14 @@ open class ExternalAppBrowserActivity : HomeActivity() { override fun onEnterAnimationComplete() { super.onEnterAnimationComplete() isFinishedAnimating = true + + val fragments = supportFragmentManager.fragments.toMutableList() + while (fragments.isNotEmpty()) { + val fragment = fragments.removeAt(0) + if (fragment is OnEnterAnimationCompleteListener) { + fragment.onEnterAnimationComplete() + } + fragments.addAll(fragment.childFragmentManager.fragments) + } } } ===================================== mobile/android/fenix/app/src/main/java/org/mozilla/fenix/downloads/RenameAndChangeLocationDialogFragment.kt ===================================== @@ -20,6 +20,8 @@ import androidx.fragment.app.DialogFragment import com.google.android.material.dialog.MaterialAlertDialogBuilder import mozilla.components.concept.base.crash.Breadcrumb import mozilla.components.support.base.log.logger.Logger +import mozilla.components.support.ktx.util.PromptAbuserDetector +import mozilla.components.support.utils.OnEnterAnimationCompleteListener import org.mozilla.fenix.R import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.requireComponents @@ -38,10 +40,12 @@ import org.mozilla.fenix.theme.FirefoxTheme * * The callback [onConfirmSave] is invoked with the final file name and directory path. */ -class RenameAndChangeLocationDialogFragment : DialogFragment() { +class RenameAndChangeLocationDialogFragment : DialogFragment(), OnEnterAnimationCompleteListener { private val logger = Logger("RenameAndChangeLocationDialogFragment") private val safeArguments get() = requireNotNull(arguments) + private val promptAbuserDetector = PromptAbuserDetector(TIME_SHOWN_OFFSET_MILLIS) + internal val fileName: String get() = safeArguments.getString(KEY_FILE_NAME, "") @@ -75,6 +79,15 @@ class RenameAndChangeLocationDialogFragment : DialogFragment() { } } + override fun onResume() { + super.onResume() + promptAbuserDetector.start() + } + + override fun onEnterAnimationComplete() { + promptAbuserDetector.start() + } + override fun onCancel(dialog: DialogInterface) { super.onCancel(dialog) onCancel() @@ -99,6 +112,8 @@ class RenameAndChangeLocationDialogFragment : DialogFragment() { val composeView = createComposeView() + promptAbuserDetector.start() + return MaterialAlertDialogBuilder(requireContext()) .setView(composeView) .create() @@ -144,11 +159,15 @@ class RenameAndChangeLocationDialogFragment : DialogFragment() { directoryLauncher.launch(null) }, onConfirm = { - onConfirmSave( - dialogState.fileName, - dialogState.directoryPath, - ) - dismiss() + if (promptAbuserDetector.areDialogsBeingAbused()) { + promptAbuserDetector.updateJSDialogAbusedState() + } else { + onConfirmSave( + dialogState.fileName, + dialogState.directoryPath, + ) + dismiss() + } }, onCancel = { onCancel() @@ -182,6 +201,7 @@ class RenameAndChangeLocationDialogFragment : DialogFragment() { private const val KEY_DIRECTORY_PATH = "directory_path" private const val KEY_CONTENT_SIZE = "content_size" const val RENAME_AND_CHANGE_LOCATION_DIALOG_TAG = "RENAME_AND_CHANGE_LOCATION_DIALOG_TAG" + private const val TIME_SHOWN_OFFSET_MILLIS = 500 /** * Creates a new instance of [RenameAndChangeLocationDialogFragment]. @@ -203,3 +223,14 @@ class RenameAndChangeLocationDialogFragment : DialogFragment() { } } } + +/** + * Starts (or restarts) the time-based check without increasing the "click count". + * + * Makes it safe to call from multiple/successive lifecycle methods, without running into the risk + * of triggering the more restrictive count-based protection on the 1st click (or even before it). + */ +private fun PromptAbuserDetector.start() { + resetJSAlertAbuseState() + updateJSDialogAbusedState() +} View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/9857f69... -- View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/9857f69... You're receiving this email because of your account on gitlab.torproject.org. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help
participants (1)
-
ma1 (@ma1)