зеркало из https://github.com/mozilla/gecko-dev.git
3194 строки
103 KiB
C++
3194 строки
103 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 "AndroidGraphics.h"
|
|
#include "AndroidBridge.h"
|
|
#include "AndroidBridgeUtilities.h"
|
|
#include "AndroidCompositorWidget.h"
|
|
#include "AndroidContentController.h"
|
|
#include "AndroidUiThread.h"
|
|
#include "AndroidView.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 "SurfaceViewWrapperSupport.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 "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/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/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)
|
|
|
|
// Use vsync for touch resampling on API level 19 and above.
|
|
// See gfxAndroidPlatform::CreateGlobalHardwareVsyncSource() for comparison.
|
|
if (jni::GetAPIVersion() >= 19) {
|
|
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);
|
|
});
|
|
|
|
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;
|
|
}
|
|
nsWindow* gkWindow = win->GetNsWindow();
|
|
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 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) {
|
|
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;
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
mUiCompositorControllerChild->ResumeAndResize(mX, mY, mWidth, mHeight);
|
|
}
|
|
}
|
|
|
|
/// 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());
|
|
|
|
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());
|
|
|
|
if (mUiCompositorControllerChild) {
|
|
mCompositorPaused = false;
|
|
mUiCompositorControllerChild->Resume();
|
|
}
|
|
}
|
|
|
|
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());
|
|
|
|
// If our Surface is in an abandoned state then we will never succesfully
|
|
// create an EGL Surface, and will eventually crash. Better to explicitly
|
|
// crash now.
|
|
if (SurfaceViewWrapperSupport::IsSurfaceAbandoned(aSurface)) {
|
|
MOZ_CRASH("Compositor resumed with abandoned Surface");
|
|
}
|
|
|
|
mX = aX;
|
|
mY = aY;
|
|
mWidth = aWidth;
|
|
mHeight = aHeight;
|
|
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);
|
|
}
|
|
}
|
|
|
|
mUiCompositorControllerChild->ResumeAndResize(aX, aY, aWidth, aHeight);
|
|
}
|
|
|
|
mCompositorPaused = false;
|
|
|
|
class OnResumedEvent : public nsAppShell::Event {
|
|
GeckoSession::Compositor::GlobalRef mCompositor;
|
|
|
|
public:
|
|
explicit OnResumedEvent(GeckoSession::Compositor::GlobalRef&& aCompositor)
|
|
: mCompositor(std::move(aCompositor)) {}
|
|
|
|
void Run() override {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
JNIEnv* const env = jni::GetGeckoThreadEnv();
|
|
const auto lvsHolder =
|
|
GetNative(GeckoSession::Compositor::LocalRef(env, mCompositor));
|
|
|
|
if (!lvsHolder) {
|
|
env->ExceptionClear();
|
|
return; // Already shut down.
|
|
}
|
|
|
|
auto lvs(lvsHolder->Access());
|
|
if (!lvs) {
|
|
env->ExceptionClear();
|
|
return; // Already shut down.
|
|
}
|
|
|
|
auto win = lvs->mWindow.Access();
|
|
if (!win) {
|
|
env->ExceptionClear();
|
|
return; // Already shut down.
|
|
}
|
|
|
|
// When we get here, the compositor has already been told to
|
|
// resume. This means it's now safe for layer updates to occur.
|
|
// Since we might have prevented one or more draw events from
|
|
// occurring while the compositor was paused, we need to
|
|
// schedule a draw event now.
|
|
if (!lvs->mCompositorPaused) {
|
|
nsWindow* const gkWindow = win->GetNsWindow();
|
|
if (gkWindow) {
|
|
gkWindow->RedrawAll();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// Use priority queue for timing-sensitive event.
|
|
nsAppShell::PostEvent(
|
|
MakeUnique<LayerViewEvent>(MakeUnique<OnResumedEvent>(aObj)));
|
|
}
|
|
|
|
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);
|
|
|
|
mWindow->mSessionAccessibility.Detach();
|
|
if (aSessionAccessibility) {
|
|
AttachAccessibility(inst, aSessionAccessibility);
|
|
}
|
|
|
|
if (mIsReady) {
|
|
// We're in a transfer; update init-data and notify JS code.
|
|
mWindow->mAndroidView->mInitData = java::GeckoBundle::Ref::From(aInitData);
|
|
OnReady(aQueue);
|
|
mWindow->mAndroidView->mEventDispatcher->Dispatch(
|
|
u"GeckoView:UpdateInitData");
|
|
}
|
|
|
|
DispatchToUiThread("GeckoViewSupport::Transfer",
|
|
[compositor = GeckoSession::Compositor::GlobalRef(
|
|
compositor)] { compositor->OnCompositorAttached(); });
|
|
}
|
|
|
|
void GeckoViewSupport::AttachEditable(
|
|
const GeckoSession::Window::LocalRef& inst,
|
|
jni::Object::Param aEditableParent) {
|
|
if (auto win{mWindow->mEditableSupport.Access()}) {
|
|
win->TransferParent(aEditableParent);
|
|
} else {
|
|
auto editableChild = java::GeckoEditableChild::New(aEditableParent,
|
|
/* default */ true);
|
|
mWindow->mEditableSupport =
|
|
jni::NativeWeakPtrHolder<GeckoEditableSupport>::Attach(
|
|
editableChild, mWindow->mGeckoViewSupport, editableChild);
|
|
}
|
|
|
|
mWindow->mEditableParent = aEditableParent;
|
|
}
|
|
|
|
void GeckoViewSupport::AttachAccessibility(
|
|
const GeckoSession::Window::LocalRef& inst,
|
|
jni::Object::Param aSessionAccessibility) {
|
|
java::SessionAccessibility::NativeProvider::LocalRef sessionAccessibility(
|
|
inst.Env());
|
|
sessionAccessibility = java::SessionAccessibility::NativeProvider::Ref::From(
|
|
aSessionAccessibility);
|
|
|
|
mWindow->mSessionAccessibility =
|
|
jni::NativeWeakPtrHolder<a11y::SessionAccessibility>::Attach(
|
|
sessionAccessibility, mWindow->mGeckoViewSupport,
|
|
sessionAccessibility);
|
|
}
|
|
|
|
auto GeckoViewSupport::OnLoadRequest(mozilla::jni::String::Param aUri,
|
|
int32_t aWindowType, int32_t aFlags,
|
|
mozilla::jni::String::Param aTriggeringUri,
|
|
bool aHasUserGesture,
|
|
bool aIsTopLevel) const
|
|
-> java::GeckoResult::LocalRef {
|
|
GeckoSession::Window::LocalRef window(mGeckoViewWindow);
|
|
if (!window) {
|
|
return nullptr;
|
|
}
|
|
return window->OnLoadRequest(aUri, aWindowType, aFlags, aTriggeringUri,
|
|
aHasUserGesture, aIsTopLevel);
|
|
}
|
|
|
|
void GeckoViewSupport::OnShowDynamicToolbar() const {
|
|
GeckoSession::Window::LocalRef window(mGeckoViewWindow);
|
|
if (!window) {
|
|
return;
|
|
}
|
|
|
|
window->OnShowDynamicToolbar();
|
|
}
|
|
|
|
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->SendError();
|
|
}
|
|
});
|
|
}
|
|
|
|
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) {}
|
|
|
|
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() {
|
|
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(bool aAllowSlop, int32_t* aX, int32_t* aY) {
|
|
ALOG("nsWindow[%p]::ConstrainPosition %d [%d %d]", (void*)this, aAllowSlop,
|
|
*aX, *aY);
|
|
|
|
// constrain toplevel windows; children we don't care about
|
|
if (IsTopLevel()) {
|
|
*aX = 0;
|
|
*aY = 0;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
nsIWidgetListener* listener = GetWidgetListener();
|
|
if (listener) {
|
|
listener->FullscreenWillChange(aFullScreen);
|
|
}
|
|
|
|
mIsFullScreen = aFullScreen;
|
|
mAndroidView->mEventDispatcher->Dispatch(
|
|
aFullScreen ? u"GeckoView:FullScreenEnter" : u"GeckoView:FullScreenExit");
|
|
|
|
if (listener) {
|
|
mSizeMode = mIsFullScreen ? nsSizeMode_Fullscreen : nsSizeMode_Normal;
|
|
listener->SizeModeChanged(mSizeMode);
|
|
listener->FullscreenChanged(mIsFullScreen);
|
|
}
|
|
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);
|
|
}
|
|
|
|
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_DISPLAY:
|
|
return nullptr;
|
|
|
|
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::SetNativeData(uint32_t aDataType, uintptr_t aVal) {
|
|
switch (aDataType) {}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
RefPtr<DataSourceSurface> srcDataSurface = surface->GetDataSurface();
|
|
if (NS_WARN_IF(!srcDataSurface)) {
|
|
return nullptr;
|
|
}
|
|
|
|
DataSourceSurface::ScopedMap sourceMap(srcDataSurface,
|
|
DataSourceSurface::READ);
|
|
|
|
destDataSurface = gfx::Factory::CreateDataSourceSurfaceWithStride(
|
|
srcDataSurface->GetSize(), SurfaceFormat::R8G8B8A8,
|
|
sourceMap.GetStride());
|
|
if (NS_WARN_IF(!destDataSurface)) {
|
|
return nullptr;
|
|
}
|
|
|
|
DataSourceSurface::ScopedMap destMap(destDataSurface,
|
|
DataSourceSurface::READ_WRITE);
|
|
|
|
SwizzleData(sourceMap.GetData(), sourceMap.GetStride(), surface->GetFormat(),
|
|
destMap.GetData(), destMap.GetStride(), SurfaceFormat::R8G8B8A8,
|
|
destDataSurface->GetSize());
|
|
|
|
return destDataSurface.forget();
|
|
}
|
|
|
|
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);
|
|
});
|
|
}
|
|
}
|