Ejemplo n.º 1
0
TEST_F(APZCSnappingTester, Bug1265510)
{
  const char* layerTreeSyntax = "c(t)";
  nsIntRegion layerVisibleRegion[] = {
    nsIntRegion(IntRect(0, 0, 100, 100)),
    nsIntRegion(IntRect(0, 100, 100, 100))
  };
  root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
  SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 100, 200));
  SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 200));
  SetScrollHandoff(layers[1], root);

  ScrollSnapInfo snap;
  snap.mScrollSnapTypeY = NS_STYLE_SCROLL_SNAP_TYPE_MANDATORY;
  snap.mScrollSnapIntervalY = Some(100 * AppUnitsPerCSSPixel());

  ScrollMetadata metadata = root->GetScrollMetadata(0);
  metadata.SetSnapInfo(ScrollSnapInfo(snap));
  root->SetScrollMetadata(metadata);

  UniquePtr<ScopedLayerTreeRegistration> registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc);
  manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);

  TestAsyncPanZoomController* outer = ApzcOf(layers[0]);
  TestAsyncPanZoomController* inner = ApzcOf(layers[1]);

  // Position the mouse near the bottom of the outer frame and scroll by 60px.
  // (6 lines of 10px each). APZC will actually scroll to y=100 because of the
  // mandatory snap coordinate there.
  TimeStamp now = mcc->Time();
  SmoothWheel(manager, ScreenIntPoint(50, 80), ScreenPoint(0, 6), now);
  // Advance in 5ms increments until we've scrolled by 70px. At this point, the
  // closest snap point is y=100, and the inner frame should be under the mouse
  // cursor.
  while (outer->GetCurrentAsyncScrollOffset(AsyncPanZoomController::AsyncMode::NORMAL).y < 70) {
    mcc->AdvanceByMillis(5);
    outer->AdvanceAnimations(mcc->Time());
  }
  // Now do another wheel in a new transaction. This should start scrolling the
  // inner frame; we verify that it does by checking the inner scroll position.
  TimeStamp newTransactionTime = now + TimeDuration::FromMilliseconds(gfxPrefs::MouseWheelTransactionTimeoutMs() + 100);
  SmoothWheel(manager, ScreenIntPoint(50, 80), ScreenPoint(0, 6), newTransactionTime);
  inner->AdvanceAnimationsUntilEnd();
  EXPECT_LT(0.0f, inner->GetCurrentAsyncScrollOffset(AsyncPanZoomController::AsyncMode::NORMAL).y);

  // However, the outer frame should also continue to the snap point, otherwise
  // it is demonstrating incorrect behaviour by violating the mandatory snapping.
  outer->AdvanceAnimationsUntilEnd();
  EXPECT_EQ(100.0f, outer->GetCurrentAsyncScrollOffset(AsyncPanZoomController::AsyncMode::NORMAL).y);
}
Ejemplo n.º 2
0
int InteractiveUI::handle(int event) {
    static bool s_bDoingCamera = false;
    bool bRedraw = false;
    const bool bRightButton = Fl::event_state( FL_SHIFT ) || Fl::event_button3();

    switch (event) {
        case FL_SHORTCUT: {
            if ( interactiveUI->m_bInteractive->value() ) {
                scene.changeCamera().moveKeyboard();
                RedrawWindow();
            }
            break;
        } return 1;
        case FL_PUSH: {
            if ( bRightButton == false && interactiveUI->m_bIBar->value() ) {
                s_bDoingCamera = ibar.MouseDown( ScreenPoint( Fl::event_x(), Fl::event_y() ), Fl::event_state(FL_SHIFT) ? true : false );
            } else {
                s_bDoingCamera = false;
            }
            if ( s_bDoingCamera == false && bRightButton == false && interactiveUI->m_bWidget->value() ) {
                widget.MouseDown( scene.getCamera(), ScreenPoint( Fl::event_x(), Fl::event_y() ) );
            }
        } return 1;
        case FL_DRAG: {
            if ( s_bDoingCamera == true ) {
                ibar.MouseDrag( ScreenPoint( Fl::event_x(), Fl::event_y() ), Fl::event_state(FL_SHIFT) ? true : false );
            } else if ( s_bDoingCamera == false && bRightButton == false && interactiveUI->m_bWidget->value() ) {
                const Matrix4 mat = widget.MouseDrag( scene.getCamera(), ScreenPoint( Fl::event_x(), Fl::event_y() ) );
                if ( interactiveUI->m_bWidgetMove->value()) {
                    scene.moveSelectedNode( mat );
                }
            }
            RedrawWindow();
              } return 1;
        case FL_MOVE: 
            if ( interactiveUI->m_bIBar->value() ) 
                bIsMouseOverIBar = ibar.MouseMove( ScreenPoint( Fl::event_x(), Fl::event_y() ), bRedraw );
            if ( s_bDoingCamera == false && bRightButton == false && interactiveUI->m_bWidget->value() ) 
                if ( widget.MouseMove( scene.getCamera(), ScreenPoint( Fl::event_x(), Fl::event_y() ) ) )
                    bRedraw = true;

            if ( bRedraw == true )
                RedrawWindow();
            return 1;
        case FL_RELEASE: 
            if ( s_bDoingCamera == true && interactiveUI->m_bIBar->value() ) 
                ibar.MouseRelease( ScreenPoint( Fl::event_x(), Fl::event_y() ), Fl::event_state(FL_SHIFT) ? true : false );
            if ( s_bDoingCamera == false && bRightButton == false && interactiveUI->m_bWidget->value() ) 
                widget.MouseRelease( scene.getCamera(), ScreenPoint( Fl::event_x(), Fl::event_y() ) );

            if ( bRightButton == true ) {
                // ToDo: select object and orient widget
            }

            s_bDoingCamera = false;
            RedrawWindow();
            return 1;
    }
    return 0;
}
Ejemplo n.º 3
0
int MyBrush::handle(int event) {
    // OpenGL & FLTK's y axes are oriented differently
    const ScreenPoint pt = ScreenPoint( Fl::event_x(), screenHeight - 1 - Fl::event_y() );

    switch (event) {
        case FL_PUSH: {
            mouseDrag = pt;
            mouseDown = pt;

            if (brushUI->getToolType() == TOOL_POLYGON) {
                if (isMouseDown == true) {
                    polygon.push_back( mouseDrag );
                } else {
                    isMouseDown = true;
                    polygon.resize(0);
                    polygon.push_back( mouseDrag );
                }
            } else {
                isMouseDown = true;
                if (brushUI->getToolType() == TOOL_BRUSH)
                    Fl::add_timeout(0, draw_callback, this);
            }
            return 1;
        }
        case FL_DRAG: mouseDrag = pt; RedrawWindow(); return 1;
        case FL_MOVE: 
            mouseDrag = pt;
            if ( brushUI->getToolType() == TOOL_BRUSH || ( brushUI->getToolType() == TOOL_POLYGON && isMouseDown ) )
                RedrawWindow();
            return 1;
        case FL_RELEASE: {
            mouseDrag = pt;
             if (brushUI->getToolType() != TOOL_POLYGON) {
                isMouseDown = false;
                switch (brushUI->getToolType()) {
                    case TOOL_BRUSH: 
                        break;
                    case TOOL_LINE: 
                        drawLine( ); 
                        break;
                    case TOOL_CIRCLE: 
                        drawCircle( );
                        break;
                    case TOOL_FILTER: 
                        filterRegion( ); 
                        break;
                    default: break;
                }
             } else if ( Fl::event_button3() || Fl::event_state( FL_SHIFT ) ) {
                 isMouseDown = false;
                 drawPolygon();
             }
             RedrawWindow();
            return 1;
        }
        default: return 0;
    }
}
ClientTiledThebesLayer::ClientTiledThebesLayer(ClientLayerManager* const aManager)
  : ThebesLayer(aManager,
                static_cast<ClientLayer*>(MOZ_THIS_IN_INITIALIZER_LIST()))
  , mContentClient()
{
  MOZ_COUNT_CTOR(ClientTiledThebesLayer);
  mPaintData.mLastScrollOffset = ScreenPoint(0, 0);
  mPaintData.mFirstPaint = true;
}
Ejemplo n.º 5
0
TEST_F(APZEventRegionsTester, Bug1119497) {
  CreateBug1119497LayerTree();

  HitTestResult result;
  RefPtr<AsyncPanZoomController> hit = manager->GetTargetAPZC(ScreenPoint(50, 50), &result);
  // We should hit layers[2], so |result| will be HitLayer but there's no
  // actual APZC on layers[2], so it will be the APZC of the root layer.
  EXPECT_EQ(ApzcOf(layers[0]), hit.get());
  EXPECT_EQ(HitTestResult::HitLayer, result);
}
Ejemplo n.º 6
0
TEST_F(APZEventRegionsTester, Bug1119497) {
  CreateBug1119497LayerTree();

  gfx::CompositorHitTestInfo result;
  RefPtr<AsyncPanZoomController> hit =
      manager->GetTargetAPZC(ScreenPoint(50, 50), &result, nullptr);
  // We should hit layers[2], so |result| will be eVisibleToHitTest but there's
  // no actual APZC on layers[2], so it will be the APZC of the root layer.
  EXPECT_EQ(ApzcOf(layers[0]), hit.get());
  EXPECT_EQ(result, CompositorHitTestFlags::eVisibleToHitTest);
}
OverlayItemVisualSpec*
OverlayItemVisualSpec::allocate(const ImageSpec* backgroundImage,
                                const ScreenPoint& focusPoint)
{
   CORE_FUNCTION_PROLOGUE();
   
   return new OverlayItemVisualSpec(backgroundImage,
                                    focusPoint,
                                    ScreenPoint(0,0),
                                    WF_MAX_UINT32);
}
Ejemplo n.º 8
0
TEST_F(APZCGestureDetectorTester, LongPressInterruptedByWheel) {
  // Since the wheel block interrupted the long-press, we don't expect
  // any long-press notifications. However, this also shouldn't crash, which
  // is what it used to do.
  EXPECT_CALL(*mcc, HandleLongTap(_, _, _, _)).Times(0);

  uint64_t touchBlockId = 0;
  uint64_t wheelBlockId = 0;
  TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time(), &touchBlockId);
  mcc->AdvanceByMillis(10);
  Wheel(apzc, ScreenIntPoint(10, 10), ScreenPoint(0, -10), mcc->Time(), &wheelBlockId);
  EXPECT_NE(touchBlockId, wheelBlockId);
  mcc->AdvanceByMillis(1000);
}
Ejemplo n.º 9
0
TEST_F(APZEventRegionsTester, Obscuration) {
  CreateObscuringLayerTree();
  ScopedLayerTreeRegistration registration(manager, 0, root, mcc);

  manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);

  TestAsyncPanZoomController* parent = ApzcOf(layers[1]);
  TestAsyncPanZoomController* child = ApzcOf(layers[2]);

  ApzcPanNoFling(parent, mcc, 75, 25);

  HitTestResult result;
  RefPtr<AsyncPanZoomController> hit = manager->GetTargetAPZC(ScreenPoint(50, 75), &result);
  EXPECT_EQ(child, hit.get());
  EXPECT_EQ(HitTestResult::HitLayer, result);
}
Ejemplo n.º 10
0
TEST_F(APZCGestureDetectorTester, LongPressInterruptedByWheel) {
  // Since we try to allow concurrent input blocks of different types to
  // co-exist, the wheel block shouldn't interrupt the long-press detection.
  // But more importantly, this shouldn't crash, which is what it did at one
  // point in time.
  EXPECT_CALL(*mcc, HandleTap(TapType::eLongTap, _, _, _, _)).Times(1);

  uint64_t touchBlockId = 0;
  uint64_t wheelBlockId = 0;
  nsEventStatus status = TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time(), &touchBlockId);
  if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) {
    SetDefaultAllowedTouchBehavior(apzc, touchBlockId);
  }
  mcc->AdvanceByMillis(10);
  Wheel(apzc, ScreenIntPoint(10, 10), ScreenPoint(0, -10), mcc->Time(), &wheelBlockId);
  EXPECT_NE(touchBlockId, wheelBlockId);
  mcc->AdvanceByMillis(1000);
}
Ejemplo n.º 11
0
TEST_F(APZEventRegionsTester, Obscuration) {
  SCOPED_GFX_VAR(UseWebRender, bool, false);

  CreateObscuringLayerTree();
  ScopedLayerTreeRegistration registration(manager, LayersId{0}, root, mcc);

  manager->UpdateHitTestingTree(LayersId{0}, root, false, LayersId{0}, 0);

  RefPtr<TestAsyncPanZoomController> parent = ApzcOf(layers[1]);
  TestAsyncPanZoomController* child = ApzcOf(layers[2]);

  Pan(parent, 75, 25, PanOptions::NoFling);

  gfx::CompositorHitTestInfo result;
  RefPtr<AsyncPanZoomController> hit =
      manager->GetTargetAPZC(ScreenPoint(50, 75), &result, nullptr);
  EXPECT_EQ(child, hit.get());
  EXPECT_EQ(result, CompositorHitTestFlags::eVisibleToHitTest);
}
Ejemplo n.º 12
0
TEST_F(APZCGestureDetectorTester, TapTimeoutInterruptedByWheel) {
  // In this test, even though the wheel block comes right after the tap, the
  // tap should still be dispatched because it completes fully before the wheel
  // block arrived.
  EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1);

  // We make the APZC zoomable so the gesture detector needs to wait to
  // distinguish between tap and double-tap. During that timeout is when we
  // insert the wheel event.
  MakeApzcZoomable();

  uint64_t touchBlockId = 0;
  uint64_t wheelBlockId = 0;
  Tap(apzc, ScreenIntPoint(10, 10), TimeDuration::FromMilliseconds(100),
      nullptr, &touchBlockId);
  mcc->AdvanceByMillis(10);
  Wheel(apzc, ScreenIntPoint(10, 10), ScreenPoint(0, -10), mcc->Time(), &wheelBlockId);
  EXPECT_NE(touchBlockId, wheelBlockId);
  while (mcc->RunThroughDelayedTasks());
}
Ejemplo n.º 13
0
// A more involved hit testing test that involves css and async transforms.
TEST_F(APZHitTestingTester, HitTesting2) {
  SCOPED_GFX_PREF(APZVelocityBias, float, 0.0); // Velocity bias can cause extra repaint requests

  CreateHitTesting2LayerTree();
  ScopedLayerTreeRegistration registration(manager, 0, root, mcc);

  manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);

  // At this point, the following holds (all coordinates in screen pixels):
  // layers[0] has content from (0,0)-(200,200), clipped by composition bounds (0,0)-(100,100)
  // layers[1] has content from (10,10)-(90,90), clipped by composition bounds (10,10)-(50,50)
  // layers[2] has content from (20,60)-(100,100). no clipping as it's not a scrollable layer
  // layers[3] has content from (20,60)-(180,140), clipped by composition bounds (20,60)-(100,100)

  TestAsyncPanZoomController* apzcroot = ApzcOf(root);
  TestAsyncPanZoomController* apzc1 = ApzcOf(layers[1]);
  TestAsyncPanZoomController* apzc3 = ApzcOf(layers[3]);

  // Hit an area that's clearly on the root layer but not any of the child layers.
  RefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(75, 25));
  EXPECT_EQ(apzcroot, hit.get());
  EXPECT_EQ(ParentLayerPoint(75, 25), transformToApzc * ScreenPoint(75, 25));
  EXPECT_EQ(ScreenPoint(75, 25), transformToGecko * ParentLayerPoint(75, 25));

  // Hit an area on the root that would be on layers[3] if layers[2]
  // weren't transformed.
  // Note that if layers[2] were scrollable, then this would hit layers[2]
  // because its composition bounds would be at (10,60)-(50,100) (and the
  // scale-only transform that we set on layers[2] would be invalid because
  // it would place the layer into overscroll, as its composition bounds
  // start at x=10 but its content at x=20).
  hit = GetTargetAPZC(ScreenPoint(15, 75));
  EXPECT_EQ(apzcroot, hit.get());
  EXPECT_EQ(ParentLayerPoint(15, 75), transformToApzc * ScreenPoint(15, 75));
  EXPECT_EQ(ScreenPoint(15, 75), transformToGecko * ParentLayerPoint(15, 75));

  // Hit an area on layers[1].
  hit = GetTargetAPZC(ScreenPoint(25, 25));
  EXPECT_EQ(apzc1, hit.get());
  EXPECT_EQ(ParentLayerPoint(25, 25), transformToApzc * ScreenPoint(25, 25));
  EXPECT_EQ(ScreenPoint(25, 25), transformToGecko * ParentLayerPoint(25, 25));

  // Hit an area on layers[3].
  hit = GetTargetAPZC(ScreenPoint(25, 75));
  EXPECT_EQ(apzc3, hit.get());
  // transformToApzc should unapply layers[2]'s transform
  EXPECT_EQ(ParentLayerPoint(12.5, 75), transformToApzc * ScreenPoint(25, 75));
  // and transformToGecko should reapply it
  EXPECT_EQ(ScreenPoint(25, 75), transformToGecko * ParentLayerPoint(12.5, 75));

  // Hit an area on layers[3] that would be on the root if layers[2]
  // weren't transformed.
  hit = GetTargetAPZC(ScreenPoint(75, 75));
  EXPECT_EQ(apzc3, hit.get());
  // transformToApzc should unapply layers[2]'s transform
  EXPECT_EQ(ParentLayerPoint(37.5, 75), transformToApzc * ScreenPoint(75, 75));
  // and transformToGecko should reapply it
  EXPECT_EQ(ScreenPoint(75, 75), transformToGecko * ParentLayerPoint(37.5, 75));

  // Pan the root layer upward by 50 pixels.
  // This causes layers[1] to scroll out of view, and an async transform
  // of -50 to be set on the root layer.
  EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);

  // This first pan will move the APZC by 50 pixels, and dispatch a paint request.
  // Since this paint request is in the queue to Gecko, transformToGecko will
  // take it into account.
  ApzcPanNoFling(apzcroot, 100, 50);

  // Hit where layers[3] used to be. It should now hit the root.
  hit = GetTargetAPZC(ScreenPoint(75, 75));
  EXPECT_EQ(apzcroot, hit.get());
  // transformToApzc doesn't unapply the root's own async transform
  EXPECT_EQ(ParentLayerPoint(75, 75), transformToApzc * ScreenPoint(75, 75));
  // and transformToGecko unapplies it and then reapplies it, because by the
  // time the event being transformed reaches Gecko the new paint request will
  // have been handled.
  EXPECT_EQ(ScreenPoint(75, 75), transformToGecko * ParentLayerPoint(75, 75));

  // Hit where layers[1] used to be and where layers[3] should now be.
  hit = GetTargetAPZC(ScreenPoint(25, 25));
  EXPECT_EQ(apzc3, hit.get());
  // transformToApzc unapplies both layers[2]'s css transform and the root's
  // async transform
  EXPECT_EQ(ParentLayerPoint(12.5, 75), transformToApzc * ScreenPoint(25, 25));
  // transformToGecko reapplies both the css transform and the async transform
  // because we have already issued a paint request with it.
  EXPECT_EQ(ScreenPoint(25, 25), transformToGecko * ParentLayerPoint(12.5, 75));

  // This second pan will move the APZC by another 50 pixels.
  EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);
  ApzcPanNoFling(apzcroot, 100, 50);

  // Hit where layers[3] used to be. It should now hit the root.
  hit = GetTargetAPZC(ScreenPoint(75, 75));
  EXPECT_EQ(apzcroot, hit.get());
  // transformToApzc doesn't unapply the root's own async transform
  EXPECT_EQ(ParentLayerPoint(75, 75), transformToApzc * ScreenPoint(75, 75));
  // transformToGecko unapplies the full async transform of -100 pixels
  EXPECT_EQ(ScreenPoint(75, 75), transformToGecko * ParentLayerPoint(75, 75));

  // Hit where layers[1] used to be. It should now hit the root.
  hit = GetTargetAPZC(ScreenPoint(25, 25));
  EXPECT_EQ(apzcroot, hit.get());
  // transformToApzc doesn't unapply the root's own async transform
  EXPECT_EQ(ParentLayerPoint(25, 25), transformToApzc * ScreenPoint(25, 25));
  // transformToGecko unapplies the full async transform of -100 pixels
  EXPECT_EQ(ScreenPoint(25, 25), transformToGecko * ParentLayerPoint(25, 25));
}
Ejemplo n.º 14
0
nsEventStatus
APZInputBridge::ReceiveInputEvent(
    WidgetInputEvent& aEvent,
    ScrollableLayerGuid* aOutTargetGuid,
    uint64_t* aOutInputBlockId)
{
  APZThreadUtils::AssertOnControllerThread();

  // Initialize aOutInputBlockId to a sane value, and then later we overwrite
  // it if the input event goes into a block.
  if (aOutInputBlockId) {
    *aOutInputBlockId = 0;
  }

  switch (aEvent.mClass) {
    case eMouseEventClass:
    case eDragEventClass: {

      WidgetMouseEvent& mouseEvent = *aEvent.AsMouseEvent();

      // Note, we call this before having transformed the reference point.
      if (mouseEvent.IsReal()) {
        UpdateWheelTransaction(mouseEvent.mRefPoint, mouseEvent.mMessage);
      }

      if (WillHandleMouseEvent(mouseEvent)) {

        MouseInput input(mouseEvent);
        input.mOrigin = ScreenPoint(mouseEvent.mRefPoint.x, mouseEvent.mRefPoint.y);

        nsEventStatus status = ReceiveInputEvent(input, aOutTargetGuid, aOutInputBlockId);

        mouseEvent.mRefPoint.x = input.mOrigin.x;
        mouseEvent.mRefPoint.y = input.mOrigin.y;
        mouseEvent.mFlags.mHandledByAPZ = input.mHandledByAPZ;
        mouseEvent.mFocusSequenceNumber = input.mFocusSequenceNumber;
        return status;

      }

      ProcessUnhandledEvent(&mouseEvent.mRefPoint, aOutTargetGuid, &aEvent.mFocusSequenceNumber);
      return nsEventStatus_eIgnore;
    }
    case eTouchEventClass: {

      WidgetTouchEvent& touchEvent = *aEvent.AsTouchEvent();
      MultiTouchInput touchInput(touchEvent);
      nsEventStatus result = ReceiveInputEvent(touchInput, aOutTargetGuid, aOutInputBlockId);
      // touchInput was modified in-place to possibly remove some
      // touch points (if we are overscrolled), and the coordinates were
      // modified using the APZ untransform. We need to copy these changes
      // back into the WidgetInputEvent.
      touchEvent.mTouches.Clear();
      touchEvent.mTouches.SetCapacity(touchInput.mTouches.Length());
      for (size_t i = 0; i < touchInput.mTouches.Length(); i++) {
        *touchEvent.mTouches.AppendElement() =
          touchInput.mTouches[i].ToNewDOMTouch();
      }
      touchEvent.mFlags.mHandledByAPZ = touchInput.mHandledByAPZ;
      touchEvent.mFocusSequenceNumber = touchInput.mFocusSequenceNumber;
      return result;

    }
    case eWheelEventClass: {
      WidgetWheelEvent& wheelEvent = *aEvent.AsWheelEvent();

      if (Maybe<APZWheelAction> action = ActionForWheelEvent(&wheelEvent)) {

        ScrollWheelInput::ScrollMode scrollMode = ScrollWheelInput::SCROLLMODE_INSTANT;
        if (gfxPrefs::SmoothScrollEnabled() &&
            ((wheelEvent.mDeltaMode == dom::WheelEventBinding::DOM_DELTA_LINE &&
              gfxPrefs::WheelSmoothScrollEnabled()) ||
             (wheelEvent.mDeltaMode == dom::WheelEventBinding::DOM_DELTA_PAGE &&
              gfxPrefs::PageSmoothScrollEnabled())))
        {
          scrollMode = ScrollWheelInput::SCROLLMODE_SMOOTH;
        }

        WheelDeltaAdjustmentStrategy strategy =
          EventStateManager::GetWheelDeltaAdjustmentStrategy(wheelEvent);
        // Adjust the delta values of the wheel event if the current default
        // action is to horizontalize scrolling. I.e., deltaY values are set to
        // deltaX and deltaY and deltaZ values are set to 0.
        // If horizontalized, the delta values will be restored and its overflow
        // deltaX will become 0 when the WheelDeltaHorizontalizer instance is
        // being destroyed.
        WheelDeltaHorizontalizer horizontalizer(wheelEvent);
        if (WheelDeltaAdjustmentStrategy::eHorizontalize == strategy) {
          horizontalizer.Horizontalize();
        }

        // If the wheel event becomes no-op event, don't handle it as scroll.
        if (wheelEvent.mDeltaX || wheelEvent.mDeltaY) {
          ScreenPoint origin(wheelEvent.mRefPoint.x, wheelEvent.mRefPoint.y);
          ScrollWheelInput input(wheelEvent.mTime, wheelEvent.mTimeStamp, 0,
                                 scrollMode,
                                 ScrollWheelInput::DeltaTypeForDeltaMode(
                                                     wheelEvent.mDeltaMode),
                                 origin,
                                 wheelEvent.mDeltaX, wheelEvent.mDeltaY,
                                 wheelEvent.mAllowToOverrideSystemScrollSpeed,
                                 strategy);
          input.mAPZAction = action.value();

          // We add the user multiplier as a separate field, rather than premultiplying
          // it, because if the input is converted back to a WidgetWheelEvent, then
          // EventStateManager would apply the delta a second time. We could in theory
          // work around this by asking ESM to customize the event much sooner, and
          // then save the "mCustomizedByUserPrefs" bit on ScrollWheelInput - but for
          // now, this seems easier.
          EventStateManager::GetUserPrefsForWheelEvent(&wheelEvent,
            &input.mUserDeltaMultiplierX,
            &input.mUserDeltaMultiplierY);

          nsEventStatus status = ReceiveInputEvent(input, aOutTargetGuid, aOutInputBlockId);
          wheelEvent.mRefPoint.x = input.mOrigin.x;
          wheelEvent.mRefPoint.y = input.mOrigin.y;
          wheelEvent.mFlags.mHandledByAPZ = input.mHandledByAPZ;
          wheelEvent.mFocusSequenceNumber = input.mFocusSequenceNumber;
          return status;
        }
      }

      UpdateWheelTransaction(aEvent.mRefPoint, aEvent.mMessage);
      ProcessUnhandledEvent(&aEvent.mRefPoint, aOutTargetGuid, &aEvent.mFocusSequenceNumber);
      return nsEventStatus_eIgnore;

    }
    case eKeyboardEventClass: {
      WidgetKeyboardEvent& keyboardEvent = *aEvent.AsKeyboardEvent();

      KeyboardInput input(keyboardEvent);

      nsEventStatus status = ReceiveInputEvent(input, aOutTargetGuid, aOutInputBlockId);

      keyboardEvent.mFlags.mHandledByAPZ = input.mHandledByAPZ;
      keyboardEvent.mFocusSequenceNumber = input.mFocusSequenceNumber;
      return status;
    }
    default: {

      UpdateWheelTransaction(aEvent.mRefPoint, aEvent.mMessage);
      ProcessUnhandledEvent(&aEvent.mRefPoint, aOutTargetGuid, &aEvent.mFocusSequenceNumber);
      return nsEventStatus_eIgnore;

    }
  }

  MOZ_ASSERT_UNREACHABLE("Invalid WidgetInputEvent type.");
  return nsEventStatus_eConsumeNoDefault;
}
Ejemplo n.º 15
0
ScreenPoint AxisY::MakePoint(ScreenCoord aCoord) const
{
  return ScreenPoint(0, aCoord);
}
Ejemplo n.º 16
0
ScreenPoint AxisX::MakePoint(ScreenCoord aCoord) const
{
  return ScreenPoint(aCoord, 0);
}
Ejemplo n.º 17
0
nsEventStatus GestureEventListener::HandleInputTouchEnd()
{
    // We intentionally do not pass apzc return statuses up since
    // it may cause apzc stay in the touching state even after
    // gestures are completed (please see Bug 1013378 for reference).

    nsEventStatus rv = nsEventStatus_eIgnore;

    switch (mState) {
    case GESTURE_NONE:
        // GEL doesn't have a dedicated state for PANNING handled in APZC thus ignore.
        break;

    case GESTURE_FIRST_SINGLE_TOUCH_DOWN: {
        CancelLongTapTimeoutTask();
        CancelMaxTapTimeoutTask();
        nsEventStatus tapupStatus = mAsyncPanZoomController->HandleGestureEvent(
                                        CreateTapEvent(mLastTouchInput, TapGestureInput::TAPGESTURE_UP));
        if (tapupStatus == nsEventStatus_eIgnore) {
            SetState(GESTURE_FIRST_SINGLE_TOUCH_UP);
            CreateMaxTapTimeoutTask();
        } else {
            // We sent the tapup into content without waiting for a double tap
            SetState(GESTURE_NONE);
        }
        break;
    }

    case GESTURE_SECOND_SINGLE_TOUCH_DOWN: {
        CancelMaxTapTimeoutTask();
        SetState(GESTURE_NONE);
        mAsyncPanZoomController->HandleGestureEvent(
            CreateTapEvent(mLastTouchInput, TapGestureInput::TAPGESTURE_DOUBLE));
        break;
    }

    case GESTURE_FIRST_SINGLE_TOUCH_MAX_TAP_DOWN:
        CancelLongTapTimeoutTask();
        SetState(GESTURE_NONE);
        TriggerSingleTapConfirmedEvent();
        break;

    case GESTURE_LONG_TOUCH_DOWN: {
        SetState(GESTURE_NONE);
        mAsyncPanZoomController->HandleGestureEvent(
            CreateTapEvent(mLastTouchInput, TapGestureInput::TAPGESTURE_LONG_UP));
        break;
    }

    case GESTURE_MULTI_TOUCH_DOWN:
        if (mTouches.Length() < 2) {
            SetState(GESTURE_NONE);
        }
        break;

    case GESTURE_PINCH:
        if (mTouches.Length() < 2) {
            SetState(GESTURE_NONE);
            PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_END,
                                         mLastTouchInput.mTime,
                                         mLastTouchInput.mTimeStamp,
                                         ScreenPoint(),
                                         1.0f,
                                         1.0f,
                                         mLastTouchInput.modifiers);
            mAsyncPanZoomController->HandleGestureEvent(pinchEvent);
        }

        rv = nsEventStatus_eConsumeNoDefault;

        break;

    default:
        NS_WARNING("Unhandled state upon touch end");
        SetState(GESTURE_NONE);
        break;
    }

    return rv;
}
Ejemplo n.º 18
0
ScreenPoint GetCurrentFocus(const MultiTouchInput& aEvent)
{
  const ScreenIntPoint& firstTouch = aEvent.mTouches[0].mScreenPoint,
                       secondTouch = aEvent.mTouches[1].mScreenPoint;
  return ScreenPoint(firstTouch + secondTouch) / 2;
}
Ejemplo n.º 19
0
nsEventStatus GestureEventListener::HandleInputTouchEnd()
{
  nsEventStatus rv = nsEventStatus_eIgnore;

  switch (mState) {
  case GESTURE_NONE:
    // GEL doesn't have a dedicated state for PANNING handled in APZC thus ignore.
    break;

  case GESTURE_FIRST_SINGLE_TOUCH_DOWN: {
    CancelLongTapTimeoutTask();
    CancelMaxTapTimeoutTask();
    TapGestureInput tapEvent(TapGestureInput::TAPGESTURE_UP,
                             mLastTouchInput.mTime,
                             mLastTouchInput.mTouches[0].mScreenPoint,
                             mLastTouchInput.modifiers);
    nsEventStatus tapupStatus = mAsyncPanZoomController->HandleGestureEvent(tapEvent);
    if (tapupStatus == nsEventStatus_eIgnore) {
      SetState(GESTURE_FIRST_SINGLE_TOUCH_UP);
      CreateMaxTapTimeoutTask();
    } else {
      // We sent the tapup into content without waiting for a double tap
      SetState(GESTURE_NONE);
    }
    break;
  }

  case GESTURE_SECOND_SINGLE_TOUCH_DOWN: {
    CancelMaxTapTimeoutTask();
    SetState(GESTURE_NONE);
    TapGestureInput tapEvent(TapGestureInput::TAPGESTURE_DOUBLE,
                             mLastTouchInput.mTime,
                             mLastTouchInput.mTouches[0].mScreenPoint,
                             mLastTouchInput.modifiers);
    mAsyncPanZoomController->HandleGestureEvent(tapEvent);
    break;
  }

  case GESTURE_FIRST_SINGLE_TOUCH_MAX_TAP_DOWN:
    CancelLongTapTimeoutTask();
    SetState(GESTURE_NONE);
    TriggerSingleTapConfirmedEvent();
    break;

  case GESTURE_LONG_TOUCH_DOWN: {
    SetState(GESTURE_NONE);
    TapGestureInput tapEvent(TapGestureInput::TAPGESTURE_LONG_UP,
                             mLastTouchInput.mTime,
                             mLastTouchInput.mTouches[0].mScreenPoint,
                             mLastTouchInput.modifiers);
    mAsyncPanZoomController->HandleGestureEvent(tapEvent);
    break;
  }

  case GESTURE_MULTI_TOUCH_DOWN:
    if (mTouches.Length() < 2) {
      SetState(GESTURE_NONE);
    }
    break;

  case GESTURE_PINCH:
    if (mTouches.Length() < 2) {
      SetState(GESTURE_NONE);
      PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_END,
                                   mLastTouchInput.mTime,
                                   ScreenPoint(),
                                   1.0f,
                                   1.0f,
                                   mLastTouchInput.modifiers);
      mAsyncPanZoomController->HandleGestureEvent(pinchEvent);
    }
    rv = nsEventStatus_eConsumeNoDefault;
    break;

  default:
    NS_WARNING("Unhandled state upon touch end");
    SetState(GESTURE_NONE);
    break;
  }

  return rv;
}
nsEventStatus GestureEventListener::HandlePinchGestureEvent(const MultiTouchInput& aEvent, bool aClearTouches)
{
  nsEventStatus rv = nsEventStatus_eIgnore;

  if (mTouches.Length() > 1 && !aClearTouches) {
    const ScreenIntPoint& firstTouch = mTouches[0].mScreenPoint,
                         secondTouch = mTouches[mTouches.Length() - 1].mScreenPoint;
    ScreenPoint focusPoint = ScreenPoint(firstTouch + secondTouch) / 2;
    ScreenIntPoint delta = secondTouch - firstTouch;
    float currentSpan = float(NS_hypot(delta.x, delta.y));

    switch (mState) {
    case GESTURE_NONE:
      mPreviousSpan = currentSpan;
      mState = GESTURE_WAITING_PINCH;
      // Deliberately fall through. If the user pinched and took their fingers
      // off the screen such that they still had 1 left on it, we want there to
      // be no resistance. We should only reset |mSpanChange| once all fingers
      // are off the screen.
    case GESTURE_WAITING_PINCH: {
      mSpanChange += fabsf(currentSpan - mPreviousSpan);
      if (mSpanChange > PINCH_START_THRESHOLD) {
        PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_START,
                                     aEvent.mTime,
                                     focusPoint,
                                     currentSpan,
                                     currentSpan);

        mAsyncPanZoomController->HandleInputEvent(pinchEvent);

        mState = GESTURE_PINCH;
      }

      break;
    }
    case GESTURE_PINCH: {
      PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_SCALE,
                                   aEvent.mTime,
                                   focusPoint,
                                   currentSpan,
                                   mPreviousSpan);

      mAsyncPanZoomController->HandleInputEvent(pinchEvent);
      break;
    }
    default:
      // What?
      break;
    }

    mPreviousSpan = currentSpan;

    rv = nsEventStatus_eConsumeNoDefault;
  } else if (mState == GESTURE_PINCH) {
    PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_END,
                                 aEvent.mTime,
                                 mTouches[0].mScreenPoint,
                                 1.0f,
                                 1.0f);

    mAsyncPanZoomController->HandleInputEvent(pinchEvent);

    mState = GESTURE_NONE;

    rv = nsEventStatus_eConsumeNoDefault;
  }

  if (aClearTouches) {
    mTouches.Clear();
  }

  return rv;
}
Ejemplo n.º 21
0
// A simple hit testing test that doesn't involve any transforms on layers.
TEST_F(APZHitTestingTester, HitTesting1) {
  CreateHitTesting1LayerTree();
  ScopedLayerTreeRegistration registration(manager, 0, root, mcc);

  // No APZC attached so hit testing will return no APZC at (20,20)
  RefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(20, 20));
  TestAsyncPanZoomController* nullAPZC = nullptr;
  EXPECT_EQ(nullAPZC, hit.get());
  EXPECT_EQ(ScreenToParentLayerMatrix4x4(), transformToApzc);
  EXPECT_EQ(ParentLayerToScreenMatrix4x4(), transformToGecko);

  uint32_t paintSequenceNumber = 0;

  // Now we have a root APZC that will match the page
  SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID);
  manager->UpdateHitTestingTree(nullptr, root, false, 0, paintSequenceNumber++);
  hit = GetTargetAPZC(ScreenPoint(15, 15));
  EXPECT_EQ(ApzcOf(root), hit.get());
  // expect hit point at LayerIntPoint(15, 15)
  EXPECT_EQ(ParentLayerPoint(15, 15), transformToApzc * ScreenPoint(15, 15));
  EXPECT_EQ(ScreenPoint(15, 15), transformToGecko * ParentLayerPoint(15, 15));

  // Now we have a sub APZC with a better fit
  SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 1);
  manager->UpdateHitTestingTree(nullptr, root, false, 0, paintSequenceNumber++);
  EXPECT_NE(ApzcOf(root), ApzcOf(layers[3]));
  hit = GetTargetAPZC(ScreenPoint(25, 25));
  EXPECT_EQ(ApzcOf(layers[3]), hit.get());
  // expect hit point at LayerIntPoint(25, 25)
  EXPECT_EQ(ParentLayerPoint(25, 25), transformToApzc * ScreenPoint(25, 25));
  EXPECT_EQ(ScreenPoint(25, 25), transformToGecko * ParentLayerPoint(25, 25));

  // At this point, layers[4] obscures layers[3] at the point (15, 15) so
  // hitting there should hit the root APZC
  hit = GetTargetAPZC(ScreenPoint(15, 15));
  EXPECT_EQ(ApzcOf(root), hit.get());

  // Now test hit testing when we have two scrollable layers
  SetScrollableFrameMetrics(layers[4], FrameMetrics::START_SCROLL_ID + 2);
  manager->UpdateHitTestingTree(nullptr, root, false, 0, paintSequenceNumber++);
  hit = GetTargetAPZC(ScreenPoint(15, 15));
  EXPECT_EQ(ApzcOf(layers[4]), hit.get());
  // expect hit point at LayerIntPoint(15, 15)
  EXPECT_EQ(ParentLayerPoint(15, 15), transformToApzc * ScreenPoint(15, 15));
  EXPECT_EQ(ScreenPoint(15, 15), transformToGecko * ParentLayerPoint(15, 15));

  // Hit test ouside the reach of layer[3,4] but inside root
  hit = GetTargetAPZC(ScreenPoint(90, 90));
  EXPECT_EQ(ApzcOf(root), hit.get());
  // expect hit point at LayerIntPoint(90, 90)
  EXPECT_EQ(ParentLayerPoint(90, 90), transformToApzc * ScreenPoint(90, 90));
  EXPECT_EQ(ScreenPoint(90, 90), transformToGecko * ParentLayerPoint(90, 90));

  // Hit test ouside the reach of any layer
  hit = GetTargetAPZC(ScreenPoint(1000, 10));
  EXPECT_EQ(nullAPZC, hit.get());
  EXPECT_EQ(ScreenToParentLayerMatrix4x4(), transformToApzc);
  EXPECT_EQ(ParentLayerToScreenMatrix4x4(), transformToGecko);
  hit = GetTargetAPZC(ScreenPoint(-1000, 10));
  EXPECT_EQ(nullAPZC, hit.get());
  EXPECT_EQ(ScreenToParentLayerMatrix4x4(), transformToApzc);
  EXPECT_EQ(ParentLayerToScreenMatrix4x4(), transformToGecko);
}
Ejemplo n.º 22
0
nsEventStatus GestureEventListener::HandlePinchGestureEvent(const MultiTouchInput& aEvent)
{
  nsEventStatus rv = nsEventStatus_eIgnore;

  if (aEvent.mType == MultiTouchInput::MULTITOUCH_CANCEL) {
    mTouches.Clear();
    mState = GESTURE_NONE;
    return rv;
  }

  if (mTouches.Length() > 1) {
    const ScreenIntPoint& firstTouch = mTouches[0].mScreenPoint,
                         secondTouch = mTouches[1].mScreenPoint;
    ScreenPoint focusPoint = ScreenPoint(firstTouch + secondTouch) / 2;
    ScreenIntPoint delta = secondTouch - firstTouch;
    float currentSpan = float(NS_hypot(delta.x, delta.y));

    switch (mState) {
    case GESTURE_NONE:
      mPreviousSpan = currentSpan;
      mState = GESTURE_WAITING_PINCH;
      // Deliberately fall through. If the user pinched and took their fingers
      // off the screen such that they still had 1 left on it, we want there to
      // be no resistance. We should only reset |mSpanChange| once all fingers
      // are off the screen.
    case GESTURE_WAITING_PINCH: {
      mSpanChange += fabsf(currentSpan - mPreviousSpan);
      if (mSpanChange > PINCH_START_THRESHOLD) {
        PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_START,
                                     aEvent.mTime,
                                     focusPoint,
                                     currentSpan,
                                     currentSpan,
                                     aEvent.modifiers);

        mAsyncPanZoomController->HandleInputEvent(pinchEvent);

        mState = GESTURE_PINCH;
      }

      break;
    }
    case GESTURE_PINCH: {
      PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_SCALE,
                                   aEvent.mTime,
                                   focusPoint,
                                   currentSpan,
                                   mPreviousSpan,
                                   aEvent.modifiers);

      mAsyncPanZoomController->HandleInputEvent(pinchEvent);
      break;
    }
    default:
      // What?
      break;
    }

    mPreviousSpan = currentSpan;

    rv = nsEventStatus_eConsumeNoDefault;
  } else if (mState == GESTURE_PINCH) {
    PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_END,
                                 aEvent.mTime,
                                 ScreenPoint(),
                                 1.0f,
                                 1.0f,
                                 aEvent.modifiers);
    mAsyncPanZoomController->HandleInputEvent(pinchEvent);

    mState = GESTURE_NONE;

    // If the user left a finger on the screen, spoof a touch start event and
    // send it to APZC so that they can continue panning from that point.
    if (mTouches.Length() == 1) {
      MultiTouchInput touchEvent(MultiTouchInput::MULTITOUCH_START,
                                 aEvent.mTime,
                                 aEvent.modifiers);
      touchEvent.mTouches.AppendElement(mTouches[0]);
      mAsyncPanZoomController->HandleInputEvent(touchEvent);

      // The spoofed touch start will get back to GEL and make us enter the
      // GESTURE_WAITING_SINGLE_TAP state, but this isn't a new touch, so there
      // is no condition under which this touch should turn into any tap.
      mState = GESTURE_NONE;
    }

    rv = nsEventStatus_eConsumeNoDefault;
  } else if (mState == GESTURE_WAITING_PINCH) {
    mState = GESTURE_NONE;
  }

  return rv;
}
Ejemplo n.º 23
0
TEST_F(APZHitTestingTester, ComplexMultiLayerTree) {
  CreateComplexMultiLayerTree();
  ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
  manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);

  /* The layer tree looks like this:

                0
        |----|--+--|----|
        1    2     4    5
             |         /|\
             3        6 8 9
                      |
                      7

     Layers 1,2 have the same APZC
     Layers 4,6,8 have the same APZC
     Layer 7 has an APZC
     Layer 9 has an APZC
  */

  TestAsyncPanZoomController* nullAPZC = nullptr;
  // Ensure all the scrollable layers have an APZC
  EXPECT_FALSE(layers[0]->HasScrollableFrameMetrics());
  EXPECT_NE(nullAPZC, ApzcOf(layers[1]));
  EXPECT_NE(nullAPZC, ApzcOf(layers[2]));
  EXPECT_FALSE(layers[3]->HasScrollableFrameMetrics());
  EXPECT_NE(nullAPZC, ApzcOf(layers[4]));
  EXPECT_FALSE(layers[5]->HasScrollableFrameMetrics());
  EXPECT_NE(nullAPZC, ApzcOf(layers[6]));
  EXPECT_NE(nullAPZC, ApzcOf(layers[7]));
  EXPECT_NE(nullAPZC, ApzcOf(layers[8]));
  EXPECT_NE(nullAPZC, ApzcOf(layers[9]));
  // Ensure those that scroll together have the same APZCs
  EXPECT_EQ(ApzcOf(layers[1]), ApzcOf(layers[2]));
  EXPECT_EQ(ApzcOf(layers[4]), ApzcOf(layers[6]));
  EXPECT_EQ(ApzcOf(layers[8]), ApzcOf(layers[6]));
  // Ensure those that don't scroll together have different APZCs
  EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[4]));
  EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[7]));
  EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[9]));
  EXPECT_NE(ApzcOf(layers[4]), ApzcOf(layers[7]));
  EXPECT_NE(ApzcOf(layers[4]), ApzcOf(layers[9]));
  EXPECT_NE(ApzcOf(layers[7]), ApzcOf(layers[9]));
  // Ensure the APZC parent chains are set up correctly
  TestAsyncPanZoomController* layers1_2 = ApzcOf(layers[1]);
  TestAsyncPanZoomController* layers4_6_8 = ApzcOf(layers[4]);
  TestAsyncPanZoomController* layer7 = ApzcOf(layers[7]);
  TestAsyncPanZoomController* layer9 = ApzcOf(layers[9]);
  EXPECT_EQ(nullptr, layers1_2->GetParent());
  EXPECT_EQ(nullptr, layers4_6_8->GetParent());
  EXPECT_EQ(layers4_6_8, layer7->GetParent());
  EXPECT_EQ(nullptr, layer9->GetParent());
  // Ensure the hit-testing tree looks like the layer tree
  RefPtr<HitTestingTreeNode> root = manager->GetRootNode();
  RefPtr<HitTestingTreeNode> node5 = root->GetLastChild();
  RefPtr<HitTestingTreeNode> node4 = node5->GetPrevSibling();
  RefPtr<HitTestingTreeNode> node2 = node4->GetPrevSibling();
  RefPtr<HitTestingTreeNode> node1 = node2->GetPrevSibling();
  RefPtr<HitTestingTreeNode> node3 = node2->GetLastChild();
  RefPtr<HitTestingTreeNode> node9 = node5->GetLastChild();
  RefPtr<HitTestingTreeNode> node8 = node9->GetPrevSibling();
  RefPtr<HitTestingTreeNode> node6 = node8->GetPrevSibling();
  RefPtr<HitTestingTreeNode> node7 = node6->GetLastChild();
  EXPECT_EQ(nullptr, node1->GetPrevSibling());
  EXPECT_EQ(nullptr, node3->GetPrevSibling());
  EXPECT_EQ(nullptr, node6->GetPrevSibling());
  EXPECT_EQ(nullptr, node7->GetPrevSibling());
  EXPECT_EQ(nullptr, node1->GetLastChild());
  EXPECT_EQ(nullptr, node3->GetLastChild());
  EXPECT_EQ(nullptr, node4->GetLastChild());
  EXPECT_EQ(nullptr, node7->GetLastChild());
  EXPECT_EQ(nullptr, node8->GetLastChild());
  EXPECT_EQ(nullptr, node9->GetLastChild());

  RefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(25, 25));
  EXPECT_EQ(ApzcOf(layers[1]), hit.get());
  hit = GetTargetAPZC(ScreenPoint(275, 375));
  EXPECT_EQ(ApzcOf(layers[9]), hit.get());
  hit = GetTargetAPZC(ScreenPoint(250, 100));
  EXPECT_EQ(ApzcOf(layers[7]), hit.get());
}