/// <summary> /// A linked list node that represents one step in a GUI path. /// A step in the typedPath is represented by this node. /// Following steps generate subsequent GuiPath nodes recursively. /// </summary> /// <param name="typedPath">a typed path</param> public GuiPath(string typedPath) { // remove the first step from the typedPath and process it int last = 0; string step = Utilities.GetFirstStep(typedPath, out last); Utilities.SplitTypedToken(step, out m_name, out m_type); if (m_type == "value" && (m_name == null || m_name == "")) m_name = ""; // want it to be empty else if (m_name == null || m_name == "") m_name = "NAMELESS"; try { int roleValue = Convert.ToInt32(m_type); m_role = (AccessibleRole)roleValue; } catch (Exception) { m_role = Utilities.TypeToRole(m_type); } // Parse the name string name; string varId; m_Nth = parseIndex(m_name, out name, out varId); if (m_name == name) m_Nth = 1; m_name = name; m_varId = varId; // if not consumed, split off the end of the path as a child path if (1 + last < typedPath.Length) { m_child = new GuiPath(typedPath.Substring(last + 1)); m_child.m_parent = this; } }
/// <summary> /// A linked list node that represents one step in a GUI path. /// A step in the typedPath is represented by this node. /// Following steps generate subsequent GuiPath nodes recursively. /// </summary> /// <param name="typedPath">a typed path</param> public GuiPath(string typedPath) { // remove the first step from the typedPath and process it int last = 0; string step = Utilities.GetFirstStep(typedPath, out last); Utilities.SplitTypedToken(step, out m_name, out m_type); if (m_type == "value" && (m_name == null || m_name == "")) { m_name = ""; // want it to be empty } else if (m_name == null || m_name == "") { m_name = "NAMELESS"; } try { int roleValue = Convert.ToInt32(m_type); m_role = (AccessibleRole)roleValue; } catch (Exception) { m_role = Utilities.TypeToRole(m_type); } // Parse the name string name; string varId; m_Nth = parseIndex(m_name, out name, out varId); if (m_name == name) { m_Nth = 1; } m_name = name; m_varId = varId; // if not consumed, split off the end of the path as a child path if (1 + last < typedPath.Length) { m_child = new GuiPath(typedPath.Substring(last + 1)); m_child.m_parent = this; } }
/// <summary> /// Find an Active Accessible object on an appPath via the /// gui model and/or a guiPath via Active Accessibility. /// The appPath is applied first if not null, then the guiPath. /// </summary> /// <param name="xmlPath">the xmlPath to a control or null</param> /// <param name="guiPath">the guiPath to click or null</param> /// <returns>The Active Accessible object or null</returns> private AccessibilityHelper findInGui(XmlPath xmlPath, string guiPath) { string path = null; if (xmlPath != null && xmlPath.isValid()) { path = xmlPath.Path; } if (Utilities.isGoodStr(guiPath)) { path += guiPath; } if (!Utilities.isGoodStr(path)) { return(null); } GuiPath gp = new GuiPath(Utilities.evalExpr(path)); if (gp == null || !gp.isValid()) { return(null); } return(gp.FindInGui(m_ah, this)); }
/// <summary> /// Find an Active Accessible object on an appPath via the /// gui model and/or a guiPath via Active Accessibility. /// The appPath is applied first if not null, then the guiPath. /// </summary> /// <param name="xmlPath">the xmlPath to a control or null</param> /// <param name="guiPath">the guiPath to click or null</param> /// <returns>The Active Accessible object or null</returns> private AccessibilityHelper findInGui(XmlPath xmlPath, string guiPath) { string path = null; if (xmlPath != null && xmlPath.isValid()) path = xmlPath.Path; if (Utilities.isGoodStr(guiPath)) path += guiPath; if (!Utilities.isGoodStr(path)) return null; GuiPath gp = new GuiPath(Utilities.evalExpr(path)); if (gp == null || !gp.isValid()) return null; return gp.FindInGui(m_ah, this); }
/// <summary> /// Determines the result of a Simian sensation. /// Problems with sensors are not generally logged /// since they are frequently called and most are not /// fatal. /// </summary> /// <param name="sensorRef">A sensor expression in a rule.</param> /// <returns>true if the sensor detected its target.</returns> public bool sensation(EmptyElement sensorRef) { if (sensorRef.getName().Equals("window")) { // is the window showing? string id = sensorRef.getValue("id"); // a VarNode string title = sensorRef.getValue("title"); MarkedNode mn = null; if (Utilities.isGoodStr(id) && Utilities.isGoodStr(title)) { // fetch the title from the model node via id + title mn = m_varNodes.get(id); // bail out if id not defined yet. if (mn == null) return false; if (mn.node != null) title = m_GuiModel.selectToString(mn.node, title, "title"); if (!Utilities.isGoodStr(title)) return false; } else if (id == null && title == null) { // get the main window title from the model title = m_Config.getDataBase()+m_GuiModel.getTitle(); } if (title == null) return false; // model lacks title if (Utilities.isGoodStr(title)) { IntPtr winHand = FindWindow(null, title); if ((int)winHand != 0) { AccessibilityHelper ah = new AccessibilityHelper(winHand); // look for a titlebar GuiPath gPath = new GuiPath("1:NAMELESS"); AccessibilityHelper tah = ah.SearchPath(gPath, this); if (tah == null || !tah.Value.Equals(title)) return false; m_ah = ah; return true; } } } if (sensorRef.getName().Equals("tested")) { string id = sensorRef.getValue("id"); // which controls string control = sensorRef.getValue("control"); // which controls string count = sensorRef.getValue("count"); // indicates how many if (control != null && count == null) return false; if (control == null && count != null) return false; if (control == null && count == null && id == null) return false; if (id != null) { MarkedNode mn = m_varNodes.get(id); if (mn != null) { if (mn.mark == null) return false; return mn.mark.Equals("tested"); } } // if id fails to return a marked node, try a control count if (control != null) { int n = 0; int k = 0; if (control.Equals("view")) { if (m_views == null) m_views = new MarkedList(m_GuiModel, "//*[@role='view']"); n = m_views.Count(); k = m_views.Count("tested"); } if (count.Equals("all") && n == k) return true; if (count.Equals("not-all") && k < n) return true; return false; } return false; } if (sensorRef.getName().Equals("glimpse")) { string id = sensorRef.getValue("id"); // a VarNode string appPath = sensorRef.getValue("on"); // an appPath string guiPath = sensorRef.getValue("at"); // a giuPath string property = sensorRef.getValue("prop"); // an ah property string expect = sensorRef.getValue("expect"); // value expected // Id provides a context ah that must be used to find the rest of the path! // can't just use the appPath from it. What if it's a dialog? XmlNode context = null; MarkedNode mn = null; if (Utilities.isGoodStr(id)) { mn = m_varNodes.get(id); // bail out if id not defined yet. if (mn == null) return false; if (mn.node != null) context = mn.node; } return glimpse(context, appPath, guiPath, property, expect); } return false; }
/// <summary> /// Method to apply when a path node is not found. /// No path above it and none below will call this method; /// only the one not found. /// </summary> /// <param name="path">The path step last tried.</param> public void notFound(GuiPath path) { m_log.writeElt("not-found"); m_log.writeAttr(path.Role.ToString(), path.Name); m_log.endElt(); }
/// <summary> /// Determines the result of a Simian sensation. /// Problems with sensors are not generally logged /// since they are frequently called and most are not /// fatal. /// </summary> /// <param name="sensorRef">A sensor expression in a rule.</param> /// <returns>true if the sensor detected its target.</returns> public bool sensation(EmptyElement sensorRef) { if (sensorRef.getName().Equals("window")) { // is the window showing? string id = sensorRef.getValue("id"); // a VarNode string title = sensorRef.getValue("title"); MarkedNode mn = null; if (Utilities.isGoodStr(id) && Utilities.isGoodStr(title)) { // fetch the title from the model node via id + title mn = m_varNodes.get(id); // bail out if id not defined yet. if (mn == null) { return(false); } if (mn.node != null) { title = m_GuiModel.selectToString(mn.node, title, "title"); } if (!Utilities.isGoodStr(title)) { return(false); } } else if (id == null && title == null) { // get the main window title from the model title = m_Config.getDataBase() + m_GuiModel.getTitle(); } if (title == null) { return(false); // model lacks title } if (Utilities.isGoodStr(title)) { IntPtr winHand = FindWindow(null, title); if ((int)winHand != 0) { AccessibilityHelper ah = new AccessibilityHelper(winHand); // look for a titlebar GuiPath gPath = new GuiPath("1:NAMELESS"); AccessibilityHelper tah = ah.SearchPath(gPath, this); if (tah == null || !tah.Value.Equals(title)) { return(false); } m_ah = ah; return(true); } } } if (sensorRef.getName().Equals("tested")) { string id = sensorRef.getValue("id"); // which controls string control = sensorRef.getValue("control"); // which controls string count = sensorRef.getValue("count"); // indicates how many if (control != null && count == null) { return(false); } if (control == null && count != null) { return(false); } if (control == null && count == null && id == null) { return(false); } if (id != null) { MarkedNode mn = m_varNodes.get(id); if (mn != null) { if (mn.mark == null) { return(false); } return(mn.mark.Equals("tested")); } } // if id fails to return a marked node, try a control count if (control != null) { int n = 0; int k = 0; if (control.Equals("view")) { if (m_views == null) { m_views = new MarkedList(m_GuiModel, "//*[@role='view']"); } n = m_views.Count(); k = m_views.Count("tested"); } if (count.Equals("all") && n == k) { return(true); } if (count.Equals("not-all") && k < n) { return(true); } return(false); } return(false); } if (sensorRef.getName().Equals("glimpse")) { string id = sensorRef.getValue("id"); // a VarNode string appPath = sensorRef.getValue("on"); // an appPath string guiPath = sensorRef.getValue("at"); // a giuPath string property = sensorRef.getValue("prop"); // an ah property string expect = sensorRef.getValue("expect"); // value expected // Id provides a context ah that must be used to find the rest of the path! // can't just use the appPath from it. What if it's a dialog? XmlNode context = null; MarkedNode mn = null; if (Utilities.isGoodStr(id)) { mn = m_varNodes.get(id); // bail out if id not defined yet. if (mn == null) { return(false); } if (mn.node != null) { context = mn.node; } } return(glimpse(context, appPath, guiPath, property, expect)); } return(false); }
/// <summary> /// ValueOrChild determines if the ah's value should be macthed or /// if a child should be matched. /// </summary> /// <param name="nextGP">The next path step beyond this ah</param> /// <param name="ah">The accessibility object currently considered</param> /// <param name="visitor">null or the object providing methods that are called when steps are found.</param> /// <returns>An AccessibilityHelper if checking for a value, otherwise null</returns> public AccessibilityHelper ValueOrChild(GuiPath nextGP, AccessibilityHelper ah, IPathVisitor visitor) { bool result = false; if (nextGP.Role == AccessibleRole.Alert) { // check if the name is a regular expression if (nextGP.Name != null && nextGP.Name.StartsWith("rexp#")) { Regex rx = new Regex(nextGP.Name.Substring(5)); result = rx.IsMatch(ah.Value); Log log = Log.getOnly(); log.writeElt("ValueOrChild"); log.writeAttr("pattern", nextGP.Name.Substring(5)); log.writeAttr("to", ah.Value); log.writeAttr("result", result.ToString()); log.endElt(); } else result = nextGP.Name == ah.Value; // match the value to the next path's name if (!result) { // it didn't match, so the search failed if (visitor != null) visitor.notFound(nextGP); return null; } } if (result) return ah; return ah.SearchPath(nextGP, visitor); // continue on the path }
/// <summary> /// Searches the path depth-first. /// Nth is not used - if it were, it could get a sibling, ancestor or child. /// </summary> /// <param name="path">The path to search. Each step in the path contains an /// Accessibility name and Accessibility role of the matching gui object.</param> /// <param name="visitor">null or the object providing methods that are called when steps are found.</param> /// <returns>A new <see cref="AccessibilityHelper"/> object that wraps the matched /// window.</returns> private AccessibilityHelper SearchPathByDepth(GuiPath path, IPathVisitor visitor) { Log log = Log.getOnly(); log.writeElt("SearchPathByDepth"); log.writeAttr("from", this.Role + ":" + this.Name); log.writeAttr("to", path.toString()); log.endElt(); if (ChildCount <= 0) { if (visitor != null) visitor.notFound(path); return null; } AccessibilityHelper ah = null; // try the client first if there is one, but don't visit it AccessibilityHelper client = FindClient(); if (client != null) ah = client.SearchPathByDepth(path, visitor); if (ah != null) { log.endElt(); // end head element return ah; } // Rats!! It wasn't below the client, the caller wants some system widget or something foreach (AccessibilityHelper child in this) { if (child == null || child.Equals(client)) continue; // does this object match? // treat name == "" and child.Name == null as a match // Note: a null string shows as "" in the debugger! log.writeElt("head"); log.writeElt("SearchPathByDepth"); log.writeAttr("path", path.toString()); log.writeAttr("child", child.Role + ":" + child.Name); log.endElt(); if (MatchNameAndRole(path.Name, path.Role, child)) { if (path.Next != null) { if (visitor != null) visitor.visitNode(child); // allow processing of this ah by caller log.endElt(); // end head element return ValueOrChild(path.Next, child, visitor); } } // if not a "real" object, a child takes on it's parent's // attributes, so it appears to have the same # of children. // The first child is always the first child, so you get // infinite recursion on it if you don't check "realness". if (child.m_fRealAccessibleObject && child.ChildCount > 0) { // look through the child objects ah = child.SearchPathByDepth(path, visitor); if (ah != null) { if (path.Next != null) { if (visitor != null) visitor.visitNode(ah); // allow processing of this ah by caller log.endElt(); // end head element return ValueOrChild(path.Next, ah, visitor); } log.endElt(); // end head element return ah; } } } if (visitor != null) visitor.notFound(path); log.endElt(); // end head element return null; }
/// <summary> /// The search is almost breadth-first: /// The first step in the path has an unknown sibling index and its role is not 'value'. /// If this ah has children of the step type, they are searched until all the /// subsequent steps find matching ah's. This search substitutes its own visitor /// that records the successful path ah's. Upon success, a sibling index is assigned to /// the variable named in the step, the non-terminal subpath ah's are exposed /// to the caller's visitor in turn and this method returns the last ah on the path. /// Subpath steps are searched for depending on their content. /// </summary> /// <param name="path">The path to search. Each step in the path contains an /// Accessibility name and Accessibility role of the matching gui object.</param> /// <param name="visitor">null or the object providing methods that are called when steps are found.</param> /// <returns>A new <see cref="AccessibilityHelper"/> object that wraps the matched /// window.</returns> private AccessibilityHelper SearchPathForIndex(GuiPath path, IPathVisitor visitor) { Log log = Log.getOnly(); log.writeElt("head"); log.writeElt("SearchPathForIndex"); log.writeAttr("from", this.Role + ":" + this.Name); log.writeAttr("to", path.toString()); log.endElt(); if (ChildCount <= 0) { if (visitor != null) visitor.notFound(path); return null; } AccessibilityHelper ah = null; int nnum = 0; int index = 0; string parentName = null; if (path.Prev != null) parentName = path.Prev.Name; foreach (AccessibilityHelper child in this) { if (child == null) continue; log.writeElt("SearchPathForIndex"); log.writeAttr("child", (++nnum)); log.writeAttr("path", ah.Role + ":" + ah.Name); // not supposed to be a value! if (path.Next != null && path.Next.Role == AccessibleRole.Alert) log.writeAttr("bad-value", ah.Value); log.endElt(); if (MatchNameAndRole(path.Name, path.Role, child)) { // this is a candidate for matching the path ++index; // the first one is 1 if (path.Next != null) { // This method won't allow for caller's visitors on candidate nodes until proved needed TrackingVisitor tv = new TrackingVisitor(); // tv may need to open sub menus if (tv != null) tv.visitNode(child); // add child to node list to visit later if this is the right path ah = ValueOrChild(path.Next, child, tv); if (ah != null) { // the subpath was matched to the end if (path.VarId != null && path.VarId != "") { // Create and execute a variable to record the index // Var var = new Var(); // var.Id = path.VarId; // var.Set = System.Convert.ToString(index); // 1 based count // var.Execute(); // puts the var in the TestState hash // don't set path.Nth = index since it might change if the path is used again } // let the caller's visitor tend to all the path ah's if (visitor != null) { if (tv != null) tv.StepDownPath(visitor); } log.endElt(); // end head element return ah; } } } else if (parentName != null && child.Role == AccessibleRole.Client && child.Name == parentName) { // try the client instead ah = child.SearchPathForIndex(path, visitor); if (ah != null) { log.endElt(); // end head element return ah; // found it } } } if (visitor != null) visitor.notFound(path); log.endElt(); // end head element return null; }
/// <summary> /// The search is almost breadth-first: /// The first step in the path is searched breath-first. /// Subsequent path steps are searched for depending on their content. /// The Accessibility helper representing the step is returned it is found and is the last step. /// Otherwise, the search continues with the next path step or if not found, null is returned. /// If the first step is found, the visitor is applied before continuing to the next step. /// </summary> /// <param name="path">The path to search. Each step in the path contains an /// Accessibility name and Accessibility role of the matching gui object.</param> /// <param name="visitor">null or the object providing methods that are called when steps are found.</param> /// <returns>A new <see cref="AccessibilityHelper"/> object that wraps the matched /// window.</returns> private AccessibilityHelper SearchPathByBreadth(GuiPath path, IPathVisitor visitor) { if (ChildCount <= 0 || path.Nth <= 0) { if (visitor != null) visitor.notFound(path); return null; } int place = path.Nth; Log log = Log.getOnly(); log.writeElt("SearchPathByBreadth"); log.writeAttr("from", this.Role + ":" + this.Name); log.writeAttr("to", path.toString()); log.endElt(); ArrayList lists = new ArrayList(); // list of child lists ArrayList nodes = MakeChildList(this); lists.Add(nodes); // Examine the first node in the list, dropping it after examination // and adding its children to the end if prudent. log.writeElt("head"); int lnum = 0; while (lists.Count > 0) { int count = 0; // reset match count nodes = (ArrayList)lists[0]; int nnum = 0; ++lnum; while (nodes.Count > 0) { AccessibilityHelper ah = (AccessibilityHelper)nodes[0]; log.writeElt("SearchPathByBreadth"); log.writeAttr("child", lnum + ":" + (++nnum)); log.writeAttr("path", ah.Role + ":" + ah.Name); if (path.Next != null && path.Next.Role == AccessibleRole.Alert) log.writeAttr("value", ah.Value); log.endElt(); if (MatchNameAndRole(path.Name, path.Role, ah)) { // this is the only way to return if (++count >= place) { // Found this step, keep stepping along the path if (path.Next != null) { if (visitor != null) visitor.visitNode(ah); // allow processing of this ah by caller ah = ValueOrChild(path.Next, ah, visitor); } log.endElt(); // end head element return ah; } } if (ah.m_fRealAccessibleObject && ah.ChildCount > 0) lists.Add(MakeChildList(ah)); nodes.RemoveAt(0); // when 0 is removed, all indices slide down 1 } lists.RemoveAt(0); // when 0 is removed, all indices slide down 1 } if (visitor != null) visitor.notFound(path); log.endElt(); // end head element return null; }
/// <summary> /// Finds the accessible object at the end of the path with optional place and varId from /// which it creates a variable object to hold the sibling number of the object found. /// An optional path visitor allows the caller to do something with intermediate steps /// along the path for debugging or interface control. /// The value of a node can be specified via a path step like "value:whatever the value is". /// When place is greater than 0, the search is breadth-first. /// Generally, clients need not be included in the "path". /// When place is 0, a value must be the leaf node of the path /// as there is typically some repeated structure /// like a table, row and cell with value. The structure is /// traversed repeatedly until the leaf node with the value is found. /// When place is negative, depth-first search is used. It tends to be /// the slowest way to search the Accessibility tree. /// </summary> /// <param name="path">The path to search. Each step in the path contains an /// Accessibility name and Accessibility role of the matching gui object.</param> /// <param name="visitor">null or the object providing methods that are called when steps are found.</param> /// <returns>A new <see cref="AccessibilityHelper"/> object that wraps the object found at the end of the path.</returns> public AccessibilityHelper SearchPath(GuiPath path, IPathVisitor visitor) { if (path == null) return null; // three cases based on Nth if (path.Nth > 0) { // psuedo breadth-first return this.SearchPathByBreadth(path, visitor); } if (path.Nth == 0) { // loop over FindChildBreadth to find a sibling index to set a var with varId return this.SearchPathForIndex(path, visitor); } else if (path.Nth < 0) { // depth-first return this.SearchPathByDepth(path, visitor); } return null; }
/// <summary> /// Method to apply when a path node is not found. /// No path above it and none below will call this method; /// only the one not found. /// </summary> /// <param name="path">The path step last tried.</param> public void notFound(GuiPath path) { // do nothing }