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:
Changes:
gfx/layers/ipc/PUiCompositorController.ipdl
| ... |
... |
@@ -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
|
gfx/layers/ipc/UiCompositorControllerChild.cpp
| ... |
... |
@@ -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
|
|
gfx/layers/ipc/UiCompositorControllerChild.h
| ... |
... |
@@ -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.
|
gfx/layers/ipc/UiCompositorControllerParent.cpp
| ... |
... |
@@ -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
|
|
gfx/layers/ipc/UiCompositorControllerParent.h
| ... |
... |
@@ -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;
|
gfx/layers/wr/WebRenderBridgeParent.cpp
| ... |
... |
@@ -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
|
gfx/layers/wr/WebRenderBridgeParent.h
| ... |
... |
@@ -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;
|
gfx/webrender_bindings/RenderCompositor.h
| ... |
... |
@@ -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
|
gfx/webrender_bindings/RenderCompositorOGLSWGL.cpp
| ... |
... |
@@ -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
|
|
gfx/webrender_bindings/RenderCompositorOGLSWGL.h
| ... |
... |
@@ -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:
|
gfx/webrender_bindings/RendererOGL.cpp
| ... |
... |
@@ -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
|
|
gfx/webrender_bindings/RendererOGL.h
| ... |
... |
@@ -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;
|
gfx/webrender_bindings/WebRenderAPI.cpp
| ... |
... |
@@ -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;
|
gfx/webrender_bindings/WebRenderAPI.h
| ... |
... |
@@ -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();
|
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) |
|
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
|
mobile/android/android-components/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadsFeatureTest.kt
| ... |
... |
@@ -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)
|
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 |
|
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)
|
mobile/android/android-components/components/support/utils/src/main/java/mozilla/components/support/utils/OnEnterAnimationCompleteListener.kt
|
|
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
|
+} |
mobile/android/components/geckoview/GeckoViewContentChannelParent.cpp
| ... |
... |
@@ -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))) {
|
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 |
|
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()
|
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/addons/AddonPopupBaseFragment.kt
| ... |
... |
@@ -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
|
*/
|
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt
| ... |
... |
@@ -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,
|
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivity.kt
| ... |
... |
@@ -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
|
} |
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/downloads/RenameAndChangeLocationDialogFragment.kt
| ... |
... |
@@ -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
|
+} |
|