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