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