1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/vr/test/ui_test.h"
6 
7 #include "chrome/browser/vr/elements/rect.h"
8 #include "chrome/browser/vr/model/model.h"
9 #include "chrome/browser/vr/render_info.h"
10 #include "chrome/browser/vr/test/animation_utils.h"
11 #include "chrome/browser/vr/test/constants.h"
12 #include "chrome/browser/vr/ui.h"
13 #include "chrome/browser/vr/ui_scene.h"
14 #include "chrome/browser/vr/ui_scene_creator.h"
15 #include "ui/gfx/geometry/vector3d_f.h"
16 
17 namespace vr {
18 
19 namespace {
20 
ComputeNormal(const gfx::Transform & transform)21 gfx::Vector3dF ComputeNormal(const gfx::Transform& transform) {
22   gfx::Vector3dF x_axis(1, 0, 0);
23   gfx::Vector3dF y_axis(0, 1, 0);
24   transform.TransformVector(&x_axis);
25   transform.TransformVector(&y_axis);
26   gfx::Vector3dF normal = CrossProduct(x_axis, y_axis);
27   normal.GetNormalized(&normal);
28   return normal;
29 }
30 
WillElementFaceCamera(const UiElement * element)31 bool WillElementFaceCamera(const UiElement* element) {
32   // Element might become invisible due to incorrect rotation, i.e when rotation
33   // cause the visible side of the element flip.
34   // Here we calculate the dot product of (origin - center) and normal. If the
35   // result is greater than 0, it means the visible side of this element is
36   // facing camera.
37   gfx::Point3F center;
38   gfx::Transform transform = element->ComputeTargetWorldSpaceTransform();
39   transform.TransformPoint(&center);
40 
41   gfx::Point3F origin;
42   gfx::Vector3dF normal = ComputeNormal(transform);
43   if (center == origin) {
44     // If the center of element is at origin, as our camera facing negative z.
45     // we only need to make sure the normal of the element have positive z.
46     return normal.z() > 0.f;
47   }
48   return gfx::DotProduct(origin - center, normal) > 0.f;
49 }
50 
51 // This method tests whether an element will be visible after all pending scene
52 // animations complete.
WillElementBeVisible(const UiElement * element)53 bool WillElementBeVisible(const UiElement* element) {
54   if (!element)
55     return false;
56   if (element->ComputeTargetOpacity() == 0.f)
57     return false;
58   if (!element->IsWorldPositioned())
59     return true;
60   return WillElementFaceCamera(element);
61 }
62 
NumVisibleInTreeRecursive(const UiElement * element)63 int NumVisibleInTreeRecursive(const UiElement* element) {
64   int visible = WillElementBeVisible(element) ? 1 : 0;
65   for (auto& child : element->children())
66     visible += NumVisibleInTreeRecursive(child.get());
67   return visible;
68 }
69 
70 }  // namespace
71 
UiTest()72 UiTest::UiTest() {}
~UiTest()73 UiTest::~UiTest() {}
74 
SetUp()75 void UiTest::SetUp() {
76   browser_ = std::make_unique<testing::NiceMock<MockUiBrowserInterface>>();
77 }
78 
CreateSceneInternal(const UiInitialState & state,std::unique_ptr<MockContentInputDelegate> content_input_delegate)79 void UiTest::CreateSceneInternal(
80     const UiInitialState& state,
81     std::unique_ptr<MockContentInputDelegate> content_input_delegate) {
82   content_input_delegate_ = content_input_delegate.get();
83   ui_instance_ = std::make_unique<Ui>(std::move(browser_.get()),
84                                       std::move(content_input_delegate),
85                                       nullptr, nullptr, nullptr, state);
86   ui_ = ui_instance_.get();
87   scene_ = ui_instance_->scene();
88   model_ = ui_instance_->model_for_test();
89   model_->controllers[0].transform.Translate3d(kStartControllerPosition);
90 
91   OnBeginFrame();
92   // Need a second BeginFrame here because the first one will add controllers
93   // to the scene, which need an additional frame to get into a good state.
94   OnBeginFrame();
95 }
96 
CreateScene(const UiInitialState & state)97 void UiTest::CreateScene(const UiInitialState& state) {
98   auto content_input_delegate =
99       std::make_unique<testing::NiceMock<MockContentInputDelegate>>();
100   CreateSceneInternal(state, std::move(content_input_delegate));
101 }
102 
CreateScene(InWebVr in_web_vr)103 void UiTest::CreateScene(InWebVr in_web_vr) {
104   UiInitialState state;
105   state.in_web_vr = in_web_vr;
106   state.gvr_input_support = true;
107   CreateScene(state);
108 }
109 
SetIncognito(bool incognito)110 void UiTest::SetIncognito(bool incognito) {
111   model_->incognito = incognito;
112 }
113 
IsVisible(UiElementName name) const114 bool UiTest::IsVisible(UiElementName name) const {
115   OnBeginFrame();
116   UiElement* element = scene_->GetUiElementByName(name);
117   return WillElementBeVisible(element);
118 }
119 
VerifyVisibility(const std::set<UiElementName> & names,bool expected_visibility) const120 bool UiTest::VerifyVisibility(const std::set<UiElementName>& names,
121                               bool expected_visibility) const {
122   OnBeginFrame();
123   for (auto name : names) {
124     SCOPED_TRACE(UiElementNameToString(name));
125     UiElement* element = scene_->GetUiElementByName(name);
126     bool will_be_visible = WillElementBeVisible(element);
127     EXPECT_EQ(will_be_visible, expected_visibility);
128     if (will_be_visible != expected_visibility)
129       return false;
130   }
131   return true;
132 }
133 
VerifyOnlyElementsVisible(const std::string & trace_context,const std::set<UiElementName> & names) const134 void UiTest::VerifyOnlyElementsVisible(
135     const std::string& trace_context,
136     const std::set<UiElementName>& names) const {
137   OnBeginFrame();
138   SCOPED_TRACE(trace_context);
139   for (auto* element : scene_->GetAllElements()) {
140     SCOPED_TRACE(element->DebugName());
141     UiElementName name = element->name();
142     UiElementName owner_name = element->owner_name_for_test();
143     if (element->draw_phase() == kPhaseNone && owner_name == kNone) {
144       EXPECT_TRUE(names.find(name) == names.end());
145       continue;
146     }
147     if (name == kNone)
148       name = owner_name;
149     bool should_be_visible = (names.find(name) != names.end());
150     EXPECT_EQ(WillElementBeVisible(element), should_be_visible);
151   }
152 }
153 
NumVisibleInTree(UiElementName name) const154 int UiTest::NumVisibleInTree(UiElementName name) const {
155   OnBeginFrame();
156   auto* root = scene_->GetUiElementByName(name);
157   EXPECT_NE(root, nullptr);
158   if (!root) {
159     return 0;
160   }
161   return NumVisibleInTreeRecursive(root);
162 }
163 
VerifyIsAnimating(const std::set<UiElementName> & names,const std::vector<TargetProperty> & properties,bool animating) const164 bool UiTest::VerifyIsAnimating(const std::set<UiElementName>& names,
165                                const std::vector<TargetProperty>& properties,
166                                bool animating) const {
167   OnBeginFrame();
168   for (auto name : names) {
169     auto* element = scene_->GetUiElementByName(name);
170     EXPECT_NE(nullptr, element);
171     SCOPED_TRACE(element->DebugName());
172     if (IsAnimating(element, properties) != animating) {
173       return false;
174     }
175   }
176   return true;
177 }
178 
VerifyRequiresLayout(const std::set<UiElementName> & names,bool requires_layout) const179 bool UiTest::VerifyRequiresLayout(const std::set<UiElementName>& names,
180                                   bool requires_layout) const {
181   OnBeginFrame();
182   for (auto name : names) {
183     SCOPED_TRACE(name);
184     auto* element = scene_->GetUiElementByName(name);
185     EXPECT_NE(nullptr, element);
186     if (!element || element->requires_layout() != requires_layout) {
187       return false;
188     }
189   }
190   return true;
191 }
192 
RunForMs(float milliseconds)193 bool UiTest::RunForMs(float milliseconds) {
194   return RunFor(base::TimeDelta::FromMilliseconds(milliseconds));
195 }
196 
RunForSeconds(float seconds)197 bool UiTest::RunForSeconds(float seconds) {
198   return RunFor(base::TimeDelta::FromSecondsD(seconds));
199 }
200 
AdvanceFrame()201 bool UiTest::AdvanceFrame() {
202   current_time_ += base::TimeDelta::FromMilliseconds(16);
203   return OnBeginFrame();
204 }
205 
GetBackgroundColor(SkColor * background_color) const206 void UiTest::GetBackgroundColor(SkColor* background_color) const {
207   OnBeginFrame();
208   Rect* background =
209       static_cast<Rect*>(scene_->GetUiElementByName(kSolidBackground));
210   ASSERT_NE(nullptr, background);
211   EXPECT_EQ(background->center_color(), background->edge_color());
212   *background_color = background->edge_color();
213 }
214 
ClickElement(UiElement * element)215 void UiTest::ClickElement(UiElement* element) {
216   // Synthesize a controller vector targeting the element.
217   gfx::Point3F target;
218   element->ComputeTargetWorldSpaceTransform().TransformPoint(&target);
219   gfx::Point3F origin;
220   gfx::Vector3dF direction(target - origin);
221   direction.GetNormalized(&direction);
222 
223   RenderInfo render_info;
224   ReticleModel reticle_model;
225   InputEventList input_event_list;
226   ControllerModel controller_model;
227   controller_model.laser_direction = direction;
228   controller_model.laser_origin = origin;
229 
230   controller_model.touchpad_button_state = ControllerModel::ButtonState::kDown;
231   ui_instance_->input_manager()->HandleInput(current_time_, render_info,
232                                              controller_model, &reticle_model,
233                                              &input_event_list);
234   OnBeginFrame();
235 
236   controller_model.touchpad_button_state = ControllerModel::ButtonState::kUp;
237   ui_instance_->input_manager()->HandleInput(current_time_, render_info,
238                                              controller_model, &reticle_model,
239                                              &input_event_list);
240   OnBeginFrame();
241 }
242 
RunFor(base::TimeDelta delta)243 bool UiTest::RunFor(base::TimeDelta delta) {
244   base::TimeTicks target_time = current_time_ + delta;
245   base::TimeDelta frame_time = base::TimeDelta::FromMilliseconds(16);
246   bool changed = false;
247 
248   // Run a frame in the near future to trigger new state changes.
249   current_time_ += frame_time;
250   changed |= OnBeginFrame();
251 
252   // If needed, skip ahead and run another frame at the target time.
253   if (current_time_ < target_time) {
254     current_time_ = target_time;
255     changed |= OnBeginFrame();
256   }
257 
258   return changed;
259 }
260 
OnBeginFrame() const261 bool UiTest::OnBeginFrame() const {
262   bool changed = false;
263   model_->current_time = current_time_;
264   changed |= scene_->OnBeginFrame(current_time_, kStartHeadPose);
265   if (scene_->HasDirtyTextures()) {
266     scene_->UpdateTextures();
267     changed = true;
268   }
269   return changed;
270 }
271 
272 }  // namespace vr
273