gfx/layers/apz/test/gtest/TestEventRegions.cpp
author Ryan Hunt <rhunt@eqrion.net>
Thu, 01 Nov 2018 15:15:46 -0500
changeset 444295 ad0782d7c503f33cfb554d08dedc96287e1ed3f2
parent 438390 712cd59c243762c23248cc07c6fcf300fa2bf65a
child 448947 6f3709b3878117466168c40affa7bca0b60cf75b
permissions -rw-r--r--
Bug 1504220 - Move ScrollableLayerGuid, ViewID, ZoomConstraints from FrameMetrics.h r=botond This commit attempts to lower the pain of modifying FrameMetrics.h. It looks like most includes really only want ViewID or ScrollableLayerGuid, so this commit factors them out into a separate header. In the process FrameMetrics::ViewID is changed to ScrollableLayerGuid::ViewID, which personally seems like a better place for it now that we have RepaintRequest. Unfortunately that requires a lot of places to be updated. After this commit there are still a couple of major places that FrameMetrics is included. * nsDisplayList.h * nsIScrollableFrame.h * Layers.h Those are going to be more tricky or impossible to fix so they're not in this commit. Differential Revision: https://phabricator.services.mozilla.com/D10722

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "APZCTreeManagerTester.h"
#include "APZTestCommon.h"
#include "InputUtils.h"

class APZEventRegionsTester : public APZCTreeManagerTester {
protected:
  UniquePtr<ScopedLayerTreeRegistration> registration;
  TestAsyncPanZoomController* rootApzc;

  void CreateEventRegionsLayerTree1() {
    const char* layerTreeSyntax = "c(tt)";
    nsIntRegion layerVisibleRegions[] = {
      nsIntRegion(IntRect(0, 0, 200, 200)),     // root
      nsIntRegion(IntRect(0, 0, 100, 200)),     // left half
      nsIntRegion(IntRect(0, 100, 200, 100)),   // bottom half
    };
    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers);
    SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID);
    SetScrollableFrameMetrics(layers[1], ScrollableLayerGuid::START_SCROLL_ID + 1);
    SetScrollableFrameMetrics(layers[2], ScrollableLayerGuid::START_SCROLL_ID + 2);
    SetScrollHandoff(layers[1], root);
    SetScrollHandoff(layers[2], root);

    // Set up the event regions over a 200x200 area. The root layer has the
    // whole 200x200 as the hit region; layers[1] has the left half and
    // layers[2] has the bottom half. The bottom-left 100x100 area is also
    // in the d-t-c region for both layers[1] and layers[2] (but layers[2] is
    // on top so it gets the events by default if the main thread doesn't
    // respond).
    EventRegions regions(nsIntRegion(IntRect(0, 0, 200, 200)));
    root->SetEventRegions(regions);
    regions.mDispatchToContentHitRegion = nsIntRegion(IntRect(0, 100, 100, 100));
    regions.mHitRegion = nsIntRegion(IntRect(0, 0, 100, 200));
    layers[1]->SetEventRegions(regions);
    regions.mHitRegion = nsIntRegion(IntRect(0, 100, 200, 100));
    layers[2]->SetEventRegions(regions);

    registration = MakeUnique<ScopedLayerTreeRegistration>(manager, LayersId{0}, root, mcc);
    manager->UpdateHitTestingTree(LayersId{0}, root, false, LayersId{0}, 0);
    rootApzc = ApzcOf(root);
  }

  void CreateEventRegionsLayerTree2() {
    const char* layerTreeSyntax = "c(t)";
    nsIntRegion layerVisibleRegions[] = {
      nsIntRegion(IntRect(0, 0, 100, 500)),
      nsIntRegion(IntRect(0, 150, 100, 100)),
    };
    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers);
    SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID);

    // Set up the event regions so that the child thebes layer is positioned far
    // away from the scrolling container layer.
    EventRegions regions(nsIntRegion(IntRect(0, 0, 100, 100)));
    root->SetEventRegions(regions);
    regions.mHitRegion = nsIntRegion(IntRect(0, 150, 100, 100));
    layers[1]->SetEventRegions(regions);

    registration = MakeUnique<ScopedLayerTreeRegistration>(manager, LayersId{0}, root, mcc);
    manager->UpdateHitTestingTree(LayersId{0}, root, false, LayersId{0}, 0);
    rootApzc = ApzcOf(root);
  }

  void CreateObscuringLayerTree() {
    const char* layerTreeSyntax = "c(c(t)t)";
    // LayerID                     0 1 2 3
    // 0 is the root.
    // 1 is a parent scrollable layer.
    // 2 is a child scrollable layer.
    // 3 is the Obscurer, who ruins everything.
    nsIntRegion layerVisibleRegions[] = {
        // x coordinates are uninteresting
        nsIntRegion(IntRect(0,   0, 200, 200)),  // [0, 200]
        nsIntRegion(IntRect(0,   0, 200, 200)),  // [0, 200]
        nsIntRegion(IntRect(0, 100, 200,  50)),  // [100, 150]
        nsIntRegion(IntRect(0, 100, 200, 100))   // [100, 200]
    };
    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers);

    SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, CSSRect(0, 0, 200, 200));
    SetScrollableFrameMetrics(layers[1], ScrollableLayerGuid::START_SCROLL_ID + 1, CSSRect(0, 0, 200, 300));
    SetScrollableFrameMetrics(layers[2], ScrollableLayerGuid::START_SCROLL_ID + 2, CSSRect(0, 0, 200, 100));
    SetScrollHandoff(layers[2], layers[1]);
    SetScrollHandoff(layers[1], root);

    EventRegions regions(nsIntRegion(IntRect(0, 0, 200, 200)));
    root->SetEventRegions(regions);
    regions.mHitRegion = nsIntRegion(IntRect(0, 0, 200, 300));
    layers[1]->SetEventRegions(regions);
    regions.mHitRegion = nsIntRegion(IntRect(0, 100, 200, 100));
    layers[2]->SetEventRegions(regions);

    registration = MakeUnique<ScopedLayerTreeRegistration>(manager, LayersId{0}, root, mcc);
    manager->UpdateHitTestingTree(LayersId{0}, root, false, LayersId{0}, 0);
    rootApzc = ApzcOf(root);
  }

  void CreateBug1119497LayerTree() {
    const char* layerTreeSyntax = "c(tt)";
    // LayerID                     0 12
    // 0 is the root and has an APZC
    // 1 is behind 2 and has an APZC
    // 2 entirely covers 1 and should take all the input events, but has no APZC
    // so hits to 2 should go to to the root APZC
    nsIntRegion layerVisibleRegions[] = {
      nsIntRegion(IntRect(0, 0, 100, 100)),
      nsIntRegion(IntRect(0, 0, 100, 100)),
      nsIntRegion(IntRect(0, 0, 100, 100)),
    };
    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers);

    SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID);
    SetScrollableFrameMetrics(layers[1], ScrollableLayerGuid::START_SCROLL_ID + 1);

    registration = MakeUnique<ScopedLayerTreeRegistration>(manager, LayersId{0}, root, mcc);
    manager->UpdateHitTestingTree(LayersId{0}, root, false, LayersId{0}, 0);
  }

  void CreateBug1117712LayerTree() {
    const char* layerTreeSyntax = "c(c(t)t)";
    // LayerID                     0 1 2 3
    // 0 is the root
    // 1 is a container layer whose sole purpose to make a non-empty ancestor
    //   transform for 2, so that 2's screen-to-apzc and apzc-to-gecko
    //   transforms are different from 3's.
    // 2 is a small layer that is the actual target
    // 3 is a big layer obscuring 2 with a dispatch-to-content region
    nsIntRegion layerVisibleRegions[] = {
      nsIntRegion(IntRect(0, 0, 100, 100)),
      nsIntRegion(IntRect(0, 0, 0, 0)),
      nsIntRegion(IntRect(0, 0, 10, 10)),
      nsIntRegion(IntRect(0, 0, 100, 100)),
    };
    Matrix4x4 layerTransforms[] = {
      Matrix4x4(),
      Matrix4x4::Translation(50, 0, 0),
      Matrix4x4(),
      Matrix4x4(),
    };
    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, layerTransforms, lm, layers);

    SetScrollableFrameMetrics(layers[2], ScrollableLayerGuid::START_SCROLL_ID, CSSRect(0, 0, 10, 10));
    SetScrollableFrameMetrics(layers[3], ScrollableLayerGuid::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 100));
    SetScrollHandoff(layers[3], layers[2]);

    EventRegions regions(nsIntRegion(IntRect(0, 0, 10, 10)));
    layers[2]->SetEventRegions(regions);
    regions.mHitRegion = nsIntRegion(IntRect(0, 0, 100, 100));
    regions.mDispatchToContentHitRegion = nsIntRegion(IntRect(0, 0, 100, 100));
    layers[3]->SetEventRegions(regions);

    registration = MakeUnique<ScopedLayerTreeRegistration>(manager, LayersId{0}, root, mcc);
    manager->UpdateHitTestingTree(LayersId{0}, root, false, LayersId{0}, 0);
  }
};

TEST_F(APZEventRegionsTester, HitRegionImmediateResponse) {
  SCOPED_GFX_VAR(UseWebRender, bool, false);

  CreateEventRegionsLayerTree1();

  TestAsyncPanZoomController* root = ApzcOf(layers[0]);
  TestAsyncPanZoomController* left = ApzcOf(layers[1]);
  TestAsyncPanZoomController* bottom = ApzcOf(layers[2]);

  MockFunction<void(std::string checkPointName)> check;
  {
    InSequence s;
    EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, _, _, left->GetGuid(), _)).Times(1);
    EXPECT_CALL(check, Call("Tapped on left"));
    EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, _, _, bottom->GetGuid(), _)).Times(1);
    EXPECT_CALL(check, Call("Tapped on bottom"));
    EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, _, _, root->GetGuid(), _)).Times(1);
    EXPECT_CALL(check, Call("Tapped on root"));
    EXPECT_CALL(check, Call("Tap pending on d-t-c region"));
    EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, _, _, bottom->GetGuid(), _)).Times(1);
    EXPECT_CALL(check, Call("Tapped on bottom again"));
    EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, _, _, left->GetGuid(), _)).Times(1);
    EXPECT_CALL(check, Call("Tapped on left this time"));
  }

  TimeDuration tapDuration = TimeDuration::FromMilliseconds(100);

  // Tap in the exposed hit regions of each of the layers once and ensure
  // the clicks are dispatched right away
  Tap(manager, ScreenIntPoint(10, 10), tapDuration);
  mcc->RunThroughDelayedTasks();    // this runs the tap event
  check.Call("Tapped on left");
  Tap(manager, ScreenIntPoint(110, 110), tapDuration);
  mcc->RunThroughDelayedTasks();    // this runs the tap event
  check.Call("Tapped on bottom");
  Tap(manager, ScreenIntPoint(110, 10), tapDuration);
  mcc->RunThroughDelayedTasks();    // this runs the tap event
  check.Call("Tapped on root");

  // Now tap on the dispatch-to-content region where the layers overlap
  Tap(manager, ScreenIntPoint(10, 110), tapDuration);
  mcc->RunThroughDelayedTasks();    // this runs the main-thread timeout
  check.Call("Tap pending on d-t-c region");
  mcc->RunThroughDelayedTasks();    // this runs the tap event
  check.Call("Tapped on bottom again");

  // Now let's do that again, but simulate a main-thread response
  uint64_t inputBlockId = 0;
  Tap(manager, ScreenIntPoint(10, 110), tapDuration, nullptr, &inputBlockId);
  nsTArray<ScrollableLayerGuid> targets;
  targets.AppendElement(left->GetGuid());
  manager->SetTargetAPZC(inputBlockId, targets);
  while (mcc->RunThroughDelayedTasks());    // this runs the tap event
  check.Call("Tapped on left this time");
}

TEST_F(APZEventRegionsTester, HitRegionAccumulatesChildren) {
  CreateEventRegionsLayerTree2();

  // Tap in the area of the child layer that's not directly included in the
  // parent layer's hit region. Verify that it comes out of the APZC's
  // content controller, which indicates the input events got routed correctly
  // to the APZC.
  EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, _, _, rootApzc->GetGuid(), _)).Times(1);
  Tap(manager, ScreenIntPoint(10, 160), TimeDuration::FromMilliseconds(100));
}

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);
  EXPECT_EQ(child, hit.get());
  EXPECT_EQ(result, CompositorHitTestFlags::eVisibleToHitTest);
}

TEST_F(APZEventRegionsTester, Bug1119497) {
  CreateBug1119497LayerTree();

  gfx::CompositorHitTestInfo result;
  RefPtr<AsyncPanZoomController> hit = manager->GetTargetAPZC(ScreenPoint(50, 50), &result);
  // 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);
}

TEST_F(APZEventRegionsTester, Bug1117712) {
  CreateBug1117712LayerTree();

  TestAsyncPanZoomController* apzc2 = ApzcOf(layers[2]);

  // These touch events should hit the dispatch-to-content region of layers[3]
  // and so get queued with that APZC as the tentative target.
  uint64_t inputBlockId = 0;
  Tap(manager, ScreenIntPoint(55, 5), TimeDuration::FromMilliseconds(100), nullptr, &inputBlockId);
  // But now we tell the APZ that really it hit layers[2], and expect the tap
  // to be delivered at the correct coordinates.
  EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(55, 5), 0, apzc2->GetGuid(), _)).Times(1);

  nsTArray<ScrollableLayerGuid> targets;
  targets.AppendElement(apzc2->GetGuid());
  manager->SetTargetAPZC(inputBlockId, targets);
}