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
-
7655bbd2
by Marcin Koziński at 2026-08-16T00:41:44+02:00
-
d13ffd02
by Marcin Koziński at 2026-08-16T00:56:58+02:00
-
4e27f4b0
by Jamie Nicol at 2026-08-17T08:17:03+02:00
-
a6ed37e7
by owlishDeveloper at 2026-08-17T10:29:07+02:00
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:
| ... | ... | @@ -3,9 +3,7 @@ |
| 3 | 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
| 4 | 4 | |
| 5 | 5 | using mozilla::gfx::IntRect from "mozilla/gfx/Rect.h";
|
| 6 | -using mozilla::gfx::IntSize from "mozilla/gfx/Point.h";
|
|
| 7 | 6 | using mozilla::layers::CompositorScrollUpdate from "mozilla/layers/CompositorScrollUpdate.h";
|
| 8 | -using mozilla::void_t from "mozilla/ipc/IPCCore.h";
|
|
| 9 | 7 | |
| 10 | 8 | include "mozilla/GfxMessageUtils.h";
|
| 11 | 9 | include "mozilla/layers/LayersMessageUtils.h";
|
| ... | ... | @@ -34,15 +32,14 @@ parent: |
| 34 | 32 | async MaxToolbarHeight(int32_t aHeight);
|
| 35 | 33 | async FixedBottomOffset(int32_t aOffset);
|
| 36 | 34 | async DefaultClearColor(uint32_t aColor);
|
| 37 | - async RequestScreenPixels(uint64_t aRequestId, IntRect aSourceRect, IntSize aDestSize);
|
|
| 35 | + async RequestScreenPixels(uint64_t aRequestId, IntRect aSourceRect,
|
|
| 36 | + FileDescriptor aHardwareBuffer);
|
|
| 38 | 37 | async EnableLayerUpdateNotifications(bool aEnable);
|
| 39 | 38 | child:
|
| 40 | 39 | async ToolbarAnimatorMessageFromCompositor(int32_t aMessage);
|
| 41 | 40 | async NotifyCompositorScrollUpdate(CompositorScrollUpdate aUpdate);
|
| 42 | - // Returns when the child side has finished using the HardwareBuffer,
|
|
| 43 | - // indicating that the parent side can now release it.
|
|
| 44 | - async ScreenPixels(uint64_t aRequestId, FileDescriptor? aHardwareBuffer, FileDescriptor? aAcquireFence)
|
|
| 45 | - returns (void_t ok);
|
|
| 41 | + async ScreenPixels(uint64_t aRequestId, bool aSuccess,
|
|
| 42 | + FileDescriptor? aAcquireFence);
|
|
| 46 | 43 | };
|
| 47 | 44 | |
| 48 | 45 | } // layers
|
| ... | ... | @@ -148,19 +148,36 @@ UiCompositorControllerChild::RequestScreenPixels(gfx::IntRect aSourceRect, |
| 148 | 148 | |
| 149 | 149 | // We only support one request at a time. If an old request is still
|
| 150 | 150 | // outstanding when a new request is made, just reject the old request.
|
| 151 | - if (mScreenPixelsPromise) {
|
|
| 152 | - mScreenPixelsPromise.extract().second->Reject(NS_ERROR_ABORT, __func__);
|
|
| 151 | + if (mScreenPixelsRequest) {
|
|
| 152 | + mScreenPixelsRequest.extract().mPromise->Reject(NS_ERROR_ABORT, __func__);
|
|
| 153 | + }
|
|
| 154 | + |
|
| 155 | + RefPtr<layers::AndroidHardwareBuffer> hardwareBuffer =
|
|
| 156 | + layers::AndroidHardwareBuffer::Create(aDestSize,
|
|
| 157 | + gfx::SurfaceFormat::R8G8B8A8);
|
|
| 158 | + if (!hardwareBuffer) {
|
|
| 159 | + return ScreenPixelsPromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY,
|
|
| 160 | + __func__);
|
|
| 161 | + }
|
|
| 162 | + |
|
| 163 | + UniqueFileHandle bufferFd = hardwareBuffer->SerializeToFileDescriptor();
|
|
| 164 | + if (!bufferFd) {
|
|
| 165 | + return ScreenPixelsPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
|
| 153 | 166 | }
|
| 154 | 167 | |
| 155 | 168 | static uint64_t nextRequestId = 0;
|
| 156 | 169 | const uint64_t requestId = nextRequestId++;
|
| 157 | 170 | auto promise = MakeRefPtr<ScreenPixelsPromise::Private>(__func__);
|
| 158 | - // Using synchronous dispatch ensures we are done using the hardware buffer
|
|
| 159 | - // prior to RecvScreenPixels calling aResolver which in turn will cause the
|
|
| 160 | - // hardware buffer on the parent side to be released.
|
|
| 161 | - promise->UseSynchronousTaskDispatch(__func__);
|
|
| 162 | - mScreenPixelsPromise.emplace(requestId, promise);
|
|
| 163 | - (void)SendRequestScreenPixels(requestId, aSourceRect, aDestSize);
|
|
| 171 | + mScreenPixelsRequest.emplace(ScreenPixelsRequest{
|
|
| 172 | + .mRequestId = requestId,
|
|
| 173 | + .mHardwareBuffer = hardwareBuffer,
|
|
| 174 | + .mPromise = promise,
|
|
| 175 | + });
|
|
| 176 | + if (!SendRequestScreenPixels(requestId, aSourceRect,
|
|
| 177 | + ipc::FileDescriptor(std::move(bufferFd)))) {
|
|
| 178 | + mScreenPixelsRequest.extract().mPromise->Reject(NS_ERROR_NOT_AVAILABLE,
|
|
| 179 | + __func__);
|
|
| 180 | + }
|
|
| 164 | 181 | return promise;
|
| 165 | 182 | }
|
| 166 | 183 | #endif
|
| ... | ... | @@ -213,8 +230,8 @@ void UiCompositorControllerChild::ActorDestroy(ActorDestroyReason aWhy) { |
| 213 | 230 | mParent = nullptr;
|
| 214 | 231 | |
| 215 | 232 | #ifdef MOZ_WIDGET_ANDROID
|
| 216 | - if (mScreenPixelsPromise) {
|
|
| 217 | - mScreenPixelsPromise->second->Reject(NS_ERROR_ABORT, __func__);
|
|
| 233 | + if (mScreenPixelsRequest) {
|
|
| 234 | + mScreenPixelsRequest->mPromise->Reject(NS_ERROR_ABORT, __func__);
|
|
| 218 | 235 | }
|
| 219 | 236 | #endif
|
| 220 | 237 | if (mProcessToken) {
|
| ... | ... | @@ -258,39 +275,28 @@ UiCompositorControllerChild::RecvNotifyCompositorScrollUpdate( |
| 258 | 275 | }
|
| 259 | 276 | |
| 260 | 277 | mozilla::ipc::IPCResult UiCompositorControllerChild::RecvScreenPixels(
|
| 261 | - uint64_t aRequestId, Maybe<ipc::FileDescriptor>&& aHardwareBuffer,
|
|
| 262 | - Maybe<ipc::FileDescriptor>&& aAcquireFence,
|
|
| 263 | - ScreenPixelsResolver&& aResolver) {
|
|
| 278 | + uint64_t aRequestId, bool aSuccess,
|
|
| 279 | + Maybe<ipc::FileDescriptor>&& aAcquireFence) {
|
|
| 264 | 280 | #if defined(MOZ_WIDGET_ANDROID)
|
| 265 | - if (!mScreenPixelsPromise || mScreenPixelsPromise->first != aRequestId) {
|
|
| 281 | + if (!mScreenPixelsRequest || mScreenPixelsRequest->mRequestId != aRequestId) {
|
|
| 266 | 282 | // Response is for an outdated request whose promise will have already been
|
| 267 | 283 | // rejected. Just ignore it.
|
| 268 | 284 | return IPC_OK();
|
| 269 | 285 | }
|
| 270 | 286 | |
| 271 | - RefPtr<layers::AndroidHardwareBuffer> hardwareBuffer;
|
|
| 272 | - if (aHardwareBuffer) {
|
|
| 273 | - hardwareBuffer =
|
|
| 274 | - layers::AndroidHardwareBuffer::DeserializeFromFileDescriptor(
|
|
| 275 | - aHardwareBuffer->TakePlatformHandle());
|
|
| 287 | + auto request = mScreenPixelsRequest.extract();
|
|
| 288 | + if (!aSuccess) {
|
|
| 289 | + request.mPromise->Reject(NS_ERROR_FAILURE, __func__);
|
|
| 290 | + return IPC_OK();
|
|
| 276 | 291 | }
|
| 277 | - if (hardwareBuffer && aAcquireFence) {
|
|
| 278 | - hardwareBuffer->SetAcquireFence(aAcquireFence->TakePlatformHandle());
|
|
| 292 | + |
|
| 293 | + if (aAcquireFence) {
|
|
| 294 | + request.mHardwareBuffer->SetAcquireFence(
|
|
| 295 | + aAcquireFence->TakePlatformHandle());
|
|
| 279 | 296 | }
|
| 280 | - // Note this is resolved synchronously, ensuring we have finished using the
|
|
| 281 | - // hardware buffer as soon as this call returns (and importantly before the
|
|
| 282 | - // aResolver call below).
|
|
| 283 | - mScreenPixelsPromise.extract().second->Resolve(std::move(hardwareBuffer),
|
|
| 284 | - __func__);
|
|
| 297 | + request.mPromise->Resolve(std::move(request.mHardwareBuffer), __func__);
|
|
| 285 | 298 | #endif // defined(MOZ_WIDGET_ANDROID)
|
| 286 | 299 | |
| 287 | - // Notify the parent side that it can drop its reference to the hardware
|
|
| 288 | - // buffer. In theory this could be done as soon as we have called
|
|
| 289 | - // DeserializeFromFileDescriptor(). However, on certain Exynos devices we have
|
|
| 290 | - // seen that releasing the original hardware buffer frees the underlying
|
|
| 291 | - // resource even if a reference obtained via (de)serialization remains alive.
|
|
| 292 | - // See bug 2017901.
|
|
| 293 | - aResolver(void_t{});
|
|
| 294 | 300 | return IPC_OK();
|
| 295 | 301 | }
|
| 296 | 302 |
| ... | ... | @@ -84,9 +84,8 @@ class UiCompositorControllerChild final |
| 84 | 84 | mozilla::ipc::IPCResult RecvNotifyCompositorScrollUpdate(
|
| 85 | 85 | const CompositorScrollUpdate& aUpdate);
|
| 86 | 86 | mozilla::ipc::IPCResult RecvScreenPixels(
|
| 87 | - uint64_t aRequestId, Maybe<ipc::FileDescriptor>&& aHardwareBuffer,
|
|
| 88 | - Maybe<ipc::FileDescriptor>&& aAcquireFence,
|
|
| 89 | - ScreenPixelsResolver&& aResolver);
|
|
| 87 | + uint64_t aRequestId, bool aSuccess,
|
|
| 88 | + Maybe<ipc::FileDescriptor>&& aAcquireFence);
|
|
| 90 | 89 | |
| 91 | 90 | private:
|
| 92 | 91 | explicit UiCompositorControllerChild(const uint64_t& aProcessToken,
|
| ... | ... | @@ -118,8 +117,12 @@ class UiCompositorControllerChild final |
| 118 | 117 | // RecvScreenPixels() altogether. Unfortunately, however, we cannot chain to a
|
| 119 | 118 | // promise returned from an IPDL function on the Android UI thread, as the
|
| 120 | 119 | // thread does not support direct task dispatch.
|
| 121 | - Maybe<std::pair<uint64_t, RefPtr<ScreenPixelsPromise::Private>>>
|
|
| 122 | - mScreenPixelsPromise;
|
|
| 120 | + struct ScreenPixelsRequest {
|
|
| 121 | + uint64_t mRequestId;
|
|
| 122 | + RefPtr<layers::AndroidHardwareBuffer> mHardwareBuffer;
|
|
| 123 | + RefPtr<ScreenPixelsPromise::Private> mPromise;
|
|
| 124 | + };
|
|
| 125 | + Maybe<ScreenPixelsRequest> mScreenPixelsRequest;
|
|
| 123 | 126 | #endif
|
| 124 | 127 | |
| 125 | 128 | // Should only be set when compositor is in process.
|
| ... | ... | @@ -139,39 +139,39 @@ mozilla::ipc::IPCResult UiCompositorControllerParent::RecvDefaultClearColor( |
| 139 | 139 | }
|
| 140 | 140 | |
| 141 | 141 | mozilla::ipc::IPCResult UiCompositorControllerParent::RecvRequestScreenPixels(
|
| 142 | - uint64_t aRequestId, gfx::IntRect aSourceRect, gfx::IntSize aDestSize) {
|
|
| 142 | + uint64_t aRequestId, gfx::IntRect aSourceRect,
|
|
| 143 | + ipc::FileDescriptor&& aHardwareBuffer) {
|
|
| 143 | 144 | #if defined(MOZ_WIDGET_ANDROID)
|
| 145 | + RefPtr<AndroidHardwareBuffer> hardwareBuffer =
|
|
| 146 | + AndroidHardwareBuffer::DeserializeFromFileDescriptor(
|
|
| 147 | + aHardwareBuffer.TakePlatformHandle());
|
|
| 148 | + if (!hardwareBuffer) {
|
|
| 149 | + (void)SendScreenPixels(aRequestId, false, Nothing());
|
|
| 150 | + return IPC_OK();
|
|
| 151 | + }
|
|
| 152 | + |
|
| 144 | 153 | LayerTreeState* state =
|
| 145 | 154 | CompositorBridgeParent::GetLayerTreeState(mRootLayerTreeId);
|
| 146 | 155 | |
| 147 | 156 | if (state && state->mWrBridge) {
|
| 148 | - state->mWrBridge->RequestScreenPixels(aSourceRect, aDestSize)
|
|
| 157 | + state->mWrBridge->RequestScreenPixels(aSourceRect, hardwareBuffer)
|
|
| 149 | 158 | ->Then(
|
| 150 | 159 | GetCurrentSerialEventTarget(), __func__,
|
| 151 | - [target = RefPtr{this},
|
|
| 152 | - aRequestId](RefPtr<AndroidHardwareBuffer> aHardwareBuffer) {
|
|
| 153 | - UniqueFileHandle bufferFd =
|
|
| 154 | - aHardwareBuffer->SerializeToFileDescriptor();
|
|
| 160 | + [target = RefPtr{this}, aRequestId,
|
|
| 161 | + hardwareBuffer = std::move(hardwareBuffer)](Ok) {
|
|
| 155 | 162 | UniqueFileHandle fenceFd =
|
| 156 | - aHardwareBuffer->GetAndResetAcquireFence();
|
|
| 157 | - target
|
|
| 158 | - ->SendScreenPixels(
|
|
| 159 | - aRequestId,
|
|
| 160 | - aHardwareBuffer
|
|
| 161 | - ? Some(ipc::FileDescriptor(std::move(bufferFd)))
|
|
| 162 | - : Nothing(),
|
|
| 163 | - fenceFd ? Some(ipc::FileDescriptor(std::move(fenceFd)))
|
|
| 164 | - : Nothing())
|
|
| 165 | - // Ensure the hardware buffer remains alive until child side
|
|
| 166 | - // has finished using it.
|
|
| 167 | - ->Then(GetCurrentSerialEventTarget(), __func__,
|
|
| 168 | - [aHardwareBuffer](
|
|
| 169 | - ScreenPixelsPromise::ResolveOrRejectValue&&) {});
|
|
| 163 | + hardwareBuffer->GetAndResetAcquireFence();
|
|
| 164 | + (void)target->SendScreenPixels(
|
|
| 165 | + aRequestId, true,
|
|
| 166 | + fenceFd ? Some(ipc::FileDescriptor(std::move(fenceFd)))
|
|
| 167 | + : Nothing());
|
|
| 170 | 168 | },
|
| 171 | 169 | [target = RefPtr{this}, aRequestId](nsresult aError) {
|
| 172 | - (void)target->SendScreenPixels(aRequestId, Nothing(), Nothing());
|
|
| 170 | + (void)target->SendScreenPixels(aRequestId, false, Nothing());
|
|
| 173 | 171 | });
|
| 174 | 172 | state->mWrBridge->ScheduleForcedGenerateFrame(wr::RenderReasons::OTHER);
|
| 173 | + } else {
|
|
| 174 | + (void)SendScreenPixels(aRequestId, false, Nothing());
|
|
| 175 | 175 | }
|
| 176 | 176 | #endif // defined(MOZ_WIDGET_ANDROID)
|
| 177 | 177 |
| ... | ... | @@ -41,9 +41,9 @@ class UiCompositorControllerParent final |
| 41 | 41 | mozilla::ipc::IPCResult RecvMaxToolbarHeight(const int32_t& aHeight);
|
| 42 | 42 | mozilla::ipc::IPCResult RecvFixedBottomOffset(const int32_t& aOffset);
|
| 43 | 43 | mozilla::ipc::IPCResult RecvDefaultClearColor(const uint32_t& aColor);
|
| 44 | - mozilla::ipc::IPCResult RecvRequestScreenPixels(uint64_t aRequestId,
|
|
| 45 | - gfx::IntRect aSourceRect,
|
|
| 46 | - gfx::IntSize aDestSize);
|
|
| 44 | + mozilla::ipc::IPCResult RecvRequestScreenPixels(
|
|
| 45 | + uint64_t aRequestId, gfx::IntRect aSourceRect,
|
|
| 46 | + ipc::FileDescriptor&& aHardwareBuffer);
|
|
| 47 | 47 | mozilla::ipc::IPCResult RecvEnableLayerUpdateNotifications(
|
| 48 | 48 | const bool& aEnable);
|
| 49 | 49 | void ActorDestroy(ActorDestroyReason aWhy) override;
|
| ... | ... | @@ -1945,8 +1945,8 @@ void WebRenderBridgeParent::UpdateBoolParameters() { |
| 1945 | 1945 | |
| 1946 | 1946 | #if defined(MOZ_WIDGET_ANDROID)
|
| 1947 | 1947 | RefPtr<WebRenderBridgeParent::ScreenPixelsPromise>
|
| 1948 | -WebRenderBridgeParent::RequestScreenPixels(gfx::IntRect aSourceRect,
|
|
| 1949 | - gfx::IntSize aDestSize) {
|
|
| 1948 | +WebRenderBridgeParent::RequestScreenPixels(
|
|
| 1949 | + gfx::IntRect aSourceRect, RefPtr<AndroidHardwareBuffer> aHardwareBuffer) {
|
|
| 1950 | 1950 | if (mDestroyed) {
|
| 1951 | 1951 | return ScreenPixelsPromise::CreateAndReject(NS_ERROR_ABORT, __func__);
|
| 1952 | 1952 | }
|
| ... | ... | @@ -1962,7 +1962,7 @@ WebRenderBridgeParent::RequestScreenPixels(gfx::IntRect aSourceRect, |
| 1962 | 1962 | }
|
| 1963 | 1963 | mScreenPixelsRequest.emplace(ScreenPixelsRequest{
|
| 1964 | 1964 | .mSourceRect = aSourceRect,
|
| 1965 | - .mDestSize = aDestSize,
|
|
| 1965 | + .mHardwareBuffer = std::move(aHardwareBuffer),
|
|
| 1966 | 1966 | .mPromise = new ScreenPixelsPromise::Private(__func__),
|
| 1967 | 1967 | });
|
| 1968 | 1968 | return mScreenPixelsRequest->mPromise;
|
| ... | ... | @@ -1982,7 +1982,9 @@ void WebRenderBridgeParent::MaybeCaptureScreenPixels() { |
| 1982 | 1982 | MOZ_ASSERT(cbp && !cbp->IsPaused());
|
| 1983 | 1983 | # endif
|
| 1984 | 1984 | |
| 1985 | - mLateInit->mApi->RequestScreenPixels(request.mSourceRect, request.mDestSize)
|
|
| 1985 | + mLateInit->mApi
|
|
| 1986 | + ->RequestScreenPixels(request.mSourceRect,
|
|
| 1987 | + std::move(request.mHardwareBuffer))
|
|
| 1986 | 1988 | ->ChainTo(request.mPromise.forget(), __func__);
|
| 1987 | 1989 | }
|
| 1988 | 1990 | #endif
|
| ... | ... | @@ -325,13 +325,13 @@ class WebRenderBridgeParent final : public PWebRenderBridgeParent, |
| 325 | 325 | void BeginRecording(const TimeStamp& aRecordingStart);
|
| 326 | 326 | |
| 327 | 327 | #if defined(MOZ_WIDGET_ANDROID)
|
| 328 | - using ScreenPixelsPromise =
|
|
| 329 | - MozPromise<RefPtr<layers::AndroidHardwareBuffer>, nsresult, true>;
|
|
| 328 | + using ScreenPixelsPromise = MozPromise<Ok, nsresult, true>;
|
|
| 330 | 329 | /**
|
| 331 | 330 | * Request a screengrab for android
|
| 332 | 331 | */
|
| 333 | - RefPtr<ScreenPixelsPromise> RequestScreenPixels(gfx::IntRect aSourceRect,
|
|
| 334 | - gfx::IntSize aDestSize);
|
|
| 332 | + RefPtr<ScreenPixelsPromise> RequestScreenPixels(
|
|
| 333 | + gfx::IntRect aSourceRect,
|
|
| 334 | + RefPtr<layers::AndroidHardwareBuffer> aHardwareBuffer);
|
|
| 335 | 335 | #endif
|
| 336 | 336 | |
| 337 | 337 | /**
|
| ... | ... | @@ -539,7 +539,7 @@ class WebRenderBridgeParent final : public PWebRenderBridgeParent, |
| 539 | 539 | #if defined(MOZ_WIDGET_ANDROID)
|
| 540 | 540 | struct ScreenPixelsRequest {
|
| 541 | 541 | gfx::IntRect mSourceRect;
|
| 542 | - gfx::IntSize mDestSize;
|
|
| 542 | + RefPtr<layers::AndroidHardwareBuffer> mHardwareBuffer;
|
|
| 543 | 543 | RefPtr<ScreenPixelsPromise::Private> mPromise;
|
| 544 | 544 | };
|
| 545 | 545 | Maybe<ScreenPixelsRequest> mScreenPixelsRequest;
|
| ... | ... | @@ -235,7 +235,7 @@ class RenderCompositor { |
| 235 | 235 | #ifdef MOZ_WIDGET_ANDROID
|
| 236 | 236 | virtual bool MaybeCaptureScreenPixels(
|
| 237 | 237 | const gfx::IntRect& aSourceRect,
|
| 238 | - RefPtr<layers::AndroidHardwareBuffer> aHardwareBuffer) {
|
|
| 238 | + layers::AndroidHardwareBuffer* aHardwareBuffer) {
|
|
| 239 | 239 | return false;
|
| 240 | 240 | }
|
| 241 | 241 | #endif
|
| ... | ... | @@ -315,7 +315,7 @@ bool RenderCompositorOGLSWGL::MaybeReadback( |
| 315 | 315 | #ifdef MOZ_WIDGET_ANDROID
|
| 316 | 316 | bool RenderCompositorOGLSWGL::MaybeCaptureScreenPixels(
|
| 317 | 317 | const gfx::IntRect& aSourceRect,
|
| 318 | - RefPtr<layers::AndroidHardwareBuffer> aHardwareBuffer) {
|
|
| 318 | + layers::AndroidHardwareBuffer* aHardwareBuffer) {
|
|
| 319 | 319 | auto* const gl = GetGLContext();
|
| 320 | 320 | gl::ScopedBindFramebuffer scopedBind(gl);
|
| 321 | 321 |
| ... | ... | @@ -59,7 +59,7 @@ class RenderCompositorOGLSWGL : public RenderCompositorLayersSWGL { |
| 59 | 59 | #ifdef MOZ_WIDGET_ANDROID
|
| 60 | 60 | bool MaybeCaptureScreenPixels(
|
| 61 | 61 | const gfx::IntRect& aSourceRect,
|
| 62 | - RefPtr<layers::AndroidHardwareBuffer> aHardwareBuffer) override;
|
|
| 62 | + layers::AndroidHardwareBuffer* aHardwareBuffer) override;
|
|
| 63 | 63 | #endif
|
| 64 | 64 | |
| 65 | 65 | private:
|
| ... | ... | @@ -469,7 +469,13 @@ Maybe<layers::FrameRecording> RendererOGL::EndRecording() { |
| 469 | 469 | |
| 470 | 470 | #ifdef MOZ_WIDGET_ANDROID
|
| 471 | 471 | RefPtr<RendererOGL::ScreenPixelsPromise> RendererOGL::RequestScreenPixels(
|
| 472 | - gfx::IntRect aSourceRect, gfx::IntSize aDestSize) {
|
|
| 472 | + gfx::IntRect aSourceRect,
|
|
| 473 | + RefPtr<layers::AndroidHardwareBuffer> aHardwareBuffer) {
|
|
| 474 | + if (!aHardwareBuffer) {
|
|
| 475 | + return ScreenPixelsPromise::CreateAndReject(NS_ERROR_ILLEGAL_VALUE,
|
|
| 476 | + __func__);
|
|
| 477 | + }
|
|
| 478 | + |
|
| 473 | 479 | // If a new request is made we no longer care about the result of the previous
|
| 474 | 480 | // one, so just reject it if it exists.
|
| 475 | 481 | if (mPendingScreenPixelsRequest) {
|
| ... | ... | @@ -478,7 +484,7 @@ RefPtr<RendererOGL::ScreenPixelsPromise> RendererOGL::RequestScreenPixels( |
| 478 | 484 | }
|
| 479 | 485 | mPendingScreenPixelsRequest.emplace(ScreenPixelsRequest{
|
| 480 | 486 | .mSourceRect = aSourceRect,
|
| 481 | - .mDestSize = aDestSize,
|
|
| 487 | + .mHardwareBuffer = std::move(aHardwareBuffer),
|
|
| 482 | 488 | .mPromise = new ScreenPixelsPromise::Private(__func__),
|
| 483 | 489 | });
|
| 484 | 490 | return mPendingScreenPixelsRequest->mPromise;
|
| ... | ... | @@ -491,19 +497,16 @@ void RendererOGL::MaybeCaptureScreenPixels() { |
| 491 | 497 | |
| 492 | 498 | auto request = mPendingScreenPixelsRequest.extract();
|
| 493 | 499 | |
| 494 | - const RefPtr<layers::AndroidHardwareBuffer> hardwareBuffer =
|
|
| 495 | - layers::AndroidHardwareBuffer::Create(request.mDestSize,
|
|
| 496 | - gfx::SurfaceFormat::R8G8B8A8);
|
|
| 497 | - |
|
| 498 | 500 | if (mCompositor->MaybeCaptureScreenPixels(request.mSourceRect,
|
| 499 | - hardwareBuffer)) {
|
|
| 500 | - request.mPromise->Resolve(hardwareBuffer, __func__);
|
|
| 501 | + request.mHardwareBuffer)) {
|
|
| 502 | + request.mPromise->Resolve(Ok{}, __func__);
|
|
| 501 | 503 | return;
|
| 502 | 504 | }
|
| 503 | 505 | |
| 504 | 506 | auto* const gle = gl::GLContextEGL::Cast(gl());
|
| 505 | 507 | const auto& egl = gle->mEgl;
|
| 506 | - gl::ScopedEGLImageForAndroidHardwareBuffer eglImage(gle, hardwareBuffer);
|
|
| 508 | + gl::ScopedEGLImageForAndroidHardwareBuffer eglImage(gle,
|
|
| 509 | + request.mHardwareBuffer);
|
|
| 507 | 510 | gl::ScopedBindFramebuffer scopedBind(gl());
|
| 508 | 511 | gl::ScopedRenderbuffer rb(gl());
|
| 509 | 512 | gl()->fBindRenderbuffer(LOCAL_GL_RENDERBUFFER, rb);
|
| ... | ... | @@ -517,7 +520,7 @@ void RendererOGL::MaybeCaptureScreenPixels() { |
| 517 | 520 | request.mSourceRect.x,
|
| 518 | 521 | mCompositor->GetBufferSize().height - request.mSourceRect.y,
|
| 519 | 522 | request.mSourceRect.width, -request.mSourceRect.height);
|
| 520 | - const auto destRect = gfx::IntRect({}, hardwareBuffer->mSize);
|
|
| 523 | + const auto destRect = gfx::IntRect({}, request.mHardwareBuffer->mSize);
|
|
| 521 | 524 | gl()->BindReadFB(0);
|
| 522 | 525 | gl()->BindDrawFB(fb.FB());
|
| 523 | 526 | gl()->fBlitFramebuffer(srcRect.x, srcRect.y, srcRect.XMost(), srcRect.YMost(),
|
| ... | ... | @@ -529,12 +532,12 @@ void RendererOGL::MaybeCaptureScreenPixels() { |
| 529 | 532 | egl->fCreateSync(LOCAL_EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr)) {
|
| 530 | 533 | auto fence = UniqueFileHandle(egl->fDupNativeFenceFDANDROID(sync));
|
| 531 | 534 | if (fence) {
|
| 532 | - hardwareBuffer->SetAcquireFence(std::move(fence));
|
|
| 535 | + request.mHardwareBuffer->SetAcquireFence(std::move(fence));
|
|
| 533 | 536 | }
|
| 534 | 537 | egl->fDestroySync(sync);
|
| 535 | 538 | }
|
| 536 | 539 | |
| 537 | - request.mPromise->Resolve(hardwareBuffer, __func__);
|
|
| 540 | + request.mPromise->Resolve(Ok{}, __func__);
|
|
| 538 | 541 | }
|
| 539 | 542 | #endif
|
| 540 | 543 |
| ... | ... | @@ -93,12 +93,12 @@ class RendererOGL { |
| 93 | 93 | Maybe<layers::FrameRecording> EndRecording();
|
| 94 | 94 | |
| 95 | 95 | #ifdef MOZ_WIDGET_ANDROID
|
| 96 | - using ScreenPixelsPromise =
|
|
| 97 | - MozPromise<RefPtr<layers::AndroidHardwareBuffer>, nsresult, true>;
|
|
| 96 | + using ScreenPixelsPromise = MozPromise<Ok, nsresult, true>;
|
|
| 98 | 97 | // Captures the pixels for the next rendered frame. Returns a promise that
|
| 99 | 98 | // resolves once the pixels are captured.
|
| 100 | - RefPtr<ScreenPixelsPromise> RequestScreenPixels(gfx::IntRect aSourceRect,
|
|
| 101 | - gfx::IntSize aDestSize);
|
|
| 99 | + RefPtr<ScreenPixelsPromise> RequestScreenPixels(
|
|
| 100 | + gfx::IntRect aSourceRect,
|
|
| 101 | + RefPtr<layers::AndroidHardwareBuffer> aHardwareBuffer);
|
|
| 102 | 102 | #endif
|
| 103 | 103 | |
| 104 | 104 | /// This can be called on the render thread only.
|
| ... | ... | @@ -174,7 +174,7 @@ class RendererOGL { |
| 174 | 174 | #ifdef MOZ_WIDGET_ANDROID
|
| 175 | 175 | struct ScreenPixelsRequest {
|
| 176 | 176 | gfx::IntRect mSourceRect;
|
| 177 | - gfx::IntSize mDestSize;
|
|
| 177 | + RefPtr<layers::AndroidHardwareBuffer> mHardwareBuffer;
|
|
| 178 | 178 | RefPtr<ScreenPixelsPromise::Private> mPromise;
|
| 179 | 179 | };
|
| 180 | 180 | Maybe<ScreenPixelsRequest> mPendingScreenPixelsRequest;
|
| ... | ... | @@ -962,12 +962,17 @@ RefPtr<WebRenderAPI::EndRecordingPromise> WebRenderAPI::EndRecording() { |
| 962 | 962 | |
| 963 | 963 | #ifdef MOZ_WIDGET_ANDROID
|
| 964 | 964 | RefPtr<WebRenderAPI::ScreenPixelsPromise> WebRenderAPI::RequestScreenPixels(
|
| 965 | - gfx::IntRect aSourceRect, gfx::IntSize aDestSize) {
|
|
| 965 | + gfx::IntRect aSourceRect,
|
|
| 966 | + RefPtr<layers::AndroidHardwareBuffer> aHardwareBuffer) {
|
|
| 966 | 967 | class ScreenshotEvent final : public RendererEvent {
|
| 967 | 968 | public:
|
| 968 | - explicit ScreenshotEvent(gfx::IntRect aSourceRect, gfx::IntSize aDestSize,
|
|
| 969 | - RefPtr<ScreenPixelsPromise::Private> aPromise)
|
|
| 970 | - : mSourceRect(aSourceRect), mDestSize(aDestSize), mPromise(aPromise) {
|
|
| 969 | + explicit ScreenshotEvent(
|
|
| 970 | + gfx::IntRect aSourceRect,
|
|
| 971 | + RefPtr<layers::AndroidHardwareBuffer> aHardwareBuffer,
|
|
| 972 | + RefPtr<ScreenPixelsPromise::Private> aPromise)
|
|
| 973 | + : mSourceRect(aSourceRect),
|
|
| 974 | + mHardwareBuffer(std::move(aHardwareBuffer)),
|
|
| 975 | + mPromise(aPromise) {
|
|
| 971 | 976 | MOZ_COUNT_CTOR(ScreenshotEvent);
|
| 972 | 977 | }
|
| 973 | 978 | |
| ... | ... | @@ -977,8 +982,9 @@ RefPtr<WebRenderAPI::ScreenPixelsPromise> WebRenderAPI::RequestScreenPixels( |
| 977 | 982 | RendererOGL* const renderer = aRenderThread.GetRenderer(aWindowId);
|
| 978 | 983 | if (!renderer) {
|
| 979 | 984 | mPromise->Reject(NS_ERROR_FAILURE, __func__);
|
| 985 | + return;
|
|
| 980 | 986 | }
|
| 981 | - renderer->RequestScreenPixels(mSourceRect, mDestSize)
|
|
| 987 | + renderer->RequestScreenPixels(mSourceRect, std::move(mHardwareBuffer))
|
|
| 982 | 988 | ->ChainTo(mPromise.forget(), __func__);
|
| 983 | 989 | }
|
| 984 | 990 | |
| ... | ... | @@ -986,12 +992,13 @@ RefPtr<WebRenderAPI::ScreenPixelsPromise> WebRenderAPI::RequestScreenPixels( |
| 986 | 992 | |
| 987 | 993 | private:
|
| 988 | 994 | const gfx::IntRect mSourceRect;
|
| 989 | - const gfx::IntSize mDestSize;
|
|
| 995 | + RefPtr<layers::AndroidHardwareBuffer> mHardwareBuffer;
|
|
| 990 | 996 | RefPtr<ScreenPixelsPromise::Private> mPromise;
|
| 991 | 997 | };
|
| 992 | 998 | |
| 993 | 999 | auto promise = MakeRefPtr<ScreenPixelsPromise::Private>(__func__);
|
| 994 | - auto event = MakeUnique<ScreenshotEvent>(aSourceRect, aDestSize, promise);
|
|
| 1000 | + auto event = MakeUnique<ScreenshotEvent>(aSourceRect,
|
|
| 1001 | + std::move(aHardwareBuffer), promise);
|
|
| 995 | 1002 | |
| 996 | 1003 | RenderThread::Get()->PostEvent(mId, std::move(event));
|
| 997 | 1004 | return promise;
|
| ... | ... | @@ -322,13 +322,13 @@ class WebRenderAPI final { |
| 322 | 322 | RefPtr<EndRecordingPromise> EndRecording();
|
| 323 | 323 | |
| 324 | 324 | #ifdef MOZ_WIDGET_ANDROID
|
| 325 | - using ScreenPixelsPromise =
|
|
| 326 | - MozPromise<RefPtr<layers::AndroidHardwareBuffer>, nsresult, true>;
|
|
| 325 | + using ScreenPixelsPromise = MozPromise<Ok, nsresult, true>;
|
|
| 327 | 326 | // Queues a task to the render thread to capture screen pixels for the next
|
| 328 | 327 | // rendered frame. Returns a promise that resolves once the pixels are
|
| 329 | 328 | // captured.
|
| 330 | - RefPtr<ScreenPixelsPromise> RequestScreenPixels(gfx::IntRect aSourceRect,
|
|
| 331 | - gfx::IntSize aDestSize);
|
|
| 329 | + RefPtr<ScreenPixelsPromise> RequestScreenPixels(
|
|
| 330 | + gfx::IntRect aSourceRect,
|
|
| 331 | + RefPtr<layers::AndroidHardwareBuffer> aHardwareBuffer);
|
|
| 332 | 332 | #endif
|
| 333 | 333 | |
| 334 | 334 | layers::RemoteTextureInfoList* GetPendingRemoteTextureInfoList();
|
| ... | ... | @@ -122,6 +122,8 @@ value class OpenFileCallback(val value: () -> Unit) |
| 122 | 122 | * manager is provided, a dialog will be shown before every download.
|
| 123 | 123 | * @property promptsStyling styling properties for the dialog.
|
| 124 | 124 | * @property onDownloadStartedListener a callback invoked when a download is started.
|
| 125 | + * @property dismissCustomFirstPartyDownloadDialog A callback invoked when the custom first party
|
|
| 126 | + * download dialog should be dismissed.
|
|
| 125 | 127 | * @property shouldForwardToThirdParties Indicates if downloads should be forward to third party apps,
|
| 126 | 128 | * if there are multiple apps a chooser dialog will shown.
|
| 127 | 129 | * @property customFirstPartyDownloadDialog An optional delegate for showing a dialog for a download
|
| ... | ... | @@ -145,6 +147,7 @@ class DownloadsFeature( |
| 145 | 147 | private val fragmentManager: FragmentManager? = null,
|
| 146 | 148 | private val promptsStyling: PromptsStyling? = null,
|
| 147 | 149 | private val onDownloadStartedListener: ((String) -> Unit) = {},
|
| 150 | + private val dismissCustomFirstPartyDownloadDialog: () -> Unit = {},
|
|
| 148 | 151 | private val shouldForwardToThirdParties: () -> Boolean = { false },
|
| 149 | 152 | private val customFirstPartyDownloadDialog: (
|
| 150 | 153 | (
|
| ... | ... | @@ -555,6 +558,7 @@ class DownloadsFeature( |
| 555 | 558 | internal fun dismissAllDownloadDialogs() {
|
| 556 | 559 | findPreviousDownloadDialogFragment()?.dismiss()
|
| 557 | 560 | findPreviousAppDownloaderDialogFragment()?.dismiss()
|
| 561 | + dismissCustomFirstPartyDownloadDialog.invoke()
|
|
| 558 | 562 | }
|
| 559 | 563 | |
| 560 | 564 | private val ActivityInfo.identifier: String get() = packageName + name
|
| ... | ... | @@ -1513,6 +1513,64 @@ class DownloadsFeatureTest { |
| 1513 | 1513 | verify(cancelDownloadRequestUseCase).invoke(anyString(), anyString())
|
| 1514 | 1514 | }
|
| 1515 | 1515 | |
| 1516 | + @Test
|
|
| 1517 | + fun `GIVEN a custom download dialog is used WHEN dismissAllDownloadDialogs is called THEN the dialog is dismissed`() = runTest(testDispatcher) {
|
|
| 1518 | + val dismissCustomDialog = mock<() -> Unit>()
|
|
| 1519 | + val feature = DownloadsFeature(
|
|
| 1520 | + testContext,
|
|
| 1521 | + store,
|
|
| 1522 | + useCases = DownloadsUseCases(store, mock()),
|
|
| 1523 | + downloadFileUtils = FakeDownloadFileUtils(),
|
|
| 1524 | + downloadManager = mock(),
|
|
| 1525 | + mainDispatcher = testDispatcher,
|
|
| 1526 | + dismissCustomFirstPartyDownloadDialog = dismissCustomDialog,
|
|
| 1527 | + )
|
|
| 1528 | + |
|
| 1529 | + feature.dismissAllDownloadDialogs()
|
|
| 1530 | + |
|
| 1531 | + verify(dismissCustomDialog).invoke()
|
|
| 1532 | + }
|
|
| 1533 | + |
|
| 1534 | + @Test
|
|
| 1535 | + fun `GIVEN a custom download dialog is used WHEN navigating to another website THEN the dialog is dismissed`() = runTest(testDispatcher) {
|
|
| 1536 | + val dismissCustomDialog = mock<() -> Unit>()
|
|
| 1537 | + val downloadsUseCases = spy(DownloadsUseCases(store, mock()))
|
|
| 1538 | + val cancelDownloadRequestUseCase = mock<CancelDownloadRequestUseCase>()
|
|
| 1539 | + val download = DownloadState(url = "https://www.mozilla.org", sessionId = "test-tab")
|
|
| 1540 | + store.dispatch(ContentAction.UpdateDownloadAction("test-tab", download = download))
|
|
| 1541 | + |
|
| 1542 | + doReturn(cancelDownloadRequestUseCase).`when`(downloadsUseCases).cancelDownloadRequest
|
|
| 1543 | + |
|
| 1544 | + val feature = spy(
|
|
| 1545 | + DownloadsFeature(
|
|
| 1546 | + testContext,
|
|
| 1547 | + store,
|
|
| 1548 | + useCases = downloadsUseCases,
|
|
| 1549 | + downloadFileUtils = FakeDownloadFileUtils(),
|
|
| 1550 | + downloadManager = mock(),
|
|
| 1551 | + mainDispatcher = testDispatcher,
|
|
| 1552 | + dismissCustomFirstPartyDownloadDialog = dismissCustomDialog,
|
|
| 1553 | + ),
|
|
| 1554 | + )
|
|
| 1555 | + |
|
| 1556 | + doReturn(true).`when`(feature).processDownload(any(), any())
|
|
| 1557 | + |
|
| 1558 | + feature.start()
|
|
| 1559 | + testDispatcher.scheduler.advanceUntilIdle()
|
|
| 1560 | + |
|
| 1561 | + store.dispatch(ContentAction.UpdateDownloadAction("test-tab", download = download))
|
|
| 1562 | + testDispatcher.scheduler.advanceUntilIdle()
|
|
| 1563 | + |
|
| 1564 | + grantPermissions()
|
|
| 1565 | + |
|
| 1566 | + val tab = createTab("https://www.firefox.com")
|
|
| 1567 | + store.dispatch(TabListAction.AddTabAction(tab, select = true))
|
|
| 1568 | + testDispatcher.scheduler.advanceUntilIdle()
|
|
| 1569 | + |
|
| 1570 | + verify(feature).dismissAllDownloadDialogs()
|
|
| 1571 | + verify(dismissCustomDialog).invoke()
|
|
| 1572 | + }
|
|
| 1573 | + |
|
| 1516 | 1574 | @Test
|
| 1517 | 1575 | fun `ResolveInfo to DownloaderApps`() = runTest(testDispatcher) {
|
| 1518 | 1576 | val spyContext = spy(testContext)
|
| ... | ... | @@ -32,6 +32,7 @@ import mozilla.components.support.base.log.logger.Logger |
| 32 | 32 | import mozilla.components.support.ktx.android.content.appName
|
| 33 | 33 | import mozilla.components.support.ktx.kotlin.ifNullOrEmpty
|
| 34 | 34 | import mozilla.components.support.ktx.util.PromptAbuserDetector
|
| 35 | +import mozilla.components.support.utils.OnEnterAnimationCompleteListener
|
|
| 35 | 36 | |
| 36 | 37 | internal const val KEY_SESSION_ID = "KEY_SESSION_ID"
|
| 37 | 38 | internal const val KEY_TITLE = "KEY_TITLE"
|
| ... | ... | @@ -51,7 +52,9 @@ private const val KEY_IS_NOTIFICATION_REQUEST = "KEY_IS_NOTIFICATION_REQUEST" |
| 51 | 52 | private const val DEFAULT_VALUE = Int.MAX_VALUE
|
| 52 | 53 | private const val KEY_PERMISSION_ID = "KEY_PERMISSION_ID"
|
| 53 | 54 | |
| 54 | -internal open class SitePermissionsDialogFragment : NoObscuredTouchesDialogFragment() {
|
|
| 55 | +internal open class SitePermissionsDialogFragment :
|
|
| 56 | + NoObscuredTouchesDialogFragment(),
|
|
| 57 | + OnEnterAnimationCompleteListener {
|
|
| 55 | 58 | |
| 56 | 59 | private val logger = Logger("SitePermissionsDialogFragment")
|
| 57 | 60 | |
| ... | ... | @@ -134,6 +137,11 @@ internal open class SitePermissionsDialogFragment : NoObscuredTouchesDialogFragm |
| 134 | 137 | feature?.onDismiss(permissionRequestId, sessionId)
|
| 135 | 138 | }
|
| 136 | 139 | |
| 140 | + override fun onEnterAnimationComplete() {
|
|
| 141 | + // Extend the positive button click delay.
|
|
| 142 | + promptAbuserDetector.updateJSDialogAbusedState()
|
|
| 143 | + }
|
|
| 144 | + |
|
| 137 | 145 | private fun Dialog.setContainerView(rootView: View) {
|
| 138 | 146 | if (dialogShouldWidthMatchParent) {
|
| 139 | 147 | setContentView(rootView)
|
| 1 | +/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
| 2 | + * License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
| 3 | + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
| 4 | + |
|
| 5 | +package mozilla.components.support.utils
|
|
| 6 | + |
|
| 7 | +/**
|
|
| 8 | + * Allows forwarding [android.app.Activity.onEnterAnimationComplete] to other classes
|
|
| 9 | + * (e.g. fragments) that want to participate in handling it.
|
|
| 10 | + */
|
|
| 11 | +interface OnEnterAnimationCompleteListener {
|
|
| 12 | + /**
|
|
| 13 | + * Called when the Activity's entering animation has completed.
|
|
| 14 | + */
|
|
| 15 | + fun onEnterAnimationComplete()
|
|
| 16 | +} |
| ... | ... | @@ -159,6 +159,11 @@ bool GeckoViewContentChannelParent::Init( |
| 159 | 159 | |
| 160 | 160 | nsCOMPtr<nsIURI> uri = ipc::DeserializeURI(aArgs.uri());
|
| 161 | 161 | |
| 162 | + if (!uri || !uri->SchemeIs("content")) {
|
|
| 163 | + rv = NS_ERROR_UNKNOWN_PROTOCOL;
|
|
| 164 | + return false;
|
|
| 165 | + }
|
|
| 166 | + |
|
| 162 | 167 | nsAutoCString remoteType;
|
| 163 | 168 | rv = GetRemoteType(remoteType);
|
| 164 | 169 | if (MOZ_UNLIKELY(NS_FAILED(rv))) {
|
| ... | ... | @@ -29,6 +29,7 @@ import androidx.test.espresso.intent.matcher.IntentMatchers |
| 29 | 29 | import androidx.test.uiautomator.By
|
| 30 | 30 | import androidx.test.uiautomator.UiSelector
|
| 31 | 31 | import androidx.test.uiautomator.Until
|
| 32 | +import mozilla.components.support.ktx.util.PromptAbuserDetector
|
|
| 32 | 33 | import org.hamcrest.CoreMatchers.allOf
|
| 33 | 34 | import org.mozilla.fenix.R
|
| 34 | 35 | import org.mozilla.fenix.compose.snackbar.SNACKBAR_TEST_TAG
|
| ... | ... | @@ -257,7 +258,9 @@ class DownloadRobot(private val composeTestRule: ComposeTestRule) { |
| 257 | 258 | class Transition(private val composeTestRule: ComposeTestRule) {
|
| 258 | 259 | fun clickDownload(composeTestRule: ComposeTestRule, interact: DownloadRobot.() -> Unit): Transition {
|
| 259 | 260 | Log.i(TAG, "clickDownload: Trying to click the \"Download\" download prompt button")
|
| 261 | + PromptAbuserDetector.validationsEnabled = false
|
|
| 260 | 262 | composeTestRule.downloadButton().performClick()
|
| 263 | + PromptAbuserDetector.validationsEnabled = true
|
|
| 261 | 264 | Log.i(TAG, "clickDownload: Clicked the \"Download\" download prompt button")
|
| 262 | 265 | |
| 263 | 266 | DownloadRobot(composeTestRule).interact()
|
| ... | ... | @@ -161,6 +161,10 @@ abstract class AddonPopupBaseFragment : |
| 161 | 161 | onNeedToRequestPermissions = { permissions ->
|
| 162 | 162 | requestPermissions(permissions, REQUEST_CODE_DOWNLOAD_PERMISSIONS)
|
| 163 | 163 | },
|
| 164 | + dismissCustomFirstPartyDownloadDialog = {
|
|
| 165 | + dismissRenameDialog()
|
|
| 166 | + downloadDialog?.dismiss()
|
|
| 167 | + },
|
|
| 164 | 168 | customFirstPartyDownloadDialog = { currentDownloadState, _, positiveAction, negativeAction, _ ->
|
| 165 | 169 | run {
|
| 166 | 170 | if (canShowDownloadDialog()) {
|
| ... | ... | @@ -394,6 +398,13 @@ abstract class AddonPopupBaseFragment : |
| 394 | 398 | return downloadDialog == null && !isRenameFragmentShowing
|
| 395 | 399 | }
|
| 396 | 400 | |
| 401 | + private fun dismissRenameDialog() {
|
|
| 402 | + val renameDialog = childFragmentManager.findFragmentByTag(
|
|
| 403 | + RenameAndChangeLocationDialogFragment.RENAME_AND_CHANGE_LOCATION_DIALOG_TAG,
|
|
| 404 | + ) as? RenameAndChangeLocationDialogFragment
|
|
| 405 | + renameDialog?.dismissAllowingStateLoss()
|
|
| 406 | + }
|
|
| 407 | + |
|
| 397 | 408 | /**
|
| 398 | 409 | * Forwards activity results to the [ActivityResultHandler] features.
|
| 399 | 410 | */
|
| ... | ... | @@ -742,6 +742,10 @@ abstract class BaseBrowserFragment : |
| 742 | 742 | onNeedToRequestPermissions = { permissions ->
|
| 743 | 743 | requestPermissions(permissions, REQUEST_CODE_DOWNLOAD_PERMISSIONS)
|
| 744 | 744 | },
|
| 745 | + dismissCustomFirstPartyDownloadDialog = {
|
|
| 746 | + dismissRenameDialog()
|
|
| 747 | + dismissDownloadDialogs()
|
|
| 748 | + },
|
|
| 745 | 749 | customFirstPartyDownloadDialog = {
|
| 746 | 750 | currentDownloadState,
|
| 747 | 751 | fileNameIfAlreadyDownloaded,
|
| ... | ... | @@ -11,6 +11,7 @@ import androidx.annotation.VisibleForTesting |
| 11 | 11 | import androidx.core.net.toUri
|
| 12 | 12 | import mozilla.components.browser.state.selector.findCustomTab
|
| 13 | 13 | import mozilla.components.browser.state.state.SessionState
|
| 14 | +import mozilla.components.support.utils.OnEnterAnimationCompleteListener
|
|
| 14 | 15 | import mozilla.components.support.utils.SafeIntent
|
| 15 | 16 | import org.mozilla.fenix.HomeActivity
|
| 16 | 17 | import org.mozilla.fenix.ext.components
|
| ... | ... | @@ -92,5 +93,14 @@ open class ExternalAppBrowserActivity : HomeActivity() { |
| 92 | 93 | override fun onEnterAnimationComplete() {
|
| 93 | 94 | super.onEnterAnimationComplete()
|
| 94 | 95 | isFinishedAnimating = true
|
| 96 | + |
|
| 97 | + val fragments = supportFragmentManager.fragments.toMutableList()
|
|
| 98 | + while (fragments.isNotEmpty()) {
|
|
| 99 | + val fragment = fragments.removeAt(0)
|
|
| 100 | + if (fragment is OnEnterAnimationCompleteListener) {
|
|
| 101 | + fragment.onEnterAnimationComplete()
|
|
| 102 | + }
|
|
| 103 | + fragments.addAll(fragment.childFragmentManager.fragments)
|
|
| 104 | + }
|
|
| 95 | 105 | }
|
| 96 | 106 | } |
| ... | ... | @@ -20,6 +20,8 @@ import androidx.fragment.app.DialogFragment |
| 20 | 20 | import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
| 21 | 21 | import mozilla.components.concept.base.crash.Breadcrumb
|
| 22 | 22 | import mozilla.components.support.base.log.logger.Logger
|
| 23 | +import mozilla.components.support.ktx.util.PromptAbuserDetector
|
|
| 24 | +import mozilla.components.support.utils.OnEnterAnimationCompleteListener
|
|
| 23 | 25 | import org.mozilla.fenix.R
|
| 24 | 26 | import org.mozilla.fenix.ext.components
|
| 25 | 27 | import org.mozilla.fenix.ext.requireComponents
|
| ... | ... | @@ -38,10 +40,12 @@ import org.mozilla.fenix.theme.FirefoxTheme |
| 38 | 40 | *
|
| 39 | 41 | * The callback [onConfirmSave] is invoked with the final file name and directory path.
|
| 40 | 42 | */
|
| 41 | -class RenameAndChangeLocationDialogFragment : DialogFragment() {
|
|
| 43 | +class RenameAndChangeLocationDialogFragment : DialogFragment(), OnEnterAnimationCompleteListener {
|
|
| 42 | 44 | private val logger = Logger("RenameAndChangeLocationDialogFragment")
|
| 43 | 45 | private val safeArguments get() = requireNotNull(arguments)
|
| 44 | 46 | |
| 47 | + private val promptAbuserDetector = PromptAbuserDetector(TIME_SHOWN_OFFSET_MILLIS)
|
|
| 48 | + |
|
| 45 | 49 | internal val fileName: String
|
| 46 | 50 | get() = safeArguments.getString(KEY_FILE_NAME, "")
|
| 47 | 51 | |
| ... | ... | @@ -75,6 +79,15 @@ class RenameAndChangeLocationDialogFragment : DialogFragment() { |
| 75 | 79 | }
|
| 76 | 80 | }
|
| 77 | 81 | |
| 82 | + override fun onResume() {
|
|
| 83 | + super.onResume()
|
|
| 84 | + promptAbuserDetector.start()
|
|
| 85 | + }
|
|
| 86 | + |
|
| 87 | + override fun onEnterAnimationComplete() {
|
|
| 88 | + promptAbuserDetector.start()
|
|
| 89 | + }
|
|
| 90 | + |
|
| 78 | 91 | override fun onCancel(dialog: DialogInterface) {
|
| 79 | 92 | super.onCancel(dialog)
|
| 80 | 93 | onCancel()
|
| ... | ... | @@ -99,6 +112,8 @@ class RenameAndChangeLocationDialogFragment : DialogFragment() { |
| 99 | 112 | |
| 100 | 113 | val composeView = createComposeView()
|
| 101 | 114 | |
| 115 | + promptAbuserDetector.start()
|
|
| 116 | + |
|
| 102 | 117 | return MaterialAlertDialogBuilder(requireContext())
|
| 103 | 118 | .setView(composeView)
|
| 104 | 119 | .create()
|
| ... | ... | @@ -144,11 +159,15 @@ class RenameAndChangeLocationDialogFragment : DialogFragment() { |
| 144 | 159 | directoryLauncher.launch(null)
|
| 145 | 160 | },
|
| 146 | 161 | onConfirm = {
|
| 147 | - onConfirmSave(
|
|
| 148 | - dialogState.fileName,
|
|
| 149 | - dialogState.directoryPath,
|
|
| 150 | - )
|
|
| 151 | - dismiss()
|
|
| 162 | + if (promptAbuserDetector.areDialogsBeingAbused()) {
|
|
| 163 | + promptAbuserDetector.updateJSDialogAbusedState()
|
|
| 164 | + } else {
|
|
| 165 | + onConfirmSave(
|
|
| 166 | + dialogState.fileName,
|
|
| 167 | + dialogState.directoryPath,
|
|
| 168 | + )
|
|
| 169 | + dismiss()
|
|
| 170 | + }
|
|
| 152 | 171 | },
|
| 153 | 172 | onCancel = {
|
| 154 | 173 | onCancel()
|
| ... | ... | @@ -182,6 +201,7 @@ class RenameAndChangeLocationDialogFragment : DialogFragment() { |
| 182 | 201 | private const val KEY_DIRECTORY_PATH = "directory_path"
|
| 183 | 202 | private const val KEY_CONTENT_SIZE = "content_size"
|
| 184 | 203 | const val RENAME_AND_CHANGE_LOCATION_DIALOG_TAG = "RENAME_AND_CHANGE_LOCATION_DIALOG_TAG"
|
| 204 | + private const val TIME_SHOWN_OFFSET_MILLIS = 500
|
|
| 185 | 205 | |
| 186 | 206 | /**
|
| 187 | 207 | * Creates a new instance of [RenameAndChangeLocationDialogFragment].
|
| ... | ... | @@ -203,3 +223,14 @@ class RenameAndChangeLocationDialogFragment : DialogFragment() { |
| 203 | 223 | }
|
| 204 | 224 | }
|
| 205 | 225 | }
|
| 226 | + |
|
| 227 | +/**
|
|
| 228 | + * Starts (or restarts) the time-based check without increasing the "click count".
|
|
| 229 | + *
|
|
| 230 | + * Makes it safe to call from multiple/successive lifecycle methods, without running into the risk
|
|
| 231 | + * of triggering the more restrictive count-based protection on the 1st click (or even before it).
|
|
| 232 | + */
|
|
| 233 | +private fun PromptAbuserDetector.start() {
|
|
| 234 | + resetJSAlertAbuseState()
|
|
| 235 | + updateJSDialogAbusedState()
|
|
| 236 | +} |