Source code

Revision control

Other Tools

/* -*- Mode: c++; c-basic-offset: 2; tab-width: 4; indent-tabs-mode: nil; -*-
* vim: set sw=2 ts=4 expandtab:
* 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/. */
#include <android/log.h>
#include <android/native_window.h>
#include <android/native_window_jni.h>
#include <math.h>
#include <queue>
#include <type_traits>
#include <unistd.h>
#include "mozilla/MiscEvents.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/Preferences.h"
#include "mozilla/StaticPrefs_android.h"
#include "mozilla/StaticPrefs_ui.h"
#include "mozilla/TouchEvents.h"
#include "mozilla/Unused.h"
#include "mozilla/WeakPtr.h"
#include "mozilla/WheelHandlingHelper.h" // for WheelDeltaAdjustmentStrategy
#include "mozilla/Preferences.h"
#include "mozilla/Unused.h"
#include "mozilla/a11y/SessionAccessibility.h"
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/MouseEventBinding.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/DataSurfaceHelpers.h"
#include "mozilla/gfx/Swizzle.h"
#include "mozilla/gfx/Types.h"
#include "mozilla/layers/LayersTypes.h"
#include "mozilla/widget/AndroidVsync.h"
#include <algorithm>
using mozilla::Unused;
using mozilla::dom::ContentChild;
using mozilla::dom::ContentParent;
using mozilla::gfx::DataSourceSurface;
using mozilla::gfx::IntSize;
using mozilla::gfx::Matrix;
using mozilla::gfx::SurfaceFormat;
#include "nsWindow.h"
#include "AndroidGraphics.h"
#include "JavaExceptions.h"
#include "nsIWidgetListener.h"
#include "nsIWindowWatcher.h"
#include "nsIAppWindow.h"
#include "nsAppShell.h"
#include "nsFocusManager.h"
#include "nsUserIdleService.h"
#include "nsLayoutUtils.h"
#include "nsNetUtil.h"
#include "nsViewManager.h"
#include "WidgetUtils.h"
#include "nsContentUtils.h"
#include "nsGfxCIID.h"
#include "nsGkAtoms.h"
#include "nsWidgetsCID.h"
#include "gfxContext.h"
#include "AndroidContentController.h"
#include "GLContext.h"
#include "GLContextProvider.h"
#include "Layers.h"
#include "WindowRenderer.h"
#include "ScopedGLHelpers.h"
#include "mozilla/layers/APZEventState.h"
#include "mozilla/layers/APZInputBridge.h"
#include "mozilla/layers/APZThreadUtils.h"
#include "mozilla/layers/CompositorOGL.h"
#include "mozilla/layers/IAPZCTreeManager.h"
#include "nsTArray.h"
#include "AndroidBridge.h"
#include "AndroidBridgeUtilities.h"
#include "AndroidUiThread.h"
#include "AndroidView.h"
#include "GeckoEditableSupport.h"
#include "GeckoViewSupport.h"
#include "KeyEvent.h"
#include "MotionEvent.h"
#include "mozilla/java/EventDispatcherWrappers.h"
#include "mozilla/java/GeckoAppShellWrappers.h"
#include "mozilla/java/GeckoEditableChildWrappers.h"
#include "mozilla/java/GeckoResultWrappers.h"
#include "mozilla/java/GeckoSessionNatives.h"
#include "mozilla/java/GeckoSystemStateListenerWrappers.h"
#include "mozilla/java/PanZoomControllerNatives.h"
#include "mozilla/java/SessionAccessibilityWrappers.h"
#include "ScreenHelperAndroid.h"
#include "TouchResampler.h"
#include "mozilla/ProfilerLabels.h"
#include "nsPrintfCString.h"
#include "nsString.h"
#include "JavaBuiltins.h"
#include "mozilla/ipc/Shmem.h"
using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::layers;
using namespace mozilla::widget;
using namespace mozilla::ipc;
using mozilla::java::GeckoSession;
#include "mozilla/layers/CompositorBridgeChild.h"
#include "mozilla/layers/CompositorSession.h"
#include "mozilla/layers/UiCompositorControllerChild.h"
#include "nsThreadUtils.h"
// All the toplevel windows that have been created; these are in
// stacking order, so the window at gTopLevelWindows[0] is the topmost
// one.
static nsTArray<nsWindow*> gTopLevelWindows;
static bool sFailedToCreateGLContext = false;
// Multitouch swipe thresholds in inches
static const double SWIPE_MAX_PINCH_DELTA_INCHES = 0.4;
static const double SWIPE_MIN_DISTANCE_INCHES = 0.6;
static const double kTouchResampleVsyncAdjustMs = 5.0;
static const int32_t INPUT_RESULT_UNHANDLED =
java::PanZoomController::INPUT_RESULT_UNHANDLED;
static const int32_t INPUT_RESULT_HANDLED =
java::PanZoomController::INPUT_RESULT_HANDLED;
static const int32_t INPUT_RESULT_HANDLED_CONTENT =
java::PanZoomController::INPUT_RESULT_HANDLED_CONTENT;
static const int32_t INPUT_RESULT_IGNORED =
java::PanZoomController::INPUT_RESULT_IGNORED;
static const nsCString::size_type MAX_TOPLEVEL_DATA_URI_LEN = 2 * 1024 * 1024;
namespace {
template <class Instance, class Impl>
std::enable_if_t<jni::detail::NativePtrPicker<Impl>::value ==
jni::detail::NativePtrType::REFPTR,
void>
CallAttachNative(Instance aInstance, Impl* aImpl) {
Impl::AttachNative(aInstance, RefPtr<Impl>(aImpl).get());
}
template <class Instance, class Impl>
std::enable_if_t<jni::detail::NativePtrPicker<Impl>::value ==
jni::detail::NativePtrType::OWNING,
void>
CallAttachNative(Instance aInstance, Impl* aImpl) {
Impl::AttachNative(aInstance, UniquePtr<Impl>(aImpl));
}
template <class Lambda>
bool DispatchToUiThread(const char* aName, Lambda&& aLambda) {
if (RefPtr<nsThread> uiThread = GetAndroidUiThread()) {
uiThread->Dispatch(NS_NewRunnableFunction(aName, std::move(aLambda)));
return true;
}
return false;
}
} // namespace
namespace mozilla {
namespace widget {
using WindowPtr = jni::NativeWeakPtr<GeckoViewSupport>;
/**
* PanZoomController handles its native calls on the UI thread, so make
* it separate from GeckoViewSupport.
*/
class NPZCSupport final
: public java::PanZoomController::NativeProvider::Natives<NPZCSupport>,
public AndroidVsync::Observer {
WindowPtr mWindow;
java::PanZoomController::NativeProvider::WeakRef mNPZC;
// Stores the returnResult of each pending motion event between
// HandleMotionEvent and FinishHandlingMotionEvent.
std::queue<std::pair<uint64_t, java::GeckoResult::GlobalRef>>
mPendingMotionEventReturnResults;
RefPtr<AndroidVsync> mAndroidVsync;
TouchResampler mTouchResampler;
int mPreviousButtons = 0;
bool mListeningToVsync = false;
// Only true if mAndroidVsync is non-null and the resampling pref is set.
bool mTouchResamplingEnabled = false;
template <typename Lambda>
class InputEvent final : public nsAppShell::Event {
java::PanZoomController::NativeProvider::GlobalRef mNPZC;
Lambda mLambda;
public:
InputEvent(const NPZCSupport* aNPZCSupport, Lambda&& aLambda)
: mNPZC(aNPZCSupport->mNPZC), mLambda(std::move(aLambda)) {}
void Run() override {
MOZ_ASSERT(NS_IsMainThread());
JNIEnv* const env = jni::GetGeckoThreadEnv();
const auto npzcSupportWeak = GetNative(
java::PanZoomController::NativeProvider::LocalRef(env, mNPZC));
if (!npzcSupportWeak) {
// We already shut down.
env->ExceptionClear();
return;
}
auto acc = npzcSupportWeak->Access();
if (!acc) {
// We already shut down.
env->ExceptionClear();
return;
}
auto win = acc->mWindow.Access();
if (!win) {
// We already shut down.
env->ExceptionClear();
return;
}
nsWindow* const window = win->GetNsWindow();
if (!window) {
// We already shut down.
env->ExceptionClear();
return;
}
window->UserActivity();
return mLambda(window);
}
bool IsUIEvent() const override { return true; }
};
template <typename Lambda>
void PostInputEvent(Lambda&& aLambda) {
// Use priority queue for input events.
nsAppShell::PostEvent(
MakeUnique<InputEvent<Lambda>>(this, std::move(aLambda)));
}
public:
typedef java::PanZoomController::NativeProvider::Natives<NPZCSupport> Base;
NPZCSupport(WindowPtr aWindow,
const java::PanZoomController::NativeProvider::LocalRef& aNPZC)
: mWindow(aWindow), mNPZC(aNPZC) {
#if defined(DEBUG)
auto win(mWindow.Access());
MOZ_ASSERT(!!win);
#endif // defined(DEBUG)
// Use vsync for touch resampling on API level 19 and above.
// See gfxAndroidPlatform::CreateHardwareVsyncSource() for comparison.
if (jni::GetAPIVersion() >= 19) {
mAndroidVsync = AndroidVsync::GetInstance();
}
}
~NPZCSupport() {
if (mListeningToVsync) {
MOZ_RELEASE_ASSERT(mAndroidVsync);
mAndroidVsync->UnregisterObserver(this, AndroidVsync::INPUT);
mListeningToVsync = false;
}
}
using Base::AttachNative;
using Base::DisposeNative;
void OnWeakNonIntrusiveDetach(already_AddRefed<Runnable> aDisposer) {
RefPtr<Runnable> disposer = aDisposer;
// There are several considerations when shutting down NPZC. 1) The
// Gecko thread may destroy NPZC at any time when nsWindow closes. 2)
// There may be pending events on the Gecko thread when NPZC is
// destroyed. 3) mWindow may not be available when the pending event
// runs. 4) The UI thread may destroy NPZC at any time when GeckoView
// is destroyed. 5) The UI thread may destroy NPZC at the same time as
// Gecko thread trying to destroy NPZC. 6) There may be pending calls
// on the UI thread when NPZC is destroyed. 7) mWindow may have been
// cleared on the Gecko thread when the pending call happens on the UI
// thread.
//
// 1) happens through OnWeakNonIntrusiveDetach, which first notifies the UI
// thread through Destroy; Destroy then calls DisposeNative, which
// finally disposes the native instance back on the Gecko thread. Using
// Destroy to indirectly call DisposeNative here also solves 5), by
// making everything go through the UI thread, avoiding contention.
//
// 2) and 3) are solved by clearing mWindow, which signals to the
// pending event that we had shut down. In that case the event bails
// and does not touch mWindow.
//
// 4) happens through DisposeNative directly.
//
// 6) is solved by keeping a destroyed flag in the Java NPZC instance,
// and only make a pending call if the destroyed flag is not set.
//
// 7) is solved by taking a lock whenever mWindow is modified on the
// Gecko thread or accessed on the UI thread. That way, we don't
// release mWindow until the UI thread is done using it, thus avoiding
// the race condition.
if (RefPtr<nsThread> uiThread = GetAndroidUiThread()) {
auto npzc = java::PanZoomController::NativeProvider::GlobalRef(mNPZC);
if (!npzc) {
return;
}
uiThread->Dispatch(
NS_NewRunnableFunction("NPZCSupport::OnWeakNonIntrusiveDetach",
[npzc, disposer = std::move(disposer)] {
npzc->SetAttached(false);
disposer->Run();
}));
}
}
const java::PanZoomController::NativeProvider::Ref& GetJavaNPZC() const {
return mNPZC;
}
public:
void SetIsLongpressEnabled(bool aIsLongpressEnabled) {
RefPtr<IAPZCTreeManager> controller;
if (auto window = mWindow.Access()) {
nsWindow* gkWindow = window->GetNsWindow();
if (gkWindow) {
controller = gkWindow->mAPZC;
}
}
if (controller) {
controller->SetLongTapEnabled(aIsLongpressEnabled);
}
}
int32_t HandleScrollEvent(int64_t aTime, int32_t aMetaState, float aX,
float aY, float aHScroll, float aVScroll) {
MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
RefPtr<IAPZCTreeManager> controller;
if (auto window = mWindow.Access()) {
nsWindow* gkWindow = window->GetNsWindow();
if (gkWindow) {
controller = gkWindow->mAPZC;
}
}
if (!controller) {
return INPUT_RESULT_UNHANDLED;
}
ScreenPoint origin = ScreenPoint(aX, aY);
if (StaticPrefs::ui_scrolling_negate_wheel_scroll()) {
aHScroll = -aHScroll;
aVScroll = -aVScroll;
}
ScrollWheelInput input(
aTime, nsWindow::GetEventTimeStamp(aTime),
nsWindow::GetModifiers(aMetaState), ScrollWheelInput::SCROLLMODE_SMOOTH,
ScrollWheelInput::SCROLLDELTA_PIXEL, origin, aHScroll, aVScroll, false,
// XXX Do we need to support auto-dir scrolling
// for Android widgets with a wheel device?
// Currently, I just leave it unimplemented. If
// we need to implement it, what's the extra work
// to do?
WheelDeltaAdjustmentStrategy::eNone);
APZEventResult result = controller->InputBridge()->ReceiveInputEvent(input);
if (result.GetStatus() == nsEventStatus_eConsumeNoDefault) {
return INPUT_RESULT_IGNORED;
}
PostInputEvent([input, result](nsWindow* window) {
WidgetWheelEvent wheelEvent = input.ToWidgetEvent(window);
window->ProcessUntransformedAPZEvent(&wheelEvent, result);
});
switch (result.GetStatus()) {
case nsEventStatus_eIgnore:
return INPUT_RESULT_UNHANDLED;
case nsEventStatus_eConsumeDoDefault:
return result.GetHandledResult()->IsHandledByRoot()
? INPUT_RESULT_HANDLED
: INPUT_RESULT_HANDLED_CONTENT;
default:
MOZ_ASSERT_UNREACHABLE("Unexpected nsEventStatus");
return INPUT_RESULT_UNHANDLED;
}
}
private:
static MouseInput::ButtonType GetButtonType(int button) {
MouseInput::ButtonType result = MouseInput::NONE;
switch (button) {
case java::sdk::MotionEvent::BUTTON_PRIMARY:
result = MouseInput::PRIMARY_BUTTON;
break;
case java::sdk::MotionEvent::BUTTON_SECONDARY:
result = MouseInput::SECONDARY_BUTTON;
break;
case java::sdk::MotionEvent::BUTTON_TERTIARY:
result = MouseInput::MIDDLE_BUTTON;
break;
default:
break;
}
return result;
}
static int16_t ConvertButtons(int buttons) {
int16_t result = 0;
if (buttons & java::sdk::MotionEvent::BUTTON_PRIMARY) {
result |= MouseButtonsFlag::ePrimaryFlag;
}
if (buttons & java::sdk::MotionEvent::BUTTON_SECONDARY) {
result |= MouseButtonsFlag::eSecondaryFlag;
}
if (buttons & java::sdk::MotionEvent::BUTTON_TERTIARY) {
result |= MouseButtonsFlag::eMiddleFlag;
}
if (buttons & java::sdk::MotionEvent::BUTTON_BACK) {
result |= MouseButtonsFlag::e4thFlag;
}
if (buttons & java::sdk::MotionEvent::BUTTON_FORWARD) {
result |= MouseButtonsFlag::e5thFlag;
}
return result;
}
static int32_t ConvertAPZHandledPlace(APZHandledPlace aHandledPlace) {
switch (aHandledPlace) {
case APZHandledPlace::Unhandled:
return INPUT_RESULT_UNHANDLED;
case APZHandledPlace::HandledByRoot:
return INPUT_RESULT_HANDLED;
case APZHandledPlace::HandledByContent:
return INPUT_RESULT_HANDLED_CONTENT;
case APZHandledPlace::Invalid:
MOZ_ASSERT_UNREACHABLE("The handled result should NOT be Invalid");
return INPUT_RESULT_UNHANDLED;
}
MOZ_ASSERT_UNREACHABLE("Unknown handled result");
return INPUT_RESULT_UNHANDLED;
}
static int32_t ConvertSideBits(SideBits aSideBits) {
int32_t ret = java::PanZoomController::SCROLLABLE_FLAG_NONE;
if (aSideBits & SideBits::eTop) {
ret |= java::PanZoomController::SCROLLABLE_FLAG_TOP;
}
if (aSideBits & SideBits::eRight) {
ret |= java::PanZoomController::SCROLLABLE_FLAG_RIGHT;
}
if (aSideBits & SideBits::eBottom) {
ret |= java::PanZoomController::SCROLLABLE_FLAG_BOTTOM;
}
if (aSideBits & SideBits::eLeft) {
ret |= java::PanZoomController::SCROLLABLE_FLAG_LEFT;
}
return ret;
}
static int32_t ConvertScrollDirections(
layers::ScrollDirections aScrollDirections) {
int32_t ret = java::PanZoomController::OVERSCROLL_FLAG_NONE;
if (aScrollDirections.contains(layers::HorizontalScrollDirection)) {
ret |= java::PanZoomController::OVERSCROLL_FLAG_HORIZONTAL;
}
if (aScrollDirections.contains(layers::VerticalScrollDirection)) {
ret |= java::PanZoomController::OVERSCROLL_FLAG_VERTICAL;
}
return ret;
}
static java::PanZoomController::InputResultDetail::LocalRef
ConvertAPZHandledResult(const APZHandledResult& aHandledResult) {
return java::PanZoomController::InputResultDetail::New(
ConvertAPZHandledPlace(aHandledResult.mPlace),
ConvertSideBits(aHandledResult.mScrollableDirections),
ConvertScrollDirections(aHandledResult.mOverscrollDirections));
}
public:
int32_t HandleMouseEvent(int32_t aAction, int64_t aTime, int32_t aMetaState,
float aX, float aY, int buttons) {
MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
RefPtr<IAPZCTreeManager> controller;
if (auto window = mWindow.Access()) {
nsWindow* gkWindow = window->GetNsWindow();
if (gkWindow) {
controller = gkWindow->mAPZC;
}
}
if (!controller) {
return INPUT_RESULT_UNHANDLED;
}
MouseInput::MouseType mouseType = MouseInput::MOUSE_NONE;
MouseInput::ButtonType buttonType = MouseInput::NONE;
switch (aAction) {
case java::sdk::MotionEvent::ACTION_DOWN:
mouseType = MouseInput::MOUSE_DOWN;
buttonType = GetButtonType(buttons ^ mPreviousButtons);
mPreviousButtons = buttons;
break;
case java::sdk::MotionEvent::ACTION_UP:
mouseType = MouseInput::MOUSE_UP;
buttonType = GetButtonType(buttons ^ mPreviousButtons);
mPreviousButtons = buttons;
break;
case java::sdk::MotionEvent::ACTION_MOVE:
mouseType = MouseInput::MOUSE_MOVE;
break;
case java::sdk::MotionEvent::ACTION_HOVER_MOVE:
mouseType = MouseInput::MOUSE_MOVE;
break;
case java::sdk::MotionEvent::ACTION_HOVER_ENTER:
mouseType = MouseInput::MOUSE_WIDGET_ENTER;
break;
case java::sdk::MotionEvent::ACTION_HOVER_EXIT:
mouseType = MouseInput::MOUSE_WIDGET_EXIT;
break;
default:
break;
}
if (mouseType == MouseInput::MOUSE_NONE) {
return INPUT_RESULT_UNHANDLED;
}
ScreenPoint origin = ScreenPoint(aX, aY);
MouseInput input(
mouseType, buttonType, MouseEvent_Binding::MOZ_SOURCE_MOUSE,
ConvertButtons(buttons), origin, aTime,
nsWindow::GetEventTimeStamp(aTime), nsWindow::GetModifiers(aMetaState));
APZEventResult result = controller->InputBridge()->ReceiveInputEvent(input);
if (result.GetStatus() == nsEventStatus_eConsumeNoDefault) {
return INPUT_RESULT_IGNORED;
}
PostInputEvent([input, result](nsWindow* window) {
WidgetMouseEvent mouseEvent = input.ToWidgetEvent(window);
window->ProcessUntransformedAPZEvent(&mouseEvent, result);
});
switch (result.GetStatus()) {
case nsEventStatus_eIgnore:
return INPUT_RESULT_UNHANDLED;
case nsEventStatus_eConsumeDoDefault:
return result.GetHandledResult()->IsHandledByRoot()
? INPUT_RESULT_HANDLED
: INPUT_RESULT_HANDLED_CONTENT;
default:
MOZ_ASSERT_UNREACHABLE("Unexpected nsEventStatus");
return INPUT_RESULT_UNHANDLED;
}
}
// Convert MotionEvent touch radius and orientation into the format required
// by w3c touchevents.
// toolMajor and toolMinor span a rectangle that's oriented as per
// aOrientation, centered around the touch point.
static std::pair<float, ScreenSize> ConvertOrientationAndRadius(
float aOrientation, float aToolMajor, float aToolMinor) {
float angle = aOrientation * 180.0f / M_PI;
// w3c touchevents spec does not allow orientations == 90
// this shifts it to -90, which will be shifted to zero below
if (angle >= 90.0) {
angle -= 180.0f;
}
// w3c touchevent radii are given with an orientation between 0 and
// 90. The radii are found by removing the orientation and
// measuring the x and y radii of the resulting ellipse. For
// Android orientations >= 0 and < 90, use the y radius as the
// major radius, and x as the minor radius. However, for an
// orientation < 0, we have to shift the orientation by adding 90,
// and reverse which radius is major and minor.
ScreenSize radius;
if (angle < 0.0f) {
angle += 90.0f;
radius =
ScreenSize(int32_t(aToolMajor / 2.0f), int32_t(aToolMinor / 2.0f));
} else {
radius =
ScreenSize(int32_t(aToolMinor / 2.0f), int32_t(aToolMajor / 2.0f));
}
return std::make_pair(angle, radius);
}
void HandleMotionEvent(
const java::PanZoomController::NativeProvider::LocalRef& aInstance,
jni::Object::Param aEventData, float aScreenX, float aScreenY,
jni::Object::Param aResult) {
MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
auto returnResult = java::GeckoResult::Ref::From(aResult);
auto eventData =
java::PanZoomController::MotionEventData::Ref::From(aEventData);
nsTArray<int32_t> pointerId(eventData->PointerId()->GetElements());
size_t pointerCount = pointerId.Length();
MultiTouchInput::MultiTouchType type;
size_t startIndex = 0;
size_t endIndex = pointerCount;
switch (eventData->Action()) {
case java::sdk::MotionEvent::ACTION_DOWN:
case java::sdk::MotionEvent::ACTION_POINTER_DOWN:
type = MultiTouchInput::MULTITOUCH_START;
break;
case java::sdk::MotionEvent::ACTION_MOVE:
type = MultiTouchInput::MULTITOUCH_MOVE;
break;
case java::sdk::MotionEvent::ACTION_UP:
case java::sdk::MotionEvent::ACTION_POINTER_UP:
// for pointer-up events we only want the data from
// the one pointer that went up
type = MultiTouchInput::MULTITOUCH_END;
startIndex = eventData->ActionIndex();
endIndex = startIndex + 1;
break;
case java::sdk::MotionEvent::ACTION_OUTSIDE:
case java::sdk::MotionEvent::ACTION_CANCEL:
type = MultiTouchInput::MULTITOUCH_CANCEL;
break;
default:
if (returnResult) {
returnResult->Complete(
java::sdk::Integer::ValueOf(INPUT_RESULT_UNHANDLED));
}
return;
}
MultiTouchInput input(type, eventData->Time(),
nsWindow::GetEventTimeStamp(eventData->Time()), 0);
input.modifiers = nsWindow::GetModifiers(eventData->MetaState());
input.mTouches.SetCapacity(endIndex - startIndex);
input.mScreenOffset =
ExternalIntPoint(int32_t(floorf(aScreenX)), int32_t(floorf(aScreenY)));
size_t historySize = eventData->HistorySize();
nsTArray<int64_t> historicalTime(
eventData->HistoricalTime()->GetElements());
MOZ_RELEASE_ASSERT(historicalTime.Length() == historySize);
// Each of these is |historySize| sets of |pointerCount| values.
size_t historicalDataCount = historySize * pointerCount;
nsTArray<float> historicalX(eventData->HistoricalX()->GetElements());
nsTArray<float> historicalY(eventData->HistoricalY()->GetElements());
nsTArray<float> historicalOrientation(
eventData->HistoricalOrientation()->GetElements());
nsTArray<float> historicalPressure(
eventData->HistoricalPressure()->GetElements());
nsTArray<float> historicalToolMajor(
eventData->HistoricalToolMajor()->GetElements());
nsTArray<float> historicalToolMinor(
eventData->HistoricalToolMinor()->GetElements());
MOZ_RELEASE_ASSERT(historicalX.Length() == historicalDataCount);
MOZ_RELEASE_ASSERT(historicalY.Length() == historicalDataCount);
MOZ_RELEASE_ASSERT(historicalOrientation.Length() == historicalDataCount);
MOZ_RELEASE_ASSERT(historicalPressure.Length() == historicalDataCount);
MOZ_RELEASE_ASSERT(historicalToolMajor.Length() == historicalDataCount);
MOZ_RELEASE_ASSERT(historicalToolMinor.Length() == historicalDataCount);
// Each of these is |pointerCount| values.
nsTArray<float> x(eventData->X()->GetElements());
nsTArray<float> y(eventData->Y()->GetElements());
nsTArray<float> orientation(eventData->Orientation()->GetElements());
nsTArray<float> pressure(eventData->Pressure()->GetElements());
nsTArray<float> toolMajor(eventData->ToolMajor()->GetElements());
nsTArray<float> toolMinor(eventData->ToolMinor()->GetElements());
MOZ_ASSERT(x.Length() == pointerCount);
MOZ_ASSERT(y.Length() == pointerCount);
MOZ_ASSERT(orientation.Length() == pointerCount);
MOZ_ASSERT(pressure.Length() == pointerCount);
MOZ_ASSERT(toolMajor.Length() == pointerCount);
MOZ_ASSERT(toolMinor.Length() == pointerCount);
for (size_t i = startIndex; i < endIndex; i++) {
float orien;
ScreenSize radius;
std::tie(orien, radius) = ConvertOrientationAndRadius(
orientation[i], toolMajor[i], toolMinor[i]);
ScreenIntPoint point(int32_t(floorf(x[i])), int32_t(floorf(y[i])));
SingleTouchData singleTouchData(pointerId[i], point, radius, orien,
pressure[i]);
for (size_t historyIndex = 0; historyIndex < historySize;
historyIndex++) {
size_t historicalI = historyIndex * pointerCount + i;
float historicalAngle;
ScreenSize historicalRadius;
std::tie(historicalAngle, historicalRadius) =
ConvertOrientationAndRadius(historicalOrientation[historicalI],
historicalToolMajor[historicalI],
historicalToolMinor[historicalI]);
ScreenIntPoint historicalPoint(
int32_t(floorf(historicalX[historicalI])),
int32_t(floorf(historicalY[historicalI])));
singleTouchData.mHistoricalData.AppendElement(
SingleTouchData::HistoricalTouchData{
nsWindow::GetEventTimeStamp(historicalTime[historyIndex]),
historicalPoint,
{}, // mLocalScreenPoint will be computed later by APZ
historicalRadius,
historicalAngle,
historicalPressure[historicalI]});
}
input.mTouches.AppendElement(singleTouchData);
}
if (mAndroidVsync &&
eventData->Action() == java::sdk::MotionEvent::ACTION_DOWN) {
// Query pref value at the beginning of a touch gesture so that we don't
// leave events stuck in the resampler after a pref flip.
mTouchResamplingEnabled = StaticPrefs::android_touch_resampling_enabled();
}
if (!mTouchResamplingEnabled) {
FinishHandlingMotionEvent(std::move(input),
java::GeckoResult::LocalRef(returnResult));
return;
}
uint64_t eventId = mTouchResampler.ProcessEvent(std::move(input));
mPendingMotionEventReturnResults.push(
{eventId, java::GeckoResult::GlobalRef(returnResult)});
RegisterOrUnregisterForVsync(mTouchResampler.InTouchingState());
ConsumeMotionEventsFromResampler();
}
void RegisterOrUnregisterForVsync(bool aNeedVsync) {
MOZ_RELEASE_ASSERT(mAndroidVsync);
if (aNeedVsync && !mListeningToVsync) {
mAndroidVsync->RegisterObserver(this, AndroidVsync::INPUT);
} else if (!aNeedVsync && mListeningToVsync) {
mAndroidVsync->UnregisterObserver(this, AndroidVsync::INPUT);
}
mListeningToVsync = aNeedVsync;
}
void OnVsync(const TimeStamp& aTimeStamp) override {
mTouchResampler.NotifyFrame(aTimeStamp - TimeDuration::FromMilliseconds(
kTouchResampleVsyncAdjustMs));
ConsumeMotionEventsFromResampler();
}
void ConsumeMotionEventsFromResampler() {
auto outgoing = mTouchResampler.ConsumeOutgoingEvents();
while (!outgoing.empty()) {
auto outgoingEvent = std::move(outgoing.front());
outgoing.pop();
java::GeckoResult::GlobalRef returnResult;
if (outgoingEvent.mEventId) {
// Look up the GeckoResult for this event.
// The outgoing events from the resampler are in the same order as the
// original events, and no event IDs are skipped.
MOZ_RELEASE_ASSERT(!mPendingMotionEventReturnResults.empty());
auto pair = mPendingMotionEventReturnResults.front();
mPendingMotionEventReturnResults.pop();
MOZ_RELEASE_ASSERT(pair.first == *outgoingEvent.mEventId);
returnResult = pair.second;
}
FinishHandlingMotionEvent(std::move(outgoingEvent.mEvent),
java::GeckoResult::LocalRef(returnResult));
}
}
void FinishHandlingMotionEvent(MultiTouchInput&& aInput,
java::GeckoResult::LocalRef&& aReturnResult) {
RefPtr<IAPZCTreeManager> controller;
if (auto window = mWindow.Access()) {
nsWindow* gkWindow = window->GetNsWindow();
if (gkWindow) {
controller = gkWindow->mAPZC;
}
}
if (!controller) {
if (aReturnResult) {
aReturnResult->Complete(java::PanZoomController::InputResultDetail::New(
INPUT_RESULT_UNHANDLED,
java::PanZoomController::SCROLLABLE_FLAG_NONE,
java::PanZoomController::OVERSCROLL_FLAG_NONE));
}
return;
}
APZEventResult result =
controller->InputBridge()->ReceiveInputEvent(aInput);
if (result.GetStatus() == nsEventStatus_eConsumeNoDefault) {
if (aReturnResult) {
aReturnResult->Complete(java::PanZoomController::InputResultDetail::New(
INPUT_RESULT_IGNORED, java::PanZoomController::SCROLLABLE_FLAG_NONE,
java::PanZoomController::OVERSCROLL_FLAG_NONE));
}
return;
}
// Dispatch APZ input event on Gecko thread.
PostInputEvent([aInput, result](nsWindow* window) {
WidgetTouchEvent touchEvent = aInput.ToWidgetEvent(window);
window->ProcessUntransformedAPZEvent(&touchEvent, result);
window->DispatchHitTest(touchEvent);
});
if (!aReturnResult) {
// We don't care how APZ handled the event so we're done here.
return;
}
if (result.GetHandledResult() != Nothing()) {
// We know conclusively that the root APZ handled this or not and
// don't need to do any more work.
switch (result.GetStatus()) {
case nsEventStatus_eIgnore:
aReturnResult->Complete(
java::PanZoomController::InputResultDetail::New(
INPUT_RESULT_UNHANDLED,
java::PanZoomController::SCROLLABLE_FLAG_NONE,
java::PanZoomController::OVERSCROLL_FLAG_NONE));
break;
case nsEventStatus_eConsumeDoDefault:
aReturnResult->Complete(
ConvertAPZHandledResult(result.GetHandledResult().value()));
break;
default:
MOZ_ASSERT_UNREACHABLE("Unexpected nsEventStatus");
aReturnResult->Complete(
java::PanZoomController::InputResultDetail::New(
INPUT_RESULT_UNHANDLED,
java::PanZoomController::SCROLLABLE_FLAG_NONE,
java::PanZoomController::OVERSCROLL_FLAG_NONE));
break;
}
return;
}
// Wait to see if APZ handled the event or not...
controller->AddInputBlockCallback(
result.mInputBlockId,
[aReturnResult = java::GeckoResult::GlobalRef(aReturnResult)](
uint64_t aInputBlockId, const APZHandledResult& aHandledResult) {
aReturnResult->Complete(ConvertAPZHandledResult(aHandledResult));
});
}
};
NS_IMPL_ISUPPORTS(AndroidView, nsIAndroidEventDispatcher, nsIAndroidView)
nsresult AndroidView::GetInitData(JSContext* aCx, JS::MutableHandleValue aOut) {
if (!mInitData) {
aOut.setNull();
return NS_OK;
}
return widget::EventDispatcher::UnboxBundle(aCx, mInitData, aOut);
}
/**
* Compositor has some unique requirements for its native calls, so make it
* separate from GeckoViewSupport.
*/
class LayerViewSupport final
: public GeckoSession::Compositor::Natives<LayerViewSupport> {
WindowPtr mWindow;
GeckoSession::Compositor::WeakRef mCompositor;
Atomic<bool, ReleaseAcquire> mCompositorPaused;
jni::Object::GlobalRef mSurface;
struct CaptureRequest {
explicit CaptureRequest() : mResult(nullptr) {}
explicit CaptureRequest(java::GeckoResult::GlobalRef aResult,
java::sdk::Bitmap::GlobalRef aBitmap,
const ScreenRect& aSource,
const IntSize& aOutputSize)
: mResult(aResult),
mBitmap(aBitmap),
mSource(aSource),
mOutputSize(aOutputSize) {}
// where to send the pixels
java::GeckoResult::GlobalRef mResult;
// where to store the pixels
java::sdk::Bitmap::GlobalRef mBitmap;
ScreenRect mSource;
IntSize mOutputSize;
};
std::queue<CaptureRequest> mCapturePixelsResults;
// In order to use Event::HasSameTypeAs in PostTo(), we cannot make
// LayerViewEvent a template because each template instantiation is
// a different type. So implement LayerViewEvent as a ProxyEvent.
class LayerViewEvent final : public nsAppShell::ProxyEvent {
using Event = nsAppShell::Event;
public:
static UniquePtr<Event> MakeEvent(UniquePtr<Event>&& event) {
return MakeUnique<LayerViewEvent>(std::move(event));
}
explicit LayerViewEvent(UniquePtr<Event>&& event)
: nsAppShell::ProxyEvent(std::move(event)) {}
void PostTo(LinkedList<Event>& queue) override {
// Give priority to compositor events, but keep in order with
// existing compositor events.
nsAppShell::Event* event = queue.getFirst();
while (event && event->HasSameTypeAs(this)) {
event = event->getNext();
}
if (event) {
event->setPrevious(this);
} else {
queue.insertBack(this);
}
}
};
public:
typedef GeckoSession::Compositor::Natives<LayerViewSupport> Base;
LayerViewSupport(WindowPtr aWindow,
const GeckoSession::Compositor::LocalRef& aInstance)
: mWindow(aWindow), mCompositor(aInstance), mCompositorPaused(true) {
#if defined(DEBUG)
auto win(mWindow.Access());
MOZ_ASSERT(!!win);
#endif // defined(DEBUG)
}
~LayerViewSupport() {}
using Base::AttachNative;
using Base::DisposeNative;
void OnWeakNonIntrusiveDetach(already_AddRefed<Runnable> aDisposer) {
RefPtr<Runnable> disposer = aDisposer;
if (RefPtr<nsThread> uiThread = GetAndroidUiThread()) {
GeckoSession::Compositor::GlobalRef compositor(mCompositor);
if (!compositor) {
return;
}
uiThread->Dispatch(NS_NewRunnableFunction(
"LayerViewSupport::OnWeakNonIntrusiveDetach",
[compositor, disposer = std::move(disposer),
results = &mCapturePixelsResults, window = mWindow]() mutable {
if (auto accWindow = window.Access()) {
while (!results->empty()) {
auto aResult =
java::GeckoResult::LocalRef(results->front().mResult);
if (aResult) {
aResult->CompleteExceptionally(
java::sdk::IllegalStateException::New(
"The compositor has detached from the session")
.Cast<jni::Throwable>());
}
results->pop();
}
compositor->OnCompositorDetached();
}
disposer->Run();
}));
}
}
const GeckoSession::Compositor::Ref& GetJavaCompositor() const {
return mCompositor;
}
bool CompositorPaused() const { return mCompositorPaused; }
jni::Object::Param GetSurface() { return mSurface; }
private:
already_AddRefed<UiCompositorControllerChild>
GetUiCompositorControllerChild() {
RefPtr<UiCompositorControllerChild> child;
if (auto window = mWindow.Access()) {
nsWindow* gkWindow = window->GetNsWindow();
if (gkWindow) {
child = gkWindow->GetUiCompositorControllerChild();
}
}
return child.forget();
}
already_AddRefed<DataSourceSurface> FlipScreenPixels(
Shmem& aMem, const ScreenIntSize& aInSize, const ScreenRect& aInRegion,
const IntSize& aOutSize) {
RefPtr<gfx::DataSourceSurface> image =
gfx::Factory::CreateWrappingDataSourceSurface(
aMem.get<uint8_t>(),
StrideForFormatAndWidth(SurfaceFormat::B8G8R8A8, aInSize.width),
IntSize(aInSize.width, aInSize.height), SurfaceFormat::B8G8R8A8);
RefPtr<gfx::DrawTarget> drawTarget =
gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
aOutSize, SurfaceFormat::B8G8R8A8);
if (!drawTarget) {
return nullptr;
}
drawTarget->SetTransform(Matrix::Scaling(1.0, -1.0) *
Matrix::Translation(0, aOutSize.height));
gfx::Rect srcRect(aInRegion.x,
(aInSize.height - aInRegion.height) - aInRegion.y,
aInRegion.width, aInRegion.height);
gfx::Rect destRect(0, 0, aOutSize.width, aOutSize.height);
drawTarget->DrawSurface(image, destRect, srcRect);
RefPtr<gfx::SourceSurface> snapshot = drawTarget->Snapshot();
RefPtr<gfx::DataSourceSurface> data = snapshot->GetDataSurface();
return data.forget();
}
/**
* Compositor methods
*/
public:
void AttachNPZC(jni::Object::Param aNPZC) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aNPZC);
auto locked(mWindow.Access());
if (!locked) {
return; // Already shut down.
}
nsWindow* gkWindow = locked->GetNsWindow();
// We can have this situation if we get two GeckoViewSupport::Transfer()
// called before the first AttachNPZC() gets here. Just detach the current
// instance since that's what happens in GeckoViewSupport::Transfer() as
// well.
gkWindow->mNPZCSupport.Detach();
auto npzc = java::PanZoomController::NativeProvider::LocalRef(
jni::GetGeckoThreadEnv(),
java::PanZoomController::NativeProvider::Ref::From(aNPZC));
gkWindow->mNPZCSupport =
jni::NativeWeakPtrHolder<NPZCSupport>::Attach(npzc, mWindow, npzc);
DispatchToUiThread(
"LayerViewSupport::AttachNPZC",
[npzc = java::PanZoomController::NativeProvider::GlobalRef(npzc)] {
npzc->SetAttached(true);
});
}
void OnBoundsChanged(int32_t aLeft, int32_t aTop, int32_t aWidth,
int32_t aHeight) {
MOZ_ASSERT(NS_IsMainThread());
auto acc = mWindow.Access();
if (!acc) {
return; // Already shut down.
}
nsWindow* gkWindow = acc->GetNsWindow();
if (!gkWindow) {
return;
}
gkWindow->Resize(aLeft, aTop, aWidth, aHeight, /* repaint */ false);
}
void SetDynamicToolbarMaxHeight(int32_t aHeight) {
MOZ_ASSERT(NS_IsMainThread());
auto acc = mWindow.Access();
if (!acc) {
return; // Already shut down.
}
nsWindow* gkWindow = acc->GetNsWindow();
if (!gkWindow) {
return;
}
gkWindow->UpdateDynamicToolbarMaxHeight(ScreenIntCoord(aHeight));
}
void SyncPauseCompositor() {
MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
if (RefPtr<UiCompositorControllerChild> child =
GetUiCompositorControllerChild()) {
mCompositorPaused = true;
child->Pause();
}
if (auto lock{mWindow.Access()}) {
while (!mCapturePixelsResults.empty()) {
auto result =
java::GeckoResult::LocalRef(mCapturePixelsResults.front().mResult);
if (result) {
result->CompleteExceptionally(
java::sdk::IllegalStateException::New(
"The compositor has detached from the session")
.Cast<jni::Throwable>());
}
mCapturePixelsResults.pop();
}
}
}
void SyncResumeCompositor() {
MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
if (RefPtr<UiCompositorControllerChild> child =
GetUiCompositorControllerChild()) {
mCompositorPaused = false;
child->Resume();
}
}
void SyncResumeResizeCompositor(
const GeckoSession::Compositor::LocalRef& aObj, int32_t aX, int32_t aY,
int32_t aWidth, int32_t aHeight, jni::Object::Param aSurface) {
MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
mSurface = aSurface;
if (RefPtr<UiCompositorControllerChild> child =
GetUiCompositorControllerChild()) {
child->ResumeAndResize(aX, aY, aWidth, aHeight);
}
mCompositorPaused = false;
class OnResumedEvent : public nsAppShell::Event {
GeckoSession::Compositor::GlobalRef mCompositor;
public:
explicit OnResumedEvent(GeckoSession::Compositor::GlobalRef&& aCompositor)
: mCompositor(std::move(aCompositor)) {}
void Run() override {
MOZ_ASSERT(NS_IsMainThread());
JNIEnv* const env = jni::GetGeckoThreadEnv();
const auto lvsHolder =
GetNative(GeckoSession::Compositor::LocalRef(env, mCompositor));
if (!lvsHolder) {
env->ExceptionClear();
return; // Already shut down.
}
auto lvs(lvsHolder->Access());
if (!lvs) {
env->ExceptionClear();
return; // Already shut down.
}
auto win = lvs->mWindow.Access();
if (!win) {
env->ExceptionClear();
return; // Already shut down.
}
// When we get here, the compositor has already been told to
// resume. This means it's now safe for layer updates to occur.
// Since we might have prevented one or more draw events from
// occurring while the compositor was paused, we need to
// schedule a draw event now.
if (!lvs->mCompositorPaused) {
nsWindow* const gkWindow = win->GetNsWindow();
if (gkWindow) {
gkWindow->RedrawAll();
}
}
}
};
// Use priority queue for timing-sensitive event.
nsAppShell::PostEvent(
MakeUnique<LayerViewEvent>(MakeUnique<OnResumedEvent>(aObj)));
}
void SyncInvalidateAndScheduleComposite() {
RefPtr<UiCompositorControllerChild> child =
GetUiCompositorControllerChild();
if (!child) {
return;
}
if (AndroidBridge::IsJavaUiThread()) {
child->InvalidateAndRender();
return;
}
if (RefPtr<nsThread> uiThread = GetAndroidUiThread()) {
uiThread->Dispatch(NewRunnableMethod<>(
"LayerViewSupport::InvalidateAndRender", child,
&UiCompositorControllerChild::InvalidateAndRender),
nsIThread::DISPATCH_NORMAL);
}
}
void SetMaxToolbarHeight(int32_t aHeight) {
MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
if (RefPtr<UiCompositorControllerChild> child =
GetUiCompositorControllerChild()) {
child->SetMaxToolbarHeight(aHeight);
}
}
void SetFixedBottomOffset(int32_t aOffset) {
if (auto acc{mWindow.Access()}) {
nsWindow* gkWindow = acc->GetNsWindow();
if (gkWindow) {
gkWindow->UpdateDynamicToolbarOffset(ScreenIntCoord(aOffset));
}
}
if (RefPtr<nsThread> uiThread = GetAndroidUiThread()) {
uiThread->Dispatch(NS_NewRunnableFunction(
"LayerViewSupport::SetFixedBottomOffset", [this, offset = aOffset] {
if (RefPtr<UiCompositorControllerChild> child =
GetUiCompositorControllerChild()) {
child->SetFixedBottomOffset(offset);
}
}));
}
}
void SendToolbarAnimatorMessage(int32_t aMessage) {
RefPtr<UiCompositorControllerChild> child =
GetUiCompositorControllerChild();
if (!child) {
return;
}
if (AndroidBridge::IsJavaUiThread()) {
child->ToolbarAnimatorMessageFromUI(aMessage);
return;
}
if (RefPtr<nsThread> uiThread = GetAndroidUiThread()) {
uiThread->Dispatch(
NewRunnableMethod<int32_t>(
"LayerViewSupport::ToolbarAnimatorMessageFromUI", child,
&UiCompositorControllerChild::ToolbarAnimatorMessageFromUI,
aMessage),
nsIThread::DISPATCH_NORMAL);
}
}
void RecvToolbarAnimatorMessage(int32_t aMessage) {
auto compositor = GeckoSession::Compositor::LocalRef(mCompositor);
if (compositor) {
compositor->RecvToolbarAnimatorMessage(aMessage);
}
}
void SetDefaultClearColor(int32_t aColor) {
MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
if (RefPtr<UiCompositorControllerChild> child =
GetUiCompositorControllerChild()) {
child->SetDefaultClearColor((uint32_t)aColor);
}
}
void RequestScreenPixels(jni::Object::Param aResult,
jni::Object::Param aTarget, int32_t aXOffset,
int32_t aYOffset, int32_t aSrcWidth,
int32_t aSrcHeight, int32_t aOutWidth,
int32_t aOutHeight) {
MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
int size = 0;
if (auto window = mWindow.Access()) {
mCapturePixelsResults.push(CaptureRequest(
java::GeckoResult::GlobalRef(java::GeckoResult::LocalRef(aResult)),
java::sdk::Bitmap::GlobalRef(java::sdk::Bitmap::LocalRef(aTarget)),
ScreenRect(aXOffset, aYOffset, aSrcWidth, aSrcHeight),
IntSize(aOutWidth, aOutHeight)));
size = mCapturePixelsResults.size();
}
if (size == 1) {
if (RefPtr<UiCompositorControllerChild> child =
GetUiCompositorControllerChild()) {
child->RequestScreenPixels();
}
}
}
void RecvScreenPixels(Shmem&& aMem, const ScreenIntSize& aSize,
bool aNeedsYFlip) {
MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
CaptureRequest request;
java::GeckoResult::LocalRef result = nullptr;
java::sdk::Bitmap::LocalRef bitmap = nullptr;
if (auto window = mWindow.Access()) {
// The result might have been already rejected if the compositor was
// detached from the session
if (!mCapturePixelsResults.empty()) {
request = mCapturePixelsResults.front();
result = java::GeckoResult::LocalRef(request.mResult);
bitmap = java::sdk::Bitmap::LocalRef(request.mBitmap);
mCapturePixelsResults.pop();
}
}
if (result) {
if (bitmap) {
RefPtr<DataSourceSurface> surf;
if (aNeedsYFlip) {
surf = FlipScreenPixels(aMem, aSize, request.mSource,
request.mOutputSize);
} else {
surf = gfx::Factory::CreateWrappingDataSourceSurface(
aMem.get<uint8_t>(),
StrideForFormatAndWidth(SurfaceFormat::B8G8R8A8, aSize.width),
IntSize(aSize.width, aSize.height), SurfaceFormat::B8G8R8A8);
}
if (surf) {
DataSourceSurface::ScopedMap smap(surf, DataSourceSurface::READ);
auto pixels = mozilla::jni::ByteBuffer::New(
reinterpret_cast<int8_t*>(smap.GetData()),
smap.GetStride() * request.mOutputSize.height);
bitmap->CopyPixelsFromBuffer(pixels);
result->Complete(bitmap);
} else {
result->CompleteExceptionally(
java::sdk::IllegalStateException::New(
"Failed to create flipped snapshot surface (probably out of "
"memory)")
.Cast<jni::Throwable>());
}
} else {
result->CompleteExceptionally(java::sdk::IllegalArgumentException::New(
"No target bitmap argument provided")
.Cast<jni::Throwable>());
}
}
// Pixels have been copied, so Dealloc Shmem
if (RefPtr<UiCompositorControllerChild> child =
GetUiCompositorControllerChild()) {
child->DeallocPixelBuffer(aMem);
if (auto window = mWindow.Access()) {
if (!mCapturePixelsResults.empty()) {
child->RequestScreenPixels();
}
}
}
}
void EnableLayerUpdateNotifications(bool aEnable) {
MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
if (RefPtr<UiCompositorControllerChild> child =
GetUiCompositorControllerChild()) {
child->EnableLayerUpdateNotifications(aEnable);
}
}
void OnSafeAreaInsetsChanged(int32_t aTop, int32_t aRight, int32_t aBottom,
int32_t aLeft) {
MOZ_ASSERT(NS_IsMainThread());
auto win(mWindow.Access());
if (!win) {
return; // Already shut down.
}
nsWindow* gkWindow = win->GetNsWindow();
if (!gkWindow) {
return;
}
ScreenIntMargin safeAreaInsets(aTop, aRight, aBottom, aLeft);
gkWindow->UpdateSafeAreaInsets(safeAreaInsets);
}
};
GeckoViewSupport::~GeckoViewSupport() {
if (mWindow) {
mWindow->DetachNatives();
}
}
/* static */
void GeckoViewSupport::Open(
const jni::Class::LocalRef& aCls, GeckoSession::Window::Param aWindow,
jni::Object::Param aQueue, jni::Object::Param aCompositor,
jni::Object::Param aDispatcher, jni::Object::Param aSessionAccessibility,
jni::Object::Param aInitData, jni::String::Param aId,
jni::String::Param aChromeURI, int32_t aScreenId, bool aPrivateMode) {
MOZ_ASSERT(NS_IsMainThread());
AUTO_PROFILER_LABEL("mozilla::widget::GeckoViewSupport::Open", OTHER);
nsCOMPtr<nsIWindowWatcher> ww = do_GetService(NS_WINDOWWATCHER_CONTRACTID);
MOZ_RELEASE_ASSERT(ww);
nsAutoCString url;
if (aChromeURI) {
url = aChromeURI->ToCString();
} else {
nsresult rv = Preferences::GetCString("toolkit.defaultChromeURI", url);
if (NS_FAILED(rv)) {
}
}
// Prepare an nsIAndroidView to pass as argument to the window.
RefPtr<AndroidView> androidView = new AndroidView();
androidView->mEventDispatcher->Attach(
java::EventDispatcher::Ref::From(aDispatcher), nullptr);
androidView->mInitData = java::GeckoBundle::Ref::From(aInitData);
nsAutoCString chromeFlags("chrome,dialog=0,remote,resizable,scrollbars");
if (aPrivateMode) {
chromeFlags += ",private";
}
nsCOMPtr<mozIDOMWindowProxy> domWindow;
ww->OpenWindow(nullptr, url, nsDependentCString(aId->ToCString().get()),
chromeFlags, androidView, getter_AddRefs(domWindow));
MOZ_RELEASE_ASSERT(domWindow);
nsCOMPtr<nsPIDOMWindowOuter> pdomWindow = nsPIDOMWindowOuter::From(domWindow);
const RefPtr<nsWindow> window = nsWindow::From(pdomWindow);
MOZ_ASSERT(window);
window->SetScreenId(aScreenId);
// Attach a new GeckoView support object to the new window.
GeckoSession::Window::LocalRef sessionWindow(aCls.Env(), aWindow);
auto weakGeckoViewSupport =
jni::NativeWeakPtrHolder<GeckoViewSupport>::Attach(
sessionWindow, window, sessionWindow, pdomWindow);
window->mGeckoViewSupport = weakGeckoViewSupport;
window->mAndroidView = androidView;
// Attach other session support objects.
{ // Scope for gvsAccess
auto gvsAccess = weakGeckoViewSupport.Access();
MOZ_ASSERT(gvsAccess);
gvsAccess->Transfer(sessionWindow, aQueue, aCompositor, aDispatcher,
aSessionAccessibility, aInitData);
}
if (window->mWidgetListener) {
nsCOMPtr<nsIAppWindow> appWindow(window->mWidgetListener->GetAppWindow());
if (appWindow) {
// Our window is not intrinsically sized, so tell AppWindow to
// not set a size for us.
appWindow->SetIntrinsicallySized(false);
}
}
}
void GeckoViewSupport::Close() {
if (mWindow) {
if (mWindow->mAndroidView) {
mWindow->mAndroidView->mEventDispatcher->Detach();
}
mWindow = nullptr;
}
if (!mDOMWindow) {
return;
}
mDOMWindow->ForceClose();
mDOMWindow = nullptr;
mGeckoViewWindow = nullptr;
}
void GeckoViewSupport::Transfer(const GeckoSession::Window::LocalRef& inst,
jni::Object::Param aQueue,
jni::Object::Param aCompositor,
jni::Object::Param aDispatcher,
jni::Object::Param aSessionAccessibility,
jni::Object::Param aInitData) {
mWindow->mNPZCSupport.Detach();
auto compositor = GeckoSession::Compositor::LocalRef(
inst.Env(), GeckoSession::Compositor::Ref::From(aCompositor));
bool attachLvs;
{ // Scope for lvsAccess
auto lvsAccess{mWindow->mLayerViewSupport.Access()};
// If we do not yet have mLayerViewSupport, or if the compositor has
// changed, then we must attach a new one.
attachLvs = !lvsAccess || lvsAccess->GetJavaCompositor() != compositor;
}
if (attachLvs) {
mWindow->mLayerViewSupport =
jni::NativeWeakPtrHolder<LayerViewSupport>::Attach(
compositor, mWindow->mGeckoViewSupport, compositor);
}
MOZ_ASSERT(mWindow->mAndroidView);
mWindow->mAndroidView->mEventDispatcher->Attach(
java::EventDispatcher::Ref::From(aDispatcher), mDOMWindow);
mWindow->mSessionAccessibility.Detach();
if (aSessionAccessibility) {
AttachAccessibility(inst, aSessionAccessibility);
}
if (mIsReady) {
// We're in a transfer; update init-data and notify JS code.
mWindow->mAndroidView->mInitData = java::GeckoBundle::Ref::From(aInitData);
OnReady(aQueue);
mWindow->mAndroidView->mEventDispatcher->Dispatch(
u"GeckoView:UpdateInitData");
}
DispatchToUiThread("GeckoViewSupport::Transfer",
[compositor = GeckoSession::Compositor::GlobalRef(
compositor)] { compositor->OnCompositorAttached(); });
}
void GeckoViewSupport::AttachEditable(
const GeckoSession::Window::LocalRef& inst,
jni::Object::Param aEditableParent) {
if (auto win{mWindow->mEditableSupport.Access()}) {
win->TransferParent(aEditableParent);
} else {
auto editableChild = java::GeckoEditableChild::New(aEditableParent,
/* default */ true);
mWindow->mEditableSupport =
jni::NativeWeakPtrHolder<GeckoEditableSupport>::Attach(
editableChild, mWindow->mGeckoViewSupport, editableChild);
}
mWindow->mEditableParent = aEditableParent;
}
void GeckoViewSupport::AttachAccessibility(
const GeckoSession::Window::LocalRef& inst,
jni::Object::Param aSessionAccessibility) {
java::SessionAccessibility::NativeProvider::LocalRef sessionAccessibility(
inst.Env());
sessionAccessibility = java::SessionAccessibility::NativeProvider::Ref::From(
aSessionAccessibility);
mWindow->mSessionAccessibility =
jni::NativeWeakPtrHolder<a11y::SessionAccessibility>::Attach(
sessionAccessibility, mWindow->mGeckoViewSupport,
sessionAccessibility);
}
auto GeckoViewSupport::OnLoadRequest(mozilla::jni::String::Param aUri,
int32_t aWindowType, int32_t aFlags,
mozilla::jni::String::Param aTriggeringUri,
bool aHasUserGesture,
bool aIsTopLevel) const
-> java::GeckoResult::LocalRef {
GeckoSession::Window::LocalRef window(mGeckoViewWindow);
if (!window) {
return nullptr;
}
return window->OnLoadRequest(aUri, aWindowType, aFlags, aTriggeringUri,
aHasUserGesture, aIsTopLevel);
}
void GeckoViewSupport::OnShowDynamicToolbar() const {
GeckoSession::Window::LocalRef window(mGeckoViewWindow);
if (!window) {
return;
}
window->OnShowDynamicToolbar();
}