diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/CommunityRepositoryModules.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/CommunityRepositoryModules.kt index 2df1028cc90e..2a500fdb43fc 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/CommunityRepositoryModules.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/CommunityRepositoryModules.kt @@ -308,7 +308,8 @@ object CommunityRepositoryModules { ) { spec -> spec.withModule("intellij.turboComplete.languages.kotlin") }, - pluginAuto(listOf("intellij.performanceTesting", "intellij.performanceTesting.remoteDriver")) { spec -> + pluginAuto(listOf("intellij.performanceTesting", + "intellij.performanceTesting.remoteDriver")) { spec -> spec.withModule("intellij.driver.model") spec.withModule("intellij.driver.impl") spec.withModule("intellij.driver.client") diff --git a/platform/platform-impl/src/com/intellij/platform/impl/toolkit/ClientToolkit.kt b/platform/platform-impl/src/com/intellij/platform/impl/toolkit/ClientToolkit.kt index 1df38507d2a6..08e4f81a5635 100644 --- a/platform/platform-impl/src/com/intellij/platform/impl/toolkit/ClientToolkit.kt +++ b/platform/platform-impl/src/com/intellij/platform/impl/toolkit/ClientToolkit.kt @@ -1,11 +1,12 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. @file:Suppress("JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE") -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.platform.impl.toolkit import com.intellij.openapi.components.service import java.awt.* import java.awt.peer.DialogPeer import java.awt.peer.FramePeer +import java.awt.peer.RobotPeer import java.awt.peer.WindowPeer interface ClientToolkit { @@ -15,4 +16,5 @@ interface ClientToolkit { fun createWindow(target: Window): WindowPeer fun createDialog(target: Dialog): DialogPeer fun createFrame(target: Frame): FramePeer + fun createRobot(screen: GraphicsDevice?): RobotPeer } \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/platform/impl/toolkit/IdeMouseInfoPeer.kt b/platform/platform-impl/src/com/intellij/platform/impl/toolkit/IdeMouseInfoPeer.kt index eaab10fc3bd1..1d540f923091 100644 --- a/platform/platform-impl/src/com/intellij/platform/impl/toolkit/IdeMouseInfoPeer.kt +++ b/platform/platform-impl/src/com/intellij/platform/impl/toolkit/IdeMouseInfoPeer.kt @@ -1,8 +1,9 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. @file:Suppress("JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE") package com.intellij.platform.impl.toolkit import java.awt.Component +import java.awt.GraphicsEnvironment import java.awt.Point import java.awt.Window import java.awt.event.MouseEvent @@ -10,16 +11,15 @@ import java.awt.peer.MouseInfoPeer import javax.swing.SwingUtilities object IdeMouseInfoPeer: MouseInfoPeer { - private const val PRIMARY_SCREEN_DEVICE_ID = 0 - private val lastMouseCoords = Point() private var lastWindowUnderMouse: Component? = null private var lastComponentUnderCursor: Component? = null override fun fillPointWithCoords(point: Point): Int { point.location = lastMouseCoords - // TODO: return correct screen id - return PRIMARY_SCREEN_DEVICE_ID + return GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices().indexOfFirst { + it.defaultConfiguration.bounds.contains(point) + } } override fun isWindowUnderMouse(w: Window): Boolean { @@ -29,7 +29,7 @@ object IdeMouseInfoPeer: MouseInfoPeer { fun getComponentUnderCursor(): Component? = lastComponentUnderCursor fun processMouseEvent(event: MouseEvent, window: Window) { - lastMouseCoords.setLocation(event.x, event.y) + lastMouseCoords.setLocation(event.xOnScreen, event.yOnScreen) lastWindowUnderMouse = window val component = SwingUtilities.getDeepestComponentAt(window, event.x, event.y) lastComponentUnderCursor = component diff --git a/platform/platform-impl/src/com/intellij/platform/impl/toolkit/IdeToolkit.kt b/platform/platform-impl/src/com/intellij/platform/impl/toolkit/IdeToolkit.kt index 00a59d9f3352..49d8a76142dc 100644 --- a/platform/platform-impl/src/com/intellij/platform/impl/toolkit/IdeToolkit.kt +++ b/platform/platform-impl/src/com/intellij/platform/impl/toolkit/IdeToolkit.kt @@ -1,4 +1,4 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. @file:Suppress("JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE") package com.intellij.platform.impl.toolkit @@ -47,6 +47,9 @@ class IdeToolkit : SunToolkit() { override fun createFrame(target: Frame): FramePeer = clientInstance().createFrame(target) override fun getSystemClipboard(): Clipboard = clipboard + override fun createRobot(screen: GraphicsDevice?): RobotPeer? = clientInstance().createRobot(screen) + override fun getMouseInfoPeer(): IdeMouseInfoPeer = IdeMouseInfoPeer + override fun getScreenResolution(): Int = 96 override fun prepareImage(img: Image, w: Int, h: Int, o: ImageObserver?): Boolean { //TODO: probably can be used for caching @@ -54,7 +57,6 @@ class IdeToolkit : SunToolkit() { } override fun createDesktopPeer(target: Desktop): DesktopPeer = IdeDesktopPeer() - override fun getMouseInfoPeer(): IdeMouseInfoPeer = IdeMouseInfoPeer override fun getKeyboardFocusManagerPeer(): IdeKeyboardFocusManagerPeer = IdeKeyboardFocusManagerPeer // Toolkit interface which shouldn't be used diff --git a/platform/remote-driver/client/src/com/intellij/driver/client/Driver.kt b/platform/remote-driver/client/src/com/intellij/driver/client/Driver.kt index 6e91915ca337..608715bb0546 100644 --- a/platform/remote-driver/client/src/com/intellij/driver/client/Driver.kt +++ b/platform/remote-driver/client/src/com/intellij/driver/client/Driver.kt @@ -67,6 +67,8 @@ interface Driver : AutoCloseable { */ val isConnected: Boolean + val isRemoteIdeMode: Boolean + /** * @return information about the product under test */ @@ -91,24 +93,28 @@ interface Driver : AutoCloseable { * @return new remote proxy for a [Remote] application service interface */ @Contract(pure = true) - fun service(clazz: KClass): T + fun service(clazz: KClass, forceRunOnBackend: Boolean = false): T /** * @return new remote proxy for a [Remote] project service interface */ @Contract(pure = true) - fun service(clazz: KClass, project: ProjectRef): T + fun service(clazz: KClass, project: ProjectRef?, forceRunOnBackend: Boolean = false): T /** * @return new remote proxy for a utility class or a class with static methods */ @Contract(pure = true) - fun utility(clazz: KClass): T + fun utility(clazz: KClass, forceRunOnBackend: Boolean = false): T /** * @return proxy reference for a newly created remote object */ - fun new(clazz: KClass, vararg args: Any?): T + fun new(clazz: KClass, vararg args: Any?): T { + return new(clazz, *args, forceRunOnBackend = false) + } + + fun new(clazz: KClass, vararg args: Any?, forceRunOnBackend: Boolean = false): T /** * Assumes that the remote reference corresponds to another type. Performs unsafe cast. @@ -140,8 +146,8 @@ interface Driver : AutoCloseable { */ @JvmStatic @Contract(pure = true) - fun create(host: JmxHost? = JmxHost(null, null, "localhost:7777")): Driver { - return DriverImpl(host) + fun create(host: JmxHost? = JmxHost(null, null, "localhost:7777"), isRemoteIdeMode: Boolean = false): Driver { + return DriverImpl(host, isRemoteIdeMode) } } } @@ -154,20 +160,20 @@ interface ProjectRef /** * @return new remote proxy for a [Remote] application service interface */ -inline fun Driver.service(): T { - return service(T::class) +inline fun Driver.service(forceRunOnBackend: Boolean = false): T { + return service(T::class, forceRunOnBackend) } /** * @return new remote proxy for a [Remote] application service interface */ -inline fun Driver.service(project: ProjectRef): T { - return service(T::class, project) +inline fun Driver.service(project: ProjectRef, forceRunOnBackend: Boolean = false): T { + return service(T::class, project, forceRunOnBackend) } /** * @return new remote proxy for a utility class or a class with static methods */ -inline fun Driver.utility(): T { - return utility(T::class) -} \ No newline at end of file +inline fun Driver.utility(forceRunOnBackend: Boolean = false): T { + return utility(T::class, forceRunOnBackend) +} diff --git a/platform/remote-driver/client/src/com/intellij/driver/client/Remote.kt b/platform/remote-driver/client/src/com/intellij/driver/client/Remote.kt index 868c30fc98cb..c95ee5853612 100644 --- a/platform/remote-driver/client/src/com/intellij/driver/client/Remote.kt +++ b/platform/remote-driver/client/src/com/intellij/driver/client/Remote.kt @@ -1,5 +1,6 @@ package com.intellij.driver.client +import com.intellij.driver.model.RdTarget import org.intellij.lang.annotations.Language /** @@ -25,5 +26,12 @@ annotation class Remote( * Identifier of a plugin where the class is located, e.g. `com.intellij.spring`. * If the class is declared in a module of a plugin (not main), use the following format: `some.plugin.id/some.plugin.id.submodule`. */ - val plugin: String = "" + val plugin: String = "", + + /** + * Determine the semantics for the remote call in case of Remote IDE. + */ + val rdTarget: RdTarget = RdTarget.FRONTEND_FIRST, + + val isControllerSession: Boolean = false ) \ No newline at end of file diff --git a/platform/remote-driver/client/src/com/intellij/driver/client/impl/DriverImpl.kt b/platform/remote-driver/client/src/com/intellij/driver/client/impl/DriverImpl.kt index 13e1d3307cb4..96eeaf71bd0e 100644 --- a/platform/remote-driver/client/src/com/intellij/driver/client/impl/DriverImpl.kt +++ b/platform/remote-driver/client/src/com/intellij/driver/client/impl/DriverImpl.kt @@ -7,7 +7,9 @@ import com.intellij.driver.client.Timed import com.intellij.driver.model.LockSemantics import com.intellij.driver.model.OnDispatcher import com.intellij.driver.model.ProductVersion +import com.intellij.driver.model.RdTarget import com.intellij.driver.model.transport.* +import java.lang.IllegalStateException import java.lang.reflect.Method import java.lang.reflect.ParameterizedType import java.lang.reflect.Proxy @@ -17,13 +19,13 @@ import javax.management.AttributeNotFoundException import javax.management.InstanceNotFoundException import kotlin.reflect.KClass -internal class DriverImpl(host: JmxHost?) : Driver { +class DriverImpl(host: JmxHost?, override val isRemoteIdeMode: Boolean) : Driver { private val invoker: Invoker = JmxCallHandler.jmx(Invoker::class.java, host) private val sessionHolder = ThreadLocal() - private val appServices: MutableMap, Any> = ConcurrentHashMap() + private val appServices: MutableMap = ConcurrentHashMap() private val projectServices: MutableMap = ConcurrentHashMap() - private val utils: MutableMap, Any> = ConcurrentHashMap() + private val utils: MutableMap = ConcurrentHashMap() override val isConnected: Boolean get() { @@ -42,6 +44,8 @@ internal class DriverImpl(host: JmxHost?) : Driver { } } + fun getInvoker() = invoker + override fun getProductVersion(): ProductVersion { return invoker.getProductVersion() } @@ -55,26 +59,27 @@ internal class DriverImpl(host: JmxHost?) : Driver { } @Suppress("UNCHECKED_CAST") - override fun service(clazz: KClass): T { - return appServices.computeIfAbsent(clazz.java, ::serviceBridge) as T + override fun service(clazz: KClass, isBackendService: Boolean): T { + return appServices.computeIfAbsent(AppServiceId(clazz.java, isBackendService)) { serviceBridge(clazz.java, null, isBackendService) } as T } @Suppress("UNCHECKED_CAST") - override fun service(clazz: KClass, project: ProjectRef): T { - val id = ProjectServiceId((project as RefWrapper).getRef().identityHashCode, clazz.java) - return projectServices.computeIfAbsent(id) { serviceBridge(clazz.java, project) } as T + override fun service(clazz: KClass, project: ProjectRef?, isBackendService: Boolean): T { + val id = ProjectServiceId((project as RefWrapper).getRef().identityHashCode, clazz.java, isBackendService) + return projectServices.computeIfAbsent(id) { serviceBridge(clazz.java, project, isBackendService) } as T } @Suppress("UNCHECKED_CAST") - override fun utility(clazz: KClass): T { - return utils.computeIfAbsent(clazz.java, ::utilityBridge) as T + override fun utility(clazz: KClass, isBackendUtility: Boolean): T { + return utils.computeIfAbsent(UtilityId(clazz.java, isBackendUtility)) { utilityBridge(clazz.java, isBackendUtility) } as T } @Suppress("UNCHECKED_CAST") - override fun new(clazz: KClass, vararg args: Any?): T { + override fun new(clazz: KClass, vararg args: Any?, forceRunOnBackend: Boolean): T { val remote = findRemoteMeta(clazz.java) ?: throw IllegalArgumentException("Class $clazz is not annotated with @Remote annotation") + val rdTarget = checkRdTargetConsistencyAndGetMostSuitable(clazz.java, if (forceRunOnBackend) RdTarget.BACKEND_ONLY else remote.rdTarget, *args) val (sessionId, dispatcher, semantics) = sessionHolder.get() ?: NO_SESSION val call = NewInstanceCall( sessionId, @@ -83,6 +88,7 @@ internal class DriverImpl(host: JmxHost?) : Driver { dispatcher, semantics, remote.value, + if (rdTarget == RdTarget.FRONTEND_FIRST) RdTarget.FRONTEND_ONLY else rdTarget, convertArgsToPass(args) ) val callResult = makeCall(call) @@ -156,48 +162,23 @@ internal class DriverImpl(host: JmxHost?) : Driver { return value // let's hope we will be able to cast it } - private fun serviceBridge(clazz: Class<*>): Any { - val remote = findRemoteMeta(clazz) ?: throw notAnnotatedError(clazz) - - return Proxy.newProxyInstance(getClassLoader(), arrayOf(clazz)) { proxy: Any?, method: Method, args: Array? -> - when (method.name) { - "equals" -> proxy === args?.firstOrNull() - "hashCode" -> clazz.hashCode() - "toString" -> "@Service " + remote.value - else -> { - val (sessionId, dispatcher, semantics) = sessionHolder.get() ?: NO_SESSION - val call = ServiceCall( - sessionId, - findTimedMeta(method)?.value, - getPluginId(remote), - dispatcher, - semantics, - remote.value, - method.name, - convertArgsToPass(args), - null, - remote.serviceInterface.takeIf { it.isNotBlank() } - ) - val callResult = makeCall(call) - convertResult(callResult, method, getPluginId(remote)) - } - } - } - } - private fun getPluginId(remote: Remote): String? { return remote.plugin.takeIf { it.isNotBlank() } } - private fun serviceBridge(clazz: Class<*>, project: ProjectRef): Any { + private fun serviceBridge(clazz: Class<*>, project: ProjectRef? = null, forceRunOnBackend: Boolean = false): Any { val remote = findRemoteMeta(clazz) ?: throw notAnnotatedError(clazz) - return Proxy.newProxyInstance(getClassLoader(), arrayOf(clazz)) { proxy: Any?, method: Method, args: Array? -> + val rdTarget = if (forceRunOnBackend) RdTarget.BACKEND_ONLY else remote.rdTarget + val isControllerSession = remote.isControllerSession + + return Proxy.newProxyInstance(clazz.classLoader, arrayOf(clazz)) { proxy: Any?, method: Method, args: Array? -> when (method.name) { "equals" -> proxy === args?.firstOrNull() "hashCode" -> clazz.hashCode() "toString" -> "@Service(APP) " + remote.value else -> { + val rdTarget = checkRdTargetConsistencyAndGetMostSuitable(clazz, rdTarget, args) val (sessionId, dispatcher, semantics) = sessionHolder.get() ?: NO_SESSION val call = ServiceCall( sessionId, @@ -208,8 +189,9 @@ internal class DriverImpl(host: JmxHost?) : Driver { remote.value, method.name, convertArgsToPass(args), - (project as RefWrapper).getRef(), - remote.serviceInterface.takeIf { it.isNotBlank() } + (project as? RefWrapper?)?.getRef(), + remote.serviceInterface.takeIf { it.isNotBlank() }, + rdTarget, isControllerSession ) val callResult = makeCall(call) convertResult(callResult, method, getPluginId(remote)) @@ -227,15 +209,17 @@ internal class DriverImpl(host: JmxHost?) : Driver { } } - private fun utilityBridge(clazz: Class<*>): Any { + private fun utilityBridge(clazz: Class<*>, forceRunOnBackend: Boolean): Any { val remote = findRemoteMeta(clazz) ?: throw notAnnotatedError(clazz) - return Proxy.newProxyInstance(getClassLoader(), arrayOf(clazz)) { proxy: Any?, method: Method, args: Array? -> + val rdTarget = if (forceRunOnBackend) RdTarget.BACKEND_ONLY else remote.rdTarget + return Proxy.newProxyInstance(clazz.classLoader, arrayOf(clazz)) { proxy: Any?, method: Method, args: Array? -> when (method.name) { "equals" -> proxy === args?.firstOrNull() "hashCode" -> clazz.hashCode() "toString" -> "Utility " + remote.value else -> { + val rdTarget = checkRdTargetConsistencyAndGetMostSuitable(clazz, rdTarget, args) val (sessionId, dispatcher, semantics) = sessionHolder.get() ?: NO_SESSION val call = UtilityCall( sessionId, @@ -245,7 +229,8 @@ internal class DriverImpl(host: JmxHost?) : Driver { semantics, remote.value, method.name, - convertArgsToPass(args) + rdTarget, + convertArgsToPass(args), ) val callResult = makeCall(call) convertResult(callResult, method, getPluginId(remote)) @@ -257,7 +242,7 @@ internal class DriverImpl(host: JmxHost?) : Driver { private fun refBridge(clazz: Class<*>, ref: Ref, pluginId: String?): Any { val remote = findRemoteMeta(clazz) ?: throw notAnnotatedError(clazz) - return Proxy.newProxyInstance(getClassLoader(), + return Proxy.newProxyInstance(clazz.classLoader, arrayOf(clazz, RefWrapper::class.java)) { proxy: Any?, method: Method, args: Array? -> when (method.name) { "equals" -> proxy === args?.firstOrNull() @@ -285,10 +270,6 @@ internal class DriverImpl(host: JmxHost?) : Driver { } } - private fun notAnnotatedError(clazz: Class<*>): IllegalArgumentException { - return IllegalArgumentException("Class $clazz is not annotated with @Remote annotation") - } - private fun getClassLoader(): ClassLoader? { return javaClass.classLoader } @@ -338,6 +319,10 @@ internal class DriverImpl(host: JmxHost?) : Driver { } } +private fun notAnnotatedError(clazz: Class<*>): IllegalArgumentException { + return IllegalArgumentException("Class $clazz is not annotated with @Remote annotation") +} + private fun findTimedMeta(method: Method): Timed? { return method.annotations .filterIsInstance() @@ -350,6 +335,29 @@ private fun findRemoteMeta(clazz: Class<*>): Remote? { .firstOrNull() } +private fun checkRdTargetConsistencyAndGetMostSuitable( + clazz: Class<*>, + rdTarget: RdTarget, + vararg args: Any? +): RdTarget { + val remote = findRemoteMeta(clazz) ?: throw notAnnotatedError(clazz) + val classRdTarget = remote.rdTarget + val argsRdTarget = + args.filterIsInstance() + .map { if (it.getRef().isBackendReference) RdTarget.BACKEND_ONLY else RdTarget.FRONTEND_ONLY } + .reduceOrNull { acc, b -> + if (acc != b) throw IllegalStateException("Inconsistent rdTarget") + acc + } ?: RdTarget.FRONTEND_FIRST + + return listOf(classRdTarget, argsRdTarget, rdTarget).reduce { acc, b -> + if (acc == RdTarget.FRONTEND_FIRST) b + else if (b == RdTarget.FRONTEND_FIRST) acc + else if (acc == b) acc + else throw IllegalStateException("Inconsistent rdTarget") + } +} + internal data class Session( val id: Int, val dispatcher: OnDispatcher, @@ -358,6 +366,12 @@ internal data class Session( private val NO_SESSION: Session = Session(0, OnDispatcher.DEFAULT, LockSemantics.NO_LOCK) +class DriverCallException(message: String, e: Throwable) : RuntimeException(message, e) + +private data class AppServiceId(val clazz: Class<*>, val isBackendService: Boolean) +private data class ProjectServiceId(val projectId: Int, val serviceClass: Class<*>, val isBackendService: Boolean) +private data class UtilityId(val clazz: Class<*>, val isBackendUtility: Boolean) + @JmxName("com.intellij.driver:type=Invoker") interface Invoker : AutoCloseable { fun getProductVersion(): ProductVersion @@ -376,7 +390,3 @@ interface Invoker : AutoCloseable { fun takeScreenshot(folder: String?): String? } - -class DriverCallException(message: String, e: Throwable) : RuntimeException(message, e) - -private data class ProjectServiceId(val projectId: Int, val serviceClass: Class<*>) \ No newline at end of file diff --git a/platform/remote-driver/core/src/com/intellij/driver/impl/Invoker.java b/platform/remote-driver/core/src/com/intellij/driver/impl/Invoker.java index 6ad39f363548..3ce91feaa2f4 100644 --- a/platform/remote-driver/core/src/com/intellij/driver/impl/Invoker.java +++ b/platform/remote-driver/core/src/com/intellij/driver/impl/Invoker.java @@ -1,9 +1,6 @@ package com.intellij.driver.impl; -import com.intellij.driver.model.LocalRefDelegate; -import com.intellij.driver.model.OnDispatcher; -import com.intellij.driver.model.ProductVersion; -import com.intellij.driver.model.RemoteRefDelegate; +import com.intellij.driver.model.*; import com.intellij.driver.model.transport.*; import com.intellij.ide.plugins.IdeaPluginDescriptor; import com.intellij.ide.plugins.IdeaPluginDescriptorImpl; @@ -12,6 +9,7 @@ import com.intellij.ide.plugins.PluginManagerCore; import com.intellij.openapi.application.*; import com.intellij.openapi.application.ex.ApplicationEx; import com.intellij.openapi.application.impl.ApplicationInfoImpl; +import com.intellij.openapi.client.ClientKind; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.extensions.PluginId; import com.intellij.openapi.project.Project; @@ -19,7 +17,6 @@ import com.intellij.openapi.util.BuildNumber; import com.intellij.openapi.util.ClearableLazyValue; import com.intellij.platform.diagnostic.telemetry.IJTracer; import com.intellij.util.ExceptionUtil; -import java.util.function.Function; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanBuilder; import io.opentelemetry.context.Context; @@ -30,14 +27,12 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.lang.ref.WeakReference; -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; +import java.lang.reflect.*; import java.util.*; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -128,7 +123,7 @@ public class Invoker implements InvokerMBean { catch (Exception e) { LOG.error("Unable to get instance for " + call); - throw new RuntimeException("Unable to get instance for " + call, e); + throw new DriverIlligalStateException("Unable to get instance for " + call, e); } if (call.getDispatcher() == OnDispatcher.EDT) { @@ -155,7 +150,7 @@ public class Invoker implements InvokerMBean { // first lookup in the current session Session session = sessions.get(sessionId); if (session == null) { - throw new IllegalStateException("No such session " + sessionId); + throw new DriverIlligalStateException("No such session " + sessionId); } Object value = session.findReference(id); @@ -167,9 +162,9 @@ public class Invoker implements InvokerMBean { if (value != null) return value; } - throw new IllegalStateException("No such reference with id " + id + ". " + - "It may happen if a weak reference to the variable expires. " + - "Please use `Driver.withContext { }` for hard variable references."); + throw new DriverIlligalStateException("No such reference with id " + id + ". " + + "It may happen if a weak reference to the variable expires. " + + "Please use `Driver.withContext { }` for hard variable references."); } private static @NotNull Object invokeConstructor(Constructor constructor, Object[] transformedArgs) throws Exception { @@ -237,6 +232,9 @@ public class Invoker implements InvokerMBean { try { return supplier.call(); } + catch (InvocationTargetException e) { + throw new DriverIlligalStateException(e); + } catch (Exception e) { ExceptionUtil.rethrow(e); throw new IllegalStateException(); @@ -364,7 +362,7 @@ public class Invoker implements InvokerMBean { clazz = getClassLoader(call).loadClass(call.getClassName()); } catch (ClassNotFoundException e) { - throw new IllegalStateException("No such class '" + call.getClassName() + "'", e); + throw new DriverIlligalStateException("No such class '" + call.getClassName() + "'", e); } return clazz; } @@ -378,7 +376,7 @@ public class Invoker implements InvokerMBean { String moduleId = StringsKt.substringAfter(pluginId, "/", pluginId); IdeaPluginDescriptor plugin = PluginManagerCore.getPlugin(PluginId.getId(mainId)); - if (plugin == null) throw new IllegalStateException("No such plugin " + mainId); + if (plugin == null) throw new DriverIlligalStateException("No such plugin " + mainId); List modules = ((IdeaPluginDescriptorImpl)plugin).content.modules; for (PluginContentDescriptor.ModuleItem module : modules) { @@ -387,11 +385,11 @@ public class Invoker implements InvokerMBean { } } - throw new IllegalStateException("No such plugin module " + pluginId); + throw new DriverIlligalStateException("No such plugin module " + pluginId); } IdeaPluginDescriptor plugin = PluginManagerCore.getPlugin(PluginId.getId(pluginId)); - if (plugin == null) throw new IllegalStateException("No such plugin " + pluginId); + if (plugin == null) throw new DriverIlligalStateException("No such plugin " + pluginId); return plugin.getClassLoader(); } @@ -409,16 +407,21 @@ public class Invoker implements InvokerMBean { if (serviceInterface != null) { serviceClass = findServiceInterface(clazz, serviceInterface); if (serviceClass == null) { - throw new IllegalStateException("Unable to find interface " + serviceInterface + " for service " + clazz); + throw new DriverIlligalStateException("Unable to find interface " + serviceInterface + " for service " + clazz); } } Object instance; + Boolean isControllerSession = ((ServiceCall)call).isControllerSession(); if (projectInstance instanceof Project) { - instance = ((Project)projectInstance).getService(serviceClass); + instance = isControllerSession + ? ((Project)projectInstance).getServices(serviceClass, ClientKind.CONTROLLER).get(0) + : ((Project)projectInstance).getService(serviceClass); } else { - instance = ApplicationManager.getApplication().getService(serviceClass); + instance = isControllerSession + ? ApplicationManager.getApplication().getServices(serviceClass, ClientKind.CONTROLLER).get(0) + : ApplicationManager.getApplication().getService(serviceClass); } return instance; } @@ -427,7 +430,7 @@ public class Invoker implements InvokerMBean { Ref ref = ((RefCall)call).getRef(); Object reference = getReference(call.getSessionId(), ref.id()); - if (reference == null) throw new IllegalStateException("No such ref exists " + ref); + if (reference == null) throw new DriverIlligalStateException("No such ref exists " + ref); return reference; } diff --git a/platform/remote-driver/model/src/com/intellij/driver/model/DriverIlligalStateException.kt b/platform/remote-driver/model/src/com/intellij/driver/model/DriverIlligalStateException.kt new file mode 100644 index 000000000000..425d144e24b3 --- /dev/null +++ b/platform/remote-driver/model/src/com/intellij/driver/model/DriverIlligalStateException.kt @@ -0,0 +1,7 @@ +package com.intellij.driver.model + +class DriverIlligalStateException : IllegalStateException { + constructor(message: String) : super(message) + constructor(cause: Throwable) : super(cause) + constructor(message: String, cause: Throwable) : super(message, cause) +} \ No newline at end of file diff --git a/platform/remote-driver/model/src/com/intellij/driver/model/RefDelegate.kt b/platform/remote-driver/model/src/com/intellij/driver/model/RemdevUtils.kt similarity index 85% rename from platform/remote-driver/model/src/com/intellij/driver/model/RefDelegate.kt rename to platform/remote-driver/model/src/com/intellij/driver/model/RemdevUtils.kt index 068c13ee0f8b..c7c40b5dbd25 100644 --- a/platform/remote-driver/model/src/com/intellij/driver/model/RefDelegate.kt +++ b/platform/remote-driver/model/src/com/intellij/driver/model/RemdevUtils.kt @@ -12,4 +12,10 @@ fun RefDelegate.unwrap(): T = when (this) { is RemoteRefDelegate -> throw UnsupportedOperationException("Cannot get remote value") } -fun List>.unwrap(): List = map { it.unwrap() } \ No newline at end of file +fun List>.unwrap(): List = map { it.unwrap() } + +enum class RdTarget { + FRONTEND_FIRST, + FRONTEND_ONLY, + BACKEND_ONLY, +} diff --git a/platform/remote-driver/model/src/com/intellij/driver/model/transport/NewInstanceCall.java b/platform/remote-driver/model/src/com/intellij/driver/model/transport/NewInstanceCall.java index 4724660642c9..296429e06546 100644 --- a/platform/remote-driver/model/src/com/intellij/driver/model/transport/NewInstanceCall.java +++ b/platform/remote-driver/model/src/com/intellij/driver/model/transport/NewInstanceCall.java @@ -2,6 +2,7 @@ package com.intellij.driver.model.transport; import com.intellij.driver.model.LockSemantics; import com.intellij.driver.model.OnDispatcher; +import com.intellij.driver.model.RdTarget; import java.io.Serial; @@ -15,8 +16,12 @@ public final class NewInstanceCall extends RemoteCall { OnDispatcher dispatcher, LockSemantics lockSemantics, String className, + RdTarget rdTarget, Object[] args) { - super(sessionId, timedSpan, pluginId, dispatcher, lockSemantics, className, "new", args); + super(sessionId, timedSpan, pluginId, dispatcher, lockSemantics, className, "new", rdTarget, args); + if (rdTarget != RdTarget.FRONTEND_ONLY && rdTarget != RdTarget.BACKEND_ONLY) { + throw new IllegalArgumentException("NewInstanceCall must have RdTarget set to either FRONTEND_ONLY or BACKEND_ONLY"); + } } @Override diff --git a/platform/remote-driver/model/src/com/intellij/driver/model/transport/Ref.java b/platform/remote-driver/model/src/com/intellij/driver/model/transport/Ref.java index 965b25d83486..385520827b71 100644 --- a/platform/remote-driver/model/src/com/intellij/driver/model/transport/Ref.java +++ b/platform/remote-driver/model/src/com/intellij/driver/model/transport/Ref.java @@ -11,4 +11,11 @@ public record Ref( ) implements Serializable { @Serial private static final long serialVersionUID = 1L; + + public static final String BACKEND_REFERENCE_PREFIX = "remdevHost_"; + public static final String FRONTEND_REFERENCE_PREFIX = "jbClient_"; + + public boolean isBackendReference() { + return id.startsWith(BACKEND_REFERENCE_PREFIX); + } } \ No newline at end of file diff --git a/platform/remote-driver/model/src/com/intellij/driver/model/transport/RefCall.java b/platform/remote-driver/model/src/com/intellij/driver/model/transport/RefCall.java index 70d2c82ace9f..4ba4f3ec1d42 100644 --- a/platform/remote-driver/model/src/com/intellij/driver/model/transport/RefCall.java +++ b/platform/remote-driver/model/src/com/intellij/driver/model/transport/RefCall.java @@ -2,6 +2,7 @@ package com.intellij.driver.model.transport; import com.intellij.driver.model.LockSemantics; import com.intellij.driver.model.OnDispatcher; +import com.intellij.driver.model.RdTarget; import java.io.Serial; @@ -20,7 +21,15 @@ public final class RefCall extends RemoteCall { String methodName, Object[] args, Ref ref) { - super(sessionId, timedSpan, pluginId, dispatcher, lockSemantics, className, methodName, args); + super(sessionId, + timedSpan, + pluginId, + dispatcher, + lockSemantics, + className, + methodName, + ref.isBackendReference() ? RdTarget.BACKEND_ONLY : RdTarget.FRONTEND_ONLY, + args); this.ref = ref; } diff --git a/platform/remote-driver/model/src/com/intellij/driver/model/transport/RemoteCall.java b/platform/remote-driver/model/src/com/intellij/driver/model/transport/RemoteCall.java index e4c193223e39..e2a7938fd19d 100644 --- a/platform/remote-driver/model/src/com/intellij/driver/model/transport/RemoteCall.java +++ b/platform/remote-driver/model/src/com/intellij/driver/model/transport/RemoteCall.java @@ -2,7 +2,9 @@ package com.intellij.driver.model.transport; import com.intellij.driver.model.LockSemantics; import com.intellij.driver.model.OnDispatcher; +import com.intellij.driver.model.RdTarget; import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; import java.awt.*; import java.io.Serial; @@ -15,6 +17,8 @@ public abstract class RemoteCall implements Serializable { @Serial private static final long serialVersionUID = 1L; + private final RdTarget rdTarget; + private final int sessionId; private final String pluginId; private final String timedSpan; @@ -23,8 +27,6 @@ public abstract class RemoteCall implements Serializable { private final LockSemantics lockSemantics; private final String className; private final String methodName; - private final Object[] args; - public RemoteCall(int sessionId, String timedSpan, String pluginId, @@ -32,6 +34,7 @@ public abstract class RemoteCall implements Serializable { LockSemantics lockSemantics, String className, String methodName, + RdTarget rdTarget, Object[] args) { this.sessionId = sessionId; this.pluginId = pluginId; @@ -40,8 +43,14 @@ public abstract class RemoteCall implements Serializable { this.lockSemantics = lockSemantics; this.className = className; this.methodName = methodName; + this.rdTarget = rdTarget; this.args = args; } + private final Object[] args; + + public @NotNull RdTarget getRdTarget() { + return rdTarget; + } public int getSessionId() { return sessionId; diff --git a/platform/remote-driver/model/src/com/intellij/driver/model/transport/ServiceCall.java b/platform/remote-driver/model/src/com/intellij/driver/model/transport/ServiceCall.java index e70dbef07b21..5e10a7d45363 100644 --- a/platform/remote-driver/model/src/com/intellij/driver/model/transport/ServiceCall.java +++ b/platform/remote-driver/model/src/com/intellij/driver/model/transport/ServiceCall.java @@ -2,6 +2,7 @@ package com.intellij.driver.model.transport; import com.intellij.driver.model.LockSemantics; import com.intellij.driver.model.OnDispatcher; +import com.intellij.driver.model.RdTarget; import org.jetbrains.annotations.Nullable; import java.io.Serial; @@ -12,6 +13,7 @@ public final class ServiceCall extends RemoteCall { private final Ref projectRef; private final String serviceInterface; + private final Boolean isControllerSession; public ServiceCall(int sessionId, String timedSpan, @@ -22,10 +24,13 @@ public final class ServiceCall extends RemoteCall { String methodName, Object[] args, @Nullable Ref projectRef, - @Nullable String serviceInterface) { - super(sessionId, timedSpan, pluginId, dispatcher, lockSemantics, className, methodName, args); + @Nullable String serviceInterface, + RdTarget rdTarget, + Boolean isControllerSession) { + super(sessionId, timedSpan, pluginId, dispatcher, lockSemantics, className, methodName, rdTarget, args); this.projectRef = projectRef; this.serviceInterface = serviceInterface; + this.isControllerSession = isControllerSession; } public Ref getProjectRef() { @@ -36,6 +41,10 @@ public final class ServiceCall extends RemoteCall { return serviceInterface; } + public Boolean isControllerSession() { + return isControllerSession; + } + @Override public String toString() { return "ServiceCall{" + diff --git a/platform/remote-driver/model/src/com/intellij/driver/model/transport/UtilityCall.java b/platform/remote-driver/model/src/com/intellij/driver/model/transport/UtilityCall.java index 4474b3b6f98a..f005470b7dfe 100644 --- a/platform/remote-driver/model/src/com/intellij/driver/model/transport/UtilityCall.java +++ b/platform/remote-driver/model/src/com/intellij/driver/model/transport/UtilityCall.java @@ -2,6 +2,7 @@ package com.intellij.driver.model.transport; import com.intellij.driver.model.LockSemantics; import com.intellij.driver.model.OnDispatcher; +import com.intellij.driver.model.RdTarget; import java.io.Serial; @@ -16,8 +17,9 @@ public final class UtilityCall extends RemoteCall { LockSemantics lockSemantics, String className, String methodName, + RdTarget rdTarget, Object[] args) { - super(sessionId, timedSpan, pluginId, dispatcher, lockSemantics, className, methodName, args); + super(sessionId, timedSpan, pluginId, dispatcher, lockSemantics, className, methodName, rdTarget, args); } @Override diff --git a/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/ProjectRootManager.kt b/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/ProjectRootManager.kt index b9f003fb20ba..54a6052aa47d 100644 --- a/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/ProjectRootManager.kt +++ b/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/ProjectRootManager.kt @@ -4,6 +4,7 @@ import com.intellij.driver.client.Driver import com.intellij.driver.client.Remote import com.intellij.driver.client.service import com.intellij.driver.client.utility +import com.intellij.driver.model.RdTarget @Remote("com.intellij.openapi.roots.ProjectRootManager") interface ProjectRootManager { @@ -30,7 +31,9 @@ interface Sdk { fun getHomePath(): String? } -@Remote(value = "com.jetbrains.performancePlugin.commands.SetupProjectSdkUtil", plugin = "com.jetbrains.performancePlugin") +@Remote(value = "com.jetbrains.performancePlugin.commands.SetupProjectSdkUtil", + plugin = "com.jetbrains.performancePlugin", + rdTarget = RdTarget.BACKEND_ONLY) interface SetupProjectSdkUtil { fun setupOrDetectSdk(project: Project, name: String, type: String, home: String) diff --git a/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/ui/Finder.kt b/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/ui/Finder.kt index b257c0d41313..ad541c6c2620 100644 --- a/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/ui/Finder.kt +++ b/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/ui/Finder.kt @@ -5,32 +5,37 @@ import com.intellij.driver.sdk.ui.components.ComponentData import com.intellij.driver.sdk.ui.components.UIComponentsList import com.intellij.driver.sdk.ui.components.UiComponent import com.intellij.driver.sdk.ui.remote.Component -import com.intellij.driver.sdk.ui.remote.RobotService +import com.intellij.driver.sdk.ui.remote.RobotServiceProvider +import com.intellij.driver.sdk.ui.remote.SearchService import org.intellij.lang.annotations.Language internal const val DEFAULT_FIND_TIMEOUT_SECONDS = 15 interface Finder { val driver: Driver - val robotService: RobotService + val searchService: SearchService + val robotServiceProvider: RobotServiceProvider val searchContext: SearchContext + val isRemoteIdeMode: Boolean + get() = driver.isRemoteIdeMode + fun x(@Language("xpath") xpath: String): UiComponent { - return UiComponent(ComponentData(xpath, driver, robotService, searchContext, null)) + return UiComponent(ComponentData(xpath, driver, searchService, robotServiceProvider, searchContext, null)) } fun x(@Language("xpath") xpath: String, type: Class): T { return type.getConstructor( ComponentData::class.java - ).newInstance(ComponentData(xpath, driver, robotService, searchContext, null)) + ).newInstance(ComponentData(xpath, driver, searchService, robotServiceProvider, searchContext, null)) } fun xx(@Language("xpath") xpath: String): UIComponentsList { - return UIComponentsList(xpath, UiComponent::class.java, driver, robotService, searchContext) + return UIComponentsList(xpath, UiComponent::class.java, driver, searchService, robotServiceProvider, searchContext) } fun xx(@Language("xpath") xpath: String, type: Class): UIComponentsList { - return UIComponentsList(xpath, type, driver, robotService, searchContext) + return UIComponentsList(xpath, type, driver, searchService, robotServiceProvider, searchContext) } } diff --git a/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/ui/UiRobot.kt b/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/ui/UiRobot.kt index b22284c22375..62989a958edd 100644 --- a/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/ui/UiRobot.kt +++ b/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/ui/UiRobot.kt @@ -2,18 +2,29 @@ package com.intellij.driver.sdk.ui import com.intellij.driver.client.Driver import com.intellij.driver.model.RemoteMouseButton +import com.intellij.driver.model.transport.Ref import com.intellij.driver.sdk.ui.keyboard.WithKeyboard import com.intellij.driver.sdk.ui.remote.Component import com.intellij.driver.sdk.ui.remote.RobotService +import com.intellij.driver.sdk.ui.remote.RobotServiceProvider +import com.intellij.driver.sdk.ui.remote.SearchService import java.awt.Point +import java.lang.reflect.Proxy /** * Provides access to UI actions with the IDE under test such as keyboard and mouse input. */ val Driver.ui - get(): UiRobot = UiRobot(this, service(RobotService::class)) + get(): UiRobot = UiRobot(this, service(SearchService::class), RemDevRobotServiceProvider(this)) -class UiRobot(override val driver: Driver, override val robotService: RobotService) : WithKeyboard, Finder { +class UiRobot( + override val driver: Driver, + override val searchService: SearchService, + override val robotServiceProvider: RobotServiceProvider +) : WithKeyboard, Finder { + + override val robotService: RobotService + get() = robotServiceProvider.getDefaultRobotService() fun moveMouse(point: Point) { robotService.robot.moveMouse(point) @@ -45,8 +56,32 @@ class UiRobot(override val driver: Driver, override val robotService: RobotServi override val context = "" override fun findAll(xpath: String): List { - return robotService.findAll(xpath) + return searchService.findAll(xpath) } } } +class RemDevRobotServiceProvider(private val driver: Driver) : RobotServiceProvider { + private val frontendRobotService: RobotService + get() = driver.service(RobotService::class) + private val backendRobotService: RobotService + get() = driver.service(RobotService::class, true) + + override fun getDefaultRobotService(): RobotService { + return frontendRobotService + } + + override fun getRobotServiceFor(obj: Any?): RobotService { + if (obj == null) { + return getDefaultRobotService() + } + val handler = Proxy.getInvocationHandler(obj) + val method = FakeRefInterface::class.java.getMethod("getRef") + val ref = handler.invoke(obj, method, arrayOf()) as Ref + return if (ref.isBackendReference) backendRobotService else frontendRobotService + } + + private interface FakeRefInterface { + fun getRef(): Ref + } +} \ No newline at end of file diff --git a/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/ui/components/IdeaFrameUI.kt b/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/ui/components/IdeaFrameUI.kt index 3854a8055ad2..54ff76efd538 100644 --- a/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/ui/components/IdeaFrameUI.kt +++ b/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/ui/components/IdeaFrameUI.kt @@ -17,8 +17,10 @@ fun Driver.ideFrame(action: IdeaFrameUI.() -> Unit) { } open class IdeaFrameUI(data: ComponentData) : UiComponent(data) { + private val projectViewTreeClass = if (isRemoteIdeMode) "ThinClientProjectViewTree" else "ProjectViewTree" + val projectViewTree = tree("//div[@class='${projectViewTreeClass}']") + private val ideaFrameComponent by lazy { driver.cast(component, IdeFrameImpl::class) } - val projectViewTree = tree("//div[@class='ProjectViewTree']") val project: Project? get() = driver.utility(ProjectFrameHelper::class).getFrameHelper(component).getProject() diff --git a/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/ui/components/UIComponentsList.kt b/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/ui/components/UIComponentsList.kt index eef6c7498560..49b249cf7b99 100644 --- a/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/ui/components/UIComponentsList.kt +++ b/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/ui/components/UIComponentsList.kt @@ -3,12 +3,14 @@ package com.intellij.driver.sdk.ui.components import com.intellij.driver.client.Driver import com.intellij.driver.sdk.ui.SearchContext import com.intellij.driver.sdk.ui.remote.Component -import com.intellij.driver.sdk.ui.remote.RobotService +import com.intellij.driver.sdk.ui.remote.RobotServiceProvider +import com.intellij.driver.sdk.ui.remote.SearchService class UIComponentsList(private val xpath: String, private val type: Class, val driver: Driver, - val robotService: RobotService, + val searchService: SearchService, + val robotServiceProvider: RobotServiceProvider, private val parentSearchContext: SearchContext) { fun list(): List { return parentSearchContext.findAll(xpath).mapIndexed { n, c -> @@ -17,12 +19,12 @@ class UIComponentsList(private val xpath: String, get() = parentSearchContext.context + xpath + "[$n]" override fun findAll(xpath: String): List { - return robotService.findAll(xpath, c) + return searchService.findAll(xpath, c) } } type.getConstructor( ComponentData::class.java - ).newInstance(ComponentData(xpath, driver, robotService, searchContext, c)) + ).newInstance(ComponentData(xpath, driver, searchService, robotServiceProvider, searchContext, c)) } } } \ No newline at end of file diff --git a/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/ui/components/UiComponent.kt b/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/ui/components/UiComponent.kt index 3af7ebcd1a57..0503df5d3a1a 100644 --- a/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/ui/components/UiComponent.kt +++ b/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/ui/components/UiComponent.kt @@ -10,6 +10,8 @@ import com.intellij.driver.sdk.ui.UiText import com.intellij.driver.sdk.ui.keyboard.WithKeyboard import com.intellij.driver.sdk.ui.remote.Component import com.intellij.driver.sdk.ui.remote.RobotService +import com.intellij.driver.sdk.ui.remote.RobotServiceProvider +import com.intellij.driver.sdk.ui.remote.SearchService import com.intellij.driver.sdk.waitFor import java.awt.Point import kotlin.time.Duration @@ -18,7 +20,8 @@ import kotlin.time.Duration.Companion.seconds data class ComponentData(val xpath: String, val driver: Driver, - val robotService: RobotService, + val searchService: SearchService, + val robotServiceProvider: RobotServiceProvider, val parentSearchContext: SearchContext, val foundComponent: Component?) @@ -32,21 +35,27 @@ open class UiComponent(private val data: ComponentData) : Finder, WithKeyboard { } private fun findThisComponent(): Component { + lateinit var result: List waitFor(DEFAULT_FIND_TIMEOUT_SECONDS.seconds, errorMessage = "Can't find component with '${data.xpath}' in ${data.parentSearchContext.context.takeIf { it.isNotEmpty() } ?: "whole hierarchy"}") { - data.parentSearchContext.findAll(data.xpath).size == 1 + result = data.parentSearchContext.findAll(data.xpath) + result.size == 1 } - return data.parentSearchContext.findAll(data.xpath).first() + return result.first() } override val driver: Driver = data.driver - override val robotService: RobotService = data.robotService + override val searchService: SearchService = data.searchService + override val robotServiceProvider: RobotServiceProvider = data.robotServiceProvider + override val robotService: RobotService by lazy { + data.robotServiceProvider.getRobotServiceFor(component) + } override val searchContext: SearchContext = object : SearchContext { override val context: String = data.parentSearchContext.context + data.xpath override fun findAll(xpath: String): List { - return robotService.findAll(xpath, component) + return searchService.findAll(xpath, component) } } @@ -92,19 +101,19 @@ open class UiComponent(private val data: ComponentData) : Finder, WithKeyboard { } fun findText(predicate: (TextData) -> Boolean): UiText { - return robotService.findAllText(component).single(predicate).let { UiText(this, it) } + return searchService.findAllText(component).single(predicate).let { UiText(this, it) } } fun present(): Boolean { - return robotService.findAll(data.xpath).isNotEmpty() + return searchService.findAll(data.xpath).isNotEmpty() } fun notPresent(): Boolean { - return robotService.findAll(data.xpath).isEmpty() + return searchService.findAll(data.xpath).isEmpty() } fun hasText(predicate: (TextData) -> Boolean): Boolean { - return robotService.findAllText(component).any(predicate) + return searchService.findAllText(component).any(predicate) } fun isVisible(): Boolean = component.isVisible() @@ -112,11 +121,11 @@ open class UiComponent(private val data: ComponentData) : Finder, WithKeyboard { fun isEnabled(): Boolean = component.isEnabled() fun findAllText(predicate: (TextData) -> Boolean): List { - return robotService.findAllText(component).filter(predicate).map { UiText(this, it) } + return searchService.findAllText(component).filter(predicate).map { UiText(this, it) } } fun findAllText(): List { - return robotService.findAllText(component).map { UiText(this, it) } + return searchService.findAllText(component).map { UiText(this, it) } } fun hasVisibleComponent(component: UiComponent): Boolean { diff --git a/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/ui/components/WelcomeScreenUI.kt b/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/ui/components/WelcomeScreenUI.kt index e25083202cc6..73b2ebfa3a55 100644 --- a/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/ui/components/WelcomeScreenUI.kt +++ b/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/ui/components/WelcomeScreenUI.kt @@ -3,8 +3,10 @@ package com.intellij.driver.sdk.ui.components import com.intellij.driver.client.Driver import com.intellij.driver.sdk.ui.* -fun Finder.welcomeScreen(action: WelcomeScreenUI.() -> Unit = {}) = - x("//div[@class='FlatWelcomeFrame']", WelcomeScreenUI::class.java).apply(action) +fun Finder.welcomeScreen(action: WelcomeScreenUI.() -> Unit = {}): WelcomeScreenUI { + val welcomeScreenClass = if (isRemoteIdeMode) "TabbedWelcomeScreen" else "FlatWelcomeFrame" + return x("//div[@class='${welcomeScreenClass}']", WelcomeScreenUI::class.java).apply(action) +} fun Driver.welcomeScreen(action: WelcomeScreenUI.() -> Unit = {}) = this.ui.welcomeScreen(action) diff --git a/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/ui/remote/RobotService.kt b/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/ui/remote/RobotService.kt index 59cb6264f842..c7740870c40e 100644 --- a/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/ui/remote/RobotService.kt +++ b/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/ui/remote/RobotService.kt @@ -1,8 +1,6 @@ package com.intellij.driver.sdk.ui.remote import com.intellij.driver.client.Remote -import com.intellij.driver.model.TextDataList -import org.intellij.lang.annotations.Language internal const val REMOTE_ROBOT_MODULE_ID = "com.jetbrains.performancePlugin/intellij.performanceTesting.remoteDriver" @@ -10,8 +8,10 @@ internal const val REMOTE_ROBOT_MODULE_ID = "com.jetbrains.performancePlugin/int plugin = REMOTE_ROBOT_MODULE_ID) interface RobotService { val robot: Robot - fun findAll(@Language("xpath") xpath: String): List - fun findAll(@Language("xpath") xpath: String, component: Component): List - fun findAllText(component: Component): TextDataList fun saveHierarchy(folderPath: String, fileName: String = "ui") -} \ No newline at end of file +} + +interface RobotServiceProvider { + fun getDefaultRobotService(): RobotService + fun getRobotServiceFor(obj: Any?): RobotService +} diff --git a/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/ui/remote/SearchService.kt b/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/ui/remote/SearchService.kt new file mode 100644 index 000000000000..3684b76e31f1 --- /dev/null +++ b/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/ui/remote/SearchService.kt @@ -0,0 +1,13 @@ +package com.intellij.driver.sdk.ui.remote + +import com.intellij.driver.client.Remote +import com.intellij.driver.model.TextDataList +import org.intellij.lang.annotations.Language + +@Remote("com.jetbrains.performancePlugin.remotedriver.SearchService", + plugin = REMOTE_ROBOT_MODULE_ID) +interface SearchService { + fun findAll(@Language("xpath") xpath: String): List + fun findAll(@Language("xpath") xpath: String, component: Component): List + fun findAllText(component: Component): TextDataList +} \ No newline at end of file diff --git a/plugins/performanceTesting/core/src/com/jetbrains/performancePlugin/jmxDriver/InvokerService.kt b/plugins/performanceTesting/core/src/com/jetbrains/performancePlugin/jmxDriver/InvokerService.kt index a5ec4652b8de..581f54d5f23c 100644 --- a/plugins/performanceTesting/core/src/com/jetbrains/performancePlugin/jmxDriver/InvokerService.kt +++ b/plugins/performanceTesting/core/src/com/jetbrains/performancePlugin/jmxDriver/InvokerService.kt @@ -2,12 +2,15 @@ package com.jetbrains.performancePlugin.jmxDriver import com.intellij.driver.impl.Invoker import com.intellij.driver.impl.InvokerMBean +import com.intellij.driver.model.transport.Ref +import com.intellij.idea.AppMode import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.components.Service import com.intellij.openapi.diagnostic.Logger import com.intellij.platform.diagnostic.telemetry.IJTracer import com.intellij.util.PlatformUtils import io.opentelemetry.context.Context +import java.awt.Component import java.lang.management.ManagementFactory import java.util.function.Consumer import java.util.function.Supplier @@ -26,10 +29,17 @@ class InvokerService { private val jmxBackendPort = System.getProperty(BACKEND_JMX_PORT_PROPERTY) private val log = Logger.getInstance(InvokerService::class.java) } - var invoker: InvokerMBean? = null - private set - fun isReady(): Boolean = invoker != null + private var myInvoker: InvokerMBean? = null + + val invoker: InvokerMBean + get() = myInvoker ?: throw IllegalStateException("Invoker is not registered") + + fun isReady(): Boolean = myInvoker != null + + fun putReference(c: Component): Ref { + return invoker.putAdhocReference(c) + } @Throws(JMException::class) fun register(tracerSupplier: Supplier, @@ -38,15 +48,21 @@ class InvokerService { val objectName = ObjectName("com.intellij.driver:type=Invoker") val server = ManagementFactory.getPlatformMBeanServer() - val localInvoker = Invoker("v", tracerSupplier, timedContextSupplier, screenshotAction) + val prefix = when { + PlatformUtils.isJetBrainsClient() -> Ref.FRONTEND_REFERENCE_PREFIX + AppMode.isRemoteDevHost() -> Ref.BACKEND_REFERENCE_PREFIX + else -> "" + } + + val localInvoker = Invoker(prefix, tracerSupplier, timedContextSupplier, screenshotAction) val remoteJmxAddress = jmxBackendPort?.let { "$JMX_BACKEND_IP:$it" } if (PlatformUtils.isJetBrainsClient() && remoteJmxAddress != null) { - log.info("Remote Dev Mode") - invoker = RemoteDevInvoker(localInvoker, remoteJmxAddress) + log.info("Remote Dev Mode, $remoteJmxAddress") + myInvoker = RemoteDevInvoker(localInvoker, remoteJmxAddress) } else { - invoker = localInvoker + myInvoker = localInvoker } server.registerMBean(invoker, objectName) } diff --git a/plugins/performanceTesting/core/src/com/jetbrains/performancePlugin/jmxDriver/RemoteDevInvoker.kt b/plugins/performanceTesting/core/src/com/jetbrains/performancePlugin/jmxDriver/RemoteDevInvoker.kt index 9b3994d0d0dc..4c57dcd6c4ff 100644 --- a/plugins/performanceTesting/core/src/com/jetbrains/performancePlugin/jmxDriver/RemoteDevInvoker.kt +++ b/plugins/performanceTesting/core/src/com/jetbrains/performancePlugin/jmxDriver/RemoteDevInvoker.kt @@ -1,20 +1,39 @@ package com.jetbrains.performancePlugin.jmxDriver +import com.intellij.driver.client.Driver +import com.intellij.driver.client.impl.DriverImpl import com.intellij.driver.client.impl.Invoker -import com.intellij.driver.client.impl.JmxCallHandler import com.intellij.driver.client.impl.JmxHost import com.intellij.driver.impl.InvokerMBean +import com.intellij.driver.model.DriverIlligalStateException import com.intellij.driver.model.ProductVersion +import com.intellij.driver.model.RdTarget import com.intellij.driver.model.transport.Ref import com.intellij.driver.model.transport.RemoteCall import com.intellij.driver.model.transport.RemoteCallResult -import kotlin.jvm.java -interface RemoteDevInvokerMBean : InvokerMBean +interface RemoteDevInvokerMBean : InvokerMBean { + val driver: Driver +} internal class RemoteDevInvoker(private val localInvoker: InvokerMBean, remoteJmxAddress: String) : RemoteDevInvokerMBean { - private val remoteInvoker = JmxCallHandler.jmx(Invoker::class.java, JmxHost(null, null, remoteJmxAddress)) + + override val driver: DriverImpl + + init { + val originalClassLoader = Thread.currentThread().getContextClassLoader() + try { + Thread.currentThread().setContextClassLoader(this::class.java.getClassLoader()) + driver = Driver.create(JmxHost(null, null, remoteJmxAddress)) as DriverImpl + } + finally { + Thread.currentThread().setContextClassLoader(originalClassLoader) + } + } + + private val remoteInvoker: Invoker + get() = driver.getInvoker() override fun getProductVersion(): ProductVersion { return localInvoker.productVersion @@ -29,14 +48,30 @@ internal class RemoteDevInvoker(private val localInvoker: InvokerMBean, remoteJm } override fun invoke(call: RemoteCall): RemoteCallResult { - - try { - return localInvoker.invoke(call) + when (call.rdTarget) { + RdTarget.FRONTEND_ONLY -> return localInvoker.invoke(call) + RdTarget.BACKEND_ONLY -> return invokeRemote(call) + RdTarget.FRONTEND_FIRST -> try { + return localInvoker.invoke(call) + } + catch (_: DriverIlligalStateException) { + return invokeRemote(call) + } + catch (t: Throwable) { + throw t + } } - // replace with ReferenceNotFoundException later - catch (e: Throwable) { + } + + private fun invokeRemote(call: RemoteCall): RemoteCallResult { + val originalClassLoader = Thread.currentThread().getContextClassLoader() + try { + Thread.currentThread().setContextClassLoader(this::class.java.getClassLoader()) return remoteInvoker.invoke(call) } + finally { + Thread.currentThread().setContextClassLoader(originalClassLoader) + } } override fun newSession(): Int { diff --git a/plugins/performanceTesting/remote-driver/intellij.performanceTesting.remoteDriver.iml b/plugins/performanceTesting/remote-driver/intellij.performanceTesting.remoteDriver.iml index a9962d784604..1addd79d4378 100644 --- a/plugins/performanceTesting/remote-driver/intellij.performanceTesting.remoteDriver.iml +++ b/plugins/performanceTesting/remote-driver/intellij.performanceTesting.remoteDriver.iml @@ -18,11 +18,11 @@ - + - - + + \ No newline at end of file diff --git a/plugins/performanceTesting/remote-driver/resources/intellij.performanceTesting.remoteDriver.xml b/plugins/performanceTesting/remote-driver/resources/intellij.performanceTesting.remoteDriver.xml index 1f5c24c01aa3..a228b9292fc1 100644 --- a/plugins/performanceTesting/remote-driver/resources/intellij.performanceTesting.remoteDriver.xml +++ b/plugins/performanceTesting/remote-driver/resources/intellij.performanceTesting.remoteDriver.xml @@ -1,10 +1,9 @@ - - + + > { - return xpathSearcher.findComponents(xpath, null) - } - - fun findAll(xpath: String, component: Component): List> { - return xpathSearcher.findComponents(xpath, component) - } - - fun findAllText(component: Component): TextDataList { - return TextParser.parseComponent(component, TextToKeyCache).let { TextDataList().apply { addAll(it) } } - } - fun saveHierarchy(folderPath: String, fileName: String = "ui.html") { - val html = xpathSearcher.modelCreator.create(null).convertToHtml() + val html = XpathDataModelCreator.create(null).convertToHtml() Paths.get(folderPath).resolve(fileName).toFile().writeText(html) staticFiles.forEach { staticFilePath -> diff --git a/plugins/performanceTesting/remote-driver/src/com/jetbrains/performancePlugin/remotedriver/SearchService.kt b/plugins/performanceTesting/remote-driver/src/com/jetbrains/performancePlugin/remotedriver/SearchService.kt new file mode 100644 index 000000000000..cd324ef79227 --- /dev/null +++ b/plugins/performanceTesting/remote-driver/src/com/jetbrains/performancePlugin/remotedriver/SearchService.kt @@ -0,0 +1,27 @@ +package com.jetbrains.performancePlugin.remotedriver + +import com.intellij.driver.model.RefDelegate +import com.intellij.driver.model.TextDataList +import com.intellij.openapi.components.Service +import com.jetbrains.performancePlugin.remotedriver.dataextractor.TextParser +import com.jetbrains.performancePlugin.remotedriver.dataextractor.TextToKeyCache +import com.jetbrains.performancePlugin.remotedriver.xpath.XpathSearcher +import java.awt.Component + +@Suppress("unused") +@Service(Service.Level.APP) +internal class SearchService { + private val xpathSearcher: XpathSearcher = XpathSearcher() + + fun findAll(xpath: String): List> { + return xpathSearcher.findComponents(xpath, null) + } + + fun findAll(xpath: String, component: Component): List> { + return xpathSearcher.findComponents(xpath, component) + } + + fun findAllText(component: Component): TextDataList { + return TextParser.parseComponent(component, TextToKeyCache).let { TextDataList().apply { addAll(it) } } + } +} \ No newline at end of file diff --git a/plugins/performanceTesting/remote-driver/src/com/jetbrains/performancePlugin/remotedriver/webservice/UiHierarchyWebService.kt b/plugins/performanceTesting/remote-driver/src/com/jetbrains/performancePlugin/remotedriver/webservice/UiHierarchyWebService.kt index a0c619373082..870d3da5e693 100644 --- a/plugins/performanceTesting/remote-driver/src/com/jetbrains/performancePlugin/remotedriver/webservice/UiHierarchyWebService.kt +++ b/plugins/performanceTesting/remote-driver/src/com/jetbrains/performancePlugin/remotedriver/webservice/UiHierarchyWebService.kt @@ -1,10 +1,7 @@ package com.jetbrains.performancePlugin.remotedriver.webservice import com.intellij.openapi.extensions.ExtensionNotApplicableException -import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.util.registry.Registry -import com.jetbrains.performancePlugin.jmxDriver.InvokerService -import com.jetbrains.performancePlugin.remotedriver.dataextractor.TextToKeyCache import com.jetbrains.performancePlugin.remotedriver.webservice.routing.CantFindRouteException import com.jetbrains.performancePlugin.remotedriver.webservice.routing.Routing import com.jetbrains.performancePlugin.remotedriver.webservice.routing.StaticFile @@ -16,8 +13,6 @@ import io.netty.channel.ChannelHandlerContext import io.netty.handler.codec.http.* import org.jetbrains.ide.RestService import org.jetbrains.io.response -import org.jsoup.helper.W3CDom -import java.awt.Component internal class UiHierarchyWebService : RestService() { init { @@ -38,33 +33,11 @@ internal class UiHierarchyWebService : RestService() { get("/") { hierarchy() } - get("/raw") { - rawHierarchy() - } - get("/{id}") { - val id = pathParameters["id"] ?: throw IllegalStateException("id parameter is not found") - val component = UiHierarchyWebServiceExtension.EP_NAME.extensionList.firstNotNullOf { it.getComponentById(id) } - partialHierarchy(component) - } static("/static") } - private fun partialHierarchy(component: Component): String { - val doc = XpathDataModelCreator(TextToKeyCache) { c, e -> - val ref = InvokerService.getInstance().invoker!!.putAdhocReference(c) - e.setAttribute("remoteId", ref.id) - e.setAttribute("hashCode", ref.identityHashCode.toString()) - }.create(component) - return W3CDom().asString(doc) - } - - private fun rawHierarchy(): String { - val doc = XpathDataModelCreator(TextToKeyCache).create(null) - return W3CDom().asString(doc) - } - private fun hierarchy(): String { - val doc = XpathDataModelCreator(TextToKeyCache).create(null) + val doc = XpathDataModelCreator.create(null) return doc.convertToHtml() } @@ -84,11 +57,3 @@ internal class UiHierarchyWebService : RestService() { return null } } - -interface UiHierarchyWebServiceExtension { - companion object { - val EP_NAME = ExtensionPointName("com.jetbrains.performancePlugin.remoteDriver.uiHierarchyExtension") - } - - fun getComponentById(id: String): Component? -} diff --git a/plugins/performanceTesting/remote-driver/src/com/jetbrains/performancePlugin/remotedriver/xpath/XpathDataModelCreator.kt b/plugins/performanceTesting/remote-driver/src/com/jetbrains/performancePlugin/remotedriver/xpath/XpathDataModelCreator.kt index 58d54ff9b23c..85e8f6d232a3 100644 --- a/plugins/performanceTesting/remote-driver/src/com/jetbrains/performancePlugin/remotedriver/xpath/XpathDataModelCreator.kt +++ b/plugins/performanceTesting/remote-driver/src/com/jetbrains/performancePlugin/remotedriver/xpath/XpathDataModelCreator.kt @@ -2,6 +2,7 @@ package com.jetbrains.performancePlugin.remotedriver.xpath +import com.jetbrains.performancePlugin.jmxDriver.InvokerService import com.jetbrains.performancePlugin.remotedriver.dataextractor.TextParser import com.jetbrains.performancePlugin.remotedriver.dataextractor.TextToKeyCache import org.assertj.swing.edt.GuiActionRunner @@ -20,27 +21,39 @@ import javax.swing.JComponent import javax.xml.parsers.DocumentBuilderFactory import kotlin.math.absoluteValue -internal class XpathDataModelCreator(private val textToKeyCache: TextToKeyCache, - private val bindComponentToElement: (Component, Element) -> Unit = { c, e -> - e.setUserData("component", c, null) - }) { +object XpathDataModelCreator { + interface ComponentTranslator { + fun translate(c: Component, e: Element) + } + + private object DefaultComponentTranslator : ComponentTranslator { + override fun translate(c: Component, e: Element) { + e.setUserData("component", c, null) + } + } + + private object RemoteComponentTranslator : ComponentTranslator { + override fun translate(c: Component, e: Element) { + val ref = InvokerService.getInstance().putReference(c) + e.setAttribute("remoteId", ref.id) + e.setAttribute("hashCode", ref.identityHashCode.toString()) + } + } private fun addComponent( doc: Document, parentElement: Element, hierarchy: ComponentHierarchy, component: Component, + translator: ComponentTranslator, targetComponent: Component? = null ) { - val subtreeOverrider = XpathDataModelSubTreeProvider.EP_NAME.extensionList.find { provider -> - provider.shouldOverrideSubtree(component) - } - if (subtreeOverrider != null) { - subtreeOverrider.overrideSubtree(doc, parentElement, component) + XpathDataModelExtension.EP_NAME.extensionList.find { it.acceptComponent(component) }?.let { + it.processComponent(doc, parentElement, component) return } - val element = createElement(doc, component, targetComponent) + val element = createElement(doc, component, translator, targetComponent) parentElement.appendChild(element) val allChildren = hierarchy.childrenOf(component) @@ -58,17 +71,17 @@ internal class XpathDataModelCreator(private val textToKeyCache: TextToKeyCache, addAll(filteredChildren) addAll(exceptionChildren) }.sortedWith(ComponentOrderComparator).forEach { - addComponent(doc, element, hierarchy, it, targetComponent) + addComponent(doc, element, hierarchy, it, translator, targetComponent) } } - private fun createElement(doc: Document, component: Component, targetComponent: Component? = null): Element { + private fun createElement(doc: Document, component: Component, translator: ComponentTranslator, targetComponent: Component? = null): Element { val element = doc.createElement("div") component.fillElement(doc, element, targetComponent) - bindComponentToElement(component, element) + translator.translate(component, element) return element } @@ -123,7 +136,7 @@ internal class XpathDataModelCreator(private val textToKeyCache: TextToKeyCache, value?.removeInvalidXmlCharacters()?.apply { if (textFieldsFilter(attributeName, value)) { elementText.append("$attributeName: '$this'. ") - textToKeyCache.findKey(value)?.apply { + TextToKeyCache.findKey(value)?.apply { elementText.append("${attributeName}.key: '$this'. ") element.setAttribute(attributeName + ".key", this) } @@ -146,13 +159,13 @@ internal class XpathDataModelCreator(private val textToKeyCache: TextToKeyCache, } if (accessibleName != null) { element.setAttribute("accessiblename", accessibleName) - textToKeyCache.findKey(accessibleName)?.apply { element.setAttribute("accessiblename.key", this) } + TextToKeyCache.findKey(accessibleName)?.apply { element.setAttribute("accessiblename.key", this) } } val tooltipText = getTooltipText(this) if (tooltipText != null) { element.setAttribute("tooltiptext", tooltipText) - textToKeyCache.findKey(tooltipText)?.apply { element.setAttribute("tooltiptext.key", this) } + TextToKeyCache.findKey(tooltipText)?.apply { element.setAttribute("tooltiptext.key", this) } } if (isShowing) { @@ -166,7 +179,7 @@ internal class XpathDataModelCreator(private val textToKeyCache: TextToKeyCache, && bounds.width > 0 && bounds.height > 0 ) { try { - val foundText = TextParser.parseComponent(this, textToKeyCache) + val foundText = TextParser.parseComponent(this, TextToKeyCache) val text = foundText.joinToString(" || ") { it.text } element.setAttribute("visible_text", text) if (text.trim().isNotEmpty()) { @@ -291,8 +304,19 @@ internal class XpathDataModelCreator(private val textToKeyCache: TextToKeyCache, } + fun create(component: Component?, targetComponent: Component? = null): Document { + return create(component, DefaultComponentTranslator, targetComponent) + } - fun create(rootComponent: Component?, targetComponent: Component? = null): Document { + fun createForRemote(component: Component?, targetComponent: Component? = null): Document { + return create(component, RemoteComponentTranslator, targetComponent) + } + + private fun create( + rootComponent: Component?, + translator: ComponentTranslator, + targetComponent: Component? = null, + ): Document { val doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument() @@ -308,7 +332,7 @@ internal class XpathDataModelCreator(private val textToKeyCache: TextToKeyCache, hierarchy.roots() } containers.filter { it.isShowing || it.javaClass.name.endsWith("SharedOwnerFrame") }.forEach { - addComponent(doc, rootElement, hierarchy, it, targetComponent) + addComponent(doc, rootElement, hierarchy, it, translator, targetComponent) } } }) diff --git a/plugins/performanceTesting/remote-driver/src/com/jetbrains/performancePlugin/remotedriver/xpath/XpathDataModelSubTreeProvider.kt b/plugins/performanceTesting/remote-driver/src/com/jetbrains/performancePlugin/remotedriver/xpath/XpathDataModelExtension.kt similarity index 51% rename from plugins/performanceTesting/remote-driver/src/com/jetbrains/performancePlugin/remotedriver/xpath/XpathDataModelSubTreeProvider.kt rename to plugins/performanceTesting/remote-driver/src/com/jetbrains/performancePlugin/remotedriver/xpath/XpathDataModelExtension.kt index 71225349bc33..059905e82867 100644 --- a/plugins/performanceTesting/remote-driver/src/com/jetbrains/performancePlugin/remotedriver/xpath/XpathDataModelSubTreeProvider.kt +++ b/plugins/performanceTesting/remote-driver/src/com/jetbrains/performancePlugin/remotedriver/xpath/XpathDataModelExtension.kt @@ -6,11 +6,11 @@ import org.w3c.dom.Document import org.w3c.dom.Element import java.awt.Component -interface XpathDataModelSubTreeProvider { +interface XpathDataModelExtension { companion object { - val EP_NAME = ExtensionPointName("com.jetbrains.performancePlugin.remoteDriver.xpathDataModelExtension") + val EP_NAME = ExtensionPointName.create("com.jetbrains.performancePlugin.remotedriver.xpathDataModelExtension") } - fun shouldOverrideSubtree(component: Component): Boolean - fun overrideSubtree(doc: Document, parentElement: Element, component: Component) + fun acceptComponent(component: Component): Boolean + fun processComponent(doc: Document, parentElement: Element, component: Component) } \ No newline at end of file diff --git a/plugins/performanceTesting/remote-driver/src/com/jetbrains/performancePlugin/remotedriver/xpath/XpathSearcher.kt b/plugins/performanceTesting/remote-driver/src/com/jetbrains/performancePlugin/remotedriver/xpath/XpathSearcher.kt index 02e029361f3f..87d37c0bdf11 100644 --- a/plugins/performanceTesting/remote-driver/src/com/jetbrains/performancePlugin/remotedriver/xpath/XpathSearcher.kt +++ b/plugins/performanceTesting/remote-driver/src/com/jetbrains/performancePlugin/remotedriver/xpath/XpathSearcher.kt @@ -6,15 +6,13 @@ import com.intellij.driver.model.LocalRefDelegate import com.intellij.driver.model.RefDelegate import com.intellij.driver.model.RemoteRefDelegate import com.intellij.driver.model.transport.Ref -import com.jetbrains.performancePlugin.remotedriver.dataextractor.TextToKeyCache import org.w3c.dom.Element import org.w3c.dom.NodeList import java.awt.Component import javax.xml.xpath.XPathConstants import javax.xml.xpath.XPathFactory -internal class XpathSearcher(textToKeyCache: TextToKeyCache) { - val modelCreator = XpathDataModelCreator(textToKeyCache) +internal class XpathSearcher { private val xPath = XPathFactory.newInstance().newXPath() fun findComponent(xpathExpression: String, component: Component?): RefDelegate { @@ -29,7 +27,7 @@ internal class XpathSearcher(textToKeyCache: TextToKeyCache) { } fun findComponents(xpathExpression: String, component: Component?): List> { - val model = modelCreator.create(component) + val model = XpathDataModelCreator.create(component) val result = xPath.compile(xpathExpression).evaluate(model, XPathConstants.NODESET) as NodeList return (0 until result.length).mapNotNull { result.item(it) }.filterIsInstance().mapNotNull { if (it.hasAttribute("remoteId")) {