content: #!/usr/bin/python
  
  # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
  #
  # This source code is licensed under the MIT license found in the
  # LICENSE file in the root directory of this source tree.
  
  import os
  import re
  
  import fbchisellldbbase as fb
  import fbchisellldbobjecthelpers as objHelpers
  
  
  # This is the key corresponding to accessibility label in
  # _accessibilityElementsInContainer:
  ACCESSIBILITY_LABEL_KEY = 2001
  
  
  def lldbcommands():
      return [
          FBPrintAccessibilityLabels(),
          FBPrintAccessibilityIdentifiers(),
          FBFindViewByAccessibilityLabelCommand(),
      ]
  
  
  class FBPrintAccessibilityLabels(fb.FBCommand):
      def name(self):
          return "pa11y"
  
      def description(self):
          return "Print accessibility labels of all views in hierarchy of <aView>"
  
      def args(self):
          return [
              fb.FBCommandArgument(
                  arg="aView",
                  type="UIView*",
                  help="The view to print the hierarchy of.",
                  default="(id)[[UIApplication sharedApplication] keyWindow]",
              )
          ]
  
      def run(self, arguments, options):
          forceStartAccessibilityServer()
          printAccessibilityHierarchy(arguments[0])
  
  
  class FBPrintAccessibilityIdentifiers(fb.FBCommand):
      def name(self):
          return "pa11yi"
  
      def description(self):
          return "Print accessibility identifiers of all views in hierarchy of <aView>"
  
      def args(self):
          return [
              fb.FBCommandArgument(
                  arg="aView",
                  type="UIView*",
                  help="The view to print the hierarchy of.",
                  default="(id)[[UIApplication sharedApplication] keyWindow]",
              )
          ]
  
      def run(self, arguments, option):
          forceStartAccessibilityServer()
          printAccessibilityIdentifiersHierarchy(arguments[0])
  
  
  class FBFindViewByAccessibilityLabelCommand(fb.FBCommand):
      def name(self):
          return "fa11y"
  
      def description(self):
          return (
              "Find the views whose accessibility labels match labelRegex "
              "and puts the address of the first result on the clipboard."
          )
  
      def args(self):
          return [
              fb.FBCommandArgument(
                  arg="labelRegex",
                  type="string",
                  help="The accessibility label regex to search the view hierarchy for.",
              )
          ]
  
      def accessibilityGrepHierarchy(self, view, needle):
          a11yLabel = accessibilityLabel(view)
          # if we don't have any accessibility string - we should have some children
          if int(a11yLabel.GetValue(), 16) == 0:
              # We call private method that gives back all visible accessibility children
              # for view iOS 10 and higher
              if fb.evaluateBooleanExpression(
                  "[UIView respondsToSelector:@selector(_accessibilityElementsAndContainersDescendingFromViews:options:sorted:)]"
              ):
                  accessibilityElements = fb.evaluateObjectExpression(
                      "[UIView _accessibilityElementsAndContainersDescendingFromViews:@[(id)%s] options:0 sorted:NO]"
                      % view
                  )
              else:
                  accessibilityElements = fb.evaluateObjectExpression(
                      "[[[UIApplication sharedApplication] keyWindow] _accessibilityElementsInContainer:0 topLevel:%s includeKB:0]"
                      % view
                  )
              accessibilityElementsCount = fb.evaluateIntegerExpression(
                  "[%s count]" % accessibilityElements
              )
              for index in range(0, accessibilityElementsCount):
                  subview = fb.evaluateObjectExpression(
                      "[%s objectAtIndex:%i]" % (accessibilityElements, index)
                  )
                  self.accessibilityGrepHierarchy(subview, needle)
          elif re.match(
              r".*" + needle + ".*", a11yLabel.GetObjectDescription(), re.IGNORECASE
          ):
              classDesc = objHelpers.className(view)
              print(
                  "({} {}) {}".format(classDesc, view, a11yLabel.GetObjectDescription())
              )
  
              # First element that is found is copied to clipboard
              if not self.foundElement:
                  self.foundElement = True
                  cmd = 'echo %s | tr -d "\n" | pbcopy' % view
                  os.system(cmd)
  
      def run(self, arguments, options):
          forceStartAccessibilityServer()
          rootView = fb.evaluateObjectExpression(
              "[[UIApplication sharedApplication] keyWindow]"
          )
          self.foundElement = False
          self.accessibilityGrepHierarchy(rootView, arguments[0])
  
  
  def isRunningInSimulator():
      return (
          fb.evaluateExpressionValue("(id)[[UIDevice currentDevice] model]")
          .GetObjectDescription()
          .lower()
          .find("simulator")
          >= 0
      ) or (
          fb.evaluateExpressionValue("(id)[[UIDevice currentDevice] name]")
          .GetObjectDescription()
          .lower()
          .find("simulator")
          >= 0
      )
  
  
  def forceStartAccessibilityServer():
      # We try to start accessibility server only if we don't have needed method active
      if not fb.evaluateBooleanExpression(
          "[UIView instancesRespondToSelector:@selector(_accessibilityElementsInContainer:)]"
      ):
          # Starting accessibility server is different for simulator and device
          if isRunningInSimulator():
              fb.evaluateEffect(
                  "[[UIApplication sharedApplication] accessibilityActivate]"
              )
          else:
              fb.evaluateEffect(
                  "[[[UIApplication sharedApplication] _accessibilityBundlePrincipalClass] _accessibilityStartServer]"
              )
  
  
  def accessibilityLabel(view):
      # using Apple private API to get real value of accessibility string for element.
      return fb.evaluateExpressionValue(
          "(id)[%s accessibilityAttributeValue:%i]" % (view, ACCESSIBILITY_LABEL_KEY),
          False,
      )
  
  
  def accessibilityIdentifier(view):
      return fb.evaluateExpressionValue(
          "(id)[{} accessibilityIdentifier]".format(view), False
      )
  
  
  def accessibilityElements(view):
      if fb.evaluateBooleanExpression(
          "[UIView instancesRespondToSelector:@selector(accessibilityElements)]"
      ):
          a11yElements = fb.evaluateExpression(
              "(id)[%s accessibilityElements]" % view, False
          )
          if int(a11yElements, 16) != 0:
              return a11yElements
      if fb.evaluateBooleanExpression(
          "[%s respondsToSelector:@selector(_accessibleSubviews)]" % view
      ):
          return fb.evaluateExpression("(id)[%s _accessibleSubviews]" % (view), False)
      else:
          return fb.evaluateObjectExpression(
              "[[[UIApplication sharedApplication] keyWindow] _accessibilityElementsInContainer:0 topLevel:%s includeKB:0]"
              % view
          )
  
  
  def printAccessibilityHierarchy(view, indent=0):
      a11yLabel = accessibilityLabel(view)
      classDesc = objHelpers.className(view)
      indentString = "   | " * indent
  
      # if we don't have any accessibility string - we should have some children
      if int(a11yLabel.GetValue(), 16) == 0:
          print(indentString + ("{} {}".format(classDesc, view)))
          # We call private method that gives back all visible accessibility children
          # for view
          a11yElements = accessibilityElements(view)
          accessibilityElementsCount = int(
              fb.evaluateExpression("(int)[%s count]" % a11yElements)
          )
          for index in range(0, accessibilityElementsCount):
              subview = fb.evaluateObjectExpression(
                  "[%s objectAtIndex:%i]" % (a11yElements, index)
              )
              printAccessibilityHierarchy(subview, indent + 1)
      else:
          print(
              indentString
              + ("({} {}) {}".format(classDesc, view, a11yLabel.GetObjectDescription()))
          )
  
  
  def printAccessibilityIdentifiersHierarchy(view, indent=0):
      a11yIdentifier = accessibilityIdentifier(view)
      classDesc = objHelpers.className(view)
      indentString = "   | " * indent
  
      # if we don't have any accessibility identifier - we should have some children
      if int(a11yIdentifier.GetValue(), 16) == 0:
          print(indentString + ("{} {}".format(classDesc, view)))
          # We call private method that gives back all visible accessibility children
          # for view
          a11yElements = accessibilityElements(view)
          accessibilityElementsCount = int(
              fb.evaluateExpression("(int)[%s count]" % a11yElements)
          )
          for index in range(0, accessibilityElementsCount):
              subview = fb.evaluateObjectExpression(
                  "[%s objectAtIndex:%i]" % (a11yElements, index)
              )
              printAccessibilityIdentifiersHierarchy(subview, indent + 1)
      else:
          print(
              indentString
              + (
                  "({} {}) {}".format(
                      classDesc, view, a11yIdentifier.GetObjectDescription()
                  )
              )
          )
  