• 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 "ui_model.h"
18 
19 namespace OHOS::uitest {
20     using namespace std;
21     using namespace nlohmann;
22 
23     static constexpr auto ROOT_HIERARCHY = "ROOT";
24 
Rect2JsonStr(const Rect & rect)25     static string Rect2JsonStr(const Rect &rect)
26     {
27         json data;
28         data["left"] = rect.left_;
29         data["top"] = rect.top_;
30         data["right"] = rect.right_;
31         data["bottom"] = rect.bottom_;
32         return data.dump();
33     }
34 
HasAttr(string_view name) const35     bool Widget::HasAttr(string_view name) const
36     {
37         return attributes_.find(string(name)) != attributes_.end();
38     }
39 
GetAttr(string_view name,string_view defaultVal) const40     string Widget::GetAttr(string_view name, string_view defaultVal) const
41     {
42         auto find = attributes_.find(string(name));
43         return find == attributes_.end() ? string(defaultVal) : find->second;
44     }
45 
SetAttr(string_view name,string_view value)46     void Widget::SetAttr(string_view name, string_view value)
47     {
48         attributes_[string(name)] = value;
49     }
50 
SetHostTreeId(string_view tid)51     void Widget::SetHostTreeId(string_view tid)
52     {
53         hostTreeId_ = tid;
54     }
55 
GetHostTreeId() const56     string Widget::GetHostTreeId() const
57     {
58         return hostTreeId_;
59     }
60 
SetBounds(const Rect & bounds)61     void Widget::SetBounds(const Rect &bounds)
62     {
63         bounds_ = bounds;
64         // save bounds attribute as structured data
65         SetAttr(ATTR_NAMES[UiAttr::BOUNDS], Rect2JsonStr(bounds_));
66     }
67 
ToStr() const68     string Widget::ToStr() const
69     {
70         stringstream os;
71         os << "Widget{";
72         for (auto &pair : attributes_) {
73             os << pair.first << "='" << pair.second << "',";
74         }
75         os << "}";
76         return os.str();
77     }
78 
Clone(string_view hostTreeId,string_view hierarchy) const79     unique_ptr<Widget> Widget::Clone(string_view hostTreeId, string_view hierarchy) const
80     {
81         auto clone = make_unique<Widget>(hierarchy);
82         clone->hostTreeId_ = hostTreeId;
83         clone->attributes_ = this->attributes_;
84         clone->bounds_ = this->bounds_;
85         clone->SetAttr(ATTR_NAMES[UiAttr::HIERARCHY], hierarchy); // ensure hiararchy consisent
86         return clone;
87     }
88 
GetAttrMap() const89     std::map<std::string, std::string> Widget::GetAttrMap() const
90     {
91         return attributes_;
92     }
93 
94     class WidgetHierarchyBuilder {
95     public:
Build(string_view parentWidgetHierarchy,uint32_t childIndex)96         static string Build(string_view parentWidgetHierarchy, uint32_t childIndex)
97         {
98             return string(parentWidgetHierarchy) + string(hierarchySeparator_) + to_string(childIndex);
99         }
100 
GetParentWidgetHierarchy(string_view hierarchy)101         static string GetParentWidgetHierarchy(string_view hierarchy)
102         {
103             if (hierarchy == ROOT_HIERARCHY) {
104                 // no parent for root widget
105                 return "";
106             }
107 
108             auto findRoot = hierarchy.find(ROOT_HIERARCHY);
109             if (findRoot != 0) {
110                 // invalid hierarchy string
111                 return "";
112             }
113             auto findLastSeparator = hierarchy.find_last_of(hierarchySeparator_);
114             if (findLastSeparator <= 0 || findLastSeparator == string::npos) {
115                 return "";
116             }
117             return string(hierarchy).substr(0, findLastSeparator);
118         }
119 
GetChildHierarchy(string_view hierarchy,uint32_t childIndex)120         static string GetChildHierarchy(string_view hierarchy, uint32_t childIndex)
121         {
122             if (hierarchy.find(ROOT_HIERARCHY) != 0) {
123                 // invalid hierarchy string
124                 return "";
125             }
126             return string(hierarchy) + string(hierarchySeparator_) + to_string(childIndex);
127         }
128 
CheckIsDescendantHierarchy(string_view hierarchy,string_view hierarchyRoot)129         inline static bool CheckIsDescendantHierarchy(string_view hierarchy, string_view hierarchyRoot)
130         {
131             // child node hierarchy must startswith parent node hierarchy
132             return hierarchy.find(hierarchyRoot) == 0;
133         }
134 
135     private:
136         static constexpr auto hierarchySeparator_ = ",";
137     };
138 
SetWidgetBounds(Widget & widget,string_view boundsStr)139     static void SetWidgetBounds(Widget &widget, string_view boundsStr)
140     {
141         // set bounds
142         int32_t val = -1;
143         int32_t integers[4];
144         int32_t index = 0;
145         bool negative = false;
146         static constexpr int32_t FACTOR = 10;
147         for (char ch : boundsStr) {
148             if (ch == '-') {
149                 DCHECK(val == -1); // should be a start of a number
150                 negative = true;
151             } else if (ch >= '0' && ch <= '9') {
152                 val = max(val, 0); // ensure accumulation
153                 val = val * FACTOR + (int32_t)(ch - '0');
154             } else if (val >= 0) {
155                 DCHECK(index < INDEX_FOUR);
156                 integers[index] = val * (negative ? -1 : 1);
157                 // after harvest, rest variables and increase ptrIdx
158                 index++;
159                 val = -1;
160                 negative = false;
161             }
162         }
163 
164         DCHECK(index == INDEX_FOUR);
165         auto rect = Rect(integers[INDEX_ZERO], integers[INDEX_TWO], integers[INDEX_ONE], integers[INDEX_THREE]);
166         widget.SetBounds(rect);
167     }
168 
SetWidgetAttributes(Widget & widget,const map<string,string> & attributes)169     static void SetWidgetAttributes(Widget &widget, const map<string, string> &attributes)
170     {
171         for (auto &item : attributes) {
172             if (item.first == ATTR_NAMES[UiAttr::BOUNDS]) {
173                 SetWidgetBounds(widget, item.second);
174             } else {
175                 widget.SetAttr(item.first, item.second);
176             }
177         }
178     }
179 
DfsTraverse(WidgetVisitor & visitor) const180     void WidgetTree::DfsTraverse(WidgetVisitor &visitor) const
181     {
182         auto root = GetRootWidget();
183         if (root != nullptr) {
184             DfsTraverseDescendants(visitor, *root);
185         }
186     }
187 
DfsTraverseFronts(WidgetVisitor & visitor,const Widget & pivot) const188     void WidgetTree::DfsTraverseFronts(WidgetVisitor &visitor, const Widget &pivot) const
189     {
190         DCHECK(widgetsConstructed_);
191         DCHECK(CheckIsMyNode(pivot));
192         auto root = GetRootWidget();
193         if (root == nullptr) {
194             return;
195         }
196         auto pivotHierarchy = pivot.GetHierarchy();
197         for (auto &hierarchy : widgetHierarchyIdDfsOrder_) {
198             if (hierarchy == pivotHierarchy) {
199                 // hit the pivot, finish traversing
200                 break;
201             }
202             auto widget = widgetMap_.find(hierarchy);
203             DCHECK(widget != widgetMap_.end());
204             visitor.Visit(widget->second);
205         }
206     }
207 
DfsTraverseRears(WidgetVisitor & visitor,const Widget & pivot) const208     void WidgetTree::DfsTraverseRears(WidgetVisitor &visitor, const Widget &pivot) const
209     {
210         DCHECK(widgetsConstructed_);
211         DCHECK(CheckIsMyNode(pivot));
212         auto root = GetRootWidget();
213         if (root == nullptr) {
214             return;
215         }
216         auto pivotHierarchy = pivot.GetHierarchy();
217         bool traverseStarted = false;
218         for (auto &hierarchy : widgetHierarchyIdDfsOrder_) {
219             if (hierarchy == pivotHierarchy) {
220                 // skip self and start traverse from next one
221                 traverseStarted = true;
222                 continue;
223             }
224             if (!traverseStarted) {
225                 // skip front widgets
226                 continue;
227             }
228             auto widget = widgetMap_.find(hierarchy);
229             DCHECK(widget != widgetMap_.end());
230             visitor.Visit(widget->second);
231         }
232     }
233 
DfsTraverseDescendants(WidgetVisitor & visitor,const Widget & root) const234     void WidgetTree::DfsTraverseDescendants(WidgetVisitor &visitor, const Widget &root) const
235     {
236         DCHECK(widgetsConstructed_);
237         DCHECK(CheckIsMyNode(root));
238         bool traverseStarted = false;
239         auto rootHierarchy = root.GetHierarchy();
240         for (auto &hierarchy : widgetHierarchyIdDfsOrder_) {
241             if (!WidgetHierarchyBuilder::CheckIsDescendantHierarchy(hierarchy, rootHierarchy)) {
242                 if (!traverseStarted) {
243                     continue; // root node not found yet, skip visiting current widget and go ahead
244                 } else {
245                     break; // descendant nodes are all visited, break
246                 }
247             }
248             traverseStarted = true;
249             auto widget = widgetMap_.find(hierarchy);
250             DCHECK(widget != widgetMap_.end());
251             visitor.Visit(widget->second);
252         }
253     }
254 
255     using NodeVisitor = function<void(string_view, map<string, string> &&)>;
256 
DfsVisitNode(const json & root,NodeVisitor visitor,string_view hierarchy)257     static void DfsVisitNode(const json &root, NodeVisitor visitor, string_view hierarchy)
258     {
259         DCHECK(visitor != nullptr);
260         auto attributesData = root["attributes"];
261         auto childrenData = root["children"];
262         map<string, string> attributeDict;
263         for (auto &item : attributesData.items()) {
264             attributeDict[item.key()] = item.value();
265         }
266         visitor(hierarchy, move(attributeDict));
267         const size_t childCount = childrenData.size();
268         for (size_t idx = 0; idx < childCount; idx++) {
269             auto &child = childrenData.at(idx);
270             auto childHierarchy = WidgetHierarchyBuilder::Build(hierarchy, idx);
271             DfsVisitNode(child, visitor, childHierarchy);
272         }
273     }
274 
ConstructFromDom(const nlohmann::json & dom,bool amendBounds)275     void WidgetTree::ConstructFromDom(const nlohmann::json &dom, bool amendBounds)
276     {
277         DCHECK(!widgetsConstructed_);
278         map<string, map<string, string>> widgetDict;
279         vector<string> visitTrace;
280         auto nodeVisitor = [&widgetDict, &visitTrace](string_view hierarchy, map<string, string> &&attrs) {
281             visitTrace.emplace_back(hierarchy);
282             widgetDict.insert(make_pair(hierarchy, attrs));
283         };
284         DfsVisitNode(dom, nodeVisitor, ROOT_HIERARCHY);
285         for (auto &hierarchy : visitTrace) {
286             auto findWidgetAttrs = widgetDict.find(hierarchy);
287             DCHECK(findWidgetAttrs != widgetDict.end());
288             Widget widget(hierarchy);
289             widget.SetHostTreeId(this->identifier_);
290             SetWidgetAttributes(widget, findWidgetAttrs->second);
291             auto findParent = widgetMap_.find(WidgetHierarchyBuilder::GetParentWidgetHierarchy(hierarchy));
292             const auto bounds = widget.GetBounds();
293             auto newBounds = Rect(0, 0, 0, 0);
294             if (!amendBounds || hierarchy == ROOT_HIERARCHY) {
295                 newBounds = bounds;
296             } else if (findParent == widgetMap_.end()) {
297                 newBounds = Rect(0, 0, 0, 0); // parent was discarded
298             } else {
299                 // amend bounds, intersect with parent, compute visibility
300                 auto parentBounds = findParent->second.GetBounds();
301                 if (!RectAlgorithm::ComputeIntersection(bounds, parentBounds, newBounds)) {
302                     newBounds = Rect(0, 0, 0, 0);
303                 }
304             }
305             if (!RectAlgorithm::CheckEqual(newBounds, bounds)) {
306                 widget.SetBounds(newBounds);
307                 LOG_D("Amend bounds %{public}s from %{public}s", widget.ToStr().c_str(), Rect2JsonStr(bounds).c_str());
308             }
309             if (!amendBounds || (newBounds.GetWidth() > 0 && newBounds.GetHeight() > 0)) {
310                 widgetMap_.insert(make_pair(hierarchy, move(widget)));
311                 widgetHierarchyIdDfsOrder_.emplace_back(hierarchy);
312             } else {
313                 LOG_D("Discard invisible node '%{public}s'and its descendants", widget.ToStr().c_str());
314             }
315         }
316         widgetsConstructed_ = true;
317     }
318 
DfsMarshalWidget(const WidgetTree & tree,const Widget & root,nlohmann::json & dom,const std::map<string,size_t> & widgetChildCountMap)319     static void DfsMarshalWidget(const WidgetTree& tree, const Widget& root, nlohmann::json& dom,
320         const std::map<string, size_t>& widgetChildCountMap)
321     {
322         auto attributesData = json();
323         // "< UiAttr::HIERARCHY" : do not expose inner used attributes
324         for (auto index = 0; index < UiAttr::HIERARCHY; index++) {
325             const auto attr = ATTR_NAMES[index].data();
326             attributesData[attr] = root.GetAttr(attr, "");
327         }
328         stringstream stream;
329         auto rect = root.GetBounds();
330         stream << "[" << rect.left_ << "," << rect.top_ << "]"
331                << "[" << rect.right_ << "," << rect.bottom_ << "]";
332         attributesData[ATTR_NAMES[UiAttr::BOUNDS].data()] = stream.str();
333 
334         auto childrenData = json::array();
335         uint32_t childIndex = 0;
336         uint32_t childCount = 0;
337         uint32_t visitCount = 0;
338         auto hierarchy = root.GetHierarchy();
339         if (widgetChildCountMap.find(hierarchy) != widgetChildCountMap.end()) {
340             childCount = widgetChildCountMap.find(hierarchy)->second;
341         }
342         while (visitCount < childCount) {
343             auto child = tree.GetChildWidget(root, childIndex);
344             childIndex++;
345             if (child == nullptr) {
346                 continue;
347             }
348             auto childData = json();
349             DfsMarshalWidget(tree, *child, childData, widgetChildCountMap);
350             childrenData.emplace_back(childData);
351             visitCount++;
352         }
353 
354         dom["attributes"] = attributesData;
355         dom["children"] = childrenData;
356     }
357 
MarshalIntoDom(nlohmann::json & dom) const358     void WidgetTree::MarshalIntoDom(nlohmann::json& dom) const
359     {
360         DCHECK(widgetsConstructed_);
361         auto root = GetRootWidget();
362         std::map<string, size_t> widgetChildCountMap;
363         for (auto &hierarchy : widgetHierarchyIdDfsOrder_) {
364             if (hierarchy == ROOT_HIERARCHY) {
365                 continue;
366             }
367             auto parentHierarchy = WidgetHierarchyBuilder::GetParentWidgetHierarchy(hierarchy);
368             if (widgetChildCountMap.find(parentHierarchy) == widgetChildCountMap.end()) {
369                 widgetChildCountMap[parentHierarchy] = 1;
370             } else {
371                 widgetChildCountMap[parentHierarchy] = widgetChildCountMap[parentHierarchy] + 1;
372             }
373         }
374         if (root != nullptr) {
375             DfsMarshalWidget(*this, *root, dom, widgetChildCountMap);
376         }
377     }
378 
GetRootWidget() const379     const Widget *WidgetTree::GetRootWidget() const
380     {
381         return GetWidgetByHierarchy(ROOT_HIERARCHY);
382     }
383 
GetParentWidget(const Widget & widget) const384     const Widget *WidgetTree::GetParentWidget(const Widget &widget) const
385     {
386         DCHECK(CheckIsMyNode(widget));
387         if (widget.GetHierarchy() == ROOT_HIERARCHY) {
388             return nullptr;
389         }
390         const auto parentHierarchy = WidgetHierarchyBuilder::GetParentWidgetHierarchy(widget.GetHierarchy());
391         return GetWidgetByHierarchy(parentHierarchy);
392     }
393 
GetChildWidget(const Widget & widget,uint32_t index) const394     const Widget *WidgetTree::GetChildWidget(const Widget &widget, uint32_t index) const
395     {
396         DCHECK(CheckIsMyNode(widget));
397         if (index < 0) {
398             return nullptr;
399         }
400         auto childHierarchy = WidgetHierarchyBuilder::GetChildHierarchy(widget.GetHierarchy(), index);
401         return GetWidgetByHierarchy(childHierarchy);
402     }
403 
GetWidgetByHierarchy(string_view hierarchy) const404     const Widget *WidgetTree::GetWidgetByHierarchy(string_view hierarchy) const
405     {
406         auto findWidget = widgetMap_.find(string(hierarchy));
407         if (findWidget != widgetMap_.end()) {
408             return &(findWidget->second);
409         }
410         return nullptr;
411     }
412 
IsRootWidgetHierarchy(string_view hierarchy)413     bool WidgetTree::IsRootWidgetHierarchy(string_view hierarchy)
414     {
415         return ROOT_HIERARCHY == hierarchy;
416     }
417 
GenerateTreeId()418     string WidgetTree::GenerateTreeId()
419     {
420         static uint32_t counter = 0;
421         auto id = string("WidgetTree@") + to_string(counter);
422         counter++;
423         return id;
424     }
425 
CheckIsMyNode(const Widget & widget) const426     inline bool WidgetTree::CheckIsMyNode(const Widget &widget) const
427     {
428         return this->identifier_ == widget.GetHostTreeId();
429     }
430 
431     /** WidgetVisitor used to visit and merge widgets of subtrees into dest root tree.*/
432     class MergerVisitor : public WidgetVisitor {
433     public:
434         // handler function to perform merging widget, arg1: the revised bounds
435         using MergerFunction = function<void(const Widget &, const Rect &)>;
MergerVisitor(MergerFunction collector)436         explicit MergerVisitor(MergerFunction collector) : collector_(collector) {};
437 
~MergerVisitor()438         ~MergerVisitor() {}
439 
440         void PrepareToVisitSubTree(const WidgetTree &tree);
441 
442         void EndVisitingSubTree();
443 
444         void Visit(const Widget &widget) override;
445 
GetMergedBounds() const446         Rect GetMergedBounds() const
447         {
448             return mergedBounds_;
449         }
450 
451     private:
452         MergerFunction collector_;
453         // the overlays of widget
454         vector<Rect> overlays_;
455         // the node hierarchy of first invisble parent
456         string maxInvisibleParent_ = "NA";
457         // the node hierarchy of first fully-visible parent
458         string maxFullyParent_ = "NA";
459         // the merged bounds
460         Rect mergedBounds_ = {0, 0, 0, 0};
461         // bounds of current visiting tree
462         Rect visitingTreeBounds_ = {0, 0, 0, 0};
463     };
464 
PrepareToVisitSubTree(const WidgetTree & tree)465     void MergerVisitor::PrepareToVisitSubTree(const WidgetTree &tree)
466     {
467         auto root = tree.GetRootWidget();
468         DCHECK(root != nullptr);
469         // collect max bounds
470         auto rootBounds = root->GetBounds();
471         mergedBounds_.left_ = min(mergedBounds_.left_, rootBounds.left_);
472         mergedBounds_.top_ = min(mergedBounds_.top_, rootBounds.top_);
473         mergedBounds_.right_ = max(mergedBounds_.right_, rootBounds.right_);
474         mergedBounds_.bottom_ = max(mergedBounds_.bottom_, rootBounds.bottom_);
475         // update visiting tree bounds
476         visitingTreeBounds_ = rootBounds;
477         // reset intermediate data
478         maxInvisibleParent_ = "NA";
479         maxFullyParent_ = "NA";
480     }
481 
EndVisitingSubTree()482     void MergerVisitor::EndVisitingSubTree()
483     {
484         // add overlays for later visited subtrees
485         overlays_.push_back(visitingTreeBounds_);
486     }
487 
Visit(const Widget & widget)488     void MergerVisitor::Visit(const Widget &widget)
489     {
490         if (collector_ == nullptr) {
491             return;
492         }
493         const auto hierarchy = widget.GetHierarchy();
494         const auto inInVisible = WidgetHierarchyBuilder::CheckIsDescendantHierarchy(hierarchy, maxInvisibleParent_);
495         const auto inFully = WidgetHierarchyBuilder::CheckIsDescendantHierarchy(hierarchy, maxFullyParent_);
496         if (inInVisible) {
497             // parent invisible, skip
498             return;
499         }
500         const auto bounds = widget.GetBounds();
501         bool visible = true;
502         auto visibleRegion = bounds;
503         if (!inFully) {
504             // parent not full-visible, need compute visible region
505             visible = RectAlgorithm::ComputeMaxVisibleRegion(bounds, overlays_, visibleRegion);
506         }
507         if (!visible) {
508             maxInvisibleParent_ = hierarchy; // update maxInvisibleParent
509             return;
510         }
511         if (!inFully && RectAlgorithm::CheckEqual(bounds, visibleRegion)) {
512             maxFullyParent_ = hierarchy; // update maxFullParent
513         }
514         // call collector with widget and revised bounds
515         collector_(widget, visibleRegion);
516     }
517 
MergeTrees(const vector<unique_ptr<WidgetTree>> & from,WidgetTree & to)518     void WidgetTree::MergeTrees(const vector<unique_ptr<WidgetTree>> &from, WidgetTree &to)
519     {
520         if (from.empty()) {
521             return;
522         }
523         to.widgetsConstructed_ = true;
524         auto virtualRoot = Widget(ROOT_HIERARCHY);
525         virtualRoot.SetHostTreeId(to.identifier_);
526         // insert virtual root node into tree
527         to.widgetHierarchyIdDfsOrder_.emplace_back(ROOT_HIERARCHY);
528         to.widgetMap_.insert(make_pair(ROOT_HIERARCHY, move(virtualRoot)));
529         auto &vitualRootWidget = to.widgetMap_.begin()->second;
530         // amend widget hierarchy with prefix to make it the descendant of the virtualRoot
531         string hierarchyPrefix = "";
532         constexpr auto offset = string_view(ROOT_HIERARCHY).length();
533         // collect widget with revised hierarchy and bounds, merge it to dest tree
534         auto merger = [&hierarchyPrefix, &tree = to](const Widget &widget, const Rect &bounds) {
535             auto newHierarchy = string(hierarchyPrefix) + widget.GetHierarchy().substr(offset);
536             auto newWidget = widget.Clone(tree.identifier_, newHierarchy);
537             newWidget->SetBounds(bounds);
538             tree.widgetMap_.insert(make_pair(newHierarchy, move(*newWidget)));
539             tree.widgetHierarchyIdDfsOrder_.emplace_back(newHierarchy);
540         };
541         auto visitor = MergerVisitor(merger);
542         size_t index = 0;
543         for (auto &tree : from) {
544             DCHECK(tree != nullptr);
545             // update hierarchy prefix
546             hierarchyPrefix = string(ROOT_HIERARCHY) + "," + to_string(index);
547             // visit tree and forward visible widgets to collector
548             visitor.PrepareToVisitSubTree(*tree);
549             tree->DfsTraverse(visitor);
550             visitor.EndVisitingSubTree();
551             index++;
552         }
553         // amend bounds of the virtual root
554         vitualRootWidget.SetBounds(visitor.GetMergedBounds());
555     }
556 } // namespace OHOS::uitest
557