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 DfsTraverseParents(WidgetVisitor & visitor,const Widget & pivot) const234 void WidgetTree::DfsTraverseParents(WidgetVisitor &visitor, const Widget &pivot) const 235 { 236 DCHECK(widgetsConstructed_); 237 DCHECK(CheckIsMyNode(pivot)); 238 auto root = GetRootWidget(); 239 if (root == nullptr) { 240 return; 241 } 242 auto parent = this->GetParentWidget(pivot); 243 while (true) { 244 if (parent == nullptr) { 245 break; 246 } 247 visitor.Visit(*parent); 248 parent = this->GetParentWidget(*parent); 249 } 250 } 251 DfsTraverseDescendants(WidgetVisitor & visitor,const Widget & root) const252 void WidgetTree::DfsTraverseDescendants(WidgetVisitor &visitor, const Widget &root) const 253 { 254 DCHECK(widgetsConstructed_); 255 DCHECK(CheckIsMyNode(root)); 256 bool traverseStarted = false; 257 auto rootHierarchy = root.GetHierarchy(); 258 for (auto &hierarchy : widgetHierarchyIdDfsOrder_) { 259 if (!WidgetHierarchyBuilder::CheckIsDescendantHierarchy(hierarchy, rootHierarchy)) { 260 if (!traverseStarted) { 261 continue; // root node not found yet, skip visiting current widget and go ahead 262 } else { 263 break; // descendant nodes are all visited, break 264 } 265 } 266 traverseStarted = true; 267 auto widget = widgetMap_.find(hierarchy); 268 DCHECK(widget != widgetMap_.end()); 269 visitor.Visit(widget->second); 270 } 271 } 272 273 using NodeVisitor = function<void(string_view, map<string, string> &&)>; 274 DfsVisitNode(const json & root,NodeVisitor visitor,string_view hierarchy)275 static void DfsVisitNode(const json &root, NodeVisitor visitor, string_view hierarchy) 276 { 277 DCHECK(visitor != nullptr); 278 auto attributesData = root["attributes"]; 279 auto childrenData = root["children"]; 280 map<string, string> attributeDict; 281 if (root.find("abilityName") != root.end()) { 282 attributeDict["abilityName"] = root["abilityName"]; 283 } 284 if (root.find("bundleName") != root.end()) { 285 attributeDict["bundleName"] = root["bundleName"]; 286 } 287 if (root.find("pagePath") != root.end()) { 288 attributeDict["pagePath"] = root["pagePath"]; 289 } 290 for (auto &item : attributesData.items()) { 291 attributeDict[item.key()] = item.value(); 292 } 293 visitor(hierarchy, move(attributeDict)); 294 const size_t childCount = childrenData.size(); 295 for (size_t idx = 0; idx < childCount; idx++) { 296 auto &child = childrenData.at(idx); 297 auto childHierarchy = WidgetHierarchyBuilder::Build(hierarchy, idx); 298 DfsVisitNode(child, visitor, childHierarchy); 299 } 300 } 301 EnsureParentVisible(const Widget & widget)302 void WidgetTree::EnsureParentVisible(const Widget &widget) 303 { 304 auto hierarchy = widget.GetHierarchy(); 305 auto findParent = widgetMap_.find(WidgetHierarchyBuilder::GetParentWidgetHierarchy(hierarchy)); 306 if (findParent == widgetMap_.end()) { 307 return; 308 } else if (!findParent->second.IsVisible()) { 309 findParent->second.SetAttr(ATTR_NAMES[UiAttr::VISIBLE], "true"); 310 EnsureParentVisible(findParent->second); 311 } 312 } 313 GetEffectiveBounds(const Widget & widget,vector<pair<string,Rect>> & boundsClips)314 static Rect GetEffectiveBounds(const Widget &widget, vector<pair<string, Rect>> &boundsClips) 315 { 316 auto result = Rect(0, 0, 0, 0); 317 auto boundsClip = Rect(0, 0, 0, 0); 318 if (boundsClips.empty()) { 319 boundsClip = widget.GetBounds(); 320 } else { 321 while (widget.GetHierarchy().find(boundsClips.back().first) == std::string::npos) { 322 boundsClips.pop_back(); 323 if (boundsClips.empty()) { 324 boundsClip = widget.GetBounds(); 325 break; 326 } 327 } 328 if (!boundsClips.empty()) { 329 boundsClip = boundsClips.back().second; 330 } 331 } 332 if (!RectAlgorithm::ComputeIntersection(widget.GetBounds(), boundsClip, result)) { 333 result = Rect(0, 0, 0, 0); 334 } 335 return result; 336 } 337 ConstructFromDom(const nlohmann::json & dom,bool amendBounds)338 void WidgetTree::ConstructFromDom(const nlohmann::json &dom, bool amendBounds) 339 { 340 DCHECK(!widgetsConstructed_); 341 static const set<string> containerTypes = {"List", "Grid", "WaterFlow", "GridCol", "GridRow", "Scroll", 342 "Flex", "ListItemGroup", "Swiper", "DecorBar", "_Common_", "TabContent", "WindowScene"}; 343 map<string, map<string, string>> widgetDict; 344 vector<string> visitTrace; 345 auto nodeVisitor = [&widgetDict, &visitTrace](string_view hierarchy, map<string, string> &&attrs) { 346 visitTrace.emplace_back(hierarchy); 347 widgetDict.insert(make_pair(hierarchy, attrs)); 348 }; 349 DfsVisitNode(dom, nodeVisitor, ROOT_HIERARCHY); 350 vector <pair<string, Rect>> boundsClips; 351 for (auto &hierarchy : visitTrace) { 352 auto findWidgetAttrs = widgetDict.find(hierarchy); 353 DCHECK(findWidgetAttrs != widgetDict.end()); 354 Widget widget(hierarchy); 355 widget.SetHostTreeId(this->identifier_); 356 SetWidgetAttributes(widget, findWidgetAttrs->second); 357 const auto bounds = widget.GetBounds(); 358 auto newBounds = Rect(0, 0, 0, 0); 359 if (!amendBounds || hierarchy == ROOT_HIERARCHY) { 360 newBounds = bounds; 361 } else { 362 newBounds = GetEffectiveBounds(widget, boundsClips); 363 } 364 if (!RectAlgorithm::CheckEqual(newBounds, bounds)) { 365 widget.SetBounds(newBounds); 366 LOG_D("Amend bounds %{public}s from %{public}s", widget.ToStr().c_str(), Rect2JsonStr(bounds).c_str()); 367 } 368 if (!amendBounds || (newBounds.GetWidth() > 0 && newBounds.GetHeight() > 0)) { 369 widget.SetAttr(ATTR_NAMES[UiAttr::VISIBLE], "true"); 370 } 371 if (widget.IsVisible()) { 372 EnsureParentVisible(widget); 373 } 374 auto type = widget.GetAttr(ATTR_NAMES[UiAttr::TYPE], ""); 375 if (containerTypes.find(type) != containerTypes.end()) { 376 boundsClips.push_back(make_pair(widget.GetHierarchy(), widget.GetBounds())); 377 } 378 widgetMap_.insert(make_pair(hierarchy, move(widget))); 379 widgetHierarchyIdDfsOrder_.emplace_back(hierarchy); 380 } 381 widgetsConstructed_ = true; 382 } 383 DfsMarshalWidget(const WidgetTree & tree,const Widget & root,nlohmann::json & dom,const std::map<string,size_t> & widgetChildCountMap)384 static void DfsMarshalWidget(const WidgetTree& tree, const Widget& root, nlohmann::json& dom, 385 const std::map<string, size_t>& widgetChildCountMap) 386 { 387 auto attributesData = json(); 388 // "< UiAttr::HIERARCHY" : do not expose inner used attributes 389 for (auto index = 0; index < UiAttr::HIERARCHY; index++) { 390 const auto attr = ATTR_NAMES[index].data(); 391 attributesData[attr] = root.GetAttr(attr, ""); 392 } 393 if (root.HasAttr("abilityName")) { 394 attributesData["abilityName"] = root.GetAttr("abilityName", ""); 395 } 396 if (root.HasAttr("bundleName")) { 397 attributesData["bundleName"] = root.GetAttr("bundleName", ""); 398 } 399 if (root.HasAttr("pagePath")) { 400 attributesData["pagePath"] = root.GetAttr("pagePath", ""); 401 } 402 stringstream stream; 403 auto rect = root.GetBounds(); 404 stream << "[" << rect.left_ << "," << rect.top_ << "]" 405 << "[" << rect.right_ << "," << rect.bottom_ << "]"; 406 attributesData[ATTR_NAMES[UiAttr::BOUNDS].data()] = stream.str(); 407 408 auto childrenData = json::array(); 409 uint32_t childIndex = 0; 410 uint32_t childCount = 0; 411 uint32_t visitCount = 0; 412 auto hierarchy = root.GetHierarchy(); 413 if (widgetChildCountMap.find(hierarchy) != widgetChildCountMap.end()) { 414 childCount = widgetChildCountMap.find(hierarchy)->second; 415 } 416 while (visitCount < childCount) { 417 auto child = tree.GetChildWidget(root, childIndex); 418 childIndex++; 419 if (child == nullptr) { 420 continue; 421 } 422 if (!child->IsVisible()) { 423 visitCount++; 424 continue; 425 } 426 auto childData = json(); 427 DfsMarshalWidget(tree, *child, childData, widgetChildCountMap); 428 childrenData.emplace_back(childData); 429 visitCount++; 430 } 431 432 dom["attributes"] = attributesData; 433 dom["children"] = childrenData; 434 } 435 MarshalIntoDom(nlohmann::json & dom) const436 void WidgetTree::MarshalIntoDom(nlohmann::json& dom) const 437 { 438 DCHECK(widgetsConstructed_); 439 auto root = GetRootWidget(); 440 std::map<string, size_t> widgetChildCountMap; 441 for (auto &hierarchy : widgetHierarchyIdDfsOrder_) { 442 if (hierarchy == ROOT_HIERARCHY) { 443 continue; 444 } 445 auto parentHierarchy = WidgetHierarchyBuilder::GetParentWidgetHierarchy(hierarchy); 446 if (widgetChildCountMap.find(parentHierarchy) == widgetChildCountMap.end()) { 447 widgetChildCountMap[parentHierarchy] = 1; 448 } else { 449 widgetChildCountMap[parentHierarchy] = widgetChildCountMap[parentHierarchy] + 1; 450 } 451 } 452 if (root != nullptr) { 453 DfsMarshalWidget(*this, *root, dom, widgetChildCountMap); 454 } 455 } 456 GetRootWidget() const457 const Widget *WidgetTree::GetRootWidget() const 458 { 459 return GetWidgetByHierarchy(ROOT_HIERARCHY); 460 } 461 GetParentWidget(const Widget & widget) const462 const Widget *WidgetTree::GetParentWidget(const Widget &widget) const 463 { 464 DCHECK(CheckIsMyNode(widget)); 465 if (widget.GetHierarchy() == ROOT_HIERARCHY) { 466 return nullptr; 467 } 468 const auto parentHierarchy = WidgetHierarchyBuilder::GetParentWidgetHierarchy(widget.GetHierarchy()); 469 return GetWidgetByHierarchy(parentHierarchy); 470 } 471 GetChildWidget(const Widget & widget,uint32_t index) const472 const Widget *WidgetTree::GetChildWidget(const Widget &widget, uint32_t index) const 473 { 474 DCHECK(CheckIsMyNode(widget)); 475 if (index < 0) { 476 return nullptr; 477 } 478 auto childHierarchy = WidgetHierarchyBuilder::GetChildHierarchy(widget.GetHierarchy(), index); 479 return GetWidgetByHierarchy(childHierarchy); 480 } 481 GetWidgetByHierarchy(string_view hierarchy) const482 const Widget *WidgetTree::GetWidgetByHierarchy(string_view hierarchy) const 483 { 484 auto findWidget = widgetMap_.find(string(hierarchy)); 485 if (findWidget != widgetMap_.end()) { 486 return &(findWidget->second); 487 } 488 return nullptr; 489 } 490 IsRootWidgetHierarchy(string_view hierarchy)491 bool WidgetTree::IsRootWidgetHierarchy(string_view hierarchy) 492 { 493 return ROOT_HIERARCHY == hierarchy; 494 } 495 GenerateTreeId()496 string WidgetTree::GenerateTreeId() 497 { 498 static uint32_t counter = 0; 499 auto id = string("WidgetTree@") + to_string(counter); 500 counter++; 501 return id; 502 } 503 CheckIsMyNode(const Widget & widget) const504 inline bool WidgetTree::CheckIsMyNode(const Widget &widget) const 505 { 506 return this->identifier_ == widget.GetHostTreeId(); 507 } 508 509 /** WidgetVisitor used to visit and merge widgets of subtrees into dest root tree.*/ 510 class MergerVisitor : public WidgetVisitor { 511 public: 512 // handler function to perform merging widget, arg1: the revised bounds 513 using MergerFunction = function<void(const Widget &, const Rect &)>; MergerVisitor(MergerFunction collector)514 explicit MergerVisitor(MergerFunction collector) : collector_(collector) {}; 515 ~MergerVisitor()516 ~MergerVisitor() {} 517 518 void PrepareToVisitSubTree(const WidgetTree &tree); 519 520 void EndVisitingSubTree(); 521 522 void Visit(const Widget &widget) override; 523 GetMergedBounds() const524 Rect GetMergedBounds() const 525 { 526 return mergedBounds_; 527 } 528 529 private: 530 MergerFunction collector_; 531 // the overlays of widget 532 vector<Rect> overlays_; 533 // the node hierarchy of first invisble parent 534 string maxInvisibleParent_ = "NA"; 535 // the node hierarchy of first fully-visible parent 536 string maxFullyParent_ = "NA"; 537 // the merged bounds 538 Rect mergedBounds_ = {0, 0, 0, 0}; 539 // bounds of current visiting tree 540 Rect visitingTreeBounds_ = {0, 0, 0, 0}; 541 }; 542 PrepareToVisitSubTree(const WidgetTree & tree)543 void MergerVisitor::PrepareToVisitSubTree(const WidgetTree &tree) 544 { 545 auto root = tree.GetRootWidget(); 546 DCHECK(root != nullptr); 547 // collect max bounds 548 auto rootBounds = root->GetBounds(); 549 mergedBounds_.left_ = min(mergedBounds_.left_, rootBounds.left_); 550 mergedBounds_.top_ = min(mergedBounds_.top_, rootBounds.top_); 551 mergedBounds_.right_ = max(mergedBounds_.right_, rootBounds.right_); 552 mergedBounds_.bottom_ = max(mergedBounds_.bottom_, rootBounds.bottom_); 553 // update visiting tree bounds 554 visitingTreeBounds_ = rootBounds; 555 // reset intermediate data 556 maxInvisibleParent_ = "NA"; 557 maxFullyParent_ = "NA"; 558 } 559 EndVisitingSubTree()560 void MergerVisitor::EndVisitingSubTree() 561 { 562 // add overlays for later visited subtrees 563 overlays_.push_back(visitingTreeBounds_); 564 } 565 Visit(const Widget & widget)566 void MergerVisitor::Visit(const Widget &widget) 567 { 568 if (collector_ == nullptr) { 569 return; 570 } 571 const auto hierarchy = widget.GetHierarchy(); 572 const auto inInVisible = WidgetHierarchyBuilder::CheckIsDescendantHierarchy(hierarchy, maxInvisibleParent_); 573 const auto inFully = WidgetHierarchyBuilder::CheckIsDescendantHierarchy(hierarchy, maxFullyParent_); 574 if (inInVisible) { 575 // parent invisible, skip 576 return; 577 } 578 const auto bounds = widget.GetBounds(); 579 bool visible = true; 580 auto visibleRegion = bounds; 581 if (!inFully) { 582 // parent not full-visible, need compute visible region 583 visible = RectAlgorithm::ComputeMaxVisibleRegion(bounds, overlays_, visibleRegion); 584 } 585 if (!visible) { 586 maxInvisibleParent_ = hierarchy; // update maxInvisibleParent 587 return; 588 } 589 if (!inFully && RectAlgorithm::CheckEqual(bounds, visibleRegion)) { 590 maxFullyParent_ = hierarchy; // update maxFullParent 591 } 592 // call collector with widget and revised bounds 593 collector_(widget, visibleRegion); 594 } 595 MergeTrees(const vector<unique_ptr<WidgetTree>> & from,WidgetTree & to,vector<int32_t> & mergedOrders)596 void WidgetTree::MergeTrees(const vector<unique_ptr<WidgetTree>> &from, WidgetTree &to, 597 vector<int32_t> &mergedOrders) 598 { 599 if (from.empty()) { 600 return; 601 } 602 size_t subtreeIndex = 0; 603 size_t hierarchyIndex = 0; 604 to.widgetsConstructed_ = true; 605 auto virtualRoot = Widget(ROOT_HIERARCHY); 606 virtualRoot.SetAttr(ATTR_NAMES[UiAttr::VISIBLE], "true"); 607 virtualRoot.SetHostTreeId(to.identifier_); 608 // insert virtual root node into tree 609 to.widgetHierarchyIdDfsOrder_.emplace_back(ROOT_HIERARCHY); 610 to.widgetMap_.insert(make_pair(ROOT_HIERARCHY, move(virtualRoot))); 611 auto &vitualRootWidget = to.widgetMap_.begin()->second; 612 // amend widget hierarchy with prefix to make it the descendant of the virtualRoot 613 string hierarchyPrefix = ""; 614 constexpr auto offset = string_view(ROOT_HIERARCHY).length(); 615 // collect widget with revised hierarchy and bounds, merge it to dest tree 616 auto merger = [&hierarchyPrefix, &tree = to, &subtreeIndex, &hierarchyIndex, &mergedOrders] 617 (const Widget &widget, const Rect &bounds) { 618 auto newHierarchy = string(hierarchyPrefix) + widget.GetHierarchy().substr(offset); 619 auto newWidget = widget.Clone(tree.identifier_, newHierarchy); 620 newWidget->SetBounds(bounds); 621 if (widget.GetHierarchy() == ROOT_HIERARCHY) { 622 mergedOrders.push_back(subtreeIndex); 623 hierarchyIndex++; 624 } 625 tree.widgetMap_.insert(make_pair(newHierarchy, move(*newWidget))); 626 tree.widgetHierarchyIdDfsOrder_.emplace_back(newHierarchy); 627 }; 628 auto visitor = MergerVisitor(merger); 629 for (auto &tree : from) { 630 DCHECK(tree != nullptr); 631 // update hierarchy prefix 632 hierarchyPrefix = string(ROOT_HIERARCHY) + "," + to_string(hierarchyIndex); 633 // visit tree and forward visible widgets to collector 634 visitor.PrepareToVisitSubTree(*tree); 635 tree->DfsTraverse(visitor); 636 visitor.EndVisitingSubTree(); 637 subtreeIndex++; 638 } 639 // amend bounds of the virtual root 640 vitualRootWidget.SetBounds(visitor.GetMergedBounds()); 641 } 642 Visit(const Widget & widget)643 void TreeSnapshotTaker::Visit(const Widget &widget) 644 { 645 auto type = widget.GetAttr(ATTR_NAMES[UiAttr::TYPE], "") + "/"; 646 auto value = widget.GetAttr(ATTR_NAMES[UiAttr::TEXT], "") + "/"; 647 auto hashcode = widget.GetAttr(ATTR_NAMES[UiAttr::HASHCODE], "") + ";"; 648 if (value != "/") { 649 allNodes_.push_back(type + value + hashcode); 650 if ((widget.GetAttr(ATTR_NAMES[UiAttr::VISIBLE], "")) == "true") { 651 displayNodes_.push_back(type + value + hashcode); 652 } 653 } 654 } 655 } // namespace OHOS::uitest 656