diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /widget/PuppetWidget.cpp | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | widget/PuppetWidget.cpp | 1291 |
1 files changed, 1291 insertions, 0 deletions
diff --git a/widget/PuppetWidget.cpp b/widget/PuppetWidget.cpp new file mode 100644 index 0000000000..159096a08b --- /dev/null +++ b/widget/PuppetWidget.cpp @@ -0,0 +1,1291 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* 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 "base/basictypes.h" + +#include "ClientLayerManager.h" +#include "gfxPlatform.h" +#include "nsRefreshDriver.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/Hal.h" +#include "mozilla/IMEStateManager.h" +#include "mozilla/layers/APZChild.h" +#include "mozilla/layers/PLayerTransactionChild.h" +#include "mozilla/layers/WebRenderLayerManager.h" +#include "mozilla/Preferences.h" +#include "mozilla/PresShell.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/StaticPrefs_browser.h" +#include "mozilla/TextComposition.h" +#include "mozilla/TextEventDispatcher.h" +#include "mozilla/TextEvents.h" +#include "mozilla/Unused.h" +#include "BasicLayers.h" +#include "PuppetWidget.h" +#include "nsContentUtils.h" +#include "nsIWidgetListener.h" +#include "imgIContainer.h" +#include "nsView.h" +#include "nsXPLookAndFeel.h" +#include "nsPrintfCString.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::hal; +using namespace mozilla::gfx; +using namespace mozilla::layers; +using namespace mozilla::widget; + +static void InvalidateRegion(nsIWidget* aWidget, + const LayoutDeviceIntRegion& aRegion) { + for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) { + aWidget->Invalidate(iter.Get()); + } +} + +/*static*/ +already_AddRefed<nsIWidget> nsIWidget::CreatePuppetWidget( + BrowserChild* aBrowserChild) { + MOZ_ASSERT(!aBrowserChild || nsIWidget::UsePuppetWidgets(), + "PuppetWidgets not allowed in this configuration"); + + nsCOMPtr<nsIWidget> widget = new PuppetWidget(aBrowserChild); + return widget.forget(); +} + +namespace mozilla { +namespace widget { + +static bool IsPopup(const nsWidgetInitData* aInitData) { + return aInitData && aInitData->mWindowType == eWindowType_popup; +} + +static bool MightNeedIMEFocus(const nsWidgetInitData* aInitData) { + // In the puppet-widget world, popup widgets are just dummies and + // shouldn't try to mess with IME state. +#ifdef MOZ_CROSS_PROCESS_IME + return !IsPopup(aInitData); +#else + return false; +#endif +} + +// Arbitrary, fungible. +const size_t PuppetWidget::kMaxDimension = 4000; + +NS_IMPL_ISUPPORTS_INHERITED(PuppetWidget, nsBaseWidget, + TextEventDispatcherListener) + +PuppetWidget::PuppetWidget(BrowserChild* aBrowserChild) + : mBrowserChild(aBrowserChild), + mMemoryPressureObserver(nullptr), + mDPI(-1), + mRounding(1), + mDefaultScale(-1), + mCursorHotspotX(0), + mCursorHotspotY(0), + mEnabled(false), + mVisible(false), + mNeedIMEStateInit(false), + mIgnoreCompositionEvents(false) { + // Setting 'Unknown' means "not yet cached". + mInputContext.mIMEState.mEnabled = IMEEnabled::Unknown; +} + +PuppetWidget::~PuppetWidget() { Destroy(); } + +void PuppetWidget::InfallibleCreate(nsIWidget* aParent, + nsNativeWidget aNativeParent, + const LayoutDeviceIntRect& aRect, + nsWidgetInitData* aInitData) { + MOZ_ASSERT(!aNativeParent, "got a non-Puppet native parent"); + + BaseCreate(nullptr, aInitData); + + mBounds = aRect; + mEnabled = true; + mVisible = true; + + mDrawTarget = gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget( + IntSize(1, 1), SurfaceFormat::B8G8R8A8); + + mNeedIMEStateInit = MightNeedIMEFocus(aInitData); + + PuppetWidget* parent = static_cast<PuppetWidget*>(aParent); + if (parent) { + parent->SetChild(this); + mLayerManager = parent->GetLayerManager(); + } else { + Resize(mBounds.X(), mBounds.Y(), mBounds.Width(), mBounds.Height(), false); + } + mMemoryPressureObserver = MemoryPressureObserver::Create(this); +} + +nsresult PuppetWidget::Create(nsIWidget* aParent, nsNativeWidget aNativeParent, + const LayoutDeviceIntRect& aRect, + nsWidgetInitData* aInitData) { + InfallibleCreate(aParent, aNativeParent, aRect, aInitData); + return NS_OK; +} + +void PuppetWidget::InitIMEState() { + MOZ_ASSERT(mBrowserChild); + if (mNeedIMEStateInit) { + mContentCache.Clear(); + mBrowserChild->SendUpdateContentCache(mContentCache); + mIMENotificationRequestsOfParent = IMENotificationRequests(); + mNeedIMEStateInit = false; + } +} + +already_AddRefed<nsIWidget> PuppetWidget::CreateChild( + const LayoutDeviceIntRect& aRect, nsWidgetInitData* aInitData, + bool aForceUseIWidgetParent) { + bool isPopup = IsPopup(aInitData); + nsCOMPtr<nsIWidget> widget = nsIWidget::CreatePuppetWidget(mBrowserChild); + return ((widget && NS_SUCCEEDED(widget->Create(isPopup ? nullptr : this, + nullptr, aRect, aInitData))) + ? widget.forget() + : nullptr); +} + +void PuppetWidget::Destroy() { + if (mOnDestroyCalled) { + return; + } + mOnDestroyCalled = true; + + Base::OnDestroy(); + Base::Destroy(); + if (mMemoryPressureObserver) { + mMemoryPressureObserver->Unregister(); + mMemoryPressureObserver = nullptr; + } + mChild = nullptr; + if (mLayerManager) { + mLayerManager->Destroy(); + } + mLayerManager = nullptr; + mBrowserChild = nullptr; +} + +void PuppetWidget::Show(bool aState) { + NS_ASSERTION(mEnabled, + "does it make sense to Show()/Hide() a disabled widget?"); + + bool wasVisible = mVisible; + mVisible = aState; + + if (mChild) { + mChild->mVisible = aState; + } + + if (!wasVisible && mVisible) { + // The previously attached widget listener is handy if + // we're transitioning from page to page without dropping + // layers (since we'll continue to show the old layers + // associated with that old widget listener). If the + // PuppetWidget was hidden, those layers are dropped, + // so the previously attached widget listener is really + // of no use anymore (and is actually actively harmful - see + // bug 1323586). + mPreviouslyAttachedWidgetListener = nullptr; + Resize(mBounds.Width(), mBounds.Height(), false); + Invalidate(mBounds); + } +} + +void PuppetWidget::Resize(double aWidth, double aHeight, bool aRepaint) { + LayoutDeviceIntRect oldBounds = mBounds; + mBounds.SizeTo( + LayoutDeviceIntSize(NSToIntRound(aWidth), NSToIntRound(aHeight))); + + if (mChild) { + mChild->Resize(aWidth, aHeight, aRepaint); + return; + } + + // XXX: roc says that |aRepaint| dictates whether or not to + // invalidate the expanded area + if (oldBounds.Size() < mBounds.Size() && aRepaint) { + LayoutDeviceIntRegion dirty(mBounds); + dirty.Sub(dirty, oldBounds); + InvalidateRegion(this, dirty); + } + + // call WindowResized() on both the current listener, and possibly + // also the previous one if we're in a state where we're drawing that one + // because the current one is paint suppressed + if (!oldBounds.IsEqualEdges(mBounds) && mAttachedWidgetListener) { + if (GetCurrentWidgetListener() && + GetCurrentWidgetListener() != mAttachedWidgetListener) { + GetCurrentWidgetListener()->WindowResized(this, mBounds.Width(), + mBounds.Height()); + } + mAttachedWidgetListener->WindowResized(this, mBounds.Width(), + mBounds.Height()); + } +} + +nsresult PuppetWidget::ConfigureChildren( + const nsTArray<Configuration>& aConfigurations) { + for (uint32_t i = 0; i < aConfigurations.Length(); ++i) { + const Configuration& configuration = aConfigurations[i]; + PuppetWidget* w = static_cast<PuppetWidget*>(configuration.mChild.get()); + NS_ASSERTION(w->GetParent() == this, "Configured widget is not a child"); + w->SetWindowClipRegion(configuration.mClipRegion, true); + LayoutDeviceIntRect bounds = w->GetBounds(); + if (bounds.Size() != configuration.mBounds.Size()) { + w->Resize(configuration.mBounds.X(), configuration.mBounds.Y(), + configuration.mBounds.Width(), configuration.mBounds.Height(), + true); + } else if (bounds.TopLeft() != configuration.mBounds.TopLeft()) { + w->Move(configuration.mBounds.X(), configuration.mBounds.Y()); + } + w->SetWindowClipRegion(configuration.mClipRegion, false); + } + return NS_OK; +} + +void PuppetWidget::SetFocus(Raise aRaise, CallerType aCallerType) { + if (aRaise == Raise::Yes && mBrowserChild) { + mBrowserChild->SendRequestFocus(true, aCallerType); + } +} + +void PuppetWidget::Invalidate(const LayoutDeviceIntRect& aRect) { +#ifdef DEBUG + debug_DumpInvalidate(stderr, this, &aRect, "PuppetWidget", 0); +#endif + + if (mChild) { + mChild->Invalidate(aRect); + return; + } + + if (mBrowserChild && !aRect.IsEmpty()) { + if (RefPtr<nsRefreshDriver> refreshDriver = GetTopLevelRefreshDriver()) { + refreshDriver->ScheduleViewManagerFlush(); + } + } +} + +mozilla::LayoutDeviceToLayoutDeviceMatrix4x4 +PuppetWidget::WidgetToTopLevelWidgetTransform() { + if (!GetOwningBrowserChild()) { + NS_WARNING("PuppetWidget without Tab does not have transform information."); + return mozilla::LayoutDeviceToLayoutDeviceMatrix4x4(); + } + return GetOwningBrowserChild()->GetChildToParentConversionMatrix(); +} + +void PuppetWidget::InitEvent(WidgetGUIEvent& aEvent, + LayoutDeviceIntPoint* aPoint) { + if (nullptr == aPoint) { + aEvent.mRefPoint = LayoutDeviceIntPoint(0, 0); + } else { + // use the point override if provided + aEvent.mRefPoint = *aPoint; + } + aEvent.mTime = PR_Now() / 1000; +} + +nsresult PuppetWidget::DispatchEvent(WidgetGUIEvent* aEvent, + nsEventStatus& aStatus) { +#ifdef DEBUG + debug_DumpEvent(stdout, aEvent->mWidget, aEvent, "PuppetWidget", 0); +#endif + + MOZ_ASSERT(!mChild || mChild->mWindowType == eWindowType_popup, + "Unexpected event dispatch!"); + + MOZ_ASSERT(!aEvent->AsKeyboardEvent() || + aEvent->mFlags.mIsSynthesizedForTests || + aEvent->AsKeyboardEvent()->AreAllEditCommandsInitialized(), + "Non-sysnthesized keyboard events should have edit commands for " + "all types " + "before dispatched"); + + if (aEvent->mClass == eCompositionEventClass) { + // If we've already requested to commit/cancel the latest composition, + // TextComposition for the old composition has been destroyed. Then, + // the DOM tree needs to listen to next eCompositionStart and its + // following events. So, until we meet new eCompositionStart, let's + // discard all unnecessary composition events here. + if (mIgnoreCompositionEvents) { + if (aEvent->mMessage != eCompositionStart) { + aStatus = nsEventStatus_eIgnore; + return NS_OK; + } + // Now, we receive new eCompositionStart. Let's restart to handle + // composition in this process. + mIgnoreCompositionEvents = false; + } + // Store the latest native IME context of parent process's widget or + // TextEventDispatcher if it's in this process. + WidgetCompositionEvent* compositionEvent = aEvent->AsCompositionEvent(); +#ifdef DEBUG + if (mNativeIMEContext.IsValid() && + mNativeIMEContext != compositionEvent->mNativeIMEContext) { + RefPtr<TextComposition> composition = + IMEStateManager::GetTextCompositionFor(this); + MOZ_ASSERT( + !composition, + "When there is composition caused by old native IME context, " + "composition events caused by different native IME context are not " + "allowed"); + } +#endif // #ifdef DEBUG + mNativeIMEContext = compositionEvent->mNativeIMEContext; + mContentCache.OnCompositionEvent(*compositionEvent); + } + + // If the event is a composition event or a keyboard event, it should be + // dispatched with TextEventDispatcher if we could do that with current + // design. However, we cannot do that without big changes and the behavior + // is not so complicated for now. Therefore, we should just notify it + // of dispatching events and TextEventDispatcher should emulate the state + // with events here. + if (aEvent->mClass == eCompositionEventClass || + aEvent->mClass == eKeyboardEventClass) { + TextEventDispatcher* dispatcher = GetTextEventDispatcher(); + // However, if the event is being dispatched by the text event dispatcher + // or, there is native text event dispatcher listener, that means that + // native text input event handler is in this process like on Android, + // and the event is not synthesized for tests, the event is coming from + // the TextEventDispatcher. In these cases, we shouldn't notify + // TextEventDispatcher of dispatching the event. + if (!dispatcher->IsDispatchingEvent() && + !(mNativeTextEventDispatcherListener && + !aEvent->mFlags.mIsSynthesizedForTests)) { + DebugOnly<nsresult> rv = + dispatcher->BeginInputTransactionFor(aEvent, this); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "The text event dispatcher should always succeed to start input " + "transaction for the event"); + } + } + + aStatus = nsEventStatus_eIgnore; + + if (GetCurrentWidgetListener()) { + aStatus = + GetCurrentWidgetListener()->HandleEvent(aEvent, mUseAttachedEvents); + } + + return NS_OK; +} + +nsEventStatus PuppetWidget::DispatchInputEvent(WidgetInputEvent* aEvent) { + if (!AsyncPanZoomEnabled()) { + nsEventStatus status = nsEventStatus_eIgnore; + DispatchEvent(aEvent, status); + return status; + } + + if (!mBrowserChild) { + return nsEventStatus_eIgnore; + } + + switch (aEvent->mClass) { + case eWheelEventClass: + Unused << mBrowserChild->SendDispatchWheelEvent(*aEvent->AsWheelEvent()); + break; + case eMouseEventClass: + Unused << mBrowserChild->SendDispatchMouseEvent(*aEvent->AsMouseEvent()); + break; + case eKeyboardEventClass: + Unused << mBrowserChild->SendDispatchKeyboardEvent( + *aEvent->AsKeyboardEvent()); + break; + default: + MOZ_ASSERT_UNREACHABLE("unsupported event type"); + } + + return nsEventStatus_eIgnore; +} + +nsresult PuppetWidget::SynthesizeNativeKeyEvent( + int32_t aNativeKeyboardLayout, int32_t aNativeKeyCode, + uint32_t aModifierFlags, const nsAString& aCharacters, + const nsAString& aUnmodifiedCharacters, nsIObserver* aObserver) { + AutoObserverNotifier notifier(aObserver, "keyevent"); + if (!mBrowserChild) { + return NS_ERROR_FAILURE; + } + mBrowserChild->SendSynthesizeNativeKeyEvent( + aNativeKeyboardLayout, aNativeKeyCode, aModifierFlags, + nsString(aCharacters), nsString(aUnmodifiedCharacters), + notifier.SaveObserver()); + return NS_OK; +} + +nsresult PuppetWidget::SynthesizeNativeMouseEvent( + mozilla::LayoutDeviceIntPoint aPoint, uint32_t aNativeMessage, + uint32_t aModifierFlags, nsIObserver* aObserver) { + AutoObserverNotifier notifier(aObserver, "mouseevent"); + if (!mBrowserChild) { + return NS_ERROR_FAILURE; + } + mBrowserChild->SendSynthesizeNativeMouseEvent( + aPoint, aNativeMessage, aModifierFlags, notifier.SaveObserver()); + return NS_OK; +} + +nsresult PuppetWidget::SynthesizeNativeMouseMove( + mozilla::LayoutDeviceIntPoint aPoint, nsIObserver* aObserver) { + AutoObserverNotifier notifier(aObserver, "mousemove"); + if (!mBrowserChild) { + return NS_ERROR_FAILURE; + } + mBrowserChild->SendSynthesizeNativeMouseMove(aPoint, notifier.SaveObserver()); + return NS_OK; +} + +nsresult PuppetWidget::SynthesizeNativeMouseScrollEvent( + mozilla::LayoutDeviceIntPoint aPoint, uint32_t aNativeMessage, + double aDeltaX, double aDeltaY, double aDeltaZ, uint32_t aModifierFlags, + uint32_t aAdditionalFlags, nsIObserver* aObserver) { + AutoObserverNotifier notifier(aObserver, "mousescrollevent"); + if (!mBrowserChild) { + return NS_ERROR_FAILURE; + } + mBrowserChild->SendSynthesizeNativeMouseScrollEvent( + aPoint, aNativeMessage, aDeltaX, aDeltaY, aDeltaZ, aModifierFlags, + aAdditionalFlags, notifier.SaveObserver()); + return NS_OK; +} + +nsresult PuppetWidget::SynthesizeNativeTouchPoint( + uint32_t aPointerId, TouchPointerState aPointerState, + LayoutDeviceIntPoint aPoint, double aPointerPressure, + uint32_t aPointerOrientation, nsIObserver* aObserver) { + AutoObserverNotifier notifier(aObserver, "touchpoint"); + if (!mBrowserChild) { + return NS_ERROR_FAILURE; + } + mBrowserChild->SendSynthesizeNativeTouchPoint( + aPointerId, aPointerState, aPoint, aPointerPressure, aPointerOrientation, + notifier.SaveObserver()); + return NS_OK; +} + +nsresult PuppetWidget::SynthesizeNativeTouchTap(LayoutDeviceIntPoint aPoint, + bool aLongTap, + nsIObserver* aObserver) { + AutoObserverNotifier notifier(aObserver, "touchtap"); + if (!mBrowserChild) { + return NS_ERROR_FAILURE; + } + mBrowserChild->SendSynthesizeNativeTouchTap(aPoint, aLongTap, + notifier.SaveObserver()); + return NS_OK; +} + +nsresult PuppetWidget::ClearNativeTouchSequence(nsIObserver* aObserver) { + AutoObserverNotifier notifier(aObserver, "cleartouch"); + if (!mBrowserChild) { + return NS_ERROR_FAILURE; + } + mBrowserChild->SendClearNativeTouchSequence(notifier.SaveObserver()); + return NS_OK; +} + +void PuppetWidget::SetConfirmedTargetAPZC( + uint64_t aInputBlockId, + const nsTArray<ScrollableLayerGuid>& aTargets) const { + if (mBrowserChild) { + mBrowserChild->SetTargetAPZC(aInputBlockId, aTargets); + } +} + +void PuppetWidget::UpdateZoomConstraints( + const uint32_t& aPresShellId, const ScrollableLayerGuid::ViewID& aViewId, + const Maybe<ZoomConstraints>& aConstraints) { + if (mBrowserChild) { + mBrowserChild->DoUpdateZoomConstraints(aPresShellId, aViewId, aConstraints); + } +} + +bool PuppetWidget::AsyncPanZoomEnabled() const { + return mBrowserChild && mBrowserChild->AsyncPanZoomEnabled(); +} + +bool PuppetWidget::GetEditCommands(NativeKeyBindingsType aType, + const WidgetKeyboardEvent& aEvent, + nsTArray<CommandInt>& aCommands) { + // Validate the arguments. + if (NS_WARN_IF(!nsIWidget::GetEditCommands(aType, aEvent, aCommands))) { + return false; + } + if (NS_WARN_IF(!mBrowserChild)) { + return false; + } + mBrowserChild->RequestEditCommands(aType, aEvent, aCommands); + return true; +} + +LayerManager* PuppetWidget::GetLayerManager( + PLayerTransactionChild* aShadowManager, LayersBackend aBackendHint, + LayerManagerPersistence aPersistence) { + if (!mLayerManager) { + if (XRE_IsParentProcess()) { + // On the parent process there is no CompositorBridgeChild which confuses + // some layers code, so we use basic layers instead. Note that we create + // a non-retaining layer manager since we don't care about performance. + mLayerManager = new BasicLayerManager(BasicLayerManager::BLM_OFFSCREEN); + return mLayerManager; + } + + // If we know for sure that the parent side of this BrowserChild is not + // connected to the compositor, we don't want to use a "remote" layer + // manager like WebRender or Client. Instead we use a Basic one which + // can do drawing in this process. + MOZ_ASSERT(!mBrowserChild || + mBrowserChild->IsLayersConnected() != Some(true)); + mLayerManager = new BasicLayerManager(this); + } + + return mLayerManager; +} + +bool PuppetWidget::CreateRemoteLayerManager( + const std::function<bool(LayerManager*)>& aInitializeFunc) { + RefPtr<LayerManager> lm; + MOZ_ASSERT(mBrowserChild); + if (mBrowserChild->GetCompositorOptions().UseWebRender()) { + lm = new WebRenderLayerManager(this); + } else { + lm = new ClientLayerManager(this); + } + + if (!aInitializeFunc(lm)) { + return false; + } + + // Force the old LM to self destruct, otherwise if the reference dangles we + // could fail to revoke the most recent transaction. We only want to replace + // it if we successfully create its successor because a partially initialized + // layer manager is worse than a fully initialized but shutdown layer manager. + DestroyLayerManager(); + mLayerManager = std::move(lm); + return true; +} + +nsresult PuppetWidget::RequestIMEToCommitComposition(bool aCancel) { + if (!mBrowserChild) { + return NS_ERROR_FAILURE; + } + + MOZ_ASSERT(!Destroyed()); + + // There must not be composition which is caused by the PuppetWidget instance. + if (NS_WARN_IF(!mNativeIMEContext.IsValid())) { + return NS_OK; + } + + // We've already requested to commit/cancel composition. + if (NS_WARN_IF(mIgnoreCompositionEvents)) { +#ifdef DEBUG + RefPtr<TextComposition> composition = + IMEStateManager::GetTextCompositionFor(this); + MOZ_ASSERT(!composition); +#endif // #ifdef DEBUG + return NS_OK; + } + + RefPtr<TextComposition> composition = + IMEStateManager::GetTextCompositionFor(this); + // This method shouldn't be called when there is no text composition instance. + if (NS_WARN_IF(!composition)) { + return NS_OK; + } + + MOZ_DIAGNOSTIC_ASSERT( + composition->IsRequestingCommitOrCancelComposition(), + "Requesting commit or cancel composition should be requested via " + "TextComposition instance"); + + bool isCommitted = false; + nsAutoString committedString; + if (NS_WARN_IF(!mBrowserChild->SendRequestIMEToCommitComposition( + aCancel, &isCommitted, &committedString))) { + return NS_ERROR_FAILURE; + } + + // If the composition wasn't committed synchronously, we need to wait async + // composition events for destroying the TextComposition instance. + if (!isCommitted) { + return NS_OK; + } + + // Dispatch eCompositionCommit event. + WidgetCompositionEvent compositionCommitEvent(true, eCompositionCommit, this); + InitEvent(compositionCommitEvent, nullptr); + compositionCommitEvent.mData = committedString; + nsEventStatus status = nsEventStatus_eIgnore; + DispatchEvent(&compositionCommitEvent, status); + +#ifdef DEBUG + RefPtr<TextComposition> currentComposition = + IMEStateManager::GetTextCompositionFor(this); + MOZ_ASSERT(!currentComposition); +#endif // #ifdef DEBUG + + // Ignore the following composition events until we receive new + // eCompositionStart event. + mIgnoreCompositionEvents = true; + + Unused << mBrowserChild->SendOnEventNeedingAckHandled( + eCompositionCommitRequestHandled); + + // NOTE: PuppetWidget might be destroyed already. + return NS_OK; +} + +// When this widget caches input context and currently managed by +// IMEStateManager, the cache is valid. +bool PuppetWidget::HaveValidInputContextCache() const { + return (mInputContext.mIMEState.mEnabled != IMEEnabled::Unknown && + IMEStateManager::GetWidgetForActiveInputContext() == this); +} + +nsRefreshDriver* PuppetWidget::GetTopLevelRefreshDriver() const { + if (!mBrowserChild) { + return nullptr; + } + + if (PresShell* presShell = mBrowserChild->GetTopLevelPresShell()) { + return presShell->GetRefreshDriver(); + } + + return nullptr; +} + +void PuppetWidget::SetInputContext(const InputContext& aContext, + const InputContextAction& aAction) { + mInputContext = aContext; + // Any widget instances cannot cache IME open state because IME open state + // can be changed by user but native IME may not notify us of changing the + // open state on some platforms. + mInputContext.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED; + if (!mBrowserChild) { + return; + } + mBrowserChild->SendSetInputContext(aContext, aAction); +} + +InputContext PuppetWidget::GetInputContext() { + // XXX Currently, we don't support retrieving IME open state from child + // process. + + // If the cache of input context is valid, we can avoid to use synchronous + // IPC. + if (HaveValidInputContextCache()) { + return mInputContext; + } + + NS_WARNING("PuppetWidget::GetInputContext() needs to retrieve it with IPC"); + + // Don't cache InputContext here because this process isn't managing IME + // state of the chrome widget. So, we cannot modify mInputContext when + // chrome widget is set to new context. + InputContext context; + if (mBrowserChild) { + mBrowserChild->SendGetInputContext(&context.mIMEState); + } + return context; +} + +NativeIMEContext PuppetWidget::GetNativeIMEContext() { + return mNativeIMEContext; +} + +nsresult PuppetWidget::NotifyIMEOfFocusChange( + const IMENotification& aIMENotification) { + MOZ_ASSERT(IMEStateManager::CanSendNotificationToWidget()); + + if (!mBrowserChild) { + return NS_ERROR_FAILURE; + } + + bool gotFocus = aIMENotification.mMessage == NOTIFY_IME_OF_FOCUS; + if (gotFocus) { + // When IME gets focus, we should initialize all information of the + // content. + if (NS_WARN_IF(!mContentCache.CacheAll(this, &aIMENotification))) { + return NS_ERROR_FAILURE; + } + } else { + // When IME loses focus, we don't need to store anything. + mContentCache.Clear(); + } + + mIMENotificationRequestsOfParent = + IMENotificationRequests(IMENotificationRequests::NOTIFY_ALL); + RefPtr<PuppetWidget> self = this; + mBrowserChild->SendNotifyIMEFocus(mContentCache, aIMENotification) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [self](IMENotificationRequests&& aRequests) { + self->mIMENotificationRequestsOfParent = aRequests; + if (TextEventDispatcher* dispatcher = + self->GetTextEventDispatcher()) { + dispatcher->OnWidgetChangeIMENotificationRequests(self); + } + }, + [self](mozilla::ipc::ResponseRejectReason&& aReason) { + NS_WARNING("SendNotifyIMEFocus got rejected."); + }); + + return NS_OK; +} + +nsresult PuppetWidget::NotifyIMEOfCompositionUpdate( + const IMENotification& aIMENotification) { + MOZ_ASSERT(IMEStateManager::CanSendNotificationToWidget()); + + if (NS_WARN_IF(!mBrowserChild)) { + return NS_ERROR_FAILURE; + } + + if (NS_WARN_IF(!mContentCache.CacheSelection(this, &aIMENotification))) { + return NS_ERROR_FAILURE; + } + mBrowserChild->SendNotifyIMECompositionUpdate(mContentCache, + aIMENotification); + return NS_OK; +} + +nsresult PuppetWidget::NotifyIMEOfTextChange( + const IMENotification& aIMENotification) { + MOZ_ASSERT(IMEStateManager::CanSendNotificationToWidget()); + MOZ_ASSERT(aIMENotification.mMessage == NOTIFY_IME_OF_TEXT_CHANGE, + "Passed wrong notification"); + + if (!mBrowserChild) { + return NS_ERROR_FAILURE; + } + + // FYI: text change notification is the first notification after + // a user operation changes the content. So, we need to modify + // the cache as far as possible here. + + if (NS_WARN_IF(!mContentCache.CacheText(this, &aIMENotification))) { + return NS_ERROR_FAILURE; + } + + // BrowserParent doesn't this this to cache. we don't send the notification + // if parent process doesn't request NOTIFY_TEXT_CHANGE. + if (mIMENotificationRequestsOfParent.WantTextChange()) { + mBrowserChild->SendNotifyIMETextChange(mContentCache, aIMENotification); + } else { + mBrowserChild->SendUpdateContentCache(mContentCache); + } + return NS_OK; +} + +nsresult PuppetWidget::NotifyIMEOfSelectionChange( + const IMENotification& aIMENotification) { + MOZ_ASSERT(IMEStateManager::CanSendNotificationToWidget()); + MOZ_ASSERT(aIMENotification.mMessage == NOTIFY_IME_OF_SELECTION_CHANGE, + "Passed wrong notification"); + if (!mBrowserChild) { + return NS_ERROR_FAILURE; + } + + // Note that selection change must be notified after text change if it occurs. + // Therefore, we don't need to query text content again here. + mContentCache.SetSelection( + this, aIMENotification.mSelectionChangeData.mOffset, + aIMENotification.mSelectionChangeData.Length(), + aIMENotification.mSelectionChangeData.mReversed, + aIMENotification.mSelectionChangeData.GetWritingMode()); + + mBrowserChild->SendNotifyIMESelection(mContentCache, aIMENotification); + + return NS_OK; +} + +nsresult PuppetWidget::NotifyIMEOfMouseButtonEvent( + const IMENotification& aIMENotification) { + MOZ_ASSERT(IMEStateManager::CanSendNotificationToWidget()); + if (!mBrowserChild) { + return NS_ERROR_FAILURE; + } + + bool consumedByIME = false; + if (!mBrowserChild->SendNotifyIMEMouseButtonEvent(aIMENotification, + &consumedByIME)) { + return NS_ERROR_FAILURE; + } + + return consumedByIME ? NS_SUCCESS_EVENT_CONSUMED : NS_OK; +} + +nsresult PuppetWidget::NotifyIMEOfPositionChange( + const IMENotification& aIMENotification) { + MOZ_ASSERT(IMEStateManager::CanSendNotificationToWidget()); + if (NS_WARN_IF(!mBrowserChild)) { + return NS_ERROR_FAILURE; + } + + if (NS_WARN_IF(!mContentCache.CacheEditorRect(this, &aIMENotification))) { + return NS_ERROR_FAILURE; + } + if (NS_WARN_IF(!mContentCache.CacheSelection(this, &aIMENotification))) { + return NS_ERROR_FAILURE; + } + if (mIMENotificationRequestsOfParent.WantPositionChanged()) { + mBrowserChild->SendNotifyIMEPositionChange(mContentCache, aIMENotification); + } else { + mBrowserChild->SendUpdateContentCache(mContentCache); + } + return NS_OK; +} + +struct CursorSurface { + UniquePtr<char[]> mData; + IntSize mSize; +}; + +void PuppetWidget::SetCursor(nsCursor aCursor, imgIContainer* aCursorImage, + uint32_t aHotspotX, uint32_t aHotspotY) { + if (!mBrowserChild) { + return; + } + + // Don't cache on windows, Windowless flash breaks this via async cursor + // updates. +#if !defined(XP_WIN) + if (!mUpdateCursor && mCursor == aCursor && mCustomCursor == aCursorImage && + (!aCursorImage || + (mCursorHotspotX == aHotspotX && mCursorHotspotY == aHotspotY))) { + return; + } +#endif + + bool hasCustomCursor = false; + UniquePtr<char[]> customCursorData; + size_t length = 0; + IntSize customCursorSize; + int32_t stride = 0; + auto format = SurfaceFormat::B8G8R8A8; + bool force = mUpdateCursor; + + if (aCursorImage) { + RefPtr<SourceSurface> surface = aCursorImage->GetFrame( + imgIContainer::FRAME_CURRENT, + imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY); + if (surface) { + if (RefPtr<DataSourceSurface> dataSurface = surface->GetDataSurface()) { + hasCustomCursor = true; + customCursorData = nsContentUtils::GetSurfaceData( + WrapNotNull(dataSurface), &length, &stride); + customCursorSize = dataSurface->GetSize(); + format = dataSurface->GetFormat(); + } + } + } + + mCustomCursor = nullptr; + + nsDependentCString cursorData(customCursorData ? customCursorData.get() : "", + length); + if (!mBrowserChild->SendSetCursor(aCursor, hasCustomCursor, cursorData, + customCursorSize.width, + customCursorSize.height, stride, format, + aHotspotX, aHotspotY, force)) { + return; + } + + mCursor = aCursor; + mCustomCursor = aCursorImage; + mCursorHotspotX = aHotspotX; + mCursorHotspotY = aHotspotY; + mUpdateCursor = false; +} + +void PuppetWidget::ClearCachedCursor() { + nsBaseWidget::ClearCachedCursor(); + mCustomCursor = nullptr; +} + +void PuppetWidget::SetChild(PuppetWidget* aChild) { + MOZ_ASSERT(this != aChild, "can't parent a widget to itself"); + MOZ_ASSERT(!aChild->mChild, + "fake widget 'hierarchy' only expected to have one level"); + + mChild = aChild; +} + +void PuppetWidget::PaintNowIfNeeded() { + if (IsVisible()) { + if (RefPtr<nsRefreshDriver> refreshDriver = GetTopLevelRefreshDriver()) { + refreshDriver->DoTick(); + } + } +} + +void PuppetWidget::OnMemoryPressure(layers::MemoryPressureReason aWhy) { + if (aWhy != MemoryPressureReason::LOW_MEMORY_ONGOING && !mVisible && + mLayerManager && XRE_IsContentProcess()) { + mLayerManager->ClearCachedResources(); + } +} + +bool PuppetWidget::NeedsPaint() { + // e10s popups are handled by the parent process, so never should be painted + // here + if (XRE_IsContentProcess() && + StaticPrefs::browser_tabs_remote_desktopbehavior() && + mWindowType == eWindowType_popup) { + NS_WARNING("Trying to paint an e10s popup in the child process!"); + return false; + } + + return mVisible; +} + +float PuppetWidget::GetDPI() { return mDPI; } + +double PuppetWidget::GetDefaultScaleInternal() { return mDefaultScale; } + +int32_t PuppetWidget::RoundsWidgetCoordinatesTo() { return mRounding; } + +void* PuppetWidget::GetNativeData(uint32_t aDataType) { + switch (aDataType) { + case NS_NATIVE_SHAREABLE_WINDOW: { + // NOTE: We can not have a tab child in some situations, such as when + // we're rendering to a fake widget for thumbnails. + if (!mBrowserChild) { + NS_WARNING("Need BrowserChild to get the nativeWindow from!"); + } + mozilla::WindowsHandle nativeData = 0; + if (mBrowserChild) { + nativeData = mBrowserChild->WidgetNativeData(); + } + return (void*)nativeData; + } + case NS_NATIVE_WINDOW: + case NS_NATIVE_WIDGET: + case NS_NATIVE_DISPLAY: + // These types are ignored (see bug 1183828, bug 1240891). + break; + case NS_RAW_NATIVE_IME_CONTEXT: + MOZ_CRASH("You need to call GetNativeIMEContext() instead"); + case NS_NATIVE_PLUGIN_PORT: + case NS_NATIVE_GRAPHIC: + case NS_NATIVE_SHELLWIDGET: + default: + NS_WARNING("nsWindow::GetNativeData called with bad value"); + break; + } + return nullptr; +} + +#if defined(XP_WIN) +void PuppetWidget::SetNativeData(uint32_t aDataType, uintptr_t aVal) { + switch (aDataType) { + case NS_NATIVE_CHILD_OF_SHAREABLE_WINDOW: + MOZ_ASSERT(mBrowserChild, "Need BrowserChild to send the message."); + if (mBrowserChild) { + mBrowserChild->SendSetNativeChildOfShareableWindow(aVal); + } + break; + default: + NS_WARNING("SetNativeData called with unsupported data type."); + } +} +#endif + +LayoutDeviceIntPoint PuppetWidget::GetChromeOffset() { + if (!GetOwningBrowserChild()) { + NS_WARNING("PuppetWidget without Tab does not have chrome information."); + return LayoutDeviceIntPoint(); + } + return GetOwningBrowserChild()->GetChromeOffset(); +} + +LayoutDeviceIntPoint PuppetWidget::WidgetToScreenOffset() { + auto positionRalativeToWindow = + WidgetToTopLevelWidgetTransform().TransformPoint(LayoutDevicePoint()); + + return GetWindowPosition() + + LayoutDeviceIntPoint::Round(positionRalativeToWindow); +} + +LayoutDeviceIntPoint PuppetWidget::GetWindowPosition() { + if (!GetOwningBrowserChild()) { + return LayoutDeviceIntPoint(); + } + + int32_t winX, winY, winW, winH; + NS_ENSURE_SUCCESS( + GetOwningBrowserChild()->GetDimensions(0, &winX, &winY, &winW, &winH), + LayoutDeviceIntPoint()); + return LayoutDeviceIntPoint(winX, winY) + + GetOwningBrowserChild()->GetClientOffset(); +} + +LayoutDeviceIntRect PuppetWidget::GetScreenBounds() { + return LayoutDeviceIntRect(WidgetToScreenOffset(), mBounds.Size()); +} + +uint32_t PuppetWidget::GetMaxTouchPoints() const { + return mBrowserChild ? mBrowserChild->MaxTouchPoints() : 0; +} + +void PuppetWidget::StartAsyncScrollbarDrag( + const AsyncDragMetrics& aDragMetrics) { + mBrowserChild->StartScrollbarDrag(aDragMetrics); +} + +PuppetScreen::PuppetScreen(void* nativeScreen) {} + +PuppetScreen::~PuppetScreen() = default; + +static ScreenConfiguration ScreenConfig() { + ScreenConfiguration config; + hal::GetCurrentScreenConfiguration(&config); + return config; +} + +nsIntSize PuppetWidget::GetScreenDimensions() { + nsIntRect r = ScreenConfig().rect(); + return nsIntSize(r.Width(), r.Height()); +} + +NS_IMETHODIMP +PuppetScreen::GetRect(int32_t* outLeft, int32_t* outTop, int32_t* outWidth, + int32_t* outHeight) { + nsIntRect r = ScreenConfig().rect(); + r.GetRect(outLeft, outTop, outWidth, outHeight); + return NS_OK; +} + +NS_IMETHODIMP +PuppetScreen::GetAvailRect(int32_t* outLeft, int32_t* outTop, int32_t* outWidth, + int32_t* outHeight) { + return GetRect(outLeft, outTop, outWidth, outHeight); +} + +NS_IMETHODIMP +PuppetScreen::GetPixelDepth(int32_t* aPixelDepth) { + *aPixelDepth = ScreenConfig().pixelDepth(); + return NS_OK; +} + +NS_IMETHODIMP +PuppetScreen::GetColorDepth(int32_t* aColorDepth) { + *aColorDepth = ScreenConfig().colorDepth(); + return NS_OK; +} + +NS_IMPL_ISUPPORTS(PuppetScreenManager, nsIScreenManager) + +PuppetScreenManager::PuppetScreenManager() { + mOneScreen = new PuppetScreen(nullptr); +} + +PuppetScreenManager::~PuppetScreenManager() = default; + +NS_IMETHODIMP +PuppetScreenManager::GetPrimaryScreen(nsIScreen** outScreen) { + NS_IF_ADDREF(*outScreen = mOneScreen.get()); + return NS_OK; +} + +NS_IMETHODIMP +PuppetScreenManager::GetTotalScreenPixels(int64_t* aTotalScreenPixels) { + MOZ_ASSERT(aTotalScreenPixels); + if (mOneScreen) { + int32_t x, y, width, height; + x = y = width = height = 0; + mOneScreen->GetRect(&x, &y, &width, &height); + *aTotalScreenPixels = width * height; + } else { + *aTotalScreenPixels = 0; + } + return NS_OK; +} + +NS_IMETHODIMP +PuppetScreenManager::ScreenForRect(int32_t inLeft, int32_t inTop, + int32_t inWidth, int32_t inHeight, + nsIScreen** outScreen) { + return GetPrimaryScreen(outScreen); +} + +ScreenIntMargin PuppetWidget::GetSafeAreaInsets() const { + return mSafeAreaInsets; +} + +void PuppetWidget::UpdateSafeAreaInsets( + const ScreenIntMargin& aSafeAreaInsets) { + mSafeAreaInsets = aSafeAreaInsets; +} + +nsIWidgetListener* PuppetWidget::GetCurrentWidgetListener() { + if (!mPreviouslyAttachedWidgetListener || !mAttachedWidgetListener) { + return mAttachedWidgetListener; + } + + if (mAttachedWidgetListener->GetView()->IsPrimaryFramePaintSuppressed()) { + return mPreviouslyAttachedWidgetListener; + } + + return mAttachedWidgetListener; +} + +void PuppetWidget::ZoomToRect(const uint32_t& aPresShellId, + const ScrollableLayerGuid::ViewID& aViewId, + const CSSRect& aRect, const uint32_t& aFlags) { + if (!mBrowserChild) { + return; + } + + mBrowserChild->ZoomToRect(aPresShellId, aViewId, aRect, aFlags); +} + +void PuppetWidget::LookUpDictionary( + const nsAString& aText, const nsTArray<mozilla::FontRange>& aFontRangeArray, + const bool aIsVertical, const LayoutDeviceIntPoint& aPoint) { + if (!mBrowserChild) { + return; + } + + mBrowserChild->SendLookUpDictionary(nsString(aText), aFontRangeArray, + aIsVertical, aPoint); +} + +bool PuppetWidget::HasPendingInputEvent() { + if (!mBrowserChild) { + return false; + } + + bool ret = false; + + mBrowserChild->GetIPCChannel()->PeekMessages( + [&ret](const IPC::Message& aMsg) -> bool { + if (nsContentUtils::IsMessageInputEvent(aMsg)) { + ret = true; + return false; // Stop peeking. + } + return true; + }); + + return ret; +} + +void PuppetWidget::HandledWindowedPluginKeyEvent( + const NativeEventData& aKeyEventData, bool aIsConsumed) { + if (NS_WARN_IF(mKeyEventInPluginCallbacks.IsEmpty())) { + return; + } + nsCOMPtr<nsIKeyEventInPluginCallback> callback = + mKeyEventInPluginCallbacks[0]; + MOZ_ASSERT(callback); + mKeyEventInPluginCallbacks.RemoveElementAt(0); + callback->HandledWindowedPluginKeyEvent(aKeyEventData, aIsConsumed); +} + +nsresult PuppetWidget::OnWindowedPluginKeyEvent( + const NativeEventData& aKeyEventData, + nsIKeyEventInPluginCallback* aCallback) { + if (NS_WARN_IF(!mBrowserChild)) { + return NS_ERROR_NOT_AVAILABLE; + } + if (NS_WARN_IF(!mBrowserChild->SendOnWindowedPluginKeyEvent(aKeyEventData))) { + return NS_ERROR_FAILURE; + } + mKeyEventInPluginCallbacks.AppendElement(aCallback); + return NS_SUCCESS_EVENT_HANDLED_ASYNCHRONOUSLY; +} + +// TextEventDispatcherListener + +NS_IMETHODIMP +PuppetWidget::NotifyIME(TextEventDispatcher* aTextEventDispatcher, + const IMENotification& aIMENotification) { + MOZ_ASSERT(aTextEventDispatcher == mTextEventDispatcher); + + // If there is different text event dispatcher listener for handling + // text event dispatcher, that means that native keyboard events and + // IME events are handled in this process. Therefore, we don't need + // to send any requests and notifications to the parent process. + if (mNativeTextEventDispatcherListener) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + switch (aIMENotification.mMessage) { + case REQUEST_TO_COMMIT_COMPOSITION: + return RequestIMEToCommitComposition(false); + case REQUEST_TO_CANCEL_COMPOSITION: + return RequestIMEToCommitComposition(true); + case NOTIFY_IME_OF_FOCUS: + case NOTIFY_IME_OF_BLUR: + return NotifyIMEOfFocusChange(aIMENotification); + case NOTIFY_IME_OF_SELECTION_CHANGE: + return NotifyIMEOfSelectionChange(aIMENotification); + case NOTIFY_IME_OF_TEXT_CHANGE: + return NotifyIMEOfTextChange(aIMENotification); + case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED: + return NotifyIMEOfCompositionUpdate(aIMENotification); + case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT: + return NotifyIMEOfMouseButtonEvent(aIMENotification); + case NOTIFY_IME_OF_POSITION_CHANGE: + return NotifyIMEOfPositionChange(aIMENotification); + default: + return NS_ERROR_NOT_IMPLEMENTED; + } + + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP_(IMENotificationRequests) +PuppetWidget::GetIMENotificationRequests() { + return IMENotificationRequests( + mIMENotificationRequestsOfParent.mWantUpdates | + IMENotificationRequests::NOTIFY_TEXT_CHANGE | + IMENotificationRequests::NOTIFY_POSITION_CHANGE); +} + +NS_IMETHODIMP_(void) +PuppetWidget::OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher) { + MOZ_ASSERT(aTextEventDispatcher == mTextEventDispatcher); +} + +NS_IMETHODIMP_(void) +PuppetWidget::WillDispatchKeyboardEvent( + TextEventDispatcher* aTextEventDispatcher, + WidgetKeyboardEvent& aKeyboardEvent, uint32_t aIndexOfKeypress, + void* aData) { + MOZ_ASSERT(aTextEventDispatcher == mTextEventDispatcher); +} + +nsresult PuppetWidget::SetSystemFont(const nsCString& aFontName) { + if (!mBrowserChild) { + return NS_ERROR_FAILURE; + } + + mBrowserChild->SendSetSystemFont(aFontName); + return NS_OK; +} + +nsresult PuppetWidget::GetSystemFont(nsCString& aFontName) { + if (!mBrowserChild) { + return NS_ERROR_FAILURE; + } + mBrowserChild->SendGetSystemFont(&aFontName); + return NS_OK; +} + +} // namespace widget +} // namespace mozilla |