Refactored the ComponentLocator class to allow for implementing different locator strategies. Change-Id: I93f3decbce4d4361cc605bcf0ce4379a187c482ctags/7.2.0.beta1
@@ -72,6 +72,7 @@ import com.vaadin.client.communication.JsonEncoder; | |||
import com.vaadin.client.communication.PushConnection; | |||
import com.vaadin.client.communication.RpcManager; | |||
import com.vaadin.client.communication.StateChangeEvent; | |||
import com.vaadin.client.componentlocator.ComponentLocator; | |||
import com.vaadin.client.extensions.AbstractExtensionConnector; | |||
import com.vaadin.client.metadata.ConnectorBundleLoader; | |||
import com.vaadin.client.metadata.Method; | |||
@@ -496,38 +497,38 @@ public class ApplicationConnection { | |||
private native void initializeTestbenchHooks( | |||
ComponentLocator componentLocator, String TTAppId) | |||
/*-{ | |||
var ap = this; | |||
var client = {}; | |||
client.isActive = $entry(function() { | |||
return ap.@com.vaadin.client.ApplicationConnection::hasActiveRequest()() | |||
|| ap.@com.vaadin.client.ApplicationConnection::isExecutingDeferredCommands()(); | |||
}); | |||
var vi = ap.@com.vaadin.client.ApplicationConnection::getVersionInfo()(); | |||
if (vi) { | |||
client.getVersionInfo = function() { | |||
return vi; | |||
} | |||
} | |||
var ap = this; | |||
var client = {}; | |||
client.isActive = $entry(function() { | |||
return ap.@com.vaadin.client.ApplicationConnection::hasActiveRequest()() | |||
|| ap.@com.vaadin.client.ApplicationConnection::isExecutingDeferredCommands()(); | |||
}); | |||
var vi = ap.@com.vaadin.client.ApplicationConnection::getVersionInfo()(); | |||
if (vi) { | |||
client.getVersionInfo = function() { | |||
return vi; | |||
} | |||
} | |||
client.getProfilingData = $entry(function() { | |||
var pd = [ | |||
ap.@com.vaadin.client.ApplicationConnection::lastProcessingTime, | |||
client.getProfilingData = $entry(function() { | |||
var pd = [ | |||
ap.@com.vaadin.client.ApplicationConnection::lastProcessingTime, | |||
ap.@com.vaadin.client.ApplicationConnection::totalProcessingTime | |||
]; | |||
pd = pd.concat(ap.@com.vaadin.client.ApplicationConnection::serverTimingInfo); | |||
pd[pd.length] = ap.@com.vaadin.client.ApplicationConnection::bootstrapTime; | |||
return pd; | |||
}); | |||
]; | |||
pd = pd.concat(ap.@com.vaadin.client.ApplicationConnection::serverTimingInfo); | |||
pd[pd.length] = ap.@com.vaadin.client.ApplicationConnection::bootstrapTime; | |||
return pd; | |||
}); | |||
client.getElementByPath = $entry(function(id) { | |||
return componentLocator.@com.vaadin.client.ComponentLocator::getElementByPath(Ljava/lang/String;)(id); | |||
}); | |||
client.getPathForElement = $entry(function(element) { | |||
return componentLocator.@com.vaadin.client.ComponentLocator::getPathForElement(Lcom/google/gwt/user/client/Element;)(element); | |||
}); | |||
client.initializing = false; | |||
client.getElementByPath = $entry(function(id) { | |||
return componentLocator.@com.vaadin.client.componentlocator.ComponentLocator::getElementByPath(Ljava/lang/String;)(id); | |||
}); | |||
client.getPathForElement = $entry(function(element) { | |||
return componentLocator.@com.vaadin.client.componentlocator.ComponentLocator::getPathForElement(Lcom/google/gwt/user/client/Element;)(element); | |||
}); | |||
client.initializing = false; | |||
$wnd.vaadin.clients[TTAppId] = client; | |||
$wnd.vaadin.clients[TTAppId] = client; | |||
}-*/; | |||
private static native final int calculateBootstrapTime() |
@@ -15,706 +15,20 @@ | |||
*/ | |||
package com.vaadin.client; | |||
import java.util.ArrayList; | |||
import java.util.Iterator; | |||
import java.util.List; | |||
import com.google.gwt.core.client.JavaScriptObject; | |||
import com.google.gwt.user.client.DOM; | |||
import com.google.gwt.user.client.Element; | |||
import com.google.gwt.user.client.ui.HasWidgets; | |||
import com.google.gwt.user.client.ui.RootPanel; | |||
import com.google.gwt.user.client.ui.Widget; | |||
import com.vaadin.client.ui.SubPartAware; | |||
import com.vaadin.client.ui.VCssLayout; | |||
import com.vaadin.client.ui.VGridLayout; | |||
import com.vaadin.client.ui.VOverlay; | |||
import com.vaadin.client.ui.VTabsheetPanel; | |||
import com.vaadin.client.ui.VUI; | |||
import com.vaadin.client.ui.VWindow; | |||
import com.vaadin.client.ui.orderedlayout.Slot; | |||
import com.vaadin.client.ui.orderedlayout.VAbstractOrderedLayout; | |||
import com.vaadin.client.ui.window.WindowConnector; | |||
import com.vaadin.shared.AbstractComponentState; | |||
import com.vaadin.shared.Connector; | |||
import com.vaadin.shared.communication.SharedState; | |||
/** | |||
* ComponentLocator provides methods for generating a String locator for a given | |||
* DOM element and for locating a DOM element using a String locator. | |||
* | |||
* @since 5.4 | |||
* @deprecated Moved to com.vaadin.client.componentlocator.ComponentLocator | |||
*/ | |||
public class ComponentLocator { | |||
/** | |||
* Separator used in the String locator between a parent and a child widget. | |||
*/ | |||
private static final String PARENTCHILD_SEPARATOR = "/"; | |||
/** | |||
* Separator used in the String locator between the part identifying the | |||
* containing widget and the part identifying the target element within the | |||
* widget. | |||
*/ | |||
private static final String SUBPART_SEPARATOR = "#"; | |||
/** | |||
* String that identifies the root panel when appearing first in the String | |||
* locator. | |||
*/ | |||
private static final String ROOT_ID = "Root"; | |||
/** | |||
* Reference to ApplicationConnection instance. | |||
*/ | |||
private ApplicationConnection client; | |||
public class ComponentLocator extends com.vaadin.client.componentlocator.ComponentLocator { | |||
/** | |||
* Construct a ComponentLocator for the given ApplicationConnection. | |||
* | |||
* @param client | |||
* ApplicationConnection instance for the application. | |||
* | |||
* @param client ApplicationConnection instance for the application. | |||
*/ | |||
public ComponentLocator(ApplicationConnection client) { | |||
this.client = client; | |||
} | |||
/** | |||
* Generates a String locator which uniquely identifies the target element. | |||
* The {@link #getElementByPath(String)} method can be used for the inverse | |||
* operation, i.e. locating an element based on the return value from this | |||
* method. | |||
* <p> | |||
* Note that getElementByPath(getPathForElement(element)) == element is not | |||
* always true as {@link #getPathForElement(Element)} can return a path to | |||
* another element if the widget determines an action on the other element | |||
* will give the same result as the action on the target element. | |||
* </p> | |||
* | |||
* @since 5.4 | |||
* @param targetElement | |||
* The element to generate a path for. | |||
* @return A String locator that identifies the target element or null if a | |||
* String locator could not be created. | |||
*/ | |||
public String getPathForElement(Element targetElement) { | |||
String pid = null; | |||
targetElement = getElement(targetElement); | |||
Element e = targetElement; | |||
while (true) { | |||
pid = ConnectorMap.get(client).getConnectorId(e); | |||
if (pid != null) { | |||
break; | |||
} | |||
e = DOM.getParent(e); | |||
if (e == null) { | |||
break; | |||
} | |||
} | |||
Widget w = null; | |||
if (pid != null) { | |||
// If we found a Paintable then we use that as reference. We should | |||
// find the Paintable for all but very special cases (like | |||
// overlays). | |||
w = ((ComponentConnector) ConnectorMap.get(client) | |||
.getConnector(pid)).getWidget(); | |||
/* | |||
* Still if the Paintable contains a widget that implements | |||
* SubPartAware, we want to use that as a reference | |||
*/ | |||
Widget targetParent = findParentWidget(targetElement, w); | |||
while (targetParent != w && targetParent != null) { | |||
if (targetParent instanceof SubPartAware) { | |||
/* | |||
* The targetParent widget is a child of the Paintable and | |||
* the first parent (of the targetElement) that implements | |||
* SubPartAware | |||
*/ | |||
w = targetParent; | |||
break; | |||
} | |||
targetParent = targetParent.getParent(); | |||
} | |||
} | |||
if (w == null) { | |||
// Check if the element is part of a widget that is attached | |||
// directly to the root panel | |||
RootPanel rootPanel = RootPanel.get(); | |||
int rootWidgetCount = rootPanel.getWidgetCount(); | |||
for (int i = 0; i < rootWidgetCount; i++) { | |||
Widget rootWidget = rootPanel.getWidget(i); | |||
if (rootWidget.getElement().isOrHasChild(targetElement)) { | |||
// The target element is contained by this root widget | |||
w = findParentWidget(targetElement, rootWidget); | |||
break; | |||
} | |||
} | |||
if (w != null) { | |||
// We found a widget but we should still see if we find a | |||
// SubPartAware implementor (we cannot find the Paintable as | |||
// there is no link from VOverlay to its paintable/owner). | |||
Widget subPartAwareWidget = findSubPartAwareParentWidget(w); | |||
if (subPartAwareWidget != null) { | |||
w = subPartAwareWidget; | |||
} | |||
} | |||
} | |||
if (w == null) { | |||
// Containing widget not found | |||
return null; | |||
} | |||
// Determine the path for the target widget | |||
String path = getPathForWidget(w); | |||
if (path == null) { | |||
/* | |||
* No path could be determined for the target widget. Cannot create | |||
* a locator string. | |||
*/ | |||
return null; | |||
} | |||
// The parent check is a work around for Firefox 15 which fails to | |||
// compare elements properly (#9534) | |||
if (w.getElement() == targetElement) { | |||
/* | |||
* We are done if the target element is the root of the target | |||
* widget. | |||
*/ | |||
return path; | |||
} else if (w instanceof SubPartAware) { | |||
/* | |||
* If the widget can provide an identifier for the targetElement we | |||
* let it do that | |||
*/ | |||
String elementLocator = ((SubPartAware) w) | |||
.getSubPartName(targetElement); | |||
if (elementLocator != null) { | |||
return path + SUBPART_SEPARATOR + elementLocator; | |||
} | |||
} | |||
/* | |||
* If everything else fails we use the DOM path to identify the target | |||
* element | |||
*/ | |||
String domPath = getDOMPathForElement(targetElement, w.getElement()); | |||
if (domPath == null) { | |||
return path; | |||
} else { | |||
return path + domPath; | |||
} | |||
} | |||
/** | |||
* Returns the element passed to the method. Or in case of Firefox 15, | |||
* returns the real element that is in the DOM instead of the element passed | |||
* to the method (which is the same element but not ==). | |||
* | |||
* @param targetElement | |||
* the element to return | |||
* @return the element passed to the method | |||
*/ | |||
private Element getElement(Element targetElement) { | |||
if (targetElement == null) { | |||
return null; | |||
} | |||
if (!BrowserInfo.get().isFirefox()) { | |||
return targetElement; | |||
} | |||
if (BrowserInfo.get().getBrowserMajorVersion() != 15) { | |||
return targetElement; | |||
} | |||
// Firefox 15, you make me sad | |||
if (targetElement.getNextSibling() != null) { | |||
return (Element) targetElement.getNextSibling() | |||
.getPreviousSibling(); | |||
} | |||
if (targetElement.getPreviousSibling() != null) { | |||
return (Element) targetElement.getPreviousSibling() | |||
.getNextSibling(); | |||
} | |||
// No siblings so this is the only child | |||
return (Element) targetElement.getParentNode().getChild(0); | |||
} | |||
/** | |||
* Finds the first widget in the hierarchy (moving upwards) that implements | |||
* SubPartAware. Returns the SubPartAware implementor or null if none is | |||
* found. | |||
* | |||
* @param w | |||
* The widget to start from. This is returned if it implements | |||
* SubPartAware. | |||
* @return The first widget (upwards in hierarchy) that implements | |||
* SubPartAware or null | |||
*/ | |||
private Widget findSubPartAwareParentWidget(Widget w) { | |||
while (w != null) { | |||
if (w instanceof SubPartAware) { | |||
return w; | |||
} | |||
w = w.getParent(); | |||
} | |||
return null; | |||
} | |||
/** | |||
* Returns the first widget found when going from {@code targetElement} | |||
* upwards in the DOM hierarchy, assuming that {@code ancestorWidget} is a | |||
* parent of {@code targetElement}. | |||
* | |||
* @param targetElement | |||
* @param ancestorWidget | |||
* @return The widget whose root element is a parent of | |||
* {@code targetElement}. | |||
*/ | |||
private Widget findParentWidget(Element targetElement, Widget ancestorWidget) { | |||
/* | |||
* As we cannot resolve Widgets from the element we start from the | |||
* widget and move downwards to the correct child widget, as long as we | |||
* find one. | |||
*/ | |||
if (ancestorWidget instanceof HasWidgets) { | |||
for (Widget w : ((HasWidgets) ancestorWidget)) { | |||
if (w.getElement().isOrHasChild(targetElement)) { | |||
return findParentWidget(targetElement, w); | |||
} | |||
} | |||
} | |||
// No children found, this is it | |||
return ancestorWidget; | |||
} | |||
/** | |||
* Locates an element based on a DOM path and a base element. | |||
* | |||
* @param baseElement | |||
* The base element which the path is relative to | |||
* @param path | |||
* String locator (consisting of domChild[x] parts) that | |||
* identifies the element | |||
* @return The element identified by path, relative to baseElement or null | |||
* if the element could not be found. | |||
*/ | |||
private Element getElementByDOMPath(Element baseElement, String path) { | |||
String parts[] = path.split(PARENTCHILD_SEPARATOR); | |||
Element element = baseElement; | |||
for (String part : parts) { | |||
if (part.startsWith("domChild[")) { | |||
String childIndexString = part.substring("domChild[".length(), | |||
part.length() - 1); | |||
if (Util.findWidget(baseElement, null) instanceof VAbstractOrderedLayout) { | |||
if (element.hasChildNodes()) { | |||
Element e = element.getFirstChildElement().cast(); | |||
String cn = e.getClassName(); | |||
if (cn != null | |||
&& (cn.equals("v-expand") || cn | |||
.contains("v-has-caption"))) { | |||
element = e; | |||
} | |||
} | |||
} | |||
try { | |||
int childIndex = Integer.parseInt(childIndexString); | |||
element = DOM.getChild(element, childIndex); | |||
} catch (Exception e) { | |||
return null; | |||
} | |||
if (element == null) { | |||
return null; | |||
} | |||
} | |||
} | |||
return element; | |||
super(client); | |||
} | |||
/** | |||
* Generates a String locator using domChild[x] parts for the element | |||
* relative to the baseElement. | |||
* | |||
* @param element | |||
* The target element | |||
* @param baseElement | |||
* The starting point for the locator. The generated path is | |||
* relative to this element. | |||
* @return A String locator that can be used to locate the target element | |||
* using {@link #getElementByDOMPath(Element, String)} or null if | |||
* the locator String cannot be created. | |||
*/ | |||
private String getDOMPathForElement(Element element, Element baseElement) { | |||
Element e = element; | |||
String path = ""; | |||
while (true) { | |||
int childIndex = -1; | |||
Element siblingIterator = e; | |||
while (siblingIterator != null) { | |||
childIndex++; | |||
siblingIterator = siblingIterator.getPreviousSiblingElement() | |||
.cast(); | |||
} | |||
path = PARENTCHILD_SEPARATOR + "domChild[" + childIndex + "]" | |||
+ path; | |||
JavaScriptObject parent = e.getParentElement(); | |||
if (parent == null) { | |||
return null; | |||
} | |||
// The parent check is a work around for Firefox 15 which fails to | |||
// compare elements properly (#9534) | |||
if (parent == baseElement) { | |||
break; | |||
} | |||
e = parent.cast(); | |||
} | |||
return path; | |||
} | |||
/** | |||
* Locates an element using a String locator (path) which identifies a DOM | |||
* element. The {@link #getPathForElement(Element)} method can be used for | |||
* the inverse operation, i.e. generating a string expression for a DOM | |||
* element. | |||
* | |||
* @since 5.4 | |||
* @param path | |||
* The String locater which identifies the target element. | |||
* @return The DOM element identified by {@code path} or null if the element | |||
* could not be located. | |||
*/ | |||
public Element getElementByPath(String path) { | |||
/* | |||
* Path is of type "targetWidgetPath#componentPart" or | |||
* "targetWidgetPath". | |||
*/ | |||
String parts[] = path.split(SUBPART_SEPARATOR, 2); | |||
String widgetPath = parts[0]; | |||
Widget w = getWidgetFromPath(widgetPath); | |||
if (w == null || !Util.isAttachedAndDisplayed(w)) { | |||
return null; | |||
} | |||
if (parts.length == 1) { | |||
int pos = widgetPath.indexOf("domChild"); | |||
if (pos == -1) { | |||
return w.getElement(); | |||
} | |||
// Contains dom reference to a sub element of the widget | |||
String subPath = widgetPath.substring(pos); | |||
return getElementByDOMPath(w.getElement(), subPath); | |||
} else if (parts.length == 2) { | |||
if (w instanceof SubPartAware) { | |||
return ((SubPartAware) w).getSubPartElement(parts[1]); | |||
} | |||
} | |||
return null; | |||
} | |||
/** | |||
* Creates a locator String for the given widget. The path can be used to | |||
* locate the widget using {@link #getWidgetFromPath(String)}. | |||
* | |||
* Returns null if no path can be determined for the widget or if the widget | |||
* is null. | |||
* | |||
* @param w | |||
* The target widget | |||
* @return A String locator for the widget | |||
*/ | |||
private String getPathForWidget(Widget w) { | |||
if (w == null) { | |||
return null; | |||
} | |||
String elementId = w.getElement().getId(); | |||
if (elementId != null && !elementId.isEmpty() | |||
&& !elementId.startsWith("gwt-uid-")) { | |||
// Use PID_S+id if the user has set an id but do not use it for auto | |||
// generated id:s as these might not be consistent | |||
return "PID_S" + elementId; | |||
} else if (w instanceof VUI) { | |||
return ""; | |||
} else if (w instanceof VWindow) { | |||
Connector windowConnector = ConnectorMap.get(client) | |||
.getConnector(w); | |||
List<WindowConnector> subWindowList = client.getUIConnector() | |||
.getSubWindows(); | |||
int indexOfSubWindow = subWindowList.indexOf(windowConnector); | |||
return PARENTCHILD_SEPARATOR + "VWindow[" + indexOfSubWindow + "]"; | |||
} else if (w instanceof RootPanel) { | |||
return ROOT_ID; | |||
} | |||
Widget parent = w.getParent(); | |||
String basePath = getPathForWidget(parent); | |||
if (basePath == null) { | |||
return null; | |||
} | |||
String simpleName = Util.getSimpleName(w); | |||
/* | |||
* Check if the parent implements Iterable. At least VPopupView does not | |||
* implement HasWdgets so we cannot check for that. | |||
*/ | |||
if (!(parent instanceof Iterable<?>)) { | |||
// Parent does not implement Iterable so we cannot find out which | |||
// child this is | |||
return null; | |||
} | |||
Iterator<?> i = ((Iterable<?>) parent).iterator(); | |||
int pos = 0; | |||
while (i.hasNext()) { | |||
Object child = i.next(); | |||
if (child == w) { | |||
return basePath + PARENTCHILD_SEPARATOR + simpleName + "[" | |||
+ pos + "]"; | |||
} | |||
String simpleName2 = Util.getSimpleName(child); | |||
if (simpleName.equals(simpleName2)) { | |||
pos++; | |||
} | |||
} | |||
return null; | |||
} | |||
/** | |||
* Locates the widget based on a String locator. | |||
* | |||
* @param path | |||
* The String locator that identifies the widget. | |||
* @return The Widget identified by the String locator or null if the widget | |||
* could not be identified. | |||
*/ | |||
private Widget getWidgetFromPath(String path) { | |||
Widget w = null; | |||
String parts[] = path.split(PARENTCHILD_SEPARATOR); | |||
for (int i = 0; i < parts.length; i++) { | |||
String part = parts[i]; | |||
if (part.equals(ROOT_ID)) { | |||
w = RootPanel.get(); | |||
} else if (part.equals("")) { | |||
w = client.getUIConnector().getWidget(); | |||
} else if (w == null) { | |||
String id = part; | |||
// Must be old static pid (PID_S*) | |||
ServerConnector connector = ConnectorMap.get(client) | |||
.getConnector(id); | |||
if (connector == null) { | |||
// Lookup by component id | |||
// TODO Optimize this | |||
connector = findConnectorById(client.getUIConnector(), | |||
id.substring(5)); | |||
} | |||
if (connector instanceof ComponentConnector) { | |||
w = ((ComponentConnector) connector).getWidget(); | |||
} else { | |||
// Not found | |||
return null; | |||
} | |||
} else if (part.startsWith("domChild[")) { | |||
// The target widget has been found and the rest identifies the | |||
// element | |||
break; | |||
} else if (w instanceof Iterable) { | |||
// W identifies a widget that contains other widgets, as it | |||
// should. Try to locate the child | |||
Iterable<?> parent = (Iterable<?>) w; | |||
// Part is of type "VVerticalLayout[0]", split this into | |||
// VVerticalLayout and 0 | |||
String[] split = part.split("\\[", 2); | |||
String widgetClassName = split[0]; | |||
String indexString = split[1].substring(0, | |||
split[1].length() - 1); | |||
int widgetPosition = Integer.parseInt(indexString); | |||
// AbsolutePanel in GridLayout has been removed -> skip it | |||
if (w instanceof VGridLayout | |||
&& "AbsolutePanel".equals(widgetClassName)) { | |||
continue; | |||
} | |||
// FlowPane in CSSLayout has been removed -> skip it | |||
if (w instanceof VCssLayout | |||
&& "VCssLayout$FlowPane".equals(widgetClassName)) { | |||
continue; | |||
} | |||
// ChildComponentContainer and VOrderedLayout$Slot have been | |||
// replaced with Slot | |||
if (w instanceof VAbstractOrderedLayout | |||
&& ("ChildComponentContainer".equals(widgetClassName) || "VOrderedLayout$Slot" | |||
.equals(widgetClassName))) { | |||
widgetClassName = "Slot"; | |||
} | |||
if (w instanceof VTabsheetPanel && widgetPosition != 0) { | |||
// TabSheetPanel now only contains 1 connector => the index | |||
// is always 0 which indicates the widget in the active tab | |||
widgetPosition = 0; | |||
} | |||
if (w instanceof VOverlay | |||
&& "VCalendarPanel".equals(widgetClassName)) { | |||
// Vaadin 7.1 adds a wrapper for datefield popups | |||
parent = (Iterable<?>) ((Iterable) parent).iterator() | |||
.next(); | |||
} | |||
/* | |||
* The new grid and ordered layotus do not contain | |||
* ChildComponentContainer widgets. This is instead simulated by | |||
* constructing a path step that would find the desired widget | |||
* from the layout and injecting it as the next search step | |||
* (which would originally have found the widget inside the | |||
* ChildComponentContainer) | |||
*/ | |||
if ((w instanceof VGridLayout) | |||
&& "ChildComponentContainer".equals(widgetClassName) | |||
&& i + 1 < parts.length) { | |||
HasWidgets layout = (HasWidgets) w; | |||
String nextPart = parts[i + 1]; | |||
String[] nextSplit = nextPart.split("\\[", 2); | |||
String nextWidgetClassName = nextSplit[0]; | |||
// Find the n:th child and count the number of children with | |||
// the same type before it | |||
int nextIndex = 0; | |||
for (Widget child : layout) { | |||
boolean matchingType = nextWidgetClassName.equals(Util | |||
.getSimpleName(child)); | |||
if (matchingType && widgetPosition == 0) { | |||
// This is the n:th child that we looked for | |||
break; | |||
} else if (widgetPosition < 0) { | |||
// Error if we're past the desired position without | |||
// a match | |||
return null; | |||
} else if (matchingType) { | |||
// If this was another child of the expected type, | |||
// increase the count for the next step | |||
nextIndex++; | |||
} | |||
// Don't count captions | |||
if (!(child instanceof VCaption)) { | |||
widgetPosition--; | |||
} | |||
} | |||
// Advance to the next step, this time checking for the | |||
// actual child widget | |||
parts[i + 1] = nextWidgetClassName + '[' + nextIndex + ']'; | |||
continue; | |||
} | |||
// Locate the child | |||
Iterator<? extends Widget> iterator; | |||
/* | |||
* VWindow and VContextMenu workarounds for backwards | |||
* compatibility | |||
*/ | |||
if (widgetClassName.equals("VWindow")) { | |||
List<WindowConnector> windows = client.getUIConnector() | |||
.getSubWindows(); | |||
List<VWindow> windowWidgets = new ArrayList<VWindow>( | |||
windows.size()); | |||
for (WindowConnector wc : windows) { | |||
windowWidgets.add(wc.getWidget()); | |||
} | |||
iterator = windowWidgets.iterator(); | |||
} else if (widgetClassName.equals("VContextMenu")) { | |||
return client.getContextMenu(); | |||
} else { | |||
iterator = (Iterator<? extends Widget>) parent.iterator(); | |||
} | |||
boolean ok = false; | |||
// Find the widgetPosition:th child of type "widgetClassName" | |||
while (iterator.hasNext()) { | |||
Widget child = iterator.next(); | |||
String simpleName2 = Util.getSimpleName(child); | |||
if (!widgetClassName.equals(simpleName2) | |||
&& child instanceof Slot) { | |||
/* | |||
* Support legacy tests without any selector for the | |||
* Slot widget (i.e. /VVerticalLayout[0]/VButton[0]) by | |||
* directly checking the stuff inside the slot | |||
*/ | |||
child = ((Slot) child).getWidget(); | |||
simpleName2 = Util.getSimpleName(child); | |||
} | |||
if (widgetClassName.equals(simpleName2)) { | |||
if (widgetPosition == 0) { | |||
w = child; | |||
ok = true; | |||
break; | |||
} | |||
widgetPosition--; | |||
} | |||
} | |||
if (!ok) { | |||
// Did not find the child | |||
return null; | |||
} | |||
} else { | |||
// W identifies something that is not a "HasWidgets". This | |||
// should not happen as all widget containers should implement | |||
// HasWidgets. | |||
return null; | |||
} | |||
} | |||
return w; | |||
} | |||
private ServerConnector findConnectorById(ServerConnector root, String id) { | |||
SharedState state = root.getState(); | |||
if (state instanceof AbstractComponentState | |||
&& id.equals(((AbstractComponentState) state).id)) { | |||
return root; | |||
} | |||
for (ServerConnector child : root.getChildren()) { | |||
ServerConnector found = findConnectorById(child, id); | |||
if (found != null) { | |||
return found; | |||
} | |||
} | |||
return null; | |||
} | |||
} |
@@ -0,0 +1,131 @@ | |||
/* | |||
* Copyright 2000-2013 Vaadin Ltd. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not | |||
* use this file except in compliance with the License. You may obtain a copy of | |||
* the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
* License for the specific language governing permissions and limitations under | |||
* the License. | |||
*/ | |||
package com.vaadin.client.componentlocator; | |||
import com.google.gwt.user.client.Element; | |||
import com.vaadin.client.ApplicationConnection; | |||
import com.vaadin.client.BrowserInfo; | |||
/** | |||
* ComponentLocator provides methods for generating a String locator for a given | |||
* DOM element and for locating a DOM element using a String locator. | |||
* <p> | |||
* The main use for this class is locating components for automated testing purposes. | |||
* | |||
* @since 7.2, moved from {@link com.vaadin.client.ComponentLocator} | |||
*/ | |||
public class ComponentLocator { | |||
private final LegacyLocatorStrategy legacyLocatorStrategy = new LegacyLocatorStrategy( | |||
this); | |||
/** | |||
* Reference to ApplicationConnection instance. | |||
*/ | |||
private ApplicationConnection client; | |||
/** | |||
* Construct a ComponentLocator for the given ApplicationConnection. | |||
* | |||
* @param client | |||
* ApplicationConnection instance for the application. | |||
*/ | |||
public ComponentLocator(ApplicationConnection client) { | |||
this.client = client; | |||
} | |||
/** | |||
* Generates a String locator which uniquely identifies the target element. | |||
* The {@link #getElementByPath(String)} method can be used for the inverse | |||
* operation, i.e. locating an element based on the return value from this | |||
* method. | |||
* <p> | |||
* Note that getElementByPath(getPathForElement(element)) == element is not | |||
* always true as #getPathForElement(Element) can return a path to another | |||
* element if the widget determines an action on the other element will give | |||
* the same result as the action on the target element. | |||
* </p> | |||
* | |||
* @since 5.4 | |||
* @param targetElement | |||
* The element to generate a path for. | |||
* @return A String locator that identifies the target element or null if a | |||
* String locator could not be created. | |||
*/ | |||
public String getPathForElement(Element targetElement) { | |||
return legacyLocatorStrategy | |||
.getPathForElement(getElement(targetElement)); | |||
} | |||
/** | |||
* Returns the element passed to the method. Or in case of Firefox 15, | |||
* returns the real element that is in the DOM instead of the element passed | |||
* to the method (which is the same element but not ==). | |||
* | |||
* @param targetElement | |||
* the element to return | |||
* @return the element passed to the method | |||
*/ | |||
private Element getElement(Element targetElement) { | |||
if (targetElement == null) { | |||
return null; | |||
} | |||
if (!BrowserInfo.get().isFirefox()) { | |||
return targetElement; | |||
} | |||
if (BrowserInfo.get().getBrowserMajorVersion() != 15) { | |||
return targetElement; | |||
} | |||
// Firefox 15, you make me sad | |||
if (targetElement.getNextSibling() != null) { | |||
return (Element) targetElement.getNextSibling() | |||
.getPreviousSibling(); | |||
} | |||
if (targetElement.getPreviousSibling() != null) { | |||
return (Element) targetElement.getPreviousSibling() | |||
.getNextSibling(); | |||
} | |||
// No siblings so this is the only child | |||
return (Element) targetElement.getParentNode().getChild(0); | |||
} | |||
/** | |||
* Locates an element using a String locator (path) which identifies a DOM | |||
* element. The {@link #getPathForElement(Element)} method can be used for | |||
* the inverse operation, i.e. generating a string expression for a DOM | |||
* element. | |||
* | |||
* @since 5.4 | |||
* @param path | |||
* The String locator which identifies the target element. | |||
* @return The DOM element identified by {@code path} or null if the element | |||
* could not be located. | |||
*/ | |||
public Element getElementByPath(String path) { | |||
return legacyLocatorStrategy.getElementByPath(path); | |||
} | |||
/** | |||
* @return the application connection | |||
*/ | |||
ApplicationConnection getClient() { | |||
return client; | |||
} | |||
} |
@@ -0,0 +1,637 @@ | |||
/* | |||
* Copyright 2000-2013 Vaadin Ltd. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not | |||
* use this file except in compliance with the License. You may obtain a copy of | |||
* the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
* License for the specific language governing permissions and limitations under | |||
* the License. | |||
*/ | |||
package com.vaadin.client.componentlocator; | |||
import java.util.ArrayList; | |||
import java.util.Iterator; | |||
import java.util.List; | |||
import com.google.gwt.core.client.JavaScriptObject; | |||
import com.google.gwt.user.client.DOM; | |||
import com.google.gwt.user.client.Element; | |||
import com.google.gwt.user.client.ui.HasWidgets; | |||
import com.google.gwt.user.client.ui.RootPanel; | |||
import com.google.gwt.user.client.ui.Widget; | |||
import com.vaadin.client.ComponentConnector; | |||
import com.vaadin.client.ConnectorMap; | |||
import com.vaadin.client.ServerConnector; | |||
import com.vaadin.client.Util; | |||
import com.vaadin.client.VCaption; | |||
import com.vaadin.client.ui.SubPartAware; | |||
import com.vaadin.client.ui.VCssLayout; | |||
import com.vaadin.client.ui.VGridLayout; | |||
import com.vaadin.client.ui.VOverlay; | |||
import com.vaadin.client.ui.VTabsheetPanel; | |||
import com.vaadin.client.ui.VUI; | |||
import com.vaadin.client.ui.VWindow; | |||
import com.vaadin.client.ui.orderedlayout.Slot; | |||
import com.vaadin.client.ui.orderedlayout.VAbstractOrderedLayout; | |||
import com.vaadin.client.ui.window.WindowConnector; | |||
import com.vaadin.shared.AbstractComponentState; | |||
import com.vaadin.shared.Connector; | |||
import com.vaadin.shared.communication.SharedState; | |||
/** | |||
* The LegacyLocatorStrategy class handles the legacy locator syntax that was | |||
* introduced in version 5.4 of the framework. The legacy locator strategy is | |||
* always used if no other strategy claims responsibility for a locator string. | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
public class LegacyLocatorStrategy implements LocatorStrategy { | |||
private final ComponentLocator componentLocator; | |||
/** | |||
* Separator used in the String locator between a parent and a child widget. | |||
*/ | |||
static final String PARENTCHILD_SEPARATOR = "/"; | |||
/** | |||
* Separator used in the String locator between the part identifying the | |||
* containing widget and the part identifying the target element within the | |||
* widget. | |||
*/ | |||
static final String SUBPART_SEPARATOR = "#"; | |||
/** | |||
* String that identifies the root panel when appearing first in the String | |||
* locator. | |||
*/ | |||
static final String ROOT_ID = "Root"; | |||
public LegacyLocatorStrategy(ComponentLocator componentLocator) { | |||
this.componentLocator = componentLocator; | |||
} | |||
@Override | |||
public String getPathForElement(Element targetElement) { | |||
ComponentConnector connector = Util.findPaintable( | |||
componentLocator.getClient(), targetElement); | |||
Widget w = null; | |||
if (connector != null) { | |||
// If we found a Paintable then we use that as reference. We should | |||
// find the Paintable for all but very special cases (like | |||
// overlays). | |||
w = connector.getWidget(); | |||
/* | |||
* Still if the Paintable contains a widget that implements | |||
* SubPartAware, we want to use that as a reference | |||
*/ | |||
Widget targetParent = findParentWidget(targetElement, w); | |||
while (targetParent != w && targetParent != null) { | |||
if (targetParent instanceof SubPartAware) { | |||
/* | |||
* The targetParent widget is a child of the Paintable and | |||
* the first parent (of the targetElement) that implements | |||
* SubPartAware | |||
*/ | |||
w = targetParent; | |||
break; | |||
} | |||
targetParent = targetParent.getParent(); | |||
} | |||
} | |||
if (w == null) { | |||
// Check if the element is part of a widget that is attached | |||
// directly to the root panel | |||
RootPanel rootPanel = RootPanel.get(); | |||
int rootWidgetCount = rootPanel.getWidgetCount(); | |||
for (int i = 0; i < rootWidgetCount; i++) { | |||
Widget rootWidget = rootPanel.getWidget(i); | |||
if (rootWidget.getElement().isOrHasChild(targetElement)) { | |||
// The target element is contained by this root widget | |||
w = findParentWidget(targetElement, rootWidget); | |||
break; | |||
} | |||
} | |||
if (w != null) { | |||
// We found a widget but we should still see if we find a | |||
// SubPartAware implementor (we cannot find the Paintable as | |||
// there is no link from VOverlay to its paintable/owner). | |||
Widget subPartAwareWidget = findSubPartAwareParentWidget(w); | |||
if (subPartAwareWidget != null) { | |||
w = subPartAwareWidget; | |||
} | |||
} | |||
} | |||
if (w == null) { | |||
// Containing widget not found | |||
return null; | |||
} | |||
// Determine the path for the target widget | |||
String path = getPathForWidget(w); | |||
if (path == null) { | |||
/* | |||
* No path could be determined for the target widget. Cannot create | |||
* a locator string. | |||
*/ | |||
return null; | |||
} | |||
// The parent check is a work around for Firefox 15 which fails to | |||
// compare elements properly (#9534) | |||
if (w.getElement() == targetElement) { | |||
/* | |||
* We are done if the target element is the root of the target | |||
* widget. | |||
*/ | |||
return path; | |||
} else if (w instanceof SubPartAware) { | |||
/* | |||
* If the widget can provide an identifier for the targetElement we | |||
* let it do that | |||
*/ | |||
String elementLocator = ((SubPartAware) w) | |||
.getSubPartName(targetElement); | |||
if (elementLocator != null) { | |||
return path + LegacyLocatorStrategy.SUBPART_SEPARATOR | |||
+ elementLocator; | |||
} | |||
} | |||
/* | |||
* If everything else fails we use the DOM path to identify the target | |||
* element | |||
*/ | |||
String domPath = getDOMPathForElement(targetElement, w.getElement()); | |||
if (domPath == null) { | |||
return path; | |||
} else { | |||
return path + domPath; | |||
} | |||
} | |||
@Override | |||
public Element getElementByPath(String path) { | |||
/* | |||
* Path is of type "targetWidgetPath#componentPart" or | |||
* "targetWidgetPath". | |||
*/ | |||
String parts[] = path.split(LegacyLocatorStrategy.SUBPART_SEPARATOR, 2); | |||
String widgetPath = parts[0]; | |||
Widget w = getWidgetFromPath(widgetPath); | |||
if (w == null || !Util.isAttachedAndDisplayed(w)) { | |||
return null; | |||
} | |||
if (parts.length == 1) { | |||
int pos = widgetPath.indexOf("domChild"); | |||
if (pos == -1) { | |||
return w.getElement(); | |||
} | |||
// Contains dom reference to a sub element of the widget | |||
String subPath = widgetPath.substring(pos); | |||
return getElementByDOMPath(w.getElement(), subPath); | |||
} else if (parts.length == 2) { | |||
if (w instanceof SubPartAware) { | |||
return ((SubPartAware) w).getSubPartElement(parts[1]); | |||
} | |||
} | |||
return null; | |||
} | |||
/** | |||
* Finds the first widget in the hierarchy (moving upwards) that implements | |||
* SubPartAware. Returns the SubPartAware implementor or null if none is | |||
* found. | |||
* | |||
* @param w | |||
* The widget to start from. This is returned if it implements | |||
* SubPartAware. | |||
* @return The first widget (upwards in hierarchy) that implements | |||
* SubPartAware or null | |||
*/ | |||
Widget findSubPartAwareParentWidget(Widget w) { | |||
while (w != null) { | |||
if (w instanceof SubPartAware) { | |||
return w; | |||
} | |||
w = w.getParent(); | |||
} | |||
return null; | |||
} | |||
/** | |||
* Returns the first widget found when going from {@code targetElement} | |||
* upwards in the DOM hierarchy, assuming that {@code ancestorWidget} is a | |||
* parent of {@code targetElement}. | |||
* | |||
* @param targetElement | |||
* @param ancestorWidget | |||
* @return The widget whose root element is a parent of | |||
* {@code targetElement}. | |||
*/ | |||
private Widget findParentWidget(Element targetElement, Widget ancestorWidget) { | |||
/* | |||
* As we cannot resolve Widgets from the element we start from the | |||
* widget and move downwards to the correct child widget, as long as we | |||
* find one. | |||
*/ | |||
if (ancestorWidget instanceof HasWidgets) { | |||
for (Widget w : ((HasWidgets) ancestorWidget)) { | |||
if (w.getElement().isOrHasChild(targetElement)) { | |||
return findParentWidget(targetElement, w); | |||
} | |||
} | |||
} | |||
// No children found, this is it | |||
return ancestorWidget; | |||
} | |||
/** | |||
* Locates an element based on a DOM path and a base element. | |||
* | |||
* @param baseElement | |||
* The base element which the path is relative to | |||
* @param path | |||
* String locator (consisting of domChild[x] parts) that | |||
* identifies the element | |||
* @return The element identified by path, relative to baseElement or null | |||
* if the element could not be found. | |||
*/ | |||
private Element getElementByDOMPath(Element baseElement, String path) { | |||
String parts[] = path.split(PARENTCHILD_SEPARATOR); | |||
Element element = baseElement; | |||
for (String part : parts) { | |||
if (part.startsWith("domChild[")) { | |||
String childIndexString = part.substring("domChild[".length(), | |||
part.length() - 1); | |||
if (Util.findWidget(baseElement, null) instanceof VAbstractOrderedLayout) { | |||
if (element.hasChildNodes()) { | |||
Element e = element.getFirstChildElement().cast(); | |||
String cn = e.getClassName(); | |||
if (cn != null | |||
&& (cn.equals("v-expand") || cn | |||
.contains("v-has-caption"))) { | |||
element = e; | |||
} | |||
} | |||
} | |||
try { | |||
int childIndex = Integer.parseInt(childIndexString); | |||
element = DOM.getChild(element, childIndex); | |||
} catch (Exception e) { | |||
return null; | |||
} | |||
if (element == null) { | |||
return null; | |||
} | |||
} | |||
} | |||
return element; | |||
} | |||
/** | |||
* Generates a String locator using domChild[x] parts for the element | |||
* relative to the baseElement. | |||
* | |||
* @param element | |||
* The target element | |||
* @param baseElement | |||
* The starting point for the locator. The generated path is | |||
* relative to this element. | |||
* @return A String locator that can be used to locate the target element | |||
* using | |||
* {@link #getElementByDOMPath(com.google.gwt.user.client.Element, String)} | |||
* or null if the locator String cannot be created. | |||
*/ | |||
private String getDOMPathForElement(Element element, Element baseElement) { | |||
Element e = element; | |||
String path = ""; | |||
while (true) { | |||
int childIndex = -1; | |||
Element siblingIterator = e; | |||
while (siblingIterator != null) { | |||
childIndex++; | |||
siblingIterator = siblingIterator.getPreviousSiblingElement() | |||
.cast(); | |||
} | |||
path = PARENTCHILD_SEPARATOR + "domChild[" + childIndex + "]" | |||
+ path; | |||
JavaScriptObject parent = e.getParentElement(); | |||
if (parent == null) { | |||
return null; | |||
} | |||
// The parent check is a work around for Firefox 15 which fails to | |||
// compare elements properly (#9534) | |||
if (parent == baseElement) { | |||
break; | |||
} | |||
e = parent.cast(); | |||
} | |||
return path; | |||
} | |||
/** | |||
* Creates a locator String for the given widget. The path can be used to | |||
* locate the widget using {@link #getWidgetFromPath(String)}. | |||
* <p/> | |||
* Returns null if no path can be determined for the widget or if the widget | |||
* is null. | |||
* | |||
* @param w | |||
* The target widget | |||
* @return A String locator for the widget | |||
*/ | |||
private String getPathForWidget(Widget w) { | |||
if (w == null) { | |||
return null; | |||
} | |||
String elementId = w.getElement().getId(); | |||
if (elementId != null && !elementId.isEmpty() | |||
&& !elementId.startsWith("gwt-uid-")) { | |||
// Use PID_S+id if the user has set an id but do not use it for auto | |||
// generated id:s as these might not be consistent | |||
return "PID_S" + elementId; | |||
} else if (w instanceof VUI) { | |||
return ""; | |||
} else if (w instanceof VWindow) { | |||
Connector windowConnector = ConnectorMap.get( | |||
componentLocator.getClient()).getConnector(w); | |||
List<WindowConnector> subWindowList = componentLocator.getClient() | |||
.getUIConnector().getSubWindows(); | |||
int indexOfSubWindow = subWindowList.indexOf(windowConnector); | |||
return PARENTCHILD_SEPARATOR + "VWindow[" + indexOfSubWindow + "]"; | |||
} else if (w instanceof RootPanel) { | |||
return ROOT_ID; | |||
} | |||
Widget parent = w.getParent(); | |||
String basePath = getPathForWidget(parent); | |||
if (basePath == null) { | |||
return null; | |||
} | |||
String simpleName = Util.getSimpleName(w); | |||
/* | |||
* Check if the parent implements Iterable. At least VPopupView does not | |||
* implement HasWdgets so we cannot check for that. | |||
*/ | |||
if (!(parent instanceof Iterable<?>)) { | |||
// Parent does not implement Iterable so we cannot find out which | |||
// child this is | |||
return null; | |||
} | |||
Iterator<?> i = ((Iterable<?>) parent).iterator(); | |||
int pos = 0; | |||
while (i.hasNext()) { | |||
Object child = i.next(); | |||
if (child == w) { | |||
return basePath + PARENTCHILD_SEPARATOR + simpleName + "[" | |||
+ pos + "]"; | |||
} | |||
String simpleName2 = Util.getSimpleName(child); | |||
if (simpleName.equals(simpleName2)) { | |||
pos++; | |||
} | |||
} | |||
return null; | |||
} | |||
/** | |||
* Locates the widget based on a String locator. | |||
* | |||
* @param path | |||
* The String locator that identifies the widget. | |||
* @return The Widget identified by the String locator or null if the widget | |||
* could not be identified. | |||
*/ | |||
private Widget getWidgetFromPath(String path) { | |||
Widget w = null; | |||
String parts[] = path.split(PARENTCHILD_SEPARATOR); | |||
for (int i = 0; i < parts.length; i++) { | |||
String part = parts[i]; | |||
if (part.equals(ROOT_ID)) { | |||
w = RootPanel.get(); | |||
} else if (part.equals("")) { | |||
w = componentLocator.getClient().getUIConnector().getWidget(); | |||
} else if (w == null) { | |||
String id = part; | |||
// Must be old static pid (PID_S*) | |||
ServerConnector connector = ConnectorMap.get( | |||
componentLocator.getClient()).getConnector(id); | |||
if (connector == null) { | |||
// Lookup by component id | |||
// TODO Optimize this | |||
connector = findConnectorById(componentLocator.getClient() | |||
.getUIConnector(), id.substring(5)); | |||
} | |||
if (connector instanceof ComponentConnector) { | |||
w = ((ComponentConnector) connector).getWidget(); | |||
} else { | |||
// Not found | |||
return null; | |||
} | |||
} else if (part.startsWith("domChild[")) { | |||
// The target widget has been found and the rest identifies the | |||
// element | |||
break; | |||
} else if (w instanceof Iterable) { | |||
// W identifies a widget that contains other widgets, as it | |||
// should. Try to locate the child | |||
Iterable<?> parent = (Iterable<?>) w; | |||
// Part is of type "VVerticalLayout[0]", split this into | |||
// VVerticalLayout and 0 | |||
String[] split = part.split("\\[", 2); | |||
String widgetClassName = split[0]; | |||
String indexString = split[1].substring(0, | |||
split[1].length() - 1); | |||
int widgetPosition = Integer.parseInt(indexString); | |||
// AbsolutePanel in GridLayout has been removed -> skip it | |||
if (w instanceof VGridLayout | |||
&& "AbsolutePanel".equals(widgetClassName)) { | |||
continue; | |||
} | |||
// FlowPane in CSSLayout has been removed -> skip it | |||
if (w instanceof VCssLayout | |||
&& "VCssLayout$FlowPane".equals(widgetClassName)) { | |||
continue; | |||
} | |||
// ChildComponentContainer and VOrderedLayout$Slot have been | |||
// replaced with Slot | |||
if (w instanceof VAbstractOrderedLayout | |||
&& ("ChildComponentContainer".equals(widgetClassName) || "VOrderedLayout$Slot" | |||
.equals(widgetClassName))) { | |||
widgetClassName = "Slot"; | |||
} | |||
if (w instanceof VTabsheetPanel && widgetPosition != 0) { | |||
// TabSheetPanel now only contains 1 connector => the index | |||
// is always 0 which indicates the widget in the active tab | |||
widgetPosition = 0; | |||
} | |||
if (w instanceof VOverlay | |||
&& "VCalendarPanel".equals(widgetClassName)) { | |||
// Vaadin 7.1 adds a wrapper for datefield popups | |||
parent = (Iterable<?>) ((Iterable) parent).iterator() | |||
.next(); | |||
} | |||
/* | |||
* The new grid and ordered layotus do not contain | |||
* ChildComponentContainer widgets. This is instead simulated by | |||
* constructing a path step that would find the desired widget | |||
* from the layout and injecting it as the next search step | |||
* (which would originally have found the widget inside the | |||
* ChildComponentContainer) | |||
*/ | |||
if ((w instanceof VGridLayout) | |||
&& "ChildComponentContainer".equals(widgetClassName) | |||
&& i + 1 < parts.length) { | |||
HasWidgets layout = (HasWidgets) w; | |||
String nextPart = parts[i + 1]; | |||
String[] nextSplit = nextPart.split("\\[", 2); | |||
String nextWidgetClassName = nextSplit[0]; | |||
// Find the n:th child and count the number of children with | |||
// the same type before it | |||
int nextIndex = 0; | |||
for (Widget child : layout) { | |||
boolean matchingType = nextWidgetClassName.equals(Util | |||
.getSimpleName(child)); | |||
if (matchingType && widgetPosition == 0) { | |||
// This is the n:th child that we looked for | |||
break; | |||
} else if (widgetPosition < 0) { | |||
// Error if we're past the desired position without | |||
// a match | |||
return null; | |||
} else if (matchingType) { | |||
// If this was another child of the expected type, | |||
// increase the count for the next step | |||
nextIndex++; | |||
} | |||
// Don't count captions | |||
if (!(child instanceof VCaption)) { | |||
widgetPosition--; | |||
} | |||
} | |||
// Advance to the next step, this time checking for the | |||
// actual child widget | |||
parts[i + 1] = nextWidgetClassName + '[' + nextIndex + ']'; | |||
continue; | |||
} | |||
// Locate the child | |||
Iterator<? extends Widget> iterator; | |||
/* | |||
* VWindow and VContextMenu workarounds for backwards | |||
* compatibility | |||
*/ | |||
if (widgetClassName.equals("VWindow")) { | |||
List<WindowConnector> windows = componentLocator | |||
.getClient().getUIConnector().getSubWindows(); | |||
List<VWindow> windowWidgets = new ArrayList<VWindow>( | |||
windows.size()); | |||
for (WindowConnector wc : windows) { | |||
windowWidgets.add(wc.getWidget()); | |||
} | |||
iterator = windowWidgets.iterator(); | |||
} else if (widgetClassName.equals("VContextMenu")) { | |||
return componentLocator.getClient().getContextMenu(); | |||
} else { | |||
iterator = (Iterator<? extends Widget>) parent.iterator(); | |||
} | |||
boolean ok = false; | |||
// Find the widgetPosition:th child of type "widgetClassName" | |||
while (iterator.hasNext()) { | |||
Widget child = iterator.next(); | |||
String simpleName2 = Util.getSimpleName(child); | |||
if (!widgetClassName.equals(simpleName2) | |||
&& child instanceof Slot) { | |||
/* | |||
* Support legacy tests without any selector for the | |||
* Slot widget (i.e. /VVerticalLayout[0]/VButton[0]) by | |||
* directly checking the stuff inside the slot | |||
*/ | |||
child = ((Slot) child).getWidget(); | |||
simpleName2 = Util.getSimpleName(child); | |||
} | |||
if (widgetClassName.equals(simpleName2)) { | |||
if (widgetPosition == 0) { | |||
w = child; | |||
ok = true; | |||
break; | |||
} | |||
widgetPosition--; | |||
} | |||
} | |||
if (!ok) { | |||
// Did not find the child | |||
return null; | |||
} | |||
} else { | |||
// W identifies something that is not a "HasWidgets". This | |||
// should not happen as all widget containers should implement | |||
// HasWidgets. | |||
return null; | |||
} | |||
} | |||
return w; | |||
} | |||
private ServerConnector findConnectorById(ServerConnector root, String id) { | |||
SharedState state = root.getState(); | |||
if (state instanceof AbstractComponentState | |||
&& id.equals(((AbstractComponentState) state).id)) { | |||
return root; | |||
} | |||
for (ServerConnector child : root.getChildren()) { | |||
ServerConnector found = findConnectorById(child, id); | |||
if (found != null) { | |||
return found; | |||
} | |||
} | |||
return null; | |||
} | |||
} |
@@ -0,0 +1,34 @@ | |||
/* | |||
* Copyright 2000-2013 Vaadin Ltd. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not | |||
* use this file except in compliance with the License. You may obtain a copy of | |||
* the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
* License for the specific language governing permissions and limitations under | |||
* the License. | |||
*/ | |||
package com.vaadin.client.componentlocator; | |||
import com.google.gwt.user.client.Element; | |||
/** | |||
* This interface should be implemented by all locator strategies. A locator | |||
* strategy is responsible for generating and decoding a string that identifies | |||
* an element in the DOM. A strategy can implement its own syntax for the | |||
* locator string, which may be completely different from any other strategy's | |||
* syntax. | |||
* | |||
* @since 7.2 | |||
* @author Vaadin Ltd | |||
*/ | |||
public interface LocatorStrategy { | |||
String getPathForElement(Element targetElement); | |||
Element getElementByPath(String path); | |||
} |
@@ -35,10 +35,10 @@ import com.google.gwt.user.client.ui.Widget; | |||
import com.vaadin.client.ApplicationConfiguration; | |||
import com.vaadin.client.ApplicationConnection; | |||
import com.vaadin.client.ComponentConnector; | |||
import com.vaadin.client.ComponentLocator; | |||
import com.vaadin.client.ServerConnector; | |||
import com.vaadin.client.Util; | |||
import com.vaadin.client.ValueMap; | |||
import com.vaadin.client.componentlocator.ComponentLocator; | |||
/** | |||
* Provides functionality for picking selectors for Vaadin TestBench. |
@@ -17,7 +17,7 @@ package com.vaadin.client.ui; | |||
import com.google.gwt.user.client.Element; | |||
import com.google.gwt.user.client.ui.Widget; | |||
import com.vaadin.client.ComponentLocator; | |||
import com.vaadin.client.componentlocator.ComponentLocator; | |||
/** | |||
* Interface implemented by {@link Widget}s which can provide identifiers for at | |||
@@ -59,4 +59,4 @@ public interface SubPartAware { | |||
*/ | |||
String getSubPartName(Element subElement); | |||
} | |||
} |