michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "APZCTreeManager.h" michael@0: #include "Compositor.h" // for Compositor michael@0: #include "CompositorParent.h" // for CompositorParent, etc michael@0: #include "InputData.h" // for InputData, etc michael@0: #include "Layers.h" // for ContainerLayer, Layer, etc michael@0: #include "gfx3DMatrix.h" // for gfx3DMatrix michael@0: #include "mozilla/dom/Touch.h" // for Touch michael@0: #include "mozilla/gfx/Point.h" // for Point michael@0: #include "mozilla/layers/AsyncCompositionManager.h" // for ViewTransform michael@0: #include "mozilla/layers/AsyncPanZoomController.h" michael@0: #include "mozilla/MouseEvents.h" michael@0: #include "mozilla/mozalloc.h" // for operator new michael@0: #include "mozilla/TouchEvents.h" michael@0: #include "mozilla/Preferences.h" // for Preferences michael@0: #include "nsDebug.h" // for NS_WARNING michael@0: #include "nsPoint.h" // for nsIntPoint michael@0: #include "nsThreadUtils.h" // for NS_IsMainThread michael@0: #include "mozilla/gfx/Logging.h" // for gfx::TreeLog michael@0: #include "UnitTransforms.h" // for ViewAs michael@0: michael@0: #include // for std::stable_sort michael@0: michael@0: #define APZC_LOG(...) michael@0: // #define APZC_LOG(...) printf_stderr("APZC: " __VA_ARGS__) michael@0: michael@0: namespace mozilla { michael@0: namespace layers { michael@0: michael@0: float APZCTreeManager::sDPI = 160.0; michael@0: michael@0: // Pref that enables printing of the APZC tree for debugging. michael@0: static bool gPrintApzcTree = false; michael@0: michael@0: APZCTreeManager::APZCTreeManager() michael@0: : mTreeLock("APZCTreeLock"), michael@0: mTouchCount(0), michael@0: mApzcTreeLog("apzctree") michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: AsyncPanZoomController::InitializeGlobalState(); michael@0: Preferences::AddBoolVarCache(&gPrintApzcTree, "apz.printtree", gPrintApzcTree); michael@0: mApzcTreeLog.ConditionOnPref(&gPrintApzcTree); michael@0: } michael@0: michael@0: APZCTreeManager::~APZCTreeManager() michael@0: { michael@0: } michael@0: michael@0: void michael@0: APZCTreeManager::GetAllowedTouchBehavior(WidgetInputEvent* aEvent, michael@0: nsTArray& aOutValues) michael@0: { michael@0: WidgetTouchEvent *touchEvent = aEvent->AsTouchEvent(); michael@0: michael@0: aOutValues.Clear(); michael@0: michael@0: for (size_t i = 0; i < touchEvent->touches.Length(); i++) { michael@0: // If aEvent wasn't transformed previously we might need to michael@0: // add transforming of the spt here. michael@0: mozilla::ScreenIntPoint spt; michael@0: spt.x = touchEvent->touches[i]->mRefPoint.x; michael@0: spt.y = touchEvent->touches[i]->mRefPoint.y; michael@0: michael@0: nsRefPtr apzc = GetTargetAPZC(spt); michael@0: aOutValues.AppendElement(apzc michael@0: ? apzc->GetAllowedTouchBehavior(spt) michael@0: : AllowedTouchBehavior::UNKNOWN); michael@0: } michael@0: } michael@0: michael@0: void michael@0: APZCTreeManager::SetAllowedTouchBehavior(const ScrollableLayerGuid& aGuid, michael@0: const nsTArray &aValues) michael@0: { michael@0: nsRefPtr apzc = GetTargetAPZC(aGuid); michael@0: if (apzc) { michael@0: apzc->SetAllowedTouchBehavior(aValues); michael@0: } michael@0: } michael@0: michael@0: void michael@0: APZCTreeManager::AssertOnCompositorThread() michael@0: { michael@0: Compositor::AssertOnCompositorThread(); michael@0: } michael@0: michael@0: /* Flatten the tree of APZC instances into the given nsTArray */ michael@0: static void michael@0: Collect(AsyncPanZoomController* aApzc, nsTArray< nsRefPtr >* aCollection) michael@0: { michael@0: if (aApzc) { michael@0: aCollection->AppendElement(aApzc); michael@0: Collect(aApzc->GetLastChild(), aCollection); michael@0: Collect(aApzc->GetPrevSibling(), aCollection); michael@0: } michael@0: } michael@0: michael@0: void michael@0: APZCTreeManager::UpdatePanZoomControllerTree(CompositorParent* aCompositor, Layer* aRoot, michael@0: bool aIsFirstPaint, uint64_t aFirstPaintLayersId) michael@0: { michael@0: AssertOnCompositorThread(); michael@0: michael@0: MonitorAutoLock lock(mTreeLock); michael@0: michael@0: // We do this business with collecting the entire tree into an array because otherwise michael@0: // it's very hard to determine which APZC instances need to be destroyed. In the worst michael@0: // case, there are two scenarios: (a) a layer with an APZC is removed from the layer michael@0: // tree and (b) a layer with an APZC is moved in the layer tree from one place to a michael@0: // completely different place. In scenario (a) we would want to destroy the APZC while michael@0: // walking the layer tree and noticing that the layer/APZC is no longer there. But if michael@0: // we do that then we run into a problem in scenario (b) because we might encounter that michael@0: // layer later during the walk. To handle both of these we have to 'remember' that the michael@0: // layer was not found, and then do the destroy only at the end of the tree walk after michael@0: // we are sure that the layer was removed and not just transplanted elsewhere. Doing that michael@0: // as part of a recursive tree walk is hard and so maintaining a list and removing michael@0: // APZCs that are still alive is much simpler. michael@0: nsTArray< nsRefPtr > apzcsToDestroy; michael@0: Collect(mRootApzc, &apzcsToDestroy); michael@0: mRootApzc = nullptr; michael@0: michael@0: if (aRoot) { michael@0: mApzcTreeLog << "[start]\n"; michael@0: UpdatePanZoomControllerTree(aCompositor, michael@0: aRoot, michael@0: // aCompositor is null in gtest scenarios michael@0: aCompositor ? aCompositor->RootLayerTreeId() : 0, michael@0: gfx3DMatrix(), nullptr, nullptr, michael@0: aIsFirstPaint, aFirstPaintLayersId, michael@0: &apzcsToDestroy); michael@0: mApzcTreeLog << "[end]\n"; michael@0: } michael@0: michael@0: for (size_t i = 0; i < apzcsToDestroy.Length(); i++) { michael@0: APZC_LOG("Destroying APZC at %p\n", apzcsToDestroy[i].get()); michael@0: apzcsToDestroy[i]->Destroy(); michael@0: } michael@0: } michael@0: michael@0: AsyncPanZoomController* michael@0: APZCTreeManager::UpdatePanZoomControllerTree(CompositorParent* aCompositor, michael@0: Layer* aLayer, uint64_t aLayersId, michael@0: gfx3DMatrix aTransform, michael@0: AsyncPanZoomController* aParent, michael@0: AsyncPanZoomController* aNextSibling, michael@0: bool aIsFirstPaint, uint64_t aFirstPaintLayersId, michael@0: nsTArray< nsRefPtr >* aApzcsToDestroy) michael@0: { michael@0: mTreeLock.AssertCurrentThreadOwns(); michael@0: michael@0: ContainerLayer* container = aLayer->AsContainerLayer(); michael@0: AsyncPanZoomController* apzc = nullptr; michael@0: mApzcTreeLog << aLayer->Name() << '\t'; michael@0: if (container) { michael@0: const FrameMetrics& metrics = container->GetFrameMetrics(); michael@0: if (metrics.IsScrollable()) { michael@0: const CompositorParent::LayerTreeState* state = CompositorParent::GetIndirectShadowTree(aLayersId); michael@0: if (state && state->mController.get()) { michael@0: // If we get here, aLayer is a scrollable container layer and somebody michael@0: // has registered a GeckoContentController for it, so we need to ensure michael@0: // it has an APZC instance to manage its scrolling. michael@0: michael@0: apzc = container->GetAsyncPanZoomController(); michael@0: michael@0: // If the content represented by the container layer has changed (which may michael@0: // be possible because of DLBI heuristics) then we don't want to keep using michael@0: // the same old APZC for the new content. Null it out so we run through the michael@0: // code to find another one or create one. michael@0: ScrollableLayerGuid guid(aLayersId, metrics); michael@0: if (apzc && !apzc->Matches(guid)) { michael@0: apzc = nullptr; michael@0: } michael@0: michael@0: // If the container doesn't have an APZC already, try to find one of our michael@0: // pre-existing ones that matches. In particular, if we find an APZC whose michael@0: // ScrollableLayerGuid is the same, then we know what happened is that the michael@0: // layout of the page changed causing the layer tree to be rebuilt, but the michael@0: // underlying content for which the APZC was originally created is still michael@0: // there. So it makes sense to pick up that APZC instance again and use it here. michael@0: if (apzc == nullptr) { michael@0: for (size_t i = 0; i < aApzcsToDestroy->Length(); i++) { michael@0: if (aApzcsToDestroy->ElementAt(i)->Matches(guid)) { michael@0: apzc = aApzcsToDestroy->ElementAt(i); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // The APZC we get off the layer may have been destroyed previously if the layer was inactive michael@0: // or omitted from the layer tree for whatever reason from a layers update. If it later comes michael@0: // back it will have a reference to a destroyed APZC and so we need to throw that out and make michael@0: // a new one. michael@0: bool newApzc = (apzc == nullptr || apzc->IsDestroyed()); michael@0: if (newApzc) { michael@0: apzc = new AsyncPanZoomController(aLayersId, this, state->mController, michael@0: AsyncPanZoomController::USE_GESTURE_DETECTOR); michael@0: apzc->SetCompositorParent(aCompositor); michael@0: apzc->SetCrossProcessCompositorParent(state->mCrossProcessParent); michael@0: } else { michael@0: // If there was already an APZC for the layer clear the tree pointers michael@0: // so that it doesn't continue pointing to APZCs that should no longer michael@0: // be in the tree. These pointers will get reset properly as we continue michael@0: // building the tree. Also remove it from the set of APZCs that are going michael@0: // to be destroyed, because it's going to remain active. michael@0: aApzcsToDestroy->RemoveElement(apzc); michael@0: apzc->SetPrevSibling(nullptr); michael@0: apzc->SetLastChild(nullptr); michael@0: } michael@0: APZC_LOG("Using APZC %p for layer %p with identifiers %lld %lld\n", apzc, aLayer, aLayersId, container->GetFrameMetrics().GetScrollId()); michael@0: michael@0: apzc->NotifyLayersUpdated(metrics, michael@0: aIsFirstPaint && (aLayersId == aFirstPaintLayersId)); michael@0: apzc->SetScrollHandoffParentId(container->GetScrollHandoffParentId()); michael@0: michael@0: // Use the composition bounds as the hit test region. michael@0: // Optionally, the GeckoContentController can provide a touch-sensitive michael@0: // region that constrains all frames associated with the controller. michael@0: // In this case we intersect the composition bounds with that region. michael@0: ParentLayerRect visible(metrics.mCompositionBounds); michael@0: CSSRect touchSensitiveRegion; michael@0: if (state->mController->GetTouchSensitiveRegion(&touchSensitiveRegion)) { michael@0: // Note: we assume here that touchSensitiveRegion is in the CSS pixels michael@0: // of our parent layer, which makes this coordinate conversion michael@0: // correct. michael@0: visible = visible.Intersect(touchSensitiveRegion michael@0: * metrics.mDevPixelsPerCSSPixel michael@0: * metrics.GetParentResolution()); michael@0: } michael@0: gfx3DMatrix transform; michael@0: gfx::To3DMatrix(aLayer->GetTransform(), transform); michael@0: michael@0: apzc->SetLayerHitTestData(visible, aTransform, transform); michael@0: APZC_LOG("Setting rect(%f %f %f %f) as visible region for APZC %p\n", visible.x, visible.y, michael@0: visible.width, visible.height, michael@0: apzc); michael@0: michael@0: mApzcTreeLog << "APZC " << guid michael@0: << "\tcb=" << visible michael@0: << "\tsr=" << container->GetFrameMetrics().mScrollableRect michael@0: << (aLayer->GetVisibleRegion().IsEmpty() ? "\tscrollinfo" : "") michael@0: << "\t" << container->GetFrameMetrics().GetContentDescription(); michael@0: michael@0: // Bind the APZC instance into the tree of APZCs michael@0: if (aNextSibling) { michael@0: aNextSibling->SetPrevSibling(apzc); michael@0: } else if (aParent) { michael@0: aParent->SetLastChild(apzc); michael@0: } else { michael@0: mRootApzc = apzc; michael@0: } michael@0: michael@0: // Let this apzc be the parent of other controllers when we recurse downwards michael@0: aParent = apzc; michael@0: michael@0: if (newApzc) { michael@0: if (apzc->IsRootForLayersId()) { michael@0: // If we just created a new apzc that is the root for its layers ID, then michael@0: // we need to update its zoom constraints which might have arrived before this michael@0: // was created michael@0: ZoomConstraints constraints; michael@0: if (state->mController->GetRootZoomConstraints(&constraints)) { michael@0: apzc->UpdateZoomConstraints(constraints); michael@0: } michael@0: } else { michael@0: // For an apzc that is not the root for its layers ID, we give it the michael@0: // same zoom constraints as its parent. This ensures that if e.g. michael@0: // user-scalable=no was specified, none of the APZCs allow double-tap michael@0: // to zoom. michael@0: apzc->UpdateZoomConstraints(apzc->GetParent()->GetZoomConstraints()); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: container->SetAsyncPanZoomController(apzc); michael@0: } michael@0: mApzcTreeLog << '\n'; michael@0: michael@0: // Accumulate the CSS transform between layers that have an APZC, but exclude any michael@0: // any layers that do have an APZC, and reset the accumulation at those layers. michael@0: if (apzc) { michael@0: aTransform = gfx3DMatrix(); michael@0: } else { michael@0: // Multiply child layer transforms on the left so they get applied first michael@0: gfx3DMatrix matrix; michael@0: gfx::To3DMatrix(aLayer->GetTransform(), matrix); michael@0: aTransform = matrix * aTransform; michael@0: } michael@0: michael@0: uint64_t childLayersId = (aLayer->AsRefLayer() ? aLayer->AsRefLayer()->GetReferentId() : aLayersId); michael@0: // If there's no APZC at this level, any APZCs for our child layers will michael@0: // have our siblings as siblings. michael@0: AsyncPanZoomController* next = apzc ? nullptr : aNextSibling; michael@0: for (Layer* child = aLayer->GetLastChild(); child; child = child->GetPrevSibling()) { michael@0: gfx::TreeAutoIndent indent(mApzcTreeLog); michael@0: next = UpdatePanZoomControllerTree(aCompositor, child, childLayersId, aTransform, aParent, next, michael@0: aIsFirstPaint, aFirstPaintLayersId, aApzcsToDestroy); michael@0: } michael@0: michael@0: // Return the APZC that should be the sibling of other APZCs as we continue michael@0: // moving towards the first child at this depth in the layer tree. michael@0: // If this layer doesn't have an APZC, we promote any APZCs in the subtree michael@0: // upwards. Otherwise we fall back to the aNextSibling that was passed in. michael@0: if (apzc) { michael@0: return apzc; michael@0: } michael@0: if (next) { michael@0: return next; michael@0: } michael@0: return aNextSibling; michael@0: } michael@0: michael@0: /*static*/ template void michael@0: ApplyTransform(gfx::PointTyped* aPoint, const gfx3DMatrix& aMatrix) michael@0: { michael@0: gfxPoint result = aMatrix.Transform(gfxPoint(aPoint->x, aPoint->y)); michael@0: aPoint->x = result.x; michael@0: aPoint->y = result.y; michael@0: } michael@0: michael@0: /*static*/ template void michael@0: ApplyTransform(gfx::IntPointTyped* aPoint, const gfx3DMatrix& aMatrix) michael@0: { michael@0: gfxPoint result = aMatrix.Transform(gfxPoint(aPoint->x, aPoint->y)); michael@0: aPoint->x = NS_lround(result.x); michael@0: aPoint->y = NS_lround(result.y); michael@0: } michael@0: michael@0: /*static*/ void michael@0: ApplyTransform(nsIntPoint* aPoint, const gfx3DMatrix& aMatrix) michael@0: { michael@0: gfxPoint result = aMatrix.Transform(gfxPoint(aPoint->x, aPoint->y)); michael@0: aPoint->x = NS_lround(result.x); michael@0: aPoint->y = NS_lround(result.y); michael@0: } michael@0: michael@0: nsEventStatus michael@0: APZCTreeManager::ReceiveInputEvent(const InputData& aEvent, michael@0: ScrollableLayerGuid* aOutTargetGuid) michael@0: { michael@0: nsEventStatus result = nsEventStatus_eIgnore; michael@0: gfx3DMatrix transformToApzc; michael@0: gfx3DMatrix transformToGecko; michael@0: switch (aEvent.mInputType) { michael@0: case MULTITOUCH_INPUT: { michael@0: const MultiTouchInput& multiTouchInput = aEvent.AsMultiTouchInput(); michael@0: if (multiTouchInput.mType == MultiTouchInput::MULTITOUCH_START) { michael@0: // MULTITOUCH_START input contains all active touches of the current michael@0: // session thus resetting mTouchCount. michael@0: mTouchCount = multiTouchInput.mTouches.Length(); michael@0: mApzcForInputBlock = GetTargetAPZC(ScreenPoint(multiTouchInput.mTouches[0].mScreenPoint)); michael@0: if (multiTouchInput.mTouches.Length() == 1) { michael@0: // If we have one touch point, this might be the start of a pan. michael@0: // Prepare for possible overscroll handoff. michael@0: BuildOverscrollHandoffChain(mApzcForInputBlock); michael@0: } michael@0: for (size_t i = 1; i < multiTouchInput.mTouches.Length(); i++) { michael@0: nsRefPtr apzc2 = GetTargetAPZC(ScreenPoint(multiTouchInput.mTouches[i].mScreenPoint)); michael@0: mApzcForInputBlock = CommonAncestor(mApzcForInputBlock.get(), apzc2.get()); michael@0: APZC_LOG("Using APZC %p as the common ancestor\n", mApzcForInputBlock.get()); michael@0: // For now, we only ever want to do pinching on the root APZC for a given layers id. So michael@0: // when we find the common ancestor of multiple points, also walk up to the root APZC. michael@0: mApzcForInputBlock = RootAPZCForLayersId(mApzcForInputBlock); michael@0: APZC_LOG("Using APZC %p as the root APZC for multi-touch\n", mApzcForInputBlock.get()); michael@0: } michael@0: michael@0: if (mApzcForInputBlock) { michael@0: // Cache transformToApzc so it can be used for future events in this block. michael@0: GetInputTransforms(mApzcForInputBlock, transformToApzc, transformToGecko); michael@0: mCachedTransformToApzcForInputBlock = transformToApzc; michael@0: } else { michael@0: // Reset the cached apz transform michael@0: mCachedTransformToApzcForInputBlock = gfx3DMatrix(); michael@0: } michael@0: } else if (mApzcForInputBlock) { michael@0: APZC_LOG("Re-using APZC %p as continuation of event block\n", mApzcForInputBlock.get()); michael@0: } michael@0: if (mApzcForInputBlock) { michael@0: mApzcForInputBlock->GetGuid(aOutTargetGuid); michael@0: // Use the cached transform to compute the point to send to the APZC. michael@0: // This ensures that the sequence of touch points an APZC sees in an michael@0: // input block are all in the same coordinate space. michael@0: transformToApzc = mCachedTransformToApzcForInputBlock; michael@0: MultiTouchInput inputForApzc(multiTouchInput); michael@0: for (size_t i = 0; i < inputForApzc.mTouches.Length(); i++) { michael@0: ApplyTransform(&(inputForApzc.mTouches[i].mScreenPoint), transformToApzc); michael@0: } michael@0: result = mApzcForInputBlock->ReceiveInputEvent(inputForApzc); michael@0: } michael@0: if (multiTouchInput.mType == MultiTouchInput::MULTITOUCH_CANCEL || michael@0: multiTouchInput.mType == MultiTouchInput::MULTITOUCH_END) { michael@0: if (mTouchCount >= multiTouchInput.mTouches.Length()) { michael@0: // MULTITOUCH_END input contains only released touches thus decrementing. michael@0: mTouchCount -= multiTouchInput.mTouches.Length(); michael@0: } else { michael@0: NS_WARNING("Got an unexpected touchend/touchcancel"); michael@0: mTouchCount = 0; michael@0: } michael@0: // If we have an mApzcForInputBlock and it's the end of the touch sequence michael@0: // then null it out so we don't keep a dangling reference and leak things. michael@0: if (mTouchCount == 0) { michael@0: mApzcForInputBlock = nullptr; michael@0: ClearOverscrollHandoffChain(); michael@0: } michael@0: } michael@0: break; michael@0: } case PINCHGESTURE_INPUT: { michael@0: const PinchGestureInput& pinchInput = aEvent.AsPinchGestureInput(); michael@0: nsRefPtr apzc = GetTargetAPZC(pinchInput.mFocusPoint); michael@0: if (apzc) { michael@0: apzc->GetGuid(aOutTargetGuid); michael@0: GetInputTransforms(apzc, transformToApzc, transformToGecko); michael@0: PinchGestureInput inputForApzc(pinchInput); michael@0: ApplyTransform(&(inputForApzc.mFocusPoint), transformToApzc); michael@0: result = apzc->ReceiveInputEvent(inputForApzc); michael@0: } michael@0: break; michael@0: } case TAPGESTURE_INPUT: { michael@0: const TapGestureInput& tapInput = aEvent.AsTapGestureInput(); michael@0: nsRefPtr apzc = GetTargetAPZC(ScreenPoint(tapInput.mPoint)); michael@0: if (apzc) { michael@0: apzc->GetGuid(aOutTargetGuid); michael@0: GetInputTransforms(apzc, transformToApzc, transformToGecko); michael@0: TapGestureInput inputForApzc(tapInput); michael@0: ApplyTransform(&(inputForApzc.mPoint), transformToApzc); michael@0: result = apzc->ReceiveInputEvent(inputForApzc); michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: already_AddRefed michael@0: APZCTreeManager::GetTouchInputBlockAPZC(const WidgetTouchEvent& aEvent) michael@0: { michael@0: ScreenPoint point = ScreenPoint(aEvent.touches[0]->mRefPoint.x, aEvent.touches[0]->mRefPoint.y); michael@0: nsRefPtr apzc = GetTargetAPZC(point); michael@0: if (aEvent.touches.Length() == 1) { michael@0: // If we have one touch point, this might be the start of a pan. michael@0: // Prepare for possible overscroll handoff. michael@0: BuildOverscrollHandoffChain(apzc); michael@0: } michael@0: for (size_t i = 1; i < aEvent.touches.Length(); i++) { michael@0: point = ScreenPoint(aEvent.touches[i]->mRefPoint.x, aEvent.touches[i]->mRefPoint.y); michael@0: nsRefPtr apzc2 = GetTargetAPZC(point); michael@0: apzc = CommonAncestor(apzc.get(), apzc2.get()); michael@0: APZC_LOG("Using APZC %p as the common ancestor\n", apzc.get()); michael@0: // For now, we only ever want to do pinching on the root APZC for a given layers id. So michael@0: // when we find the common ancestor of multiple points, also walk up to the root APZC. michael@0: apzc = RootAPZCForLayersId(apzc); michael@0: APZC_LOG("Using APZC %p as the root APZC for multi-touch\n", apzc.get()); michael@0: } michael@0: return apzc.forget(); michael@0: } michael@0: michael@0: nsEventStatus michael@0: APZCTreeManager::ProcessTouchEvent(WidgetTouchEvent& aEvent, michael@0: ScrollableLayerGuid* aOutTargetGuid) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: nsEventStatus ret = nsEventStatus_eIgnore; michael@0: if (!aEvent.touches.Length()) { michael@0: return ret; michael@0: } michael@0: if (aEvent.message == NS_TOUCH_START) { michael@0: // NS_TOUCH_START event contains all active touches of the current michael@0: // session thus resetting mTouchCount. michael@0: mTouchCount = aEvent.touches.Length(); michael@0: mApzcForInputBlock = GetTouchInputBlockAPZC(aEvent); michael@0: if (mApzcForInputBlock) { michael@0: // Cache apz transform so it can be used for future events in this block. michael@0: gfx3DMatrix transformToGecko; michael@0: GetInputTransforms(mApzcForInputBlock, mCachedTransformToApzcForInputBlock, transformToGecko); michael@0: } else { michael@0: // Reset the cached apz transform michael@0: mCachedTransformToApzcForInputBlock = gfx3DMatrix(); michael@0: } michael@0: } michael@0: michael@0: if (mApzcForInputBlock) { michael@0: mApzcForInputBlock->GetGuid(aOutTargetGuid); michael@0: // For computing the input for the APZC, used the cached transform. michael@0: // This ensures that the sequence of touch points an APZC sees in an michael@0: // input block are all in the same coordinate space. michael@0: gfx3DMatrix transformToApzc = mCachedTransformToApzcForInputBlock; michael@0: MultiTouchInput inputForApzc(aEvent); michael@0: for (size_t i = 0; i < inputForApzc.mTouches.Length(); i++) { michael@0: ApplyTransform(&(inputForApzc.mTouches[i].mScreenPoint), transformToApzc); michael@0: } michael@0: ret = mApzcForInputBlock->ReceiveInputEvent(inputForApzc); michael@0: michael@0: // For computing the event to pass back to Gecko, use the up-to-date transforms. michael@0: // This ensures that transformToApzc and transformToGecko are in sync michael@0: // (note that transformToGecko isn't cached). michael@0: gfx3DMatrix transformToGecko; michael@0: GetInputTransforms(mApzcForInputBlock, transformToApzc, transformToGecko); michael@0: gfx3DMatrix outTransform = transformToApzc * transformToGecko; michael@0: for (size_t i = 0; i < aEvent.touches.Length(); i++) { michael@0: ApplyTransform(&(aEvent.touches[i]->mRefPoint), outTransform); michael@0: } michael@0: } michael@0: // If we have an mApzcForInputBlock and it's the end of the touch sequence michael@0: // then null it out so we don't keep a dangling reference and leak things. michael@0: if (aEvent.message == NS_TOUCH_CANCEL || michael@0: aEvent.message == NS_TOUCH_END) { michael@0: if (mTouchCount >= aEvent.touches.Length()) { michael@0: // NS_TOUCH_END event contains only released touches thus decrementing. michael@0: mTouchCount -= aEvent.touches.Length(); michael@0: } else { michael@0: NS_WARNING("Got an unexpected touchend/touchcancel"); michael@0: mTouchCount = 0; michael@0: } michael@0: if (mTouchCount == 0) { michael@0: mApzcForInputBlock = nullptr; michael@0: ClearOverscrollHandoffChain(); michael@0: } michael@0: } michael@0: return ret; michael@0: } michael@0: michael@0: void michael@0: APZCTreeManager::TransformCoordinateToGecko(const ScreenIntPoint& aPoint, michael@0: LayoutDeviceIntPoint* aOutTransformedPoint) michael@0: { michael@0: MOZ_ASSERT(aOutTransformedPoint); michael@0: nsRefPtr apzc = GetTargetAPZC(aPoint); michael@0: if (apzc && aOutTransformedPoint) { michael@0: gfx3DMatrix transformToApzc; michael@0: gfx3DMatrix transformToGecko; michael@0: GetInputTransforms(apzc, transformToApzc, transformToGecko); michael@0: gfx3DMatrix outTransform = transformToApzc * transformToGecko; michael@0: aOutTransformedPoint->x = aPoint.x; michael@0: aOutTransformedPoint->y = aPoint.y; michael@0: ApplyTransform(aOutTransformedPoint, outTransform); michael@0: } michael@0: } michael@0: michael@0: nsEventStatus michael@0: APZCTreeManager::ProcessEvent(WidgetInputEvent& aEvent, michael@0: ScrollableLayerGuid* aOutTargetGuid) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // Transform the refPoint michael@0: nsRefPtr apzc = GetTargetAPZC(ScreenPoint(aEvent.refPoint.x, aEvent.refPoint.y)); michael@0: if (!apzc) { michael@0: return nsEventStatus_eIgnore; michael@0: } michael@0: apzc->GetGuid(aOutTargetGuid); michael@0: gfx3DMatrix transformToApzc; michael@0: gfx3DMatrix transformToGecko; michael@0: GetInputTransforms(apzc, transformToApzc, transformToGecko); michael@0: gfx3DMatrix outTransform = transformToApzc * transformToGecko; michael@0: ApplyTransform(&(aEvent.refPoint), outTransform); michael@0: return nsEventStatus_eIgnore; michael@0: } michael@0: michael@0: nsEventStatus michael@0: APZCTreeManager::ReceiveInputEvent(WidgetInputEvent& aEvent, michael@0: ScrollableLayerGuid* aOutTargetGuid) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: switch (aEvent.eventStructType) { michael@0: case NS_TOUCH_EVENT: { michael@0: WidgetTouchEvent& touchEvent = *aEvent.AsTouchEvent(); michael@0: return ProcessTouchEvent(touchEvent, aOutTargetGuid); michael@0: } michael@0: default: { michael@0: return ProcessEvent(aEvent, aOutTargetGuid); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: APZCTreeManager::ZoomToRect(const ScrollableLayerGuid& aGuid, michael@0: const CSSRect& aRect) michael@0: { michael@0: nsRefPtr apzc = GetTargetAPZC(aGuid); michael@0: if (apzc) { michael@0: apzc->ZoomToRect(aRect); michael@0: } michael@0: } michael@0: michael@0: void michael@0: APZCTreeManager::ContentReceivedTouch(const ScrollableLayerGuid& aGuid, michael@0: bool aPreventDefault) michael@0: { michael@0: nsRefPtr apzc = GetTargetAPZC(aGuid); michael@0: if (apzc) { michael@0: apzc->ContentReceivedTouch(aPreventDefault); michael@0: } michael@0: } michael@0: michael@0: void michael@0: APZCTreeManager::UpdateZoomConstraints(const ScrollableLayerGuid& aGuid, michael@0: const ZoomConstraints& aConstraints) michael@0: { michael@0: nsRefPtr apzc = GetTargetAPZC(aGuid); michael@0: // For a given layers id, non-root APZCs inherit the zoom constraints michael@0: // of their root. michael@0: if (apzc && apzc->IsRootForLayersId()) { michael@0: MonitorAutoLock lock(mTreeLock); michael@0: UpdateZoomConstraintsRecursively(apzc.get(), aConstraints); michael@0: } michael@0: } michael@0: michael@0: void michael@0: APZCTreeManager::UpdateZoomConstraintsRecursively(AsyncPanZoomController* aApzc, michael@0: const ZoomConstraints& aConstraints) michael@0: { michael@0: mTreeLock.AssertCurrentThreadOwns(); michael@0: michael@0: aApzc->UpdateZoomConstraints(aConstraints); michael@0: for (AsyncPanZoomController* child = aApzc->GetLastChild(); child; child = child->GetPrevSibling()) { michael@0: // We can have subtrees with their own layers id - leave those alone. michael@0: if (!child->IsRootForLayersId()) { michael@0: UpdateZoomConstraintsRecursively(child, aConstraints); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: APZCTreeManager::CancelAnimation(const ScrollableLayerGuid &aGuid) michael@0: { michael@0: nsRefPtr apzc = GetTargetAPZC(aGuid); michael@0: if (apzc) { michael@0: apzc->CancelAnimation(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: APZCTreeManager::ClearTree() michael@0: { michael@0: MonitorAutoLock lock(mTreeLock); michael@0: michael@0: // This can be done as part of a tree walk but it's easier to michael@0: // just re-use the Collect method that we need in other places. michael@0: // If this is too slow feel free to change it to a recursive walk. michael@0: nsTArray< nsRefPtr > apzcsToDestroy; michael@0: Collect(mRootApzc, &apzcsToDestroy); michael@0: for (size_t i = 0; i < apzcsToDestroy.Length(); i++) { michael@0: apzcsToDestroy[i]->Destroy(); michael@0: } michael@0: mRootApzc = nullptr; michael@0: } michael@0: michael@0: /** michael@0: * Transform a displacement from the screen coordinates of a source APZC to michael@0: * the screen coordinates of a target APZC. michael@0: * @param aTreeManager the tree manager for the APZC tree containing |aSource| michael@0: * and |aTarget| michael@0: * @param aSource the source APZC michael@0: * @param aTarget the target APZC michael@0: * @param aStartPoint the start point of the displacement michael@0: * @param aEndPoint the end point of the displacement michael@0: */ michael@0: static void michael@0: TransformDisplacement(APZCTreeManager* aTreeManager, michael@0: AsyncPanZoomController* aSource, michael@0: AsyncPanZoomController* aTarget, michael@0: ScreenPoint& aStartPoint, michael@0: ScreenPoint& aEndPoint) { michael@0: gfx3DMatrix transformToApzc; michael@0: gfx3DMatrix transformToGecko; // ignored michael@0: michael@0: // Convert start and end points to untransformed screen coordinates. michael@0: aTreeManager->GetInputTransforms(aSource, transformToApzc, transformToGecko); michael@0: ApplyTransform(&aStartPoint, transformToApzc.Inverse()); michael@0: ApplyTransform(&aEndPoint, transformToApzc.Inverse()); michael@0: michael@0: // Convert start and end points to aTarget's transformed screen coordinates. michael@0: aTreeManager->GetInputTransforms(aTarget, transformToApzc, transformToGecko); michael@0: ApplyTransform(&aStartPoint, transformToApzc); michael@0: ApplyTransform(&aEndPoint, transformToApzc); michael@0: } michael@0: michael@0: void michael@0: APZCTreeManager::DispatchScroll(AsyncPanZoomController* aPrev, ScreenPoint aStartPoint, ScreenPoint aEndPoint, michael@0: uint32_t aOverscrollHandoffChainIndex) michael@0: { michael@0: nsRefPtr next; michael@0: { michael@0: // Grab tree lock to protect mOverscrollHandoffChain from concurrent michael@0: // access from the input and compositor threads. michael@0: // Release it before calling TransformDisplacement() as that grabs the michael@0: // lock itself. michael@0: MonitorAutoLock lock(mTreeLock); michael@0: michael@0: // If we have reached the end of the overscroll handoff chain, there is michael@0: // nothing more to scroll, so we ignore the rest of the pan gesture. michael@0: if (aOverscrollHandoffChainIndex >= mOverscrollHandoffChain.length()) { michael@0: // Nothing more to scroll - ignore the rest of the pan gesture. michael@0: return; michael@0: } michael@0: michael@0: next = mOverscrollHandoffChain[aOverscrollHandoffChainIndex]; michael@0: } michael@0: michael@0: if (next == nullptr) michael@0: return; michael@0: michael@0: // Convert the start and end points from |aPrev|'s coordinate space to michael@0: // |next|'s coordinate space. Since |aPrev| may be the same as |next| michael@0: // (if |aPrev| is the APZC that is initiating the scroll and there is no michael@0: // scroll grabbing to grab the scroll from it), don't bother doing the michael@0: // transformations in that case. michael@0: if (next != aPrev) { michael@0: TransformDisplacement(this, aPrev, next, aStartPoint, aEndPoint); michael@0: } michael@0: michael@0: // Scroll |next|. If this causes overscroll, it will call DispatchScroll() michael@0: // again with an incremented index. michael@0: next->AttemptScroll(aStartPoint, aEndPoint, aOverscrollHandoffChainIndex); michael@0: } michael@0: michael@0: void michael@0: APZCTreeManager::HandOffFling(AsyncPanZoomController* aPrev, ScreenPoint aVelocity) michael@0: { michael@0: // Build the overscroll handoff chain. This is necessary because it is michael@0: // otherwise built on touch-start and cleared on touch-end, and a fling michael@0: // happens after touch-end. Note that, unlike DispatchScroll() which is michael@0: // called on every touch-move during overscroll panning, michael@0: // HandleFlingOverscroll() is only called once during a fling handoff, michael@0: // so it's not worth trying to avoid building the handoff chain here. michael@0: BuildOverscrollHandoffChain(aPrev); michael@0: michael@0: nsRefPtr next; // will be used outside monitor block michael@0: { michael@0: // Grab tree lock to protect mOverscrollHandoffChain from concurrent michael@0: // access from the input and compositor threads. michael@0: // Release it before calling GetInputTransforms() as that grabs the michael@0: // lock itself. michael@0: MonitorAutoLock lock(mTreeLock); michael@0: michael@0: // Find |aPrev| in the handoff chain. michael@0: uint32_t i; michael@0: for (i = 0; i < mOverscrollHandoffChain.length(); ++i) { michael@0: if (mOverscrollHandoffChain[i] == aPrev) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // Get the next APZC in the handoff chain, if any. michael@0: if (i + 1 < mOverscrollHandoffChain.length()) { michael@0: next = mOverscrollHandoffChain[i + 1]; michael@0: } michael@0: michael@0: // Clear the handoff chain so we don't maintain references to APZCs michael@0: // unnecessarily. michael@0: mOverscrollHandoffChain.clear(); michael@0: } michael@0: michael@0: // Nothing to hand off fling to. michael@0: if (next == nullptr) { michael@0: return; michael@0: } michael@0: michael@0: // The fling's velocity needs to be transformed from the screen coordinates michael@0: // of |aPrev| to the screen coordinates of |next|. To transform a velocity michael@0: // correctly, we need to convert it to a displacement. For now, we do this michael@0: // by anchoring it to a start point of (0, 0). michael@0: // TODO: For this to be correct in the presence of 3D transforms, we should michael@0: // use the end point of the touch that started the fling as the start point michael@0: // rather than (0, 0). michael@0: ScreenPoint startPoint; // (0, 0) michael@0: ScreenPoint endPoint = startPoint + aVelocity; michael@0: TransformDisplacement(this, aPrev, next, startPoint, endPoint); michael@0: ScreenPoint transformedVelocity = endPoint - startPoint; michael@0: michael@0: // Tell |next| to start a fling with the transformed velocity. michael@0: next->TakeOverFling(transformedVelocity); michael@0: } michael@0: michael@0: bool michael@0: APZCTreeManager::FlushRepaintsForOverscrollHandoffChain() michael@0: { michael@0: MonitorAutoLock lock(mTreeLock); // to access mOverscrollHandoffChain michael@0: if (mOverscrollHandoffChain.length() == 0) { michael@0: return false; michael@0: } michael@0: for (uint32_t i = 0; i < mOverscrollHandoffChain.length(); i++) { michael@0: nsRefPtr item = mOverscrollHandoffChain[i]; michael@0: if (item) { michael@0: item->FlushRepaintForOverscrollHandoff(); michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: APZCTreeManager::CanBePanned(AsyncPanZoomController* aApzc) michael@0: { michael@0: MonitorAutoLock lock(mTreeLock); // to access mOverscrollHandoffChain michael@0: michael@0: // Find |aApzc| in the handoff chain. michael@0: uint32_t i; michael@0: for (i = 0; i < mOverscrollHandoffChain.length(); ++i) { michael@0: if (mOverscrollHandoffChain[i] == aApzc) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // See whether any APZC in the handoff chain starting from |aApzc| michael@0: // has room to be panned. michael@0: for (uint32_t j = i; j < mOverscrollHandoffChain.length(); ++j) { michael@0: if (mOverscrollHandoffChain[j]->IsPannable()) { michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: APZCTreeManager::HitTestAPZC(const ScreenIntPoint& aPoint) michael@0: { michael@0: MonitorAutoLock lock(mTreeLock); michael@0: nsRefPtr target; michael@0: // The root may have siblings, so check those too michael@0: gfxPoint point(aPoint.x, aPoint.y); michael@0: for (AsyncPanZoomController* apzc = mRootApzc; apzc; apzc = apzc->GetPrevSibling()) { michael@0: target = GetAPZCAtPoint(apzc, point); michael@0: if (target) { michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: already_AddRefed michael@0: APZCTreeManager::GetTargetAPZC(const ScrollableLayerGuid& aGuid) michael@0: { michael@0: MonitorAutoLock lock(mTreeLock); michael@0: nsRefPtr target; michael@0: // The root may have siblings, check those too michael@0: for (AsyncPanZoomController* apzc = mRootApzc; apzc; apzc = apzc->GetPrevSibling()) { michael@0: target = FindTargetAPZC(apzc, aGuid); michael@0: if (target) { michael@0: break; michael@0: } michael@0: } michael@0: return target.forget(); michael@0: } michael@0: michael@0: struct CompareByScrollPriority michael@0: { michael@0: bool operator()(const nsRefPtr& a, const nsRefPtr& b) { michael@0: return a->HasScrollgrab() && !b->HasScrollgrab(); michael@0: } michael@0: }; michael@0: michael@0: already_AddRefed michael@0: APZCTreeManager::GetTargetAPZC(const ScreenPoint& aPoint) michael@0: { michael@0: MonitorAutoLock lock(mTreeLock); michael@0: nsRefPtr target; michael@0: // The root may have siblings, so check those too michael@0: gfxPoint point(aPoint.x, aPoint.y); michael@0: for (AsyncPanZoomController* apzc = mRootApzc; apzc; apzc = apzc->GetPrevSibling()) { michael@0: target = GetAPZCAtPoint(apzc, point); michael@0: if (target) { michael@0: break; michael@0: } michael@0: } michael@0: return target.forget(); michael@0: } michael@0: michael@0: void michael@0: APZCTreeManager::BuildOverscrollHandoffChain(const nsRefPtr& aInitialTarget) michael@0: { michael@0: // Scroll grabbing is a mechanism that allows content to specify that michael@0: // the initial target of a pan should be not the innermost scrollable michael@0: // frame at the touch point (which is what GetTargetAPZC finds), but michael@0: // something higher up in the tree. michael@0: // It's not sufficient to just find the initial target, however, as michael@0: // overscroll can be handed off to another APZC. Without scroll grabbing, michael@0: // handoff just occurs from child to parent. With scroll grabbing, the michael@0: // handoff order can be different, so we build a chain of APZCs in the michael@0: // order in which scroll will be handed off to them. michael@0: michael@0: // Grab tree lock to protect mOverscrollHandoffChain from concurrent michael@0: // access between the input and compositor threads. michael@0: MonitorAutoLock lock(mTreeLock); michael@0: michael@0: mOverscrollHandoffChain.clear(); michael@0: michael@0: // Build the chain. If there is a scroll parent link, we use that. This is michael@0: // needed to deal with scroll info layers, because they participate in handoff michael@0: // but do not follow the expected layer tree structure. If there are no michael@0: // scroll parent links we just walk up the tree to find the scroll parent. michael@0: AsyncPanZoomController* apzc = aInitialTarget; michael@0: while (apzc != nullptr) { michael@0: if (!mOverscrollHandoffChain.append(apzc)) { michael@0: NS_WARNING("Vector::append failed"); michael@0: mOverscrollHandoffChain.clear(); michael@0: return; michael@0: } michael@0: if (apzc->GetScrollHandoffParentId() == FrameMetrics::NULL_SCROLL_ID) { michael@0: if (!apzc->IsRootForLayersId()) { michael@0: // This probably indicates a bug or missed case in layout code michael@0: NS_WARNING("Found a non-root APZ with no handoff parent"); michael@0: } michael@0: apzc = apzc->GetParent(); michael@0: continue; michael@0: } michael@0: michael@0: // Find the AsyncPanZoomController instance with a matching layersId and michael@0: // the scroll id that matches apzc->GetScrollHandoffParentId(). To do this michael@0: // search the subtree with the same layersId for the apzc with the specified michael@0: // scroll id. michael@0: AsyncPanZoomController* scrollParent = nullptr; michael@0: AsyncPanZoomController* parent = apzc; michael@0: while (!parent->IsRootForLayersId()) { michael@0: parent = parent->GetParent(); michael@0: // While walking up to find the root of the subtree, if we encounter the michael@0: // handoff parent, we don't actually need to do the search so we can michael@0: // just abort here. michael@0: if (parent->GetGuid().mScrollId == apzc->GetScrollHandoffParentId()) { michael@0: scrollParent = parent; michael@0: break; michael@0: } michael@0: } michael@0: if (!scrollParent) { michael@0: scrollParent = FindTargetAPZC(parent, apzc->GetScrollHandoffParentId()); michael@0: } michael@0: apzc = scrollParent; michael@0: } michael@0: michael@0: // Now adjust the chain to account for scroll grabbing. Sorting is a bit michael@0: // of an overkill here, but scroll grabbing will likely be generalized michael@0: // to scroll priorities, so we might as well do it this way. michael@0: // The sorting being stable ensures that the relative order between michael@0: // non-scrollgrabbing APZCs remains child -> parent. michael@0: // (The relative order between scrollgrabbing APZCs will also remain michael@0: // child -> parent, though that's just an artefact of the implementation michael@0: // and users of 'scrollgrab' should not rely on this.) michael@0: std::stable_sort(mOverscrollHandoffChain.begin(), mOverscrollHandoffChain.end(), michael@0: CompareByScrollPriority()); michael@0: } michael@0: michael@0: /* Find the apzc in the subtree rooted at aApzc that has the same layers id as michael@0: aApzc, and that has the given scroll id. Generally this function should be called michael@0: with aApzc being the root of its layers id subtree. */ michael@0: AsyncPanZoomController* michael@0: APZCTreeManager::FindTargetAPZC(AsyncPanZoomController* aApzc, FrameMetrics::ViewID aScrollId) michael@0: { michael@0: mTreeLock.AssertCurrentThreadOwns(); michael@0: michael@0: if (aApzc->GetGuid().mScrollId == aScrollId) { michael@0: return aApzc; michael@0: } michael@0: for (AsyncPanZoomController* child = aApzc->GetLastChild(); child; child = child->GetPrevSibling()) { michael@0: if (child->GetGuid().mLayersId != aApzc->GetGuid().mLayersId) { michael@0: continue; michael@0: } michael@0: AsyncPanZoomController* match = FindTargetAPZC(child, aScrollId); michael@0: if (match) { michael@0: return match; michael@0: } michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: void michael@0: APZCTreeManager::ClearOverscrollHandoffChain() michael@0: { michael@0: MonitorAutoLock lock(mTreeLock); michael@0: mOverscrollHandoffChain.clear(); michael@0: } michael@0: michael@0: AsyncPanZoomController* michael@0: APZCTreeManager::FindTargetAPZC(AsyncPanZoomController* aApzc, const ScrollableLayerGuid& aGuid) michael@0: { michael@0: mTreeLock.AssertCurrentThreadOwns(); michael@0: michael@0: // This walks the tree in depth-first, reverse order, so that it encounters michael@0: // APZCs front-to-back on the screen. michael@0: for (AsyncPanZoomController* child = aApzc->GetLastChild(); child; child = child->GetPrevSibling()) { michael@0: AsyncPanZoomController* match = FindTargetAPZC(child, aGuid); michael@0: if (match) { michael@0: return match; michael@0: } michael@0: } michael@0: michael@0: if (aApzc->Matches(aGuid)) { michael@0: return aApzc; michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: AsyncPanZoomController* michael@0: APZCTreeManager::GetAPZCAtPoint(AsyncPanZoomController* aApzc, const gfxPoint& aHitTestPoint) michael@0: { michael@0: mTreeLock.AssertCurrentThreadOwns(); michael@0: michael@0: // The comments below assume there is a chain of layers L..R with L and P having APZC instances as michael@0: // explained in the comment on GetInputTransforms. This function will recurse with aApzc at L and P, and the michael@0: // comments explain what values are stored in the variables at these two levels. All the comments michael@0: // use standard matrix notation where the leftmost matrix in a multiplication is applied first. michael@0: michael@0: // ancestorUntransform takes points from aApzc's parent APZC's layer coordinates michael@0: // to aApzc's parent layer's layer coordinates. michael@0: // It is OC.Inverse() * NC.Inverse() * MC.Inverse() at recursion level for L, michael@0: // and RC.Inverse() * QC.Inverse() at recursion level for P. michael@0: gfx3DMatrix ancestorUntransform = aApzc->GetAncestorTransform().Inverse(); michael@0: michael@0: // Hit testing for this layer takes place in our parent layer coordinates, michael@0: // since the composition bounds (used to initialize the visible rect against michael@0: // which we hit test are in those coordinates). michael@0: gfxPoint hitTestPointForThisLayer = ancestorUntransform.ProjectPoint(aHitTestPoint); michael@0: APZC_LOG("Untransformed %f %f to transient coordinates %f %f for hit-testing APZC %p\n", michael@0: aHitTestPoint.x, aHitTestPoint.y, michael@0: hitTestPointForThisLayer.x, hitTestPointForThisLayer.y, aApzc); michael@0: michael@0: // childUntransform takes points from aApzc's parent APZC's layer coordinates michael@0: // to aApzc's layer coordinates (which are aApzc's children's ParentLayer coordinates). michael@0: // It is OC.Inverse() * NC.Inverse() * MC.Inverse() * LC.Inverse() * LA.Inverse() at L michael@0: // and RC.Inverse() * QC.Inverse() * PC.Inverse() * PA.Inverse() at P. michael@0: gfx3DMatrix childUntransform = ancestorUntransform michael@0: * aApzc->GetCSSTransform().Inverse() michael@0: * gfx3DMatrix(aApzc->GetCurrentAsyncTransform()).Inverse(); michael@0: gfxPoint hitTestPointForChildLayers = childUntransform.ProjectPoint(aHitTestPoint); michael@0: APZC_LOG("Untransformed %f %f to layer coordinates %f %f for APZC %p\n", michael@0: aHitTestPoint.x, aHitTestPoint.y, michael@0: hitTestPointForChildLayers.x, hitTestPointForChildLayers.y, aApzc); michael@0: michael@0: // This walks the tree in depth-first, reverse order, so that it encounters michael@0: // APZCs front-to-back on the screen. michael@0: for (AsyncPanZoomController* child = aApzc->GetLastChild(); child; child = child->GetPrevSibling()) { michael@0: AsyncPanZoomController* match = GetAPZCAtPoint(child, hitTestPointForChildLayers); michael@0: if (match) { michael@0: return match; michael@0: } michael@0: } michael@0: if (aApzc->VisibleRegionContains(ViewAs(hitTestPointForThisLayer))) { michael@0: APZC_LOG("Successfully matched untransformed point %f %f to visible region for APZC %p\n", michael@0: hitTestPointForThisLayer.x, hitTestPointForThisLayer.y, aApzc); michael@0: return aApzc; michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: /* This function sets the aTransformToApzcOut and aTransformToGeckoOut out-parameters michael@0: to some useful transformations that input events may need applied. This is best michael@0: illustrated with an example. Consider a chain of layers, L, M, N, O, P, Q, R. Layer L michael@0: is the layer that corresponds to the argument |aApzc|, and layer R is the root michael@0: of the layer tree. Layer M is the parent of L, N is the parent of M, and so on. michael@0: When layer L is displayed to the screen by the compositor, the set of transforms that michael@0: are applied to L are (in order from top to bottom): michael@0: michael@0: L's transient async transform (hereafter referred to as transform matrix LT) michael@0: L's nontransient async transform (hereafter referred to as transform matrix LN) michael@0: L's CSS transform (hereafter referred to as transform matrix LC) michael@0: M's transient async transform (hereafter referred to as transform matrix MT) michael@0: M's nontransient async transform (hereafter referred to as transform matrix MN) michael@0: M's CSS transform (hereafter referred to as transform matrix MC) michael@0: ... michael@0: R's transient async transform (hereafter referred to as transform matrix RT) michael@0: R's nontransient async transform (hereafter referred to as transform matrix RN) michael@0: R's CSS transform (hereafter referred to as transform matrix RC) michael@0: michael@0: Also, for any layer, the async transform is the combination of its transient and non-transient michael@0: parts. That is, for any layer L: michael@0: LA === LT * LN michael@0: LA.Inverse() === LN.Inverse() * LT.Inverse() michael@0: michael@0: If we want user input to modify L's transient async transform, we have to first convert michael@0: user input from screen space to the coordinate space of L's transient async transform. Doing michael@0: this involves applying the following transforms (in order from top to bottom): michael@0: RC.Inverse() michael@0: RN.Inverse() michael@0: RT.Inverse() michael@0: ... michael@0: MC.Inverse() michael@0: MN.Inverse() michael@0: MT.Inverse() michael@0: LC.Inverse() michael@0: LN.Inverse() michael@0: This combined transformation is returned in the aTransformToApzcOut out-parameter. michael@0: michael@0: Next, if we want user inputs sent to gecko for event-dispatching, we will need to strip michael@0: out all of the async transforms that are involved in this chain. This is because async michael@0: transforms are stored only in the compositor and gecko does not account for them when michael@0: doing display-list-based hit-testing for event dispatching. michael@0: Furthermore, because these input events are processed by Gecko in a FIFO queue that michael@0: includes other things (specifically paint requests), it is possible that by time the michael@0: input event reaches gecko, it will have painted something else. Therefore, we need to michael@0: apply another transform to the input events to account for the possible disparity between michael@0: what we know gecko last painted and the last paint request we sent to gecko. Let this michael@0: transform be represented by LD, MD, ... RD. michael@0: Therefore, given a user input in screen space, the following transforms need to be applied michael@0: (in order from top to bottom): michael@0: RC.Inverse() michael@0: RN.Inverse() michael@0: RT.Inverse() michael@0: ... michael@0: MC.Inverse() michael@0: MN.Inverse() michael@0: MT.Inverse() michael@0: LC.Inverse() michael@0: LN.Inverse() michael@0: LT.Inverse() michael@0: LD michael@0: LC michael@0: MD michael@0: MC michael@0: ... michael@0: RD michael@0: RC michael@0: This sequence can be simplified and refactored to the following: michael@0: aTransformToApzcOut michael@0: LT.Inverse() michael@0: LD michael@0: LC michael@0: MD michael@0: MC michael@0: ... michael@0: RD michael@0: RC michael@0: Since aTransformToApzcOut is already one of the out-parameters, we set aTransformToGeckoOut michael@0: to the remaining transforms (LT.Inverse() * LD * ... * RC), so that the caller code can michael@0: combine it with aTransformToApzcOut to get the final transform required in this case. michael@0: michael@0: Note that for many of these layers, there will be no AsyncPanZoomController attached, and michael@0: so the async transform will be the identity transform. So, in the example above, if layers michael@0: L and P have APZC instances attached, MT, MN, MD, NT, NN, ND, OT, ON, OD, QT, QN, QD, RT, michael@0: RN and RD will be identity transforms. michael@0: Additionally, for space-saving purposes, each APZC instance stores its layer's individual michael@0: CSS transform and the accumulation of CSS transforms to its parent APZC. So the APZC for michael@0: layer L would store LC and (MC * NC * OC), and the layer P would store PC and (QC * RC). michael@0: The APZC instances track the last dispatched paint request and so are able to calculate LD and michael@0: PD using those internally stored values. michael@0: The APZCs also obviously have LT, LN, PT, and PN, so all of the above transformation combinations michael@0: required can be generated. michael@0: */ michael@0: void michael@0: APZCTreeManager::GetInputTransforms(AsyncPanZoomController *aApzc, gfx3DMatrix& aTransformToApzcOut, michael@0: gfx3DMatrix& aTransformToGeckoOut) michael@0: { michael@0: MonitorAutoLock lock(mTreeLock); michael@0: michael@0: // The comments below assume there is a chain of layers L..R with L and P having APZC instances as michael@0: // explained in the comment above. This function is called with aApzc at L, and the loop michael@0: // below performs one iteration, where parent is at P. The comments explain what values are stored michael@0: // in the variables at these two levels. All the comments use standard matrix notation where the michael@0: // leftmost matrix in a multiplication is applied first. michael@0: michael@0: // ancestorUntransform is OC.Inverse() * NC.Inverse() * MC.Inverse() michael@0: gfx3DMatrix ancestorUntransform = aApzc->GetAncestorTransform().Inverse(); michael@0: // asyncUntransform is LA.Inverse() michael@0: gfx3DMatrix asyncUntransform = gfx3DMatrix(aApzc->GetCurrentAsyncTransform()).Inverse(); michael@0: // nontransientAsyncTransform is LN michael@0: gfx3DMatrix nontransientAsyncTransform = aApzc->GetNontransientAsyncTransform(); michael@0: // transientAsyncUntransform is LT.Inverse() michael@0: gfx3DMatrix transientAsyncUntransform = nontransientAsyncTransform * asyncUntransform; michael@0: michael@0: // aTransformToApzcOut is initialized to OC.Inverse() * NC.Inverse() * MC.Inverse() * LC.Inverse() * LN.Inverse() michael@0: aTransformToApzcOut = ancestorUntransform * aApzc->GetCSSTransform().Inverse() * nontransientAsyncTransform.Inverse(); michael@0: // aTransformToGeckoOut is initialized to LT.Inverse() * LD * LC * MC * NC * OC michael@0: aTransformToGeckoOut = transientAsyncUntransform * aApzc->GetTransformToLastDispatchedPaint() * aApzc->GetCSSTransform() * aApzc->GetAncestorTransform(); michael@0: michael@0: for (AsyncPanZoomController* parent = aApzc->GetParent(); parent; parent = parent->GetParent()) { michael@0: // ancestorUntransform is updated to RC.Inverse() * QC.Inverse() when parent == P michael@0: ancestorUntransform = parent->GetAncestorTransform().Inverse(); michael@0: // asyncUntransform is updated to PA.Inverse() when parent == P michael@0: asyncUntransform = gfx3DMatrix(parent->GetCurrentAsyncTransform()).Inverse(); michael@0: // untransformSinceLastApzc is RC.Inverse() * QC.Inverse() * PC.Inverse() * PA.Inverse() michael@0: gfx3DMatrix untransformSinceLastApzc = ancestorUntransform * parent->GetCSSTransform().Inverse() * asyncUntransform; michael@0: michael@0: // aTransformToApzcOut is RC.Inverse() * QC.Inverse() * PC.Inverse() * PA.Inverse() * OC.Inverse() * NC.Inverse() * MC.Inverse() * LC.Inverse() * LN.Inverse() michael@0: aTransformToApzcOut = untransformSinceLastApzc * aTransformToApzcOut; michael@0: // aTransformToGeckoOut is LT.Inverse() * LD * LC * MC * NC * OC * PD * PC * QC * RC michael@0: aTransformToGeckoOut = aTransformToGeckoOut * parent->GetTransformToLastDispatchedPaint() * parent->GetCSSTransform() * parent->GetAncestorTransform(); michael@0: michael@0: // The above values for aTransformToApzcOut and aTransformToGeckoOut when parent == P match michael@0: // the required output as explained in the comment above this method. Note that any missing michael@0: // terms are guaranteed to be identity transforms. michael@0: } michael@0: } michael@0: michael@0: already_AddRefed michael@0: APZCTreeManager::CommonAncestor(AsyncPanZoomController* aApzc1, AsyncPanZoomController* aApzc2) michael@0: { michael@0: MonitorAutoLock lock(mTreeLock); michael@0: nsRefPtr ancestor; michael@0: michael@0: // If either aApzc1 or aApzc2 is null, min(depth1, depth2) will be 0 and this function michael@0: // will return null. michael@0: michael@0: // Calculate depth of the APZCs in the tree michael@0: int depth1 = 0, depth2 = 0; michael@0: for (AsyncPanZoomController* parent = aApzc1; parent; parent = parent->GetParent()) { michael@0: depth1++; michael@0: } michael@0: for (AsyncPanZoomController* parent = aApzc2; parent; parent = parent->GetParent()) { michael@0: depth2++; michael@0: } michael@0: michael@0: // At most one of the following two loops will be executed; the deeper APZC pointer michael@0: // will get walked up to the depth of the shallower one. michael@0: int minDepth = depth1 < depth2 ? depth1 : depth2; michael@0: while (depth1 > minDepth) { michael@0: depth1--; michael@0: aApzc1 = aApzc1->GetParent(); michael@0: } michael@0: while (depth2 > minDepth) { michael@0: depth2--; michael@0: aApzc2 = aApzc2->GetParent(); michael@0: } michael@0: michael@0: // Walk up the ancestor chains of both APZCs, always staying at the same depth for michael@0: // either APZC, and return the the first common ancestor encountered. michael@0: while (true) { michael@0: if (aApzc1 == aApzc2) { michael@0: ancestor = aApzc1; michael@0: break; michael@0: } michael@0: if (depth1 <= 0) { michael@0: break; michael@0: } michael@0: aApzc1 = aApzc1->GetParent(); michael@0: aApzc2 = aApzc2->GetParent(); michael@0: } michael@0: return ancestor.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: APZCTreeManager::RootAPZCForLayersId(AsyncPanZoomController* aApzc) michael@0: { michael@0: MonitorAutoLock lock(mTreeLock); michael@0: nsRefPtr apzc = aApzc; michael@0: while (apzc && !apzc->IsRootForLayersId()) { michael@0: apzc = apzc->GetParent(); michael@0: } michael@0: return apzc.forget(); michael@0: } michael@0: michael@0: } michael@0: }