• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include <algorithm>
17 #include "common_defines.h"
18 #include "ui_model.h"
19 
20 namespace OHOS::uitest {
21     using namespace std;
22     using namespace nlohmann;
23 
24     static constexpr auto ROOT_HIERARCHY = "ROOT";
25 
ToStr() const26     string Rect::ToStr() const
27     {
28         stringstream os;
29         os << "Rect<";
30         os << "xLeft=" <<left_ << ",xRight=" << right_ << ",yTop=" << top_ << ",yButton=" << bottom_;
31         os << ">";
32         return os.str();
33     }
34 
ComputeOverlappingDimensions(const Rect & other,int32_t & width,int32_t & height) const35     void Rect::ComputeOverlappingDimensions(const Rect &other, int32_t &width, int32_t& height) const
36     {
37         if (left_ >= other.right_ || right_ <= other.left_) {
38             width = 0;
39         } else {
40             array<int32_t, INDEX_FOUR> px = {left_, right_, other.left_, other.right_};
41             sort(px.begin(), px.end());
42             width = px[INDEX_TWO] - px[INDEX_ONE];
43         }
44         if (top_ >= other.bottom_ || bottom_ <= other.top_) {
45             height = 0;
46         } else {
47             array<int32_t, INDEX_FOUR> py = {top_, bottom_, other.top_, other.bottom_};
48             sort(py.begin(), py.end());
49             height = py[INDEX_TWO] - py[INDEX_ONE];
50         }
51     }
52 
ComputeIntersection(const Rect & other,Rect & result) const53     bool Rect::ComputeIntersection(const Rect & other, Rect & result) const
54     {
55         if (left_ >= other.right_ || right_ <= other.left_) {
56             return false;
57         }
58         if (top_ >= other.bottom_ || bottom_ <= other.top_) {
59             return false;
60         }
61         array<int32_t, INDEX_FOUR> px = {left_, right_, other.left_, other.right_};
62         array<int32_t, INDEX_FOUR> py = {top_, bottom_, other.top_, other.bottom_};
63         sort(px.begin(), px.end());
64         sort(py.begin(), py.end());
65         result = {px[INDEX_ONE], px[INDEX_TWO], py[INDEX_ONE], py[INDEX_TWO]};
66         return true;
67     }
68 
HasAttr(string_view name) const69     bool Widget::HasAttr(string_view name) const
70     {
71         return attributes_.find(string(name)) != attributes_.end();
72     }
73 
GetAttr(string_view name,string_view defaultVal) const74     string Widget::GetAttr(string_view name, string_view defaultVal) const
75     {
76         auto find = attributes_.find(string(name));
77         return find == attributes_.end() ? string(defaultVal) : find->second;
78     }
79 
SetAttr(string_view name,string_view value)80     void Widget::SetAttr(string_view name, string_view value)
81     {
82         attributes_[string(name)] = value;
83     }
84 
SetHostTreeId(string_view tid)85     void Widget::SetHostTreeId(string_view tid)
86     {
87         hostTreeId_ = tid;
88     }
89 
GetHostTreeId() const90     string Widget::GetHostTreeId() const
91     {
92         return hostTreeId_;
93     }
94 
SetBounds(int32_t cl,int32_t cr,int32_t ct,int32_t cb)95     void Widget::SetBounds(int32_t cl, int32_t cr, int32_t ct, int32_t cb)
96     {
97         bounds_ = Rect {cl, cr, ct, cb};
98     }
99 
ToStr() const100     string Widget::ToStr() const
101     {
102         stringstream os;
103         os << "Widget{bounds=" << bounds_.ToStr() << ",";
104         for (auto &pair:attributes_) {
105             os << pair.first << "='" << pair.second << "',";
106         }
107         os << "}";
108         return os.str();
109     }
110 
DumpAttributes(map<string,string> & receiver) const111     void Widget::DumpAttributes(map<string, string> &receiver) const
112     {
113         for (auto&[attr, value]:attributes_) {
114             receiver[attr] = value;
115         }
116     }
117 
118     class WidgetHierarchyBuilder {
119     public:
120 
Build(string_view parentWidgetHierarchy,uint32_t childIndex)121         static string Build(string_view parentWidgetHierarchy, uint32_t childIndex)
122         {
123             return string(parentWidgetHierarchy) + string(hierarchySeparator_) + to_string(childIndex);
124         }
125 
GetParentWidgetHierarchy(string_view hierarchy)126         static string GetParentWidgetHierarchy(string_view hierarchy)
127         {
128             if (hierarchy == ROOT_HIERARCHY) {
129                 // no parent for root widget
130                 return "";
131             }
132 
133             auto findRoot = hierarchy.find(ROOT_HIERARCHY);
134             if (findRoot != 0) {
135                 // invalid hierarchy string
136                 return "";
137             }
138             auto findLastSeparator = hierarchy.find_last_of(hierarchySeparator_);
139             if (findLastSeparator <= 0 || findLastSeparator == string::npos) {
140                 return "";
141             }
142             return string(hierarchy).substr(0, findLastSeparator);
143         }
144 
GetChildHierarchy(string_view hierarchy,uint32_t childIndex)145         static string GetChildHierarchy(string_view hierarchy, uint32_t childIndex)
146         {
147             if (hierarchy.find(ROOT_HIERARCHY) != 0) {
148                 // invalid hierarchy string
149                 return "";
150             }
151             return string(hierarchy) + string(hierarchySeparator_) + to_string(childIndex);
152         }
153 
CheckIsDescendantHierarchyOfRoot(string_view hierarchy,string_view hierarchyRoot)154         static bool CheckIsDescendantHierarchyOfRoot(string_view hierarchy, string_view hierarchyRoot)
155         {
156             // child node hierarchy must startswith parent node hierarchy
157             return hierarchy.find(hierarchyRoot) == 0;
158         }
159 
160     private:
161         static constexpr auto hierarchySeparator_ = ",";
162     };
163 
SetWidgetBounds(Widget & widget,string_view boundsStr)164     static void SetWidgetBounds(Widget &widget, string_view boundsStr)
165     {
166         // set bounds
167         int32_t val = -1;
168         int32_t integers[4];
169         int32_t index = 0;
170         bool negative = false;
171         static constexpr int32_t FACTOR = 10;
172         for (char ch : boundsStr) {
173             if (ch == '-') {
174                 DCHECK(val == -1); // should be a start of a number
175                 negative = true;
176             } else if (ch >= '0' && ch <= '9') {
177                 val = max(val, 0); // ensure accumulation
178                 val = val * FACTOR + (ch - '0');
179             } else if (val >= 0) {
180                 DCHECK(index < INDEX_FOUR);
181                 integers[index] = val * (negative ? -1 : 1);
182                 // after harvest, rest variables and increase ptrIdx
183                 index++;
184                 val = -1;
185                 negative = false;
186             }
187         }
188 
189         DCHECK(index == INDEX_FOUR);
190         widget.SetBounds(integers[INDEX_ZERO], integers[INDEX_TWO], integers[INDEX_ONE], integers[INDEX_THREE]);
191     }
192 
SetWidgetAttributes(Widget & widget,const map<string,string> & attributes)193     static void SetWidgetAttributes(Widget &widget, const map<string, string> &attributes)
194     {
195         for (auto &item:attributes) {
196             if (item.first == ATTR_NAMES[UiAttr::BOUNDS]) {
197                 SetWidgetBounds(widget, item.second);
198             } else {
199                 widget.SetAttr(item.first, item.second);
200             }
201         }
202     }
203 
DfsTraverse(WidgetVisitor & visitor) const204     void WidgetTree::DfsTraverse(WidgetVisitor &visitor) const
205     {
206         auto root = GetRootWidget();
207         if (root != nullptr) {
208             DfsTraverseDescendants(visitor, *root);
209         }
210     }
211 
DfsTraverseFronts(WidgetVisitor & visitor,const Widget & pivot) const212     void WidgetTree::DfsTraverseFronts(WidgetVisitor &visitor, const Widget &pivot) const
213     {
214         DCHECK(widgetsConstructed_);
215         DCHECK(CheckIsMyNode(pivot));
216         auto root = GetRootWidget();
217         if (root == nullptr) {
218             return;
219         }
220         auto pivotHierarchy = pivot.GetHierarchy();
221         for (auto &hierarchy:widgetHierarchyIdDfsOrder_) {
222             if (hierarchy == pivotHierarchy) {
223                 // hit the pivot, finish traversing
224                 break;
225             }
226             auto widget = widgetMap_.find(hierarchy);
227             DCHECK(widget != widgetMap_.end());
228             visitor.Visit(widget->second);
229         }
230     }
231 
DfsTraverseRears(WidgetVisitor & visitor,const Widget & pivot) const232     void WidgetTree::DfsTraverseRears(WidgetVisitor &visitor, const Widget &pivot) const
233     {
234         DCHECK(widgetsConstructed_);
235         DCHECK(CheckIsMyNode(pivot));
236         auto root = GetRootWidget();
237         if (root == nullptr) {
238             return;
239         }
240         auto pivotHierarchy = pivot.GetHierarchy();
241         bool traverseStarted = false;
242         for (auto &hierarchy:widgetHierarchyIdDfsOrder_) {
243             if (hierarchy == pivotHierarchy) {
244                 // skip self and start traverse from next one
245                 traverseStarted = true;
246                 continue;
247             }
248             if (!traverseStarted) {
249                 // skip front widgets
250                 continue;
251             }
252             auto widget = widgetMap_.find(hierarchy);
253             DCHECK(widget != widgetMap_.end());
254             visitor.Visit(widget->second);
255         }
256     }
257 
DfsTraverseDescendants(WidgetVisitor & visitor,const Widget & root) const258     void WidgetTree::DfsTraverseDescendants(WidgetVisitor &visitor, const Widget &root) const
259     {
260         DCHECK(widgetsConstructed_);
261         DCHECK(CheckIsMyNode(root));
262         bool traverseStarted = false;
263         auto rootHierarchy = root.GetHierarchy();
264         for (auto &hierarchy:widgetHierarchyIdDfsOrder_) {
265             if (!WidgetHierarchyBuilder::CheckIsDescendantHierarchyOfRoot(hierarchy, rootHierarchy)) {
266                 if (!traverseStarted) {
267                     continue; // root node not found yet, skip visiting current widget and go ahead
268                 } else {
269                     break; // descendant nodes are all visited, break
270                 }
271             }
272             traverseStarted = true;
273             auto widget = widgetMap_.find(hierarchy);
274             DCHECK(widget != widgetMap_.end());
275             visitor.Visit(widget->second);
276         }
277     }
278 
279     using NodeVisitor = function<void(string_view, map<string, string>&&)>;
280 
DfsVisitNode(const json & root,NodeVisitor visitor,string_view hierarchy)281     static void DfsVisitNode(const json &root, NodeVisitor visitor, string_view hierarchy)
282     {
283         DCHECK(visitor != nullptr);
284         auto attributesData = root["attributes"];
285         auto childrenData = root["children"];
286         map<string, string> attributeDict;
287         for (auto &item:attributesData.items()) {
288             attributeDict[item.key()] = item.value();
289         }
290         visitor(hierarchy, move(attributeDict));
291         const size_t childCount = childrenData.size();
292         for (size_t idx = 0; idx < childCount; idx++) {
293             auto &child = childrenData.at(idx);
294             auto childHierarchy = WidgetHierarchyBuilder::Build(hierarchy, idx);
295             DfsVisitNode(child, visitor, childHierarchy);
296         }
297     }
298 
ConstructFromDom(const nlohmann::json & dom,bool amendBounds)299     void WidgetTree::ConstructFromDom(const nlohmann::json& dom, bool amendBounds)
300     {
301         DCHECK(!widgetsConstructed_);
302         map<string, map<string, string>> widgetDict;
303         vector<string> visitTrace;
304         auto nodeVisitor = [&widgetDict, &visitTrace](string_view hierarchy, map<string, string>&& attrs) {
305             visitTrace.emplace_back(hierarchy);
306             widgetDict.insert(make_pair(hierarchy, attrs));
307         };
308         DfsVisitNode(dom, nodeVisitor, ROOT_HIERARCHY);
309         for (auto& hierarchy : visitTrace) {
310             auto findWidgetAttrs = widgetDict.find(hierarchy);
311             DCHECK(findWidgetAttrs != widgetDict.end());
312             Widget widget(hierarchy);
313             widget.SetHostTreeId(this->identifier_);
314             SetWidgetAttributes(widget, findWidgetAttrs->second);
315             auto findParent = widgetMap_.find(WidgetHierarchyBuilder::GetParentWidgetHierarchy(hierarchy));
316             const auto bounds = widget.GetBounds();
317             auto visible = false;
318             if (!amendBounds) {
319                 visible = true;
320             } else if (hierarchy == ROOT_HIERARCHY) {
321                 visible = (bounds.right_ > bounds.left_) && (bounds.bottom_ > bounds.top_);
322             } else if (findParent == widgetMap_.end()) {
323                 visible = false; // parent was discarded
324             } else {
325                 // amend bounds, intersect with parent, compute visibility
326                 auto parentBounds = findParent->second.GetBounds();
327                 auto newBounds = Rect {0, 0, 0, 0};
328                 if (bounds.ComputeIntersection(parentBounds, newBounds)) {
329                     widget.SetBounds(newBounds.left_, newBounds.right_, newBounds.top_, newBounds.bottom_);
330                     visible = (newBounds.right_ > newBounds.left_) && (newBounds.bottom_ > newBounds.top_);
331                 } else {
332                     widget.SetBounds(0, 0, 0, 0);
333                     visible = false;
334                 }
335             }
336             if (amendBounds && bounds.ToStr().compare(widget.GetBounds().ToStr()) != 0) {
337                 LOG_D("Amend bounds %{public}s from %{public}s", widget.ToStr().c_str(), bounds.ToStr().c_str());
338             }
339             if (visible) {
340                 widgetMap_.insert(make_pair(hierarchy, move(widget)));
341                 widgetHierarchyIdDfsOrder_.emplace_back(hierarchy);
342             } else {
343                 LOG_D("Discard invisible node '%{public}s'and its descendants", widget.ToStr().c_str());
344             }
345         }
346         widgetsConstructed_ = true;
347     }
348 
GetRootWidget() const349     const Widget *WidgetTree::GetRootWidget() const
350     {
351         return GetWidgetByHierarchy(ROOT_HIERARCHY);
352     }
353 
GetParentWidget(const Widget & widget) const354     const Widget *WidgetTree::GetParentWidget(const Widget &widget) const
355     {
356         DCHECK(CheckIsMyNode(widget));
357         if (widget.GetHierarchy() == ROOT_HIERARCHY) {
358             return nullptr;
359         }
360         const auto parentHierarchy = WidgetHierarchyBuilder::GetParentWidgetHierarchy(widget.GetHierarchy());
361         return GetWidgetByHierarchy(parentHierarchy);
362     }
363 
GetChildWidget(const Widget & widget,uint32_t index) const364     const Widget *WidgetTree::GetChildWidget(const Widget &widget, uint32_t index) const
365     {
366         DCHECK(CheckIsMyNode(widget));
367         if (index < 0) {
368             return nullptr;
369         }
370         auto childHierarchy = WidgetHierarchyBuilder::GetChildHierarchy(widget.GetHierarchy(), index);
371         return GetWidgetByHierarchy(childHierarchy);
372     }
373 
GetWidgetByHierarchy(string_view hierarchy) const374     const Widget *WidgetTree::GetWidgetByHierarchy(string_view hierarchy) const
375     {
376         auto findWidget = widgetMap_.find(string(hierarchy));
377         if (findWidget != widgetMap_.end()) {
378             return &(findWidget->second);
379         }
380         return nullptr;
381     }
382 
IsRootWidgetHierarchy(string_view hierarchy)383     bool WidgetTree::IsRootWidgetHierarchy(string_view hierarchy)
384     {
385         return ROOT_HIERARCHY == hierarchy;
386     }
387 
GenerateTreeId()388     string WidgetTree::GenerateTreeId()
389     {
390         static uint32_t counter = 0;
391         auto id = string("WidgetTree@") + to_string(counter);
392         counter++;
393         return id;
394     }
395 
CheckIsMyNode(const Widget & widget) const396     inline bool WidgetTree::CheckIsMyNode(const Widget &widget) const
397     {
398         return this->identifier_ == widget.GetHostTreeId();
399     }
400 } // namespace uitest
401 
402