fune/widget/android/nsWindow.cpp
Jamie Nicol 1f35ae62aa Bug 1880503 - Ensure compositor reinitialization respects intended paused/resumed state. r=geckoview-reviewers,m_kato
When the Android frontend attempts to pause or resume the compositor
there is a chance this may fail. When this happens, the compositor
will automatically be reinitialized. However, it will currently be
reinitialized in the previous state, not the state the frontend
intended. For example, if the compositor fails to resume then it will
be reinitialized but remain in a paused state, displaying a blank
screen to the user.

To avoid this, we update the value of mCompositorPaused prior to
attempting to pause or resume the compositor. This means when
NotifyCompositorCreated eventually gets called, it knows the correct
state in which to initialize the compositor.

Differential Revision: https://phabricator.services.mozilla.com/D202165
2024-05-14 12:58:35 +00:00

3407 lines
110 KiB
C++

/* -*- 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 <algorithm>
#include <atomic>
#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 "AndroidBridge.h"
#include "AndroidBridgeUtilities.h"
#include "AndroidCompositorWidget.h"
#include "AndroidContentController.h"
#include "AndroidDragEvent.h"
#include "AndroidUiThread.h"
#include "AndroidView.h"
#include "AndroidWidgetUtils.h"
#include "gfxContext.h"
#include "GeckoEditableSupport.h"
#include "GeckoViewOutputStream.h"
#include "GeckoViewSupport.h"
#include "GLContext.h"
#include "GLContextProvider.h"
#include "JavaBuiltins.h"
#include "JavaExceptions.h"
#include "KeyEvent.h"
#include "MotionEvent.h"
#include "ScopedGLHelpers.h"
#include "ScreenHelperAndroid.h"
#include "TouchResampler.h"
#include "WidgetUtils.h"
#include "WindowRenderer.h"
#include "mozilla/EventForwards.h"
#include "nsAppShell.h"
#include "nsContentUtils.h"
#include "nsFocusManager.h"
#include "nsGkAtoms.h"
#include "nsGfxCIID.h"
#include "nsIDocShellTreeOwner.h"
#include "nsLayoutUtils.h"
#include "nsNetUtil.h"
#include "nsPrintfCString.h"
#include "nsString.h"
#include "nsTArray.h"
#include "nsThreadUtils.h"
#include "nsUserIdleService.h"
#include "nsViewManager.h"
#include "nsWidgetsCID.h"
#include "nsWindow.h"
#include "nsIWidgetListener.h"
#include "nsIWindowWatcher.h"
#include "nsIAppWindow.h"
#include "nsIPrintSettings.h"
#include "nsIPrintSettingsService.h"
#include "mozilla/Logging.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/WeakPtr.h"
#include "mozilla/WheelHandlingHelper.h" // for WheelDeltaAdjustmentStrategy
#include "mozilla/a11y/SessionAccessibility.h"
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/BrowserHost.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/Logging.h"
#include "mozilla/gfx/Swizzle.h"
#include "mozilla/gfx/Types.h"
#include "mozilla/ipc/Shmem.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 "mozilla/java/SurfaceControlManagerWrappers.h"
#include "mozilla/jni/NativesInlines.h"
#include "mozilla/layers/APZEventState.h"
#include "mozilla/layers/APZInputBridge.h"
#include "mozilla/layers/APZThreadUtils.h"
#include "mozilla/layers/CompositorBridgeChild.h"
#include "mozilla/layers/CompositorOGL.h"
#include "mozilla/layers/CompositorSession.h"
#include "mozilla/layers/LayersTypes.h"
#include "mozilla/layers/UiCompositorControllerChild.h"
#include "mozilla/layers/IAPZCTreeManager.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/widget/AndroidVsync.h"
#include "mozilla/widget/Screen.h"
#define GVS_LOG(...) MOZ_LOG(sGVSupportLog, LogLevel::Warning, (__VA_ARGS__))
using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::layers;
using namespace mozilla::widget;
using namespace mozilla::ipc;
using mozilla::dom::ContentChild;
using mozilla::dom::ContentParent;
using mozilla::gfx::DataSourceSurface;
using mozilla::gfx::IntSize;
using mozilla::gfx::Matrix;
using mozilla::gfx::SurfaceFormat;
using mozilla::java::GeckoSession;
using mozilla::java::sdk::IllegalStateException;
using GeckoPrintException = GeckoSession::GeckoPrintException;
static mozilla::LazyLogModule sGVSupportLog("GeckoViewSupport");
// 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;
// Unique ID given to each widget, to identify it for the
// CompositorSurfaceManager.
static std::atomic<int32_t> sWidgetId{0};
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> {
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; }
};
class MOZ_HEAP_CLASS Observer final : public AndroidVsync::Observer {
public:
static Observer* Create(jni::NativeWeakPtr<NPZCSupport>&& aNPZCSupport) {
return new Observer(std::move(aNPZCSupport));
}
private:
// Private constructor, part of a strategy to make sure
// we're only able to create these on the heap.
explicit Observer(jni::NativeWeakPtr<NPZCSupport>&& aNPZCSupport)
: mNPZCSupport(std::move(aNPZCSupport)) {}
void OnVsync(const TimeStamp& aTimeStamp) override {
auto accessor = mNPZCSupport.Access();
if (!accessor) {
return;
}
accessor->mTouchResampler.NotifyFrame(
aTimeStamp -
TimeDuration::FromMilliseconds(kTouchResampleVsyncAdjustMs));
accessor->ConsumeMotionEventsFromResampler();
}
void Dispose() override { delete this; }
jni::NativeWeakPtr<NPZCSupport> mNPZCSupport;
};
Observer* mObserver = nullptr;
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)
mAndroidVsync = AndroidVsync::GetInstance();
}
~NPZCSupport() {
if (mListeningToVsync) {
MOZ_RELEASE_ASSERT(mAndroidVsync);
mAndroidVsync->UnregisterObserver(mObserver, 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(
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 = std::move(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, nsWindow::GetEventTimeStamp(aTime),
nsWindow::GetModifiers(aMetaState));
APZEventResult result = controller->InputBridge()->ReceiveInputEvent(input);
if (result.GetStatus() == nsEventStatus_eConsumeNoDefault) {
return INPUT_RESULT_IGNORED;
}
PostInputEvent([input = std::move(input), result](nsWindow* window) {
WidgetMouseEvent mouseEvent = input.ToWidgetEvent(window);
window->ProcessUntransformedAPZEvent(&mouseEvent, result);
if (MouseInput::SECONDARY_BUTTON == input.mButtonType) {
if ((StaticPrefs::ui_context_menus_after_mouseup() &&
MouseInput::MOUSE_UP == input.mType) ||
(!StaticPrefs::ui_context_menus_after_mouseup() &&
MouseInput::MOUSE_DOWN == input.mType)) {
MouseInput contextMenu = input;
// Actually we don't dispatch context menu event to APZ since we don't
// handle it on APZ yet. If handling it, we need to consider how to
// dispatch it on APZ thread. It may cause a race condition.
contextMenu.mType = MouseInput::MOUSE_CONTEXTMENU;
WidgetMouseEvent contextMenuEvent = contextMenu.ToWidgetEvent(window);
window->ProcessUntransformedAPZEvent(&contextMenuEvent, 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++) {
auto [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;
auto [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) {
MOZ_ASSERT(!mObserver);
auto win = mWindow.Access();
if (!win) {
return;
}
RefPtr<nsWindow> gkWindow = win->GetNsWindow();
if (!gkWindow) {
return;
}
MutexAutoLock lock(gkWindow->GetDestroyMutex());
if (gkWindow->Destroyed()) {
return;
}
jni::NativeWeakPtr<NPZCSupport> weakPtrToThis =
gkWindow->GetNPZCSupportWeakPtr();
mObserver = Observer::Create(std::move(weakPtrToThis));
mAndroidVsync->RegisterObserver(mObserver, AndroidVsync::INPUT);
} else if (!aNeedVsync && mListeningToVsync) {
mAndroidVsync->UnregisterObserver(mObserver, AndroidVsync::INPUT);
mObserver = nullptr;
}
mListeningToVsync = aNeedVsync;
}
void HandleDragEvent(int32_t aAction, int64_t aTime, float aX, float aY,
jni::Object::Param aDropData) {
// APZ handles some drag event type on APZ thread, but it cannot handle all
// types.
MOZ_ASSERT(NS_IsMainThread());
if (auto window = mWindow.Access()) {
if (nsWindow* gkWindow = window->GetNsWindow()) {
gkWindow->OnDragEvent(aAction, aTime, aX, aY, aDropData);
}
}
}
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;
}
APZInputBridge::InputBlockCallback callback;
if (aReturnResult) {
callback = [aReturnResult = java::GeckoResult::GlobalRef(aReturnResult)](
uint64_t aInputBlockId,
const APZHandledResult& aHandledResult) {
aReturnResult->Complete(ConvertAPZHandledResult(aHandledResult));
};
}
APZEventResult result = controller->InputBridge()->ReceiveInputEvent(
aInput, std::move(callback));
if (result.GetStatus() == nsEventStatus_eConsumeNoDefault) {
if (aReturnResult) {
if (result.GetHandledResult() != Nothing()) {
aReturnResult->Complete(
ConvertAPZHandledResult(result.GetHandledResult().value()));
} else {
MOZ_ASSERT_UNREACHABLE(
"nsEventStatus_eConsumeNoDefault should involve a valid "
"APZHandledResult");
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([input = std::move(aInput), result](nsWindow* window) {
WidgetTouchEvent touchEvent = input.ToWidgetEvent(window);
window->ProcessUntransformedAPZEvent(&touchEvent, result);
window->DispatchHitTest(touchEvent);
});
if (aReturnResult && result.GetHandledResult() != Nothing()) {
MOZ_ASSERT(result.GetStatus() == nsEventStatus_eConsumeDoDefault ||
result.GetStatus() == nsEventStatus_eIgnore);
aReturnResult->Complete(
ConvertAPZHandledResult(result.GetHandledResult().value()));
}
}
};
NS_IMPL_ISUPPORTS(AndroidView, nsIAndroidEventDispatcher, nsIAndroidView)
nsresult AndroidView::GetInitData(JSContext* aCx,
JS::MutableHandle<JS::Value> 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;
java::sdk::Surface::GlobalRef mSurface;
java::sdk::SurfaceControl::GlobalRef mSurfaceControl;
int32_t mX;
int32_t mY;
int32_t mWidth;
int32_t mHeight;
// Used to communicate with the gecko compositor from the UI thread.
// Set in NotifyCompositorCreated and cleared in
// NotifyCompositorSessionLost.
RefPtr<UiCompositorControllerChild> mUiCompositorControllerChild;
// Whether we have requested a new Surface from the GeckoSession.
bool mRequestedNewSurface = false;
Maybe<uint32_t> mDefaultClearColor;
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; }
/// Called from the main thread whenever the compositor has been
/// (re)initialized.
void NotifyCompositorCreated(
RefPtr<UiCompositorControllerChild> aUiCompositorControllerChild) {
MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
mUiCompositorControllerChild = aUiCompositorControllerChild;
if (mDefaultClearColor) {
mUiCompositorControllerChild->SetDefaultClearColor(*mDefaultClearColor);
}
if (!mCompositorPaused) {
// If we are using SurfaceControl but mSurface is null, that means the
// previous surface was destroyed along with the the previous
// compositor, and we need to create a new one.
if (mSurfaceControl && !mSurface) {
mSurface = java::SurfaceControlManager::GetInstance()->GetChildSurface(
mSurfaceControl, mWidth, mHeight);
}
if (auto window{mWindow.Access()}) {
nsWindow* gkWindow = window->GetNsWindow();
if (gkWindow) {
mUiCompositorControllerChild->OnCompositorSurfaceChanged(
gkWindow->mWidgetId, mSurface);
}
}
bool resumed = mUiCompositorControllerChild->ResumeAndResize(
mX, mY, mWidth, mHeight);
if (!resumed) {
gfxCriticalNote
<< "Failed to resume compositor from NotifyCompositorCreated";
RequestNewSurface();
}
}
}
/// Called from the main thread whenever the compositor has been destroyed.
void NotifyCompositorSessionLost() {
MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
mUiCompositorControllerChild = nullptr;
if (mSurfaceControl) {
// If we are using SurfaceControl then we must set the Surface to null
// here to ensure we create a new one when the new compositor is
// created.
mSurface = nullptr;
}
if (auto window = mWindow.Access()) {
while (!mCapturePixelsResults.empty()) {
auto result =
java::GeckoResult::LocalRef(mCapturePixelsResults.front().mResult);
if (result) {
result->CompleteExceptionally(
java::sdk::IllegalStateException::New(
"Compositor session lost during screen pixels request")
.Cast<jni::Throwable>());
}
mCapturePixelsResults.pop();
}
}
}
java::sdk::Surface::Param GetSurface() { return mSurface; }
private:
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 NotifyMemoryPressure() {
MOZ_ASSERT(NS_IsMainThread());
auto acc = mWindow.Access();
if (!acc) {
return; // Already shut down.
}
nsWindow* gkWindow = acc->GetNsWindow();
if (!gkWindow || !gkWindow->mCompositorBridgeChild) {
return;
}
gkWindow->mCompositorBridgeChild->SendNotifyMemoryPressure();
}
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());
// Set this true prior to attempting to pause the compositor, so that if
// pausing fails the subsequent recovery knows to initialize the compositor
// in a paused state.
mCompositorPaused = true;
if (mUiCompositorControllerChild) {
mUiCompositorControllerChild->Pause();
mSurface = nullptr;
mSurfaceControl = nullptr;
if (auto window = mWindow.Access()) {
nsWindow* gkWindow = window->GetNsWindow();
if (gkWindow) {
mUiCompositorControllerChild->OnCompositorSurfaceChanged(
gkWindow->mWidgetId, nullptr);
}
}
}
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());
// Set this false prior to attempting to resume the compositor, so that if
// resumption fails the subsequent recovery knows to initialize the
// compositor in a resumed state.
mCompositorPaused = false;
if (mUiCompositorControllerChild) {
bool resumed = mUiCompositorControllerChild->Resume();
if (!resumed) {
gfxCriticalNote
<< "Failed to resume compositor from SyncResumeCompositor";
RequestNewSurface();
}
}
}
void SyncResumeResizeCompositor(
const GeckoSession::Compositor::LocalRef& aObj, int32_t aX, int32_t aY,
int32_t aWidth, int32_t aHeight, jni::Object::Param aSurface,
jni::Object::Param aSurfaceControl) {
MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
// Set this false prior to attempting to resume the compositor, so that if
// resumption fails the subsequent recovery knows to initialize the
// compositor in a resumed state.
mCompositorPaused = false;
mX = aX;
mY = aY;
mWidth = aWidth;
mHeight = aHeight;
if (StaticPrefs::widget_android_use_surfacecontrol_AtStartup()) {
mSurfaceControl =
java::sdk::SurfaceControl::GlobalRef::From(aSurfaceControl);
}
if (mSurfaceControl) {
// When using SurfaceControl, we create a child Surface to render in to
// rather than rendering directly in to the Surface provided by the
// application. This allows us to work around a bug on some versions of
// Android when recovering from a GPU process crash.
mSurface = java::SurfaceControlManager::GetInstance()->GetChildSurface(
mSurfaceControl, mWidth, mHeight);
} else {
mSurface = java::sdk::Surface::GlobalRef::From(aSurface);
}
if (mUiCompositorControllerChild) {
if (auto window = mWindow.Access()) {
nsWindow* gkWindow = window->GetNsWindow();
if (gkWindow) {
// Send new Surface to GPU process, if one exists.
mUiCompositorControllerChild->OnCompositorSurfaceChanged(
gkWindow->mWidgetId, mSurface);
}
}
bool resumed = mUiCompositorControllerChild->ResumeAndResize(
aX, aY, aWidth, aHeight);
if (!resumed) {
gfxCriticalNote
<< "Failed to resume compositor from SyncResumeResizeCompositor";
// Only request a new Surface if this SyncResumeAndResize call is not
// response to a previous request, otherwise we will get stuck in an
// infinite loop.
if (!mRequestedNewSurface) {
RequestNewSurface();
}
return;
}
}
mRequestedNewSurface = 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 RequestNewSurface() {
if (const auto& compositor = GetJavaCompositor()) {
mRequestedNewSurface = true;
if (mSurfaceControl) {
java::SurfaceControlManager::GetInstance()->RemoveSurface(
mSurfaceControl);
}
compositor->RequestNewSurface();
}
}
mozilla::jni::Object::LocalRef GetMagnifiableSurface() {
return mozilla::jni::Object::LocalRef::From(GetSurface());
}
void SyncInvalidateAndScheduleComposite() {
if (!mUiCompositorControllerChild) {
return;
}
if (AndroidBridge::IsJavaUiThread()) {
mUiCompositorControllerChild->InvalidateAndRender();
return;
}
if (RefPtr<nsThread> uiThread = GetAndroidUiThread()) {
uiThread->Dispatch(NewRunnableMethod<>(
"LayerViewSupport::InvalidateAndRender",
mUiCompositorControllerChild,
&UiCompositorControllerChild::InvalidateAndRender),
nsIThread::DISPATCH_NORMAL);
}
}
void SetMaxToolbarHeight(int32_t aHeight) {
MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
if (mUiCompositorControllerChild) {
mUiCompositorControllerChild->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 (mUiCompositorControllerChild) {
mUiCompositorControllerChild->SetFixedBottomOffset(offset);
}
}));
}
}
void SendToolbarAnimatorMessage(int32_t aMessage) {
if (!mUiCompositorControllerChild) {
return;
}
if (AndroidBridge::IsJavaUiThread()) {
mUiCompositorControllerChild->ToolbarAnimatorMessageFromUI(aMessage);
return;
}
if (RefPtr<nsThread> uiThread = GetAndroidUiThread()) {
uiThread->Dispatch(
NewRunnableMethod<int32_t>(
"LayerViewSupport::ToolbarAnimatorMessageFromUI",
mUiCompositorControllerChild,
&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());
mDefaultClearColor = Some((uint32_t)aColor);
if (mUiCompositorControllerChild) {
mUiCompositorControllerChild->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());
auto result = java::GeckoResult::LocalRef(aResult);
if (!mUiCompositorControllerChild) {
if (result) {
if (auto window = mWindow.Access()) {
result->CompleteExceptionally(
java::sdk::IllegalStateException::New(
"Compositor session lost prior to screen pixels request")
.Cast<jni::Throwable>());
}
}
return;
}
int size = 0;
if (auto window = mWindow.Access()) {
mCapturePixelsResults.push(CaptureRequest(
java::GeckoResult::GlobalRef(result),
java::sdk::Bitmap::GlobalRef(java::sdk::Bitmap::LocalRef(aTarget)),
ScreenRect(aXOffset, aYOffset, aSrcWidth, aSrcHeight),
IntSize(aOutWidth, aOutHeight)));
size = mCapturePixelsResults.size();
}
if (size == 1) {
mUiCompositorControllerChild->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 (mUiCompositorControllerChild) {
mUiCompositorControllerChild->DeallocPixelBuffer(aMem);
if (auto window = mWindow.Access()) {
if (!mCapturePixelsResults.empty()) {
mUiCompositorControllerChild->RequestScreenPixels();
}
}
}
}
void EnableLayerUpdateNotifications(bool aEnable) {
MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
if (mUiCompositorControllerChild) {
mUiCompositorControllerChild->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, bool aPrivateMode) {
MOZ_ASSERT(NS_IsMainThread());
AUTO_PROFILER_LABEL("mozilla::widget::GeckoViewSupport::Open", OTHER);
// We'll need gfxPlatform to be initialized to create a compositor later.
// Might as well do that now so that the GPU process launch can get a head
// start.
gfxPlatform::GetPlatform();
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)) {
url = "chrome://geckoview/content/geckoview.xhtml"_ns;
}
}
// 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);
// 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);
if (RefPtr<UiCompositorControllerChild> uiCompositorController =
mWindow->GetUiCompositorControllerChild()) {
DispatchToUiThread(
"LayerViewSupport::NotifyCompositorCreated",
[lvs = mWindow->mLayerViewSupport, uiCompositorController] {
if (auto lvsAccess{lvs.Access()}) {
lvsAccess->NotifyCompositorCreated(uiCompositorController);
}
});
}
}
MOZ_ASSERT(mWindow->mAndroidView);
mWindow->mAndroidView->mEventDispatcher->Attach(
java::EventDispatcher::Ref::From(aDispatcher), mDOMWindow);
RefPtr<jni::DetachPromise> promise = mWindow->mSessionAccessibility.Detach();
if (aSessionAccessibility) {
// SessionAccessibility's JNI object isn't released immediately, it uses
// recycled object, we have to wait for released object completely.
auto sa = java::SessionAccessibility::NativeProvider::LocalRef(
aSessionAccessibility);
promise->Then(
GetMainThreadSerialEventTarget(),
"GeckoViewSupprt::Transfer::SessionAccessibility",
[inst = GeckoSession::Window::GlobalRef(inst),
sa = java::SessionAccessibility::NativeProvider::GlobalRef(sa),
window = mWindow, gvs = mWindow->mGeckoViewSupport](
const mozilla::jni::DetachPromise::ResolveOrRejectValue& aValue) {
MOZ_ASSERT(aValue.IsResolve());
if (window->Destroyed()) {
return;
}
MOZ_ASSERT(!window->mSessionAccessibility.IsAttached());
if (auto gvsAccess{gvs.Access()}) {
gvsAccess->AttachAccessibility(inst, sa);
}
});
}
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();
}
void GeckoViewSupport::OnReady(jni::Object::Param aQueue) {
GeckoSession::Window::LocalRef window(mGeckoViewWindow);
if (!window) {
return;
}
window->OnReady(aQueue);
mIsReady = true;
}
void GeckoViewSupport::PassExternalResponse(
java::WebResponse::Param aResponse) {
GeckoSession::Window::LocalRef window(mGeckoViewWindow);
if (!window) {
return;
}
auto response = java::WebResponse::GlobalRef(aResponse);
DispatchToUiThread("GeckoViewSupport::PassExternalResponse",
[window = java::GeckoSession::Window::GlobalRef(window),
response] { window->PassExternalWebResponse(response); });
}
RefPtr<CanonicalBrowsingContext>
GeckoViewSupport::GetContentCanonicalBrowsingContext() {
nsCOMPtr<nsIDocShellTreeOwner> treeOwner = mDOMWindow->GetTreeOwner();
if (!treeOwner) {
return nullptr;
}
RefPtr<BrowsingContext> bc;
nsresult rv = treeOwner->GetPrimaryContentBrowsingContext(getter_AddRefs(bc));
if (NS_WARN_IF(NS_FAILED(rv)) || !bc) {
return nullptr;
}
return bc->Canonical();
}
void GeckoViewSupport::CreatePdf(
jni::LocalRef<mozilla::java::GeckoResult> aGeckoResult,
RefPtr<dom::CanonicalBrowsingContext> aCbc) {
MOZ_ASSERT(NS_IsMainThread());
const auto pdfErrorMsg = "Could not save this page as PDF.";
auto stream = java::GeckoInputStream::New(nullptr);
RefPtr<GeckoViewOutputStream> streamListener =
new GeckoViewOutputStream(stream);
nsCOMPtr<nsIPrintSettingsService> printSettingsService =
do_GetService("@mozilla.org/gfx/printsettings-service;1");
if (!printSettingsService) {
aGeckoResult->CompleteExceptionally(
GeckoPrintException::New(
GeckoPrintException::ERROR_PRINT_SETTINGS_SERVICE_NOT_AVAILABLE)
.Cast<jni::Throwable>());
GVS_LOG("Could not create print settings service.");
return;
}
nsCOMPtr<nsIPrintSettings> printSettings;
nsresult rv = printSettingsService->CreateNewPrintSettings(
getter_AddRefs(printSettings));
if (NS_WARN_IF(NS_FAILED(rv))) {
aGeckoResult->CompleteExceptionally(
GeckoPrintException::New(
GeckoPrintException::ERROR_UNABLE_TO_CREATE_PRINT_SETTINGS)
.Cast<jni::Throwable>());
GVS_LOG("Could not create print settings.");
return;
}
printSettings->SetPrinterName(u"Mozilla Save to PDF"_ns);
printSettings->SetOutputDestination(
nsIPrintSettings::kOutputDestinationStream);
printSettings->SetOutputFormat(nsIPrintSettings::kOutputFormatPDF);
printSettings->SetOutputStream(streamListener);
printSettings->SetPrintSilent(true);
RefPtr<CanonicalBrowsingContext::PrintPromise> print =
aCbc->Print(printSettings);
aGeckoResult->Complete(stream);
print->Then(
mozilla::GetCurrentSerialEventTarget(), __func__,
[result = java::GeckoResult::GlobalRef(aGeckoResult), stream,
pdfErrorMsg](
const CanonicalBrowsingContext::PrintPromise::ResolveOrRejectValue&
aValue) {
if (aValue.IsReject()) {
GVS_LOG("Could not print. %s", pdfErrorMsg);
stream->WriteError();
}
});
}
void GeckoViewSupport::PrintToPdf(
const java::GeckoSession::Window::LocalRef& inst,
jni::Object::Param aResult) {
auto geckoResult = java::GeckoResult::Ref::From(aResult);
RefPtr<CanonicalBrowsingContext> cbc = GetContentCanonicalBrowsingContext();
if (!cbc) {
geckoResult->CompleteExceptionally(
GeckoPrintException::New(
GeckoPrintException::
ERROR_UNABLE_TO_RETRIEVE_CANONICAL_BROWSING_CONTEXT)
.Cast<jni::Throwable>());
GVS_LOG("Could not retrieve content canonical browsing context.");
return;
}
CreatePdf(geckoResult, cbc);
}
void GeckoViewSupport::PrintToPdf(
const java::GeckoSession::Window::LocalRef& inst,
jni::Object::Param aResult, int64_t aBcId) {
auto geckoResult = java::GeckoResult::Ref::From(aResult);
RefPtr<CanonicalBrowsingContext> cbc = CanonicalBrowsingContext::Get(aBcId);
if (!cbc) {
geckoResult->CompleteExceptionally(
GeckoPrintException::New(
GeckoPrintException::
ERROR_UNABLE_TO_RETRIEVE_CANONICAL_BROWSING_CONTEXT)
.Cast<jni::Throwable>());
GVS_LOG("Could not retrieve content canonical browsing context by ID.");
return;
}
CreatePdf(geckoResult, cbc);
}
} // namespace widget
} // namespace mozilla
void nsWindow::InitNatives() {
jni::InitConversionStatics();
mozilla::widget::GeckoViewSupport::Base::Init();
mozilla::widget::LayerViewSupport::Init();
mozilla::widget::NPZCSupport::Init();
mozilla::widget::GeckoEditableSupport::Init();
a11y::SessionAccessibility::Init();
}
void nsWindow::DetachNatives() {
MOZ_ASSERT(NS_IsMainThread());
mEditableSupport.Detach();
mNPZCSupport.Detach();
mLayerViewSupport.Detach();
mSessionAccessibility.Detach();
}
/* static */
already_AddRefed<nsWindow> nsWindow::From(nsPIDOMWindowOuter* aDOMWindow) {
nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(aDOMWindow);
return From(widget);
}
/* static */
already_AddRefed<nsWindow> nsWindow::From(nsIWidget* aWidget) {
// `widget` may be one of several different types in the parent
// process, including the Android nsWindow, PuppetWidget, etc. To
// ensure that the cast to the Android nsWindow is valid, we check that the
// widget is a top-level window and that its NS_NATIVE_WIDGET value is
// non-null, which is not the case for non-native widgets like
// PuppetWidget.
if (aWidget && aWidget->GetWindowType() == WindowType::TopLevel &&
aWidget->GetNativeData(NS_NATIVE_WIDGET) == aWidget) {
RefPtr<nsWindow> window = static_cast<nsWindow*>(aWidget);
return window.forget();
}
return nullptr;
}
nsWindow* nsWindow::TopWindow() {
if (!gTopLevelWindows.IsEmpty()) return gTopLevelWindows[0];
return nullptr;
}
void nsWindow::LogWindow(nsWindow* win, int index, int indent) {
#if defined(DEBUG) || defined(FORCE_ALOG)
char spaces[] = " ";
spaces[indent < 20 ? indent : 20] = 0;
ALOG("%s [% 2d] 0x%p [parent 0x%p] [% 3d,% 3dx% 3d,% 3d] vis %d type %d",
spaces, index, win, win->mParent, win->mBounds.x, win->mBounds.y,
win->mBounds.width, win->mBounds.height, win->mIsVisible,
int(win->mWindowType));
#endif
}
void nsWindow::DumpWindows() { DumpWindows(gTopLevelWindows); }
void nsWindow::DumpWindows(const nsTArray<nsWindow*>& wins, int indent) {
for (uint32_t i = 0; i < wins.Length(); ++i) {
nsWindow* w = wins[i];
LogWindow(w, i, indent);
DumpWindows(w->mChildren, indent + 1);
}
}
nsWindow::nsWindow()
: mWidgetId(++sWidgetId),
mIsVisible(false),
mParent(nullptr),
mDynamicToolbarMaxHeight(0),
mSizeMode(nsSizeMode_Normal),
mIsFullScreen(false),
mCompositorWidgetDelegate(nullptr),
mDestroyMutex("nsWindow::mDestroyMutex") {}
nsWindow::~nsWindow() {
gTopLevelWindows.RemoveElement(this);
ALOG("nsWindow %p destructor", (void*)this);
// The mCompositorSession should have been cleaned up in nsWindow::Destroy()
// DestroyLayerManager() will call DestroyCompositor() which will crash if
// called from nsBaseWidget destructor. See Bug 1392705
MOZ_ASSERT(!mCompositorSession);
}
bool nsWindow::IsTopLevel() {
return mWindowType == WindowType::TopLevel ||
mWindowType == WindowType::Dialog ||
mWindowType == WindowType::Invisible;
}
nsresult nsWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
const LayoutDeviceIntRect& aRect,
InitData* aInitData) {
ALOG("nsWindow[%p]::Create %p [%d %d %d %d]", (void*)this, (void*)aParent,
aRect.x, aRect.y, aRect.width, aRect.height);
nsWindow* parent = (nsWindow*)aParent;
if (aNativeParent) {
if (parent) {
ALOG(
"Ignoring native parent on Android window [%p], "
"since parent was specified (%p %p)",
(void*)this, (void*)aNativeParent, (void*)aParent);
} else {
parent = (nsWindow*)aNativeParent;
}
}
// A default size of 1x1 confuses MobileViewportManager, so
// use 0x0 instead. This is also a little more fitting since
// we don't yet have a surface yet (and therefore a valid size)
// and 0x0 is usually recognized as invalid.
LayoutDeviceIntRect rect = aRect;
if (aRect.width == 1 && aRect.height == 1) {
rect.width = 0;
rect.height = 0;
}
mBounds = rect;
SetSizeConstraints(SizeConstraints());
BaseCreate(nullptr, aInitData);
NS_ASSERTION(IsTopLevel() || parent,
"non-top-level window doesn't have a parent!");
if (IsTopLevel()) {
gTopLevelWindows.AppendElement(this);
} else if (parent) {
parent->mChildren.AppendElement(this);
mParent = parent;
}
#ifdef DEBUG_ANDROID_WIDGET
DumpWindows();
#endif
return NS_OK;
}
void nsWindow::Destroy() {
MutexAutoLock lock(mDestroyMutex);
nsBaseWidget::mOnDestroyCalled = true;
// Disassociate our native object from GeckoView.
mGeckoViewSupport.Detach();
// Stuff below may release the last ref to this
nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
while (mChildren.Length()) {
// why do we still have children?
ALOG("### Warning: Destroying window %p and reparenting child %p to null!",
(void*)this, (void*)mChildren[0]);
mChildren[0]->SetParent(nullptr);
}
// Ensure the compositor has been shutdown before this nsWindow is potentially
// deleted
nsBaseWidget::DestroyCompositor();
nsBaseWidget::Destroy();
if (IsTopLevel()) gTopLevelWindows.RemoveElement(this);
SetParent(nullptr);
nsBaseWidget::OnDestroy();
#ifdef DEBUG_ANDROID_WIDGET
DumpWindows();
#endif
}
mozilla::widget::EventDispatcher* nsWindow::GetEventDispatcher() const {
if (mAndroidView) {
return mAndroidView->mEventDispatcher;
}
return nullptr;
}
void nsWindow::RedrawAll() {
if (mAttachedWidgetListener) {
mAttachedWidgetListener->RequestRepaint();
} else if (mWidgetListener) {
mWidgetListener->RequestRepaint();
}
}
RefPtr<UiCompositorControllerChild> nsWindow::GetUiCompositorControllerChild() {
return mCompositorSession
? mCompositorSession->GetUiCompositorControllerChild()
: nullptr;
}
mozilla::layers::LayersId nsWindow::GetRootLayerId() const {
return mCompositorSession ? mCompositorSession->RootLayerTreeId()
: mozilla::layers::LayersId{0};
}
void nsWindow::OnGeckoViewReady() {
auto acc(mGeckoViewSupport.Access());
if (!acc) {
return;
}
acc->OnReady();
}
void nsWindow::SetParent(nsIWidget* aNewParent) {
if ((nsIWidget*)mParent == aNewParent) return;
// If we had a parent before, remove ourselves from its list of
// children.
if (mParent) mParent->mChildren.RemoveElement(this);
mParent = (nsWindow*)aNewParent;
if (mParent) mParent->mChildren.AppendElement(this);
// if we are now in the toplevel window's hierarchy, schedule a redraw
if (FindTopLevel() == nsWindow::TopWindow()) RedrawAll();
}
nsIWidget* nsWindow::GetParent() { return mParent; }
RefPtr<MozPromise<bool, bool, false>> nsWindow::OnLoadRequest(
nsIURI* aUri, int32_t aWindowType, int32_t aFlags,
nsIPrincipal* aTriggeringPrincipal, bool aHasUserGesture,
bool aIsTopLevel) {
auto geckoViewSupport(mGeckoViewSupport.Access());
if (!geckoViewSupport) {
return MozPromise<bool, bool, false>::CreateAndResolve(false, __func__);
}
nsAutoCString spec, triggeringSpec;
if (aUri) {
aUri->GetDisplaySpec(spec);
if (aIsTopLevel && mozilla::net::SchemeIsData(aUri) &&
spec.Length() > MAX_TOPLEVEL_DATA_URI_LEN) {
return MozPromise<bool, bool, false>::CreateAndResolve(false, __func__);
}
}
bool isNullPrincipal = false;
if (aTriggeringPrincipal) {
aTriggeringPrincipal->GetIsNullPrincipal(&isNullPrincipal);
if (!isNullPrincipal) {
nsCOMPtr<nsIURI> triggeringUri;
BasePrincipal::Cast(aTriggeringPrincipal)
->GetURI(getter_AddRefs(triggeringUri));
if (triggeringUri) {
triggeringUri->GetDisplaySpec(triggeringSpec);
}
}
}
auto geckoResult = geckoViewSupport->OnLoadRequest(
spec.get(), aWindowType, aFlags,
isNullPrincipal ? nullptr : triggeringSpec.get(), aHasUserGesture,
aIsTopLevel);
return geckoResult
? MozPromise<bool, bool, false>::FromGeckoResult(geckoResult)
: nullptr;
}
void nsWindow::OnUpdateSessionStore(mozilla::jni::Object::Param aBundle) {
auto geckoViewSupport(mGeckoViewSupport.Access());
if (!geckoViewSupport) {
return;
}
geckoViewSupport->OnUpdateSessionStore(aBundle);
}
float nsWindow::GetDPI() {
float dpi = 160.0f;
nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
if (screen) {
screen->GetDpi(&dpi);
}
return dpi;
}
double nsWindow::GetDefaultScaleInternal() {
double scale = 1.0f;
nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
if (screen) {
screen->GetContentsScaleFactor(&scale);
}
return scale;
}
void nsWindow::Show(bool aState) {
ALOG("nsWindow[%p]::Show %d", (void*)this, aState);
if (mWindowType == WindowType::Invisible) {
ALOG("trying to show invisible window! ignoring..");
return;
}
if (aState == mIsVisible) return;
mIsVisible = aState;
if (IsTopLevel()) {
// XXX should we bring this to the front when it's shown,
// if it's a toplevel widget?
// XXX we should synthesize a eMouseExitFromWidget (for old top
// window)/eMouseEnterIntoWidget (for new top window) since we need
// to pretend that the top window always has focus. Not sure
// if Show() is the right place to do this, though.
if (aState) {
// It just became visible, so bring it to the front.
BringToFront();
} else if (nsWindow::TopWindow() == this) {
// find the next visible window to show
unsigned int i;
for (i = 1; i < gTopLevelWindows.Length(); i++) {
nsWindow* win = gTopLevelWindows[i];
if (!win->mIsVisible) continue;
win->BringToFront();
break;
}
}
} else if (FindTopLevel() == nsWindow::TopWindow()) {
RedrawAll();
}
#ifdef DEBUG_ANDROID_WIDGET
DumpWindows();
#endif
}
bool nsWindow::IsVisible() const { return mIsVisible; }
void nsWindow::ConstrainPosition(DesktopIntPoint& aPoint) {
ALOG("nsWindow[%p]::ConstrainPosition [%d %d]", (void*)this, aPoint.x.value,
aPoint.y.value);
// Constrain toplevel windows; children we don't care about
if (IsTopLevel()) {
aPoint = DesktopIntPoint();
}
}
void nsWindow::Move(double aX, double aY) {
if (IsTopLevel()) return;
Resize(aX, aY, mBounds.width, mBounds.height, true);
}
void nsWindow::Resize(double aWidth, double aHeight, bool aRepaint) {
Resize(mBounds.x, mBounds.y, aWidth, aHeight, aRepaint);
}
void nsWindow::Resize(double aX, double aY, double aWidth, double aHeight,
bool aRepaint) {
ALOG("nsWindow[%p]::Resize [%f %f %f %f] (repaint %d)", (void*)this, aX, aY,
aWidth, aHeight, aRepaint);
LayoutDeviceIntRect oldBounds = mBounds;
mBounds.x = NSToIntRound(aX);
mBounds.y = NSToIntRound(aY);
mBounds.width = NSToIntRound(aWidth);
mBounds.height = NSToIntRound(aHeight);
ConstrainSize(&mBounds.width, &mBounds.height);
bool needPositionDispatch = mBounds.TopLeft() != oldBounds.TopLeft();
bool needSizeDispatch = mBounds.Size() != oldBounds.Size();
if (needSizeDispatch) {
OnSizeChanged(mBounds.Size().ToUnknownSize());
}
if (needPositionDispatch) {
NotifyWindowMoved(mBounds.x, mBounds.y);
}
// Should we skip honoring aRepaint here?
if (aRepaint && FindTopLevel() == nsWindow::TopWindow()) RedrawAll();
}
void nsWindow::SetZIndex(int32_t aZIndex) {
ALOG("nsWindow[%p]::SetZIndex %d ignored", (void*)this, aZIndex);
}
void nsWindow::SetSizeMode(nsSizeMode aMode) {
if (aMode == mSizeMode) {
return;
}
mSizeMode = aMode;
switch (aMode) {
case nsSizeMode_Minimized:
java::GeckoAppShell::MoveTaskToBack();
break;
case nsSizeMode_Fullscreen:
MakeFullScreen(true);
break;
default:
break;
}
}
void nsWindow::Enable(bool aState) {
ALOG("nsWindow[%p]::Enable %d ignored", (void*)this, aState);
}
bool nsWindow::IsEnabled() const { return true; }
void nsWindow::Invalidate(const LayoutDeviceIntRect& aRect) {}
nsWindow* nsWindow::FindTopLevel() {
nsWindow* toplevel = this;
while (toplevel) {
if (toplevel->IsTopLevel()) return toplevel;
toplevel = toplevel->mParent;
}
ALOG(
"nsWindow::FindTopLevel(): couldn't find a toplevel or dialog window in "
"this [%p] widget's hierarchy!",
(void*)this);
return this;
}
void nsWindow::SetFocus(Raise, mozilla::dom::CallerType aCallerType) {
FindTopLevel()->BringToFront();
}
void nsWindow::BringToFront() {
MOZ_ASSERT(XRE_IsParentProcess());
// If the window to be raised is the same as the currently raised one,
// do nothing. We need to check the focus manager as well, as the first
// window that is created will be first in the window list but won't yet
// be focused.
nsFocusManager* fm = nsFocusManager::GetFocusManager();
if (fm && fm->GetActiveWindow() && FindTopLevel() == nsWindow::TopWindow()) {
return;
}
if (!IsTopLevel()) {
FindTopLevel()->BringToFront();
return;
}
RefPtr<nsWindow> kungFuDeathGrip(this);
nsWindow* oldTop = nullptr;
if (!gTopLevelWindows.IsEmpty()) {
oldTop = gTopLevelWindows[0];
}
gTopLevelWindows.RemoveElement(this);
gTopLevelWindows.InsertElementAt(0, this);
if (oldTop) {
nsIWidgetListener* listener = oldTop->GetWidgetListener();
if (listener) {
listener->WindowDeactivated();
}
}
if (mWidgetListener) {
mWidgetListener->WindowActivated();
}
RedrawAll();
}
LayoutDeviceIntRect nsWindow::GetScreenBounds() {
return LayoutDeviceIntRect(WidgetToScreenOffset(), mBounds.Size());
}
LayoutDeviceIntPoint nsWindow::WidgetToScreenOffset() {
LayoutDeviceIntPoint p(0, 0);
for (nsWindow* w = this; !!w; w = w->mParent) {
p.x += w->mBounds.x;
p.y += w->mBounds.y;
if (w->IsTopLevel()) {
break;
}
}
return p;
}
nsresult nsWindow::DispatchEvent(WidgetGUIEvent* aEvent,
nsEventStatus& aStatus) {
aStatus = DispatchEvent(aEvent);
return NS_OK;
}
nsEventStatus nsWindow::DispatchEvent(WidgetGUIEvent* aEvent) {
if (mAttachedWidgetListener) {
return mAttachedWidgetListener->HandleEvent(aEvent, mUseAttachedEvents);
} else if (mWidgetListener) {
return mWidgetListener->HandleEvent(aEvent, mUseAttachedEvents);
}
return nsEventStatus_eIgnore;
}
nsresult nsWindow::MakeFullScreen(bool aFullScreen) {
if (!mAndroidView) {
return NS_ERROR_NOT_AVAILABLE;
}
mIsFullScreen = aFullScreen;
mAndroidView->mEventDispatcher->Dispatch(
aFullScreen ? u"GeckoView:FullScreenEnter" : u"GeckoView:FullScreenExit");
nsIWidgetListener* listener = GetWidgetListener();
if (listener) {
mSizeMode = mIsFullScreen ? nsSizeMode_Fullscreen : nsSizeMode_Normal;
listener->SizeModeChanged(mSizeMode);
}
return NS_OK;
}
mozilla::WindowRenderer* nsWindow::GetWindowRenderer() {
if (!mWindowRenderer) {
CreateLayerManager();
}
return mWindowRenderer;
}
void nsWindow::CreateLayerManager() {
if (mWindowRenderer) {
return;
}
nsWindow* topLevelWindow = FindTopLevel();
if (!topLevelWindow || topLevelWindow->mWindowType == WindowType::Invisible) {
// don't create a layer manager for an invisible top-level window
return;
}
// Ensure that gfxPlatform is initialized first.
gfxPlatform::GetPlatform();
if (ShouldUseOffMainThreadCompositing()) {
LayoutDeviceIntRect rect = GetBounds();
CreateCompositor(rect.Width(), rect.Height());
if (mWindowRenderer) {
if (mLayerViewSupport.IsAttached()) {
DispatchToUiThread(
"LayerViewSupport::NotifyCompositorCreated",
[lvs = mLayerViewSupport,
uiCompositorController = GetUiCompositorControllerChild()] {
if (auto lvsAccess{lvs.Access()}) {
lvsAccess->NotifyCompositorCreated(uiCompositorController);
}
});
}
return;
}
// If we get here, then off main thread compositing failed to initialize.
sFailedToCreateGLContext = true;
}
if (!ComputeShouldAccelerate() || sFailedToCreateGLContext) {
printf_stderr(" -- creating basic, not accelerated\n");
mWindowRenderer = CreateFallbackRenderer();
}
}
void nsWindow::NotifyCompositorSessionLost(
mozilla::layers::CompositorSession* aSession) {
nsBaseWidget::NotifyCompositorSessionLost(aSession);
DispatchToUiThread("nsWindow::NotifyCompositorSessionLost",
[lvs = mLayerViewSupport] {
if (auto lvsAccess{lvs.Access()}) {
lvsAccess->NotifyCompositorSessionLost();
}
});
RedrawAll();
}
void nsWindow::ShowDynamicToolbar() {
auto acc(mGeckoViewSupport.Access());
if (!acc) {
return;
}
acc->OnShowDynamicToolbar();
}
void GeckoViewSupport::OnUpdateSessionStore(
mozilla::jni::Object::Param aBundle) {
GeckoSession::Window::LocalRef window(mGeckoViewWindow);
if (!window) {
return;
}
window->OnUpdateSessionStore(aBundle);
}
static EventMessage convertDragEventActionToGeckoEvent(int32_t aAction) {
switch (aAction) {
case java::sdk::DragEvent::ACTION_DRAG_ENTERED:
return eDragEnter;
case java::sdk::DragEvent::ACTION_DRAG_EXITED:
return eDragExit;
case java::sdk::DragEvent::ACTION_DRAG_LOCATION:
return eDragOver;
case java::sdk::DragEvent::ACTION_DROP:
return eDrop;
}
return eVoidEvent;
}
void nsWindow::OnDragEvent(int32_t aAction, int64_t aTime, float aX, float aY,
jni::Object::Param aDropData) {
MOZ_ASSERT(NS_IsMainThread());
RefPtr<nsDragService> dragService = nsDragService::GetInstance();
if (!dragService) {
return;
}
LayoutDeviceIntPoint point =
LayoutDeviceIntPoint(int32_t(floorf(aX)), int32_t(floorf(aY)));
if (aAction == java::sdk::DragEvent::ACTION_DRAG_STARTED) {
dragService->SetDragEndPoint(point);
return;
}
if (aAction == java::sdk::DragEvent::ACTION_DRAG_ENDED) {
dragService->EndDragSession(false, 0);
return;
}
EventMessage message = convertDragEventActionToGeckoEvent(aAction);
if (message == eDragEnter) {
dragService->StartDragSession();
// For compatibility, we have to set temporary data.
auto dropData =
mozilla::java::GeckoDragAndDrop::DropData::Ref::From(aDropData);
nsDragService::SetDropData(dropData);
}
nsCOMPtr<nsIDragSession> dragSession;
dragService->GetCurrentSession(getter_AddRefs(dragSession));
if (dragSession) {
switch (message) {
case eDragOver:
dragService->SetDragEndPoint(point);
dragService->FireDragEventAtSource(eDrag, 0);
break;
case eDrop: {
bool canDrop = false;
dragSession->GetCanDrop(&canDrop);
if (!canDrop) {
nsCOMPtr<nsINode> sourceNode;
dragSession->GetSourceNode(getter_AddRefs(sourceNode));
if (!sourceNode) {
dragService->EndDragSession(false, 0);
}
return;
}
auto dropData =
mozilla::java::GeckoDragAndDrop::DropData::Ref::From(aDropData);
nsDragService::SetDropData(dropData);
dragService->SetDragEndPoint(point);
break;
}
default:
break;
}
dragSession->SetDragAction(nsIDragService::DRAGDROP_ACTION_MOVE);
}
WidgetDragEvent geckoEvent(true, message, this);
geckoEvent.mRefPoint = point;
geckoEvent.mTimeStamp = nsWindow::GetEventTimeStamp(aTime);
geckoEvent.mModifiers = 0; // DragEvent has no modifiers
DispatchInputEvent(&geckoEvent);
if (!dragSession) {
return;
}
switch (message) {
case eDragExit: {
nsCOMPtr<nsINode> sourceNode;
dragSession->GetSourceNode(getter_AddRefs(sourceNode));
if (!sourceNode) {
// We're leaving a window while doing a drag that was
// initiated in a different app. End the drag session,
// since we're done with it for now (until the user
// drags back into mozilla).
dragService->EndDragSession(false, 0);
}
break;
}
case eDrop:
dragService->EndDragSession(true, 0);
break;
default:
break;
}
}
void nsWindow::StartDragAndDrop(java::sdk::Bitmap::LocalRef aBitmap) {
if (mozilla::jni::NativeWeakPtr<LayerViewSupport>::Accessor lvs{
mLayerViewSupport.Access()}) {
const auto& compositor = lvs->GetJavaCompositor();
DispatchToUiThread(
"nsWindow::StartDragAndDrop",
[compositor = GeckoSession::Compositor::GlobalRef(compositor),
bitmap = java::sdk::Bitmap::GlobalRef(aBitmap)] {
compositor->StartDragAndDrop(bitmap);
});
}
}
void nsWindow::UpdateDragImage(java::sdk::Bitmap::LocalRef aBitmap) {
if (mozilla::jni::NativeWeakPtr<LayerViewSupport>::Accessor lvs{
mLayerViewSupport.Access()}) {
const auto& compositor = lvs->GetJavaCompositor();
DispatchToUiThread(
"nsWindow::UpdateDragImage",
[compositor = GeckoSession::Compositor::GlobalRef(compositor),
bitmap = java::sdk::Bitmap::GlobalRef(aBitmap)] {
compositor->UpdateDragImage(bitmap);
});
}
}
void nsWindow::OnSizeChanged(const gfx::IntSize& aSize) {
ALOG("nsWindow: %p OnSizeChanged [%d %d]", (void*)this, aSize.width,
aSize.height);
if (mWidgetListener) {
mWidgetListener->WindowResized(this, aSize.width, aSize.height);
}
if (mAttachedWidgetListener) {
mAttachedWidgetListener->WindowResized(this, aSize.width, aSize.height);
}
if (mCompositorWidgetDelegate) {
mCompositorWidgetDelegate->NotifyClientSizeChanged(
LayoutDeviceIntSize::FromUnknownSize(aSize));
}
}
void nsWindow::InitEvent(WidgetGUIEvent& event, LayoutDeviceIntPoint* aPoint) {
if (aPoint) {
event.mRefPoint = *aPoint;
} else {
event.mRefPoint = LayoutDeviceIntPoint(0, 0);
}
}
void nsWindow::UpdateOverscrollVelocity(const float aX, const float aY) {
if (::mozilla::jni::NativeWeakPtr<LayerViewSupport>::Accessor lvs{
mLayerViewSupport.Access()}) {
const auto& compositor = lvs->GetJavaCompositor();
if (AndroidBridge::IsJavaUiThread()) {
compositor->UpdateOverscrollVelocity(aX, aY);
return;
}
DispatchToUiThread(
"nsWindow::UpdateOverscrollVelocity",
[compositor = GeckoSession::Compositor::GlobalRef(compositor), aX, aY] {
compositor->UpdateOverscrollVelocity(aX, aY);
});
}
}
void nsWindow::UpdateOverscrollOffset(const float aX, const float aY) {
if (::mozilla::jni::NativeWeakPtr<LayerViewSupport>::Accessor lvs{
mLayerViewSupport.Access()}) {
const auto& compositor = lvs->GetJavaCompositor();
if (AndroidBridge::IsJavaUiThread()) {
compositor->UpdateOverscrollOffset(aX, aY);
return;
}
DispatchToUiThread(
"nsWindow::UpdateOverscrollOffset",
[compositor = GeckoSession::Compositor::GlobalRef(compositor), aX, aY] {
compositor->UpdateOverscrollOffset(aX, aY);
});
}
}
void* nsWindow::GetNativeData(uint32_t aDataType) {
switch (aDataType) {
// used by GLContextProviderEGL, nullptr is EGL_DEFAULT_DISPLAY
case NS_NATIVE_WIDGET:
return (void*)this;
case NS_RAW_NATIVE_IME_CONTEXT: {
void* pseudoIMEContext = GetPseudoIMEContext();
if (pseudoIMEContext) {
return pseudoIMEContext;
}
// We assume that there is only one context per process on Android
return NS_ONLY_ONE_NATIVE_IME_CONTEXT;
}
case NS_JAVA_SURFACE:
if (::mozilla::jni::NativeWeakPtr<LayerViewSupport>::Accessor lvs{
mLayerViewSupport.Access()}) {
return lvs->GetSurface().Get();
}
return nullptr;
}
return nullptr;
}
void nsWindow::DispatchHitTest(const WidgetTouchEvent& aEvent) {
if (aEvent.mMessage == eTouchStart && aEvent.mTouches.Length() == 1) {
// Since touch events don't get retargeted by PositionedEventTargeting.cpp
// code, we dispatch a dummy mouse event that *does* get retargeted.
// Front-end code can use this to activate the highlight element in case
// this touchstart is the start of a tap.
WidgetMouseEvent hittest(true, eMouseHitTest, this,
WidgetMouseEvent::eReal);
hittest.mRefPoint = aEvent.mTouches[0]->mRefPoint;
hittest.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH;
nsEventStatus status;
DispatchEvent(&hittest, status);
}
}
void nsWindow::PassExternalResponse(java::WebResponse::Param aResponse) {
auto acc(mGeckoViewSupport.Access());
if (!acc) {
return;
}
acc->PassExternalResponse(aResponse);
}
mozilla::Modifiers nsWindow::GetModifiers(int32_t metaState) {
using mozilla::java::sdk::KeyEvent;
return (metaState & KeyEvent::META_ALT_MASK ? MODIFIER_ALT : 0) |
(metaState & KeyEvent::META_SHIFT_MASK ? MODIFIER_SHIFT : 0) |
(metaState & KeyEvent::META_CTRL_MASK ? MODIFIER_CONTROL : 0) |
(metaState & KeyEvent::META_META_MASK ? MODIFIER_META : 0) |
(metaState & KeyEvent::META_FUNCTION_ON ? MODIFIER_FN : 0) |
(metaState & KeyEvent::META_CAPS_LOCK_ON ? MODIFIER_CAPSLOCK : 0) |
(metaState & KeyEvent::META_NUM_LOCK_ON ? MODIFIER_NUMLOCK : 0) |
(metaState & KeyEvent::META_SCROLL_LOCK_ON ? MODIFIER_SCROLLLOCK : 0);
}
TimeStamp nsWindow::GetEventTimeStamp(int64_t aEventTime) {
// Android's event time is SystemClock.uptimeMillis that is counted in ms
// since OS was booted.
// (https://developer.android.com/reference/android/os/SystemClock.html)
// and this SystemClock.uptimeMillis uses SYSTEM_TIME_MONOTONIC.
// Our posix implemententaion of TimeStamp::Now uses SYSTEM_TIME_MONOTONIC
// too. Due to same implementation, we can use this via FromSystemTime.
int64_t tick =
BaseTimeDurationPlatformUtils::TicksFromMilliseconds(aEventTime);
return TimeStamp::FromSystemTime(tick);
}
void nsWindow::UserActivity() {
if (!mIdleService) {
mIdleService = do_GetService("@mozilla.org/widget/useridleservice;1");
}
if (mIdleService) {
mIdleService->ResetIdleTimeOut(0);
}
if (FindTopLevel() != nsWindow::TopWindow()) {
BringToFront();
}
}
RefPtr<mozilla::a11y::SessionAccessibility>
nsWindow::GetSessionAccessibility() {
auto acc(mSessionAccessibility.Access());
if (!acc) {
return nullptr;
}
return acc.AsRefPtr();
}
TextEventDispatcherListener* nsWindow::GetNativeTextEventDispatcherListener() {
nsWindow* top = FindTopLevel();
MOZ_ASSERT(top);
auto acc(top->mEditableSupport.Access());
if (!acc) {
// Non-GeckoView windows don't support IME operations.
return nullptr;
}
nsCOMPtr<TextEventDispatcherListener> ptr;
if (NS_FAILED(acc->QueryInterface(NS_GET_IID(TextEventDispatcherListener),
getter_AddRefs(ptr)))) {
return nullptr;
}
return ptr.get();
}
void nsWindow::SetInputContext(const InputContext& aContext,
const InputContextAction& aAction) {
nsWindow* top = FindTopLevel();
MOZ_ASSERT(top);
auto acc(top->mEditableSupport.Access());
if (!acc) {
// Non-GeckoView windows don't support IME operations.
return;
}
// We are using an IME event later to notify Java, and the IME event
// will be processed by the top window. Therefore, to ensure the
// IME event uses the correct mInputContext, we need to let the top
// window process SetInputContext
acc->SetInputContext(aContext, aAction);
}
InputContext nsWindow::GetInputContext() {
nsWindow* top = FindTopLevel();
MOZ_ASSERT(top);
auto acc(top->mEditableSupport.Access());
if (!acc) {
// Non-GeckoView windows don't support IME operations.
return InputContext();
}
// We let the top window process SetInputContext,
// so we should let it process GetInputContext as well.
return acc->GetInputContext();
}
nsresult nsWindow::SynthesizeNativeTouchPoint(uint32_t aPointerId,
TouchPointerState aPointerState,
LayoutDeviceIntPoint aPoint,
double aPointerPressure,
uint32_t aPointerOrientation,
nsIObserver* aObserver) {
mozilla::widget::AutoObserverNotifier notifier(aObserver, "touchpoint");
int eventType;
switch (aPointerState) {
case TOUCH_CONTACT:
// This could be a ACTION_DOWN or ACTION_MOVE depending on the
// existing state; it is mapped to the right thing in Java.
eventType = java::sdk::MotionEvent::ACTION_POINTER_DOWN;
break;
case TOUCH_REMOVE:
// This could be turned into a ACTION_UP in Java
eventType = java::sdk::MotionEvent::ACTION_POINTER_UP;
break;
case TOUCH_CANCEL:
eventType = java::sdk::MotionEvent::ACTION_CANCEL;
break;
case TOUCH_HOVER: // not supported for now
default:
return NS_ERROR_UNEXPECTED;
}
MOZ_ASSERT(mNPZCSupport.IsAttached());
auto npzcSup(mNPZCSupport.Access());
MOZ_ASSERT(!!npzcSup);
const auto& npzc = npzcSup->GetJavaNPZC();
const auto& bounds = FindTopLevel()->mBounds;
aPoint.x -= bounds.x;
aPoint.y -= bounds.y;
DispatchToUiThread(
"nsWindow::SynthesizeNativeTouchPoint",
[npzc = java::PanZoomController::NativeProvider::GlobalRef(npzc),
aPointerId, eventType, aPoint, aPointerPressure, aPointerOrientation] {
npzc->SynthesizeNativeTouchPoint(aPointerId, eventType, aPoint.x,
aPoint.y, aPointerPressure,
aPointerOrientation);
});
return NS_OK;
}
nsresult nsWindow::SynthesizeNativeMouseEvent(
LayoutDeviceIntPoint aPoint, NativeMouseMessage aNativeMessage,
MouseButton aButton, nsIWidget::Modifiers aModifierFlags,
nsIObserver* aObserver) {
mozilla::widget::AutoObserverNotifier notifier(aObserver, "mouseevent");
MOZ_ASSERT(mNPZCSupport.IsAttached());
auto npzcSup(mNPZCSupport.Access());
MOZ_ASSERT(!!npzcSup);
const auto& npzc = npzcSup->GetJavaNPZC();
const auto& bounds = FindTopLevel()->mBounds;
aPoint.x -= bounds.x;
aPoint.y -= bounds.y;
int32_t nativeMessage;
switch (aNativeMessage) {
case NativeMouseMessage::ButtonDown:
nativeMessage = java::sdk::MotionEvent::ACTION_POINTER_DOWN;
break;
case NativeMouseMessage::ButtonUp:
nativeMessage = java::sdk::MotionEvent::ACTION_POINTER_UP;
break;
case NativeMouseMessage::Move:
nativeMessage = java::sdk::MotionEvent::ACTION_HOVER_MOVE;
break;
default:
MOZ_ASSERT_UNREACHABLE("Non supported mouse event on Android");
return NS_ERROR_INVALID_ARG;
}
int32_t button = 0;
if (aNativeMessage != NativeMouseMessage::ButtonUp) {
switch (aButton) {
case MouseButton::ePrimary:
button = java::sdk::MotionEvent::BUTTON_PRIMARY;
break;
case MouseButton::eMiddle:
button = java::sdk::MotionEvent::BUTTON_TERTIARY;
break;
case MouseButton::eSecondary:
button = java::sdk::MotionEvent::BUTTON_SECONDARY;
break;
case MouseButton::eX1:
button = java::sdk::MotionEvent::BUTTON_BACK;
break;
case MouseButton::eX2:
button = java::sdk::MotionEvent::BUTTON_FORWARD;
break;
default:
if (aNativeMessage == NativeMouseMessage::ButtonDown) {
MOZ_ASSERT_UNREACHABLE("Non supported mouse button type on Android");
return NS_ERROR_INVALID_ARG;
}
break;
}
}
// TODO (bug 1693237): Handle aModifierFlags.
DispatchToUiThread(
"nsWindow::SynthesizeNativeMouseEvent",
[npzc = java::PanZoomController::NativeProvider::GlobalRef(npzc),
nativeMessage, aPoint, button] {
npzc->SynthesizeNativeMouseEvent(nativeMessage, aPoint.x, aPoint.y,
button);
});
return NS_OK;
}
nsresult nsWindow::SynthesizeNativeMouseMove(LayoutDeviceIntPoint aPoint,
nsIObserver* aObserver) {
return SynthesizeNativeMouseEvent(
aPoint, NativeMouseMessage::Move, MouseButton::eNotPressed,
nsIWidget::Modifiers::NO_MODIFIERS, aObserver);
}
void nsWindow::SetCompositorWidgetDelegate(CompositorWidgetDelegate* delegate) {
if (delegate) {
mCompositorWidgetDelegate = delegate->AsPlatformSpecificDelegate();
MOZ_ASSERT(mCompositorWidgetDelegate,
"nsWindow::SetCompositorWidgetDelegate called with a "
"non-PlatformCompositorWidgetDelegate");
} else {
mCompositorWidgetDelegate = nullptr;
}
}
void nsWindow::GetCompositorWidgetInitData(
mozilla::widget::CompositorWidgetInitData* aInitData) {
*aInitData = mozilla::widget::AndroidCompositorWidgetInitData(
mWidgetId, GetClientSize());
}
bool nsWindow::WidgetPaintsBackground() {
return StaticPrefs::android_widget_paints_background();
}
bool nsWindow::NeedsPaint() {
auto lvs(mLayerViewSupport.Access());
if (!lvs || lvs->CompositorPaused() || !GetWindowRenderer()) {
return false;
}
return nsIWidget::NeedsPaint();
}
void nsWindow::ConfigureAPZControllerThread() {
nsCOMPtr<nsISerialEventTarget> thread = mozilla::GetAndroidUiThread();
APZThreadUtils::SetControllerThread(thread);
}
already_AddRefed<GeckoContentController>
nsWindow::CreateRootContentController() {
RefPtr<GeckoContentController> controller =
new AndroidContentController(this, mAPZEventState, mAPZC);
return controller.forget();
}
uint32_t nsWindow::GetMaxTouchPoints() const {
return java::GeckoAppShell::GetMaxTouchPoints();
}
void nsWindow::UpdateZoomConstraints(
const uint32_t& aPresShellId, const ScrollableLayerGuid::ViewID& aViewId,
const mozilla::Maybe<ZoomConstraints>& aConstraints) {
nsBaseWidget::UpdateZoomConstraints(aPresShellId, aViewId, aConstraints);
}
CompositorBridgeChild* nsWindow::GetCompositorBridgeChild() const {
return mCompositorSession ? mCompositorSession->GetCompositorBridgeChild()
: nullptr;
}
void nsWindow::SetContentDocumentDisplayed(bool aDisplayed) {
mContentDocumentDisplayed = aDisplayed;
}
bool nsWindow::IsContentDocumentDisplayed() {
return mContentDocumentDisplayed;
}
void nsWindow::RecvToolbarAnimatorMessageFromCompositor(int32_t aMessage) {
MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
if (::mozilla::jni::NativeWeakPtr<LayerViewSupport>::Accessor lvs{
mLayerViewSupport.Access()}) {
lvs->RecvToolbarAnimatorMessage(aMessage);
}
}
void nsWindow::UpdateRootFrameMetrics(const ScreenPoint& aScrollOffset,
const CSSToScreenScale& aZoom) {
MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
if (::mozilla::jni::NativeWeakPtr<LayerViewSupport>::Accessor lvs{
mLayerViewSupport.Access()}) {
const auto& compositor = lvs->GetJavaCompositor();
mContentDocumentDisplayed = true;
compositor->UpdateRootFrameMetrics(aScrollOffset.x, aScrollOffset.y,
aZoom.scale);
}
}
void nsWindow::RecvScreenPixels(Shmem&& aMem, const ScreenIntSize& aSize,
bool aNeedsYFlip) {
MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
if (::mozilla::jni::NativeWeakPtr<LayerViewSupport>::Accessor lvs{
mLayerViewSupport.Access()}) {
lvs->RecvScreenPixels(std::move(aMem), aSize, aNeedsYFlip);
}
}
void nsWindow::UpdateDynamicToolbarMaxHeight(ScreenIntCoord aHeight) {
if (mDynamicToolbarMaxHeight == aHeight) {
return;
}
mDynamicToolbarMaxHeight = aHeight;
if (mWidgetListener) {
mWidgetListener->DynamicToolbarMaxHeightChanged(aHeight);
}
if (mAttachedWidgetListener) {
mAttachedWidgetListener->DynamicToolbarMaxHeightChanged(aHeight);
}
}
void nsWindow::UpdateDynamicToolbarOffset(ScreenIntCoord aOffset) {
if (mWidgetListener) {
mWidgetListener->DynamicToolbarOffsetChanged(aOffset);
}
if (mAttachedWidgetListener) {
mAttachedWidgetListener->DynamicToolbarOffsetChanged(aOffset);
}
}
ScreenIntMargin nsWindow::GetSafeAreaInsets() const { return mSafeAreaInsets; }
void nsWindow::UpdateSafeAreaInsets(const ScreenIntMargin& aSafeAreaInsets) {
mSafeAreaInsets = aSafeAreaInsets;
if (mWidgetListener) {
mWidgetListener->SafeAreaInsetsChanged(aSafeAreaInsets);
}
if (mAttachedWidgetListener) {
mAttachedWidgetListener->SafeAreaInsetsChanged(aSafeAreaInsets);
}
}
jni::NativeWeakPtr<NPZCSupport> nsWindow::GetNPZCSupportWeakPtr() {
return mNPZCSupport;
}
already_AddRefed<nsIWidget> nsIWidget::CreateTopLevelWindow() {
nsCOMPtr<nsIWidget> window = new nsWindow();
return window.forget();
}
already_AddRefed<nsIWidget> nsIWidget::CreateChildWindow() {
nsCOMPtr<nsIWidget> window = new nsWindow();
return window.forget();
}
static already_AddRefed<DataSourceSurface> GetCursorImage(
const nsIWidget::Cursor& aCursor, mozilla::CSSToLayoutDeviceScale aScale) {
if (!aCursor.IsCustom()) {
return nullptr;
}
RefPtr<DataSourceSurface> destDataSurface;
nsIntSize size = nsIWidget::CustomCursorSize(aCursor);
// prevent DoS attacks
if (size.width > 128 || size.height > 128) {
return nullptr;
}
RefPtr<gfx::SourceSurface> surface = aCursor.mContainer->GetFrameAtSize(
size * aScale.scale, imgIContainer::FRAME_CURRENT,
imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY);
if (NS_WARN_IF(!surface)) {
return nullptr;
}
return AndroidWidgetUtils::GetDataSourceSurfaceForAndroidBitmap(surface);
}
static int32_t GetCursorType(nsCursor aCursor) {
// When our minimal requirement of SDK version is 25+,
// we should replace with JNI auto-generator.
switch (aCursor) {
case eCursor_standard:
// android.view.PointerIcon.TYPE_ARROW
return 0x3e8;
case eCursor_wait:
// android.view.PointerIcon.TYPE_WAIT
return 0x3ec;
case eCursor_select:
// android.view.PointerIcon.TYPE_TEXT;
return 0x3f0;
case eCursor_hyperlink:
// android.view.PointerIcon.TYPE_HAND
return 0x3ea;
case eCursor_n_resize:
case eCursor_s_resize:
case eCursor_ns_resize:
case eCursor_row_resize:
// android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW
return 0x3f7;
case eCursor_w_resize:
case eCursor_e_resize:
case eCursor_ew_resize:
case eCursor_col_resize:
// android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW
return 0x3f6;
case eCursor_nw_resize:
case eCursor_se_resize:
case eCursor_nwse_resize:
// android.view.PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW
return 0x3f9;
case eCursor_ne_resize:
case eCursor_sw_resize:
case eCursor_nesw_resize:
// android.view.PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW
return 0x3f8;
case eCursor_crosshair:
// android.view.PointerIcon.TYPE_CROSSHAIR
return 0x3ef;
case eCursor_move:
// android.view.PointerIcon.TYPE_ARROW
return 0x3e8;
case eCursor_help:
// android.view.PointerIcon.TYPE_HELP
return 0x3eb;
case eCursor_copy:
// android.view.PointerIcon.TYPE_COPY
return 0x3f3;
case eCursor_alias:
// android.view.PointerIcon.TYPE_ALIAS
return 0x3f2;
case eCursor_context_menu:
// android.view.PointerIcon.TYPE_CONTEXT_MENU
return 0x3e9;
case eCursor_cell:
// android.view.PointerIcon.TYPE_CELL
return 0x3ee;
case eCursor_grab:
// android.view.PointerIcon.TYPE_GRAB
return 0x3fc;
case eCursor_grabbing:
// android.view.PointerIcon.TYPE_GRABBING
return 0x3fd;
case eCursor_spinning:
// android.view.PointerIcon.TYPE_WAIT
return 0x3ec;
case eCursor_zoom_in:
// android.view.PointerIcon.TYPE_ZOOM_IN
return 0x3fa;
case eCursor_zoom_out:
// android.view.PointerIcon.TYPE_ZOOM_OUT
return 0x3fb;
case eCursor_not_allowed:
// android.view.PointerIcon.TYPE_NO_DROP:
return 0x3f4;
case eCursor_no_drop:
// android.view.PointerIcon.TYPE_NO_DROP:
return 0x3f4;
case eCursor_vertical_text:
// android.view.PointerIcon.TYPE_VERTICAL_TEXT
return 0x3f1;
case eCursor_all_scroll:
// android.view.PointerIcon.TYPE_ALL_SCROLL
return 0x3f5;
case eCursor_none:
// android.view.PointerIcon.TYPE_NULL
return 0;
default:
NS_WARNING_ASSERTION(aCursor, "Invalid cursor type");
// android.view.PointerIcon.TYPE_ARROW
return 0x3e8;
}
}
void nsWindow::SetCursor(const Cursor& aCursor) {
if (mozilla::jni::GetAPIVersion() < 24) {
return;
}
// Only change cursor if it's actually been changed
if (!mUpdateCursor && mCursor == aCursor) {
return;
}
mUpdateCursor = false;
mCursor = aCursor;
int32_t type = 0;
RefPtr<DataSourceSurface> destDataSurface =
GetCursorImage(aCursor, GetDefaultScale());
if (!destDataSurface) {
type = GetCursorType(aCursor.mDefaultCursor);
}
if (mozilla::jni::NativeWeakPtr<LayerViewSupport>::Accessor lvs{
mLayerViewSupport.Access()}) {
const auto& compositor = lvs->GetJavaCompositor();
DispatchToUiThread(
"nsWindow::SetCursor",
[compositor = GeckoSession::Compositor::GlobalRef(compositor), type,
destDataSurface = std::move(destDataSurface),
hotspotX = aCursor.mHotspotX, hotspotY = aCursor.mHotspotY] {
java::sdk::Bitmap::LocalRef bitmap;
if (destDataSurface) {
DataSourceSurface::ScopedMap destMap(destDataSurface,
DataSourceSurface::READ);
auto pixels = mozilla::jni::ByteBuffer::New(
reinterpret_cast<int8_t*>(destMap.GetData()),
destMap.GetStride() * destDataSurface->GetSize().height);
bitmap = java::sdk::Bitmap::CreateBitmap(
destDataSurface->GetSize().width,
destDataSurface->GetSize().height,
java::sdk::Bitmap::Config::ARGB_8888());
bitmap->CopyPixelsFromBuffer(pixels);
}
compositor->SetPointerIcon(type, bitmap, hotspotX, hotspotY);
});
}
}