Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Properly exclude attributes from snapshots to speed up xpath lookup #386

Merged
merged 10 commits into from
Oct 9, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,6 @@
import static net.gcardone.junidecode.Junidecode.unidecode;

public class AccessibilityNodeInfoDumper {
// https://github.com/appium/appium/issues/10204
private static final int MAX_DEPTH = 70;
private static final String UI_ELEMENT_INDEX = "uiElementIndex";
private static final String NON_XML_CHAR_REPLACEMENT = "?";
private static final String NAMESPACE = "";
Expand All @@ -73,8 +71,7 @@ public class AccessibilityNodeInfoDumper {

@Nullable
private final AccessibilityNodeInfo root;
@Nullable
private SparseArray<UiElement<?, ?>> uiElementsMapping = null;
private final SparseArray<UiElement<?, ?>> uiElementsMapping = new SparseArray<>();
@Nullable
private final Set<Attribute> includedAttributes;
private boolean shouldAddDisplayInfo;
Expand Down Expand Up @@ -125,14 +122,13 @@ private static String toXmlNodeName(@Nullable String className) {
return fixedName;
}

private void serializeUiElement(UiElement<?, ?> uiElement, final int depth) throws IOException {
private void serializeUiElement(UiElement<?, ?> uiElement, boolean isIndexed) throws IOException {
final String className = uiElement.getClassName();
final String nodeName = toXmlNodeName(className);
serializer.startTag(NAMESPACE, nodeName);

for (Attribute attr : uiElement.attributeKeys()) {
if (!attr.isExposableToXml()
|| includedAttributes != null && !includedAttributes.contains(attr)) {
if (!attr.isExposableToXml()) {
continue;
}
Object value = uiElement.get(attr);
Expand All @@ -147,58 +143,49 @@ private void serializeUiElement(UiElement<?, ?> uiElement, final int depth) thro
shouldAddDisplayInfo = false;
}

if (uiElementsMapping != null) {
final int uiElementIndex = uiElementsMapping.size();
uiElementsMapping.put(uiElementIndex, uiElement);
final int uiElementIndex = uiElementsMapping.size();
uiElementsMapping.put(uiElementIndex, uiElement);
if (isIndexed) {
serializer.attribute(NAMESPACE, UI_ELEMENT_INDEX, Integer.toString(uiElementIndex));
}

if (depth >= MAX_DEPTH) {
Logger.error(String.format("The xml tree dump has reached its maximum depth of %s at " +
"'%s'. The recursion is stopped to avoid StackOverflowError", MAX_DEPTH, className));
} else {
for (UiElement<?, ?> child : uiElement.getChildren()) {
serializeUiElement(child, depth + 1);
}
for (UiElement<?, ?> child : uiElement.getChildren()) {
serializeUiElement(child, isIndexed);
}
serializer.endTag(NAMESPACE, nodeName);
}

private InputStream toStream() throws IOException {
private InputStream toStream(boolean isIndexed) throws IOException {
final long startTime = SystemClock.uptimeMillis();
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
serializer = Xml.newSerializer();
shouldAddDisplayInfo = root == null;
serializer.setOutput(outputStream, XML_ENCODING);
serializer.startDocument(XML_ENCODING, true);
serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
final UiElement<?, ?> xpathRoot = root == null
? UiElementSnapshot.take(getCachedWindowRoots(), NotificationListener.getInstance().getToastMessage())
: UiElementSnapshot.take(root);
serializeUiElement(xpathRoot, 0);
final UiElement<?, ?> uiRootElement = root == null
? UiElementSnapshot.take(getCachedWindowRoots(), NotificationListener.getInstance().getToastMessage(), includedAttributes)
: UiElementSnapshot.take(root, includedAttributes);
serializeUiElement(uiRootElement, isIndexed);
serializer.endDocument();
Logger.debug(String.format("The source XML tree (%s bytes) has been fetched in %sms",
outputStream.size(), SystemClock.uptimeMillis() - startTime));
return new ByteArrayInputStream(outputStream.toByteArray());
}
}

private void performCleanup() {
uiElementsMapping = null;
}

public String dumpToXml() {
try {
RESOURCES_GUARD.acquire();
} catch (InterruptedException e) {
throw new UiAutomator2Exception(e);
}
try (InputStream xmlStream = toStream()) {
try (InputStream xmlStream = toStream(false)) {
return IOUtils.toString(xmlStream, XML_ENCODING);
} catch (IOException e) {
throw new UiAutomator2Exception(e);
} finally {
performCleanup();
uiElementsMapping.clear();
RESOURCES_GUARD.release();
}
}
Expand All @@ -215,8 +202,7 @@ public NodeInfoList findNodes(String xpathSelector, boolean multiple) {
} catch (InterruptedException e) {
throw new UiAutomator2Exception(e);
}
uiElementsMapping = new SparseArray<>();
try (InputStream xmlStream = toStream()) {
try (InputStream xmlStream = toStream(true)) {
final Document document = SAX_BUILDER.build(xmlStream);
final XPathExpression<org.jdom2.Attribute> expr = XPATH
.compile(String.format("(%s)/@%s", xpathSelector, UI_ELEMENT_INDEX), Filters.attribute());
Expand All @@ -243,7 +229,7 @@ public NodeInfoList findNodes(String xpathSelector, boolean multiple) {
} catch (Exception e) {
throw new UiAutomator2Exception(e);
} finally {
performCleanup();
uiElementsMapping.clear();
RESOURCES_GUARD.release();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ protected long getToastClearTimeout() {
@NonNull
public List<CharSequence> getToastMessage() {
if (!toastMessage.isEmpty() && currentTimeMillis() - recentToastTimestamp > getToastClearTimeout()) {
Logger.debug("Clearing toast message: " + toastMessage);
Logger.info("Clearing toast message: " + toastMessage);
toastMessage.clear();
}
return toastMessage;
Expand Down
Loading