diff --git a/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java b/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java index c7b87a89..766a0315 100644 --- a/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java +++ b/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java @@ -537,10 +537,8 @@ class Interface extends TypeDefinition { output.add("public interface " + jsonName + implementsClause + " {"); offset = " "; writeSharedTypes(output, offset); + writeEvents(output, offset); super.writeTo(output, offset); - for (Event e : events) { - e.writeTo(output, offset); - } for (Method m : methods) { m.writeTo(output, offset); } @@ -552,7 +550,26 @@ class Interface extends TypeDefinition { output.add("\n"); } - void writeSharedTypes(List output, String offset) { + private void writeEvents(List output, String offset) { + if (events.isEmpty()) { + return; + } + output.add(offset + "enum EventType {"); + // TODO: fix api.json to not miss this event. + if ("Page".equals(jsonName)) { + output.add(offset + " CLOSE,"); + } + for (int i = 0; i < events.size(); i++) { + String comma = i == events.size() ? "" : ","; + output.add(offset + " " + events.get(i).jsonName.toUpperCase() + comma); + } + output.add(offset + "}"); + output.add(""); + output.add(offset + "void addListener(EventType type, Listener listener);"); + output.add(offset + "void removeListener(EventType type, Listener listener);"); + } + + private void writeSharedTypes(List output, String offset) { switch (jsonName) { case "Mouse": { output.add(offset + "enum Button { LEFT, MIDDLE, RIGHT }"); diff --git a/api-generator/src/main/java/com/microsoft/playwright/tools/Types.java b/api-generator/src/main/java/com/microsoft/playwright/tools/Types.java index 73e3f2fd..fc329cff 100644 --- a/api-generator/src/main/java/com/microsoft/playwright/tools/Types.java +++ b/api-generator/src/main/java/com/microsoft/playwright/tools/Types.java @@ -93,20 +93,12 @@ class Types { add("Mouse.down.options.button", "\"left\"|\"middle\"|\"right\"", "Button", new Empty()); add("Mouse.up.options.button", "\"left\"|\"middle\"|\"right\"", "Button", new Empty()); add("BrowserType.launchPersistentContext.options.colorScheme", "\"dark\"|\"light\"|\"no-preference\"", "ColorScheme"); - add("ChromiumBrowser.newContext.options.colorScheme", "\"dark\"|\"light\"|\"no-preference\"", "ColorScheme"); - add("ChromiumBrowser.newPage.options.colorScheme", "\"dark\"|\"light\"|\"no-preference\"", "ColorScheme"); - add("FirefoxBrowser.newContext.options.colorScheme", "\"dark\"|\"light\"|\"no-preference\"", "ColorScheme"); - add("FirefoxBrowser.newPage.options.colorScheme", "\"dark\"|\"light\"|\"no-preference\"", "ColorScheme"); - add("WebKitBrowser.newContext.options.colorScheme", "\"dark\"|\"light\"|\"no-preference\"", "ColorScheme"); - add("WebKitBrowser.newPage.options.colorScheme", "\"dark\"|\"light\"|\"no-preference\"", "ColorScheme"); // Route add("BrowserContext.route.handler", "function(Route, Request)", "BiConsumer"); add("BrowserContext.unroute.handler", "function(Route, Request)", "BiConsumer"); add("Page.route.handler", "function(Route, Request)", "BiConsumer"); add("Page.unroute.handler", "function(Route, Request)", "BiConsumer"); - add("ChromiumBrowserContext.route.handler", "function(Route, Request)", "BiConsumer"); - add("ChromiumBrowserContext.unroute.handler", "function(Route, Request)", "BiConsumer"); // Viewport size. add("Browser.newContext.options.viewport", "null|Object", "Page.Viewport", new Empty()); @@ -114,12 +106,6 @@ class Types { add("Page.setViewportSize.viewportSize", "Object", "Viewport", new Empty()); add("Page.viewportSize", "null|Object", "Viewport", new Empty()); add("BrowserType.launchPersistentContext.options.viewport", "null|Object", "Page.Viewport", new Empty()); - add("ChromiumBrowser.newContext.options.viewport", "null|Object", "Page.Viewport", new Empty()); - add("ChromiumBrowser.newPage.options.viewport", "null|Object", "Page.Viewport", new Empty()); - add("FirefoxBrowser.newContext.options.viewport", "null|Object", "Page.Viewport", new Empty()); - add("FirefoxBrowser.newPage.options.viewport", "null|Object", "Page.Viewport", new Empty()); - add("WebKitBrowser.newContext.options.viewport", "null|Object", "Page.Viewport", new Empty()); - add("WebKitBrowser.newPage.options.viewport", "null|Object", "Page.Viewport", new Empty()); // HTTP credentials. add("Browser.newContext.options.httpCredentials", "Object", "BrowserContext.HTTPCredentials", new Empty()); @@ -179,8 +165,6 @@ class Types { add("Selectors.register.script", "function|string|Object", "String"); add("Worker.evaluate.pageFunction", "function|string", "String"); add("Worker.evaluateHandle.pageFunction", "function|string", "String"); - add("ChromiumBrowserContext.addInitScript.script", "function|string|Object", "String"); - // Return structures add("ConsoleMessage.location", "Object", "Location"); @@ -204,14 +188,18 @@ class Types { add("BrowserContext.cookies.urls", "string|Array", "String"); add("BrowserContext.route.url", "string|RegExp|function(URL):boolean", "String"); add("BrowserContext.unroute.url", "string|RegExp|function(URL):boolean", "String"); + add("BrowserContext.waitForEvent.event", "string", "EventType", new Empty()); add("BrowserContext.waitForEvent.optionsOrPredicate", "Function|Object", "String"); + add("BrowserContext.waitForEvent", "Promise", "Deferred>", new Empty()); add("Page.waitForNavigation.options.url", "string|RegExp|Function", "String"); add("Page.frame.options", "string|Object", "FrameOptions", new Empty()); add("Page.route.url", "string|RegExp|function(URL):boolean", "String"); add("Page.selectOption.values", "null|string|ElementHandle|Array|Object|Array|Array", "String"); add("Page.setInputFiles.files", "string|Array|Object|Array", "String"); add("Page.unroute.url", "string|RegExp|function(URL):boolean", "String"); + add("Page.waitForEvent.event", "string", "EventType", new Empty()); add("Page.waitForEvent.optionsOrPredicate", "Function|Object", "String"); + add("Page.waitForEvent", "Promise", "Deferred>", new Empty()); add("Page.waitForRequest.urlOrPredicate", "string|RegExp|Function", "String"); add("Page.waitForResponse.urlOrPredicate", "string|RegExp|Function", "String"); add("Frame.waitForNavigation.options.url", "string|RegExp|Function", "String"); @@ -231,10 +219,6 @@ class Types { add("BrowserType.launchServer.options.firefoxUserPrefs", "Object", "String"); add("BrowserType.launchServer.options.env", "Object", "String"); add("Logger.log.message", "string|Error", "String"); - add("ChromiumBrowserContext.cookies.urls", "string|Array", "String"); - add("ChromiumBrowserContext.route.url", "string|RegExp|function(URL):boolean", "String"); - add("ChromiumBrowserContext.unroute.url", "string|RegExp|function(URL):boolean", "String"); - add("ChromiumBrowserContext.waitForEvent.optionsOrPredicate", "Function|Object", "String"); add("Browser.newContext.options.geolocation.latitude", "number", "double"); add("Browser.newContext.options.geolocation.longitude", "number", "double"); @@ -245,24 +229,6 @@ class Types { add("BrowserType.launchPersistentContext.options.geolocation.latitude", "number", "double"); add("BrowserType.launchPersistentContext.options.geolocation.longitude", "number", "double"); add("BrowserType.launchPersistentContext.options.geolocation.accuracy", "number", "double"); - add("ChromiumBrowser.newContext.options.geolocation.latitude", "number", "double"); - add("ChromiumBrowser.newContext.options.geolocation.longitude", "number", "double"); - add("ChromiumBrowser.newContext.options.geolocation.accuracy", "number", "double"); - add("ChromiumBrowser.newPage.options.geolocation.latitude", "number", "double"); - add("ChromiumBrowser.newPage.options.geolocation.longitude", "number", "double"); - add("ChromiumBrowser.newPage.options.geolocation.accuracy", "number", "double"); - add("FirefoxBrowser.newContext.options.geolocation.latitude", "number", "double"); - add("FirefoxBrowser.newContext.options.geolocation.longitude", "number", "double"); - add("FirefoxBrowser.newContext.options.geolocation.accuracy", "number", "double"); - add("FirefoxBrowser.newPage.options.geolocation.latitude", "number", "double"); - add("FirefoxBrowser.newPage.options.geolocation.longitude", "number", "double"); - add("FirefoxBrowser.newPage.options.geolocation.accuracy", "number", "double"); - add("WebKitBrowser.newContext.options.geolocation.latitude", "number", "double"); - add("WebKitBrowser.newContext.options.geolocation.longitude", "number", "double"); - add("WebKitBrowser.newContext.options.geolocation.accuracy", "number", "double"); - add("WebKitBrowser.newPage.options.geolocation.latitude", "number", "double"); - add("WebKitBrowser.newPage.options.geolocation.longitude", "number", "double"); - add("WebKitBrowser.newPage.options.geolocation.accuracy", "number", "double"); add("BrowserContext.setGeolocation.geolocation", "null|Object", "Geolocation"); @@ -282,7 +248,6 @@ class Types { // JSON type add("BrowserContext.addInitScript.arg", "Serializable", "Object"); - add("ChromiumBrowserContext.addInitScript.arg", "Serializable", "Object"); add("Page.$eval", "Promise", "Object"); add("Page.$$eval", "Promise", "Object"); add("Page.addInitScript.arg", "Serializable", "Object"); diff --git a/playwright/src/main/java/com/microsoft/playwright/Browser.java b/playwright/src/main/java/com/microsoft/playwright/Browser.java index 0812414c..e27e6e7d 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Browser.java +++ b/playwright/src/main/java/com/microsoft/playwright/Browser.java @@ -19,6 +19,12 @@ package com.microsoft.playwright; import java.util.*; public interface Browser { + enum EventType { + DISCONNECTED, + } + + void addListener(EventType type, Listener listener); + void removeListener(EventType type, Listener listener); class NewContextOptions { public enum ColorScheme { DARK, LIGHT, NO_PREFERENCE } public class Geolocation { diff --git a/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java b/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java index fff65399..a3fc387d 100644 --- a/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java +++ b/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java @@ -40,6 +40,12 @@ public interface BrowserContext { } } + enum EventType { + PAGE, + } + + void addListener(EventType type, Listener listener); + void removeListener(EventType type, Listener listener); class GrantPermissionsOptions { public String origin; @@ -66,7 +72,6 @@ public interface BrowserContext { return this; } } - Deferred waitForPage(); void close(); void addCookies(List cookies); default void addInitScript(String script) { @@ -103,9 +108,9 @@ public interface BrowserContext { void unroute(String url, BiConsumer handler); void unroute(Pattern url, BiConsumer handler); void unroute(Predicate url, BiConsumer handler); - default Object waitForEvent(String event) { + default Deferred> waitForEvent(EventType event) { return waitForEvent(event, null); } - Object waitForEvent(String event, String optionsOrPredicate); + Deferred> waitForEvent(EventType event, String optionsOrPredicate); } diff --git a/playwright/src/main/java/com/microsoft/playwright/ChromiumBrowserContext.java b/playwright/src/main/java/com/microsoft/playwright/ChromiumBrowserContext.java index 8cc047ea..1aae5788 100644 --- a/playwright/src/main/java/com/microsoft/playwright/ChromiumBrowserContext.java +++ b/playwright/src/main/java/com/microsoft/playwright/ChromiumBrowserContext.java @@ -19,6 +19,13 @@ package com.microsoft.playwright; import java.util.*; public interface ChromiumBrowserContext extends BrowserContext { + enum EventType { + BACKGROUNDPAGE, + SERVICEWORKER, + } + + void addListener(EventType type, Listener listener); + void removeListener(EventType type, Listener listener); List backgroundPages(); CDPSession newCDPSession(Page page); List serviceWorkers(); diff --git a/playwright/src/main/java/com/microsoft/playwright/Event.java b/playwright/src/main/java/com/microsoft/playwright/Event.java new file mode 100644 index 00000000..b33ffa9a --- /dev/null +++ b/playwright/src/main/java/com/microsoft/playwright/Event.java @@ -0,0 +1,22 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * 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.microsoft.playwright; + +public interface Event { + EventType type(); + Object data(); +} diff --git a/playwright/src/main/java/com/microsoft/playwright/Listener.java b/playwright/src/main/java/com/microsoft/playwright/Listener.java index ae88fbef..661df3e8 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Listener.java +++ b/playwright/src/main/java/com/microsoft/playwright/Listener.java @@ -16,6 +16,6 @@ package com.microsoft.playwright; -public interface Listener { - void handle(T event); +public interface Listener { + void handle(Event event); } diff --git a/playwright/src/main/java/com/microsoft/playwright/Page.java b/playwright/src/main/java/com/microsoft/playwright/Page.java index 369388b8..dbc9e8c9 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Page.java +++ b/playwright/src/main/java/com/microsoft/playwright/Page.java @@ -78,6 +78,29 @@ public interface Page { } } + enum EventType { + CLOSE, + CONSOLE, + CRASH, + DIALOG, + DOMCONTENTLOADED, + DOWNLOAD, + FILECHOOSER, + FRAMEATTACHED, + FRAMEDETACHED, + FRAMENAVIGATED, + LOAD, + PAGEERROR, + POPUP, + REQUEST, + REQUESTFAILED, + REQUESTFINISHED, + RESPONSE, + WORKER, + } + + void addListener(EventType type, Listener listener); + void removeListener(EventType type, Listener listener); enum LoadState { DOMCONTENTLOADED, LOAD, NETWORKIDLE } class CloseOptions { public Boolean runBeforeUnload; @@ -767,11 +790,6 @@ public interface Page { return this; } } - void addConsoleListener(Listener listener); - void removeConsoleListener(Listener listener); - void addDialogListener(Listener listener); - void removeDialogListener(Listener listener); - Deferred waitForPopup(); default void close() { close(null); } @@ -926,10 +944,10 @@ public interface Page { void unroute(Predicate url, BiConsumer handler); String url(); Viewport viewportSize(); - default Object waitForEvent(String event) { + default Deferred> waitForEvent(EventType event) { return waitForEvent(event, null); } - Object waitForEvent(String event, String optionsOrPredicate); + Deferred> waitForEvent(EventType event, String optionsOrPredicate); default JSHandle waitForFunction(String pageFunction, Object arg) { return waitForFunction(pageFunction, arg, null); } diff --git a/playwright/src/main/java/com/microsoft/playwright/Worker.java b/playwright/src/main/java/com/microsoft/playwright/Worker.java index c2ad5092..e06e90b6 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Worker.java +++ b/playwright/src/main/java/com/microsoft/playwright/Worker.java @@ -19,6 +19,12 @@ package com.microsoft.playwright; import java.util.*; public interface Worker { + enum EventType { + CLOSE, + } + + void addListener(EventType type, Listener listener); + void removeListener(EventType type, Listener listener); default Object evaluate(String pageFunction) { return evaluate(pageFunction, null); } diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java index f75b6341..b57b2987 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java @@ -38,12 +38,23 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext { private boolean isClosedOrClosing; final Map bindings = new HashMap(); PageImpl ownerPage; + private final ListenerCollection listeners = new ListenerCollection<>(); protected BrowserContextImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) { super(parent, type, guid, initializer); browser = (BrowserImpl) parent; } + @Override + public void addListener(EventType type, Listener listener) { + listeners.add(type, listener); + } + + @Override + public void removeListener(EventType type, Listener listener) { + listeners.remove(type, listener); + } + @Override public void close() { if (isClosedOrClosing) { @@ -203,6 +214,11 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext { unroute(new UrlMatcher(url), handler); } + @Override + public Deferred> waitForEvent(EventType event, String optionsOrPredicate) { + return listeners.waitForEvent(event, connection); + } + private void unroute(UrlMatcher matcher, BiConsumer handler) { routes.remove(matcher, handler); if (routes.size() == 0) { @@ -212,12 +228,6 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext { } } - @Override - public Object waitForEvent(String event, String optionsOrPredicate) { - return null; - } - - @Override public Deferred waitForPage() { CompletableFuture pageFuture = futureForEvent("page"); return () -> { @@ -238,6 +248,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext { } } else if ("page".equals(event)) { PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString()); + listeners.notify(EventType.PAGE, page); pages.add(page); } else if ("bindingCall".equals(event)) { BindingCall bindingCall = connection.getExistingObject(params.getAsJsonObject("binding").get("guid").getAsString()); diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java index 059caa95..05960bba 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java @@ -20,10 +20,7 @@ import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.microsoft.playwright.Browser; -import com.microsoft.playwright.BrowserContext; -import com.microsoft.playwright.Deferred; -import com.microsoft.playwright.Page; +import com.microsoft.playwright.*; import java.util.*; @@ -31,11 +28,22 @@ import static com.microsoft.playwright.impl.Utils.convertViaJson; class BrowserImpl extends ChannelOwner implements Browser { final Set contexts = new HashSet<>(); + private final ListenerCollection listeners = new ListenerCollection<>(); BrowserImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) { super(parent, type, guid, initializer); } + @Override + public void addListener(EventType type, Listener listener) { + listeners.add(type, listener); + } + + @Override + public void removeListener(EventType type, Listener listener) { + listeners.remove(type, listener); + } + @Override public void close() { sendMessage("close", new JsonObject()); diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/ListenerCollection.java b/playwright/src/main/java/com/microsoft/playwright/impl/ListenerCollection.java new file mode 100644 index 00000000..9f49c4f3 --- /dev/null +++ b/playwright/src/main/java/com/microsoft/playwright/impl/ListenerCollection.java @@ -0,0 +1,104 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * 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.microsoft.playwright.impl; + +import com.microsoft.playwright.Deferred; +import com.microsoft.playwright.Event; +import com.microsoft.playwright.Listener; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + +class ListenerCollection { + private final HashMap>> listeners = new HashMap<>(); + + void notify(EventType eventType, Object param) { + List> list = listeners.get(eventType); + if (list == null) { + return; + } + + Event event = new Event() { + @Override + public EventType type() { + return eventType; + } + + @Override + public Object data() { + return param; + } + }; + + for (Listener listener: new ArrayList<>(list)) { + listener.handle(event); + } + } + + void add(EventType type, Listener listener) { + List> list = listeners.get(type); + if (list == null) { + list = new ArrayList<>(); + listeners.put(type, list); + } + list.add(listener); + } + + void remove(EventType type, Listener listener) { + List> list = listeners.get(type); + if (list == null) { + return; + } + list.removeAll(Collections.singleton(listener)); + if (list.isEmpty()) { + listeners.remove(type); + } + } + + private class DeferredEvent implements Listener, Deferred> { + private final EventType type; + private final Connection connection; + private Event event; + + DeferredEvent(EventType type, Connection connection) { + add(type, this); + this.type = type; + this.connection = connection; + } + + @Override + public void handle(Event e) { + event = e; + remove(type, this); + } + + @Override + public Event get() { + while (event == null) { + connection.processOneMessage(); + } + return event; + } + } + + Deferred> waitForEvent(EventType event, Connection connection) { + return new DeferredEvent(event, connection); + } + +} diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java index eaa79e36..802d2e4e 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java @@ -39,9 +39,7 @@ public class PageImpl extends ChannelOwner implements Page { private final Router routes = new Router(); // TODO: do not rely on the frame order in the tests private final Set frames = new LinkedHashSet<>(); - private final List> consoleListeners = new ArrayList<>(); - private final List> dialogListeners = new ArrayList<>(); - private final List> closeListeners = new ArrayList<>(); + private final ListenerCollection listeners = new ListenerCollection<>(); private final List eventHelpers = new ArrayList<>(); final Map bindings = new HashMap(); BrowserContextImpl ownedContext; @@ -57,35 +55,6 @@ public class PageImpl extends ChannelOwner implements Page { frames.add(mainFrame); } - @Override - public void addConsoleListener(Listener listener) { - consoleListeners.add(listener); - } - - @Override - public void removeConsoleListener(Listener listener) { - consoleListeners.remove(listener); - } - - public void addCloseListener(Listener listener) { - closeListeners.add(listener); - } - - public void removeCloseListener(Listener listener) { - closeListeners.remove(listener); - } - - @Override - public void addDialogListener(Listener listener) { - dialogListeners.add(listener); - } - - @Override - public void removeDialogListener(Listener listener) { - dialogListeners.remove(listener); - } - - @Override public Deferred waitForPopup() { CompletableFuture popupFuture = futureForEvent("popup"); return () -> { @@ -95,25 +64,23 @@ public class PageImpl extends ChannelOwner implements Page { }; } - private static void notifyListeners(List> listeners, T subject) { - for (Listener listener: new ArrayList<>(listeners)) { - listener.handle(subject); - } - } - protected void handleEvent(String event, JsonObject params) { if ("dialog".equals(event)) { String guid = params.getAsJsonObject("dialog").get("guid").getAsString(); DialogImpl dialog = connection.getExistingObject(guid); - notifyListeners(dialogListeners, dialog); + listeners.notify(EventType.DIALOG, dialog); // If no action taken dismiss dialog to not hang. if (!dialog.isHandled()) { dialog.dismiss(); } + } else if ("popup".equals(event)) { + String guid = params.getAsJsonObject("page").get("guid").getAsString(); + PageImpl popup = connection.getExistingObject(guid); + listeners.notify(EventType.POPUP, popup); } else if ("console".equals(event)) { String guid = params.getAsJsonObject("message").get("guid").getAsString(); ConsoleMessageImpl message = connection.getExistingObject(guid); - notifyListeners(consoleListeners, message); + listeners.notify(EventType.CONSOLE, message); } else if ("frameAttached".equals(event)) { String guid = params.getAsJsonObject("frame").get("guid").getAsString(); FrameImpl frame = connection.getExistingObject(guid); @@ -122,6 +89,7 @@ public class PageImpl extends ChannelOwner implements Page { if (frame.parentFrame != null) { frame.parentFrame.childFrames.add(frame); } + listeners.notify(EventType.FRAMEATTACHED, frame); } else if ("frameDetached".equals(event)) { String guid = params.getAsJsonObject("frame").get("guid").getAsString(); FrameImpl frame = connection.getExistingObject(guid); @@ -130,6 +98,7 @@ public class PageImpl extends ChannelOwner implements Page { if (frame.parentFrame != null) { frame.parentFrame.childFrames.remove(frame); } + listeners.notify(EventType.FRAMEDETACHED, frame); } else if ("route".equals(event)) { Route route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString()); Request request = connection.getExistingObject(params.getAsJsonObject("request").get("guid").getAsString()); @@ -143,13 +112,23 @@ public class PageImpl extends ChannelOwner implements Page { } else if ("close".equals(event)) { isClosed = true; browserContext.pages.remove(this); - notifyListeners(closeListeners, this); + listeners.notify(EventType.CLOSE, this); } for (WaitEventHelper h : new ArrayList<>(eventHelpers)) { h.handleEvent(event, params); } } + @Override + public void addListener(EventType type, Listener listener) { + listeners.add(type, listener); + } + + @Override + public void removeListener(EventType type, Listener listener) { + listeners.remove(type, listener); + } + @Override public void close(CloseOptions options) { JsonObject params = options == null ? new JsonObject() : new Gson().toJsonTree(options).getAsJsonObject(); @@ -512,9 +491,8 @@ public class PageImpl extends ChannelOwner implements Page { } @Override - public Object waitForEvent(String event, String optionsOrPredicate) { - // TODO: do we want to keep this method ? - return null; + public Deferred> waitForEvent(EventType event, String optionsOrPredicate) { + return listeners.waitForEvent(event, connection); } @Override diff --git a/playwright/src/test/java/com/microsoft/playwright/TestClick.java b/playwright/src/test/java/com/microsoft/playwright/TestClick.java index 30d13ef5..8d5b97d6 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestClick.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestClick.java @@ -26,6 +26,7 @@ import java.util.List; import static com.microsoft.playwright.Keyboard.Modifier.SHIFT; import static com.microsoft.playwright.Mouse.Button.RIGHT; +import static com.microsoft.playwright.Page.EventType.CONSOLE; import static org.junit.jupiter.api.Assertions.*; public class TestClick { @@ -172,7 +173,7 @@ public class TestClick { void shouldClickOffscreenButtons() { page.navigate(server.PREFIX + "/offscreenbuttons.html"); List messages = new ArrayList<>(); - page.addConsoleListener(msg -> messages.add(msg.text())); + page.addListener(CONSOLE, event -> messages.add(((ConsoleMessage) event.data()).text())); for (int i = 0; i < 11; ++i) { // We might've scrolled to click a button - reset to (0, 0). page.evaluate("() => window.scrollTo(0, 0)"); diff --git a/playwright/src/test/java/com/microsoft/playwright/TestDialog.java b/playwright/src/test/java/com/microsoft/playwright/TestDialog.java index e01838a4..c21e953e 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestDialog.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestDialog.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.*; import java.io.IOException; +import static com.microsoft.playwright.Page.EventType.DIALOG; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; @@ -64,7 +65,8 @@ public class TestDialog { @Test void shouldFire() { - page.addDialogListener(dialog -> { + page.addListener(DIALOG, event -> { + Dialog dialog = (Dialog) event.data(); assertEquals( "alert", dialog.type()); assertEquals( "", dialog.defaultValue()); assertEquals( "yo", dialog.message()); @@ -75,7 +77,8 @@ public class TestDialog { @Test void shouldAllowAcceptingPrompts() { - page.addDialogListener(dialog -> { + page.addListener(DIALOG, event -> { + Dialog dialog = (Dialog) event.data(); assertEquals("prompt", dialog.type()); assertEquals("yes.", dialog.defaultValue()); assertEquals("question?", dialog.message()); @@ -87,7 +90,8 @@ public class TestDialog { @Test void shouldDismissThePrompt() { - page.addDialogListener(dialog -> { + page.addListener(DIALOG, event -> { + Dialog dialog = (Dialog) event.data(); dialog.dismiss(); }); Object result = page.evaluate("() => prompt('question?')"); @@ -96,7 +100,8 @@ public class TestDialog { @Test void shouldAcceptTheConfirmPrompt() { - page.addDialogListener(dialog -> { + page.addListener(DIALOG, event -> { + Dialog dialog = (Dialog) event.data(); dialog.accept(); }); Object result = page.evaluate("() => confirm('boolean?')"); @@ -105,7 +110,8 @@ public class TestDialog { @Test void shouldDismissTheConfirmPrompt() { - page.addDialogListener(dialog -> { + page.addListener(DIALOG, event -> { + Dialog dialog = (Dialog) event.data(); dialog.dismiss(); }); Object result = page.evaluate("() => confirm('boolean?')"); diff --git a/playwright/src/test/java/com/microsoft/playwright/TestPageBasic.java b/playwright/src/test/java/com/microsoft/playwright/TestPageBasic.java index 153e0feb..81a5c637 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestPageBasic.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestPageBasic.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.regex.Pattern; import java.util.stream.Collectors; +import static com.microsoft.playwright.Page.EventType.*; import static com.microsoft.playwright.Page.LoadState.DOMCONTENTLOADED; import static com.microsoft.playwright.Page.LoadState.LOAD; import static org.junit.jupiter.api.Assertions.*; @@ -100,8 +101,9 @@ public class TestPageBasic { // fire. newPage.click("body"); boolean[] didShowDialog = {false}; - newPage.addDialogListener(dialog -> { + newPage.addListener(DIALOG, event -> { didShowDialog[0] = true; + Dialog dialog = (Dialog) event.data(); assertEquals("beforeunload", dialog.type()); assertEquals("", dialog.defaultValue()); if (isChromium) { @@ -126,7 +128,7 @@ public class TestPageBasic { // fire. newPage.click("body"); boolean[] didShowDialog = {false}; - newPage.addDialogListener(dialog -> didShowDialog[0] = true); + newPage.addListener(DIALOG, event -> didShowDialog[0] = true); newPage.close(); assertFalse(didShowDialog[0]); } @@ -181,17 +183,18 @@ public class TestPageBasic { @Test void shouldProvideAccessToTheOpenerPage() { - Deferred popupEvent = page.waitForPopup(); + Deferred> popupEvent = page.waitForEvent(POPUP); page.evaluate("() => window.open('about:blank')"); - Page opener = popupEvent.get().opener(); + Page popup = (Page) popupEvent.get().data(); + Page opener = popup.opener(); assertEquals(page, opener); } @Test void shouldReturnNullIfParentPageHasBeenClosed() { - Deferred popupEvent = page.waitForPopup(); + Deferred> popupEvent = page.waitForEvent(POPUP); page.evaluate("() => window.open('about:blank')"); - Page popup = popupEvent.get(); + Page popup = (Page) popupEvent.get().data(); page.close(); Page opener = popup.opener(); assertEquals(null, opener); @@ -232,9 +235,9 @@ public class TestPageBasic { @Test void pageCloseShouldWorkWithWindowClose() { - Deferred newPagePromise = page.waitForPopup(); + Deferred> newPagePromise = page.waitForEvent(POPUP); page.evaluate("() => window['newPage'] = window.open('about:blank')"); - Page newPage = newPagePromise.get(); + Page newPage = (Page) newPagePromise.get().data(); Deferred closedPromise = newPage.waitForClose(); page.evaluate("() => window['newPage'].close()"); closedPromise.get(); @@ -310,7 +313,7 @@ public class TestPageBasic { void pagePressShouldWorkForEnter() { page.setContent(""); List messages = new ArrayList<>(); - page.addConsoleListener(message -> messages.add(message)); + page.addListener(CONSOLE, event -> messages.add((ConsoleMessage) event.data())); page.press("input", "Enter"); assertEquals("press", messages.get(0).text()); } diff --git a/playwright/src/test/java/com/microsoft/playwright/TestPopup.java b/playwright/src/test/java/com/microsoft/playwright/TestPopup.java index d4ccdc46..985bd823 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestPopup.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestPopup.java @@ -23,6 +23,7 @@ import java.util.*; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; +import static com.microsoft.playwright.Page.EventType.POPUP; import static com.microsoft.playwright.Page.LoadState.DOMCONTENTLOADED; import static com.microsoft.playwright.Utils.mapOf; import static org.junit.jupiter.api.Assertions.*; @@ -73,9 +74,9 @@ public class TestPopup { page.navigate(server.EMPTY_PAGE); page.setContent("link"); Future requestPromise = server.waitForRequest("/popup/popup.html"); - Deferred popupPromise = context.waitForPage(); + Deferred> popupEvent = context.waitForEvent(BrowserContext.EventType.PAGE); page.click("a"); - Page popup = popupPromise.get(); + Page popup = (Page) popupEvent.get().data(); popup.waitForLoadState(DOMCONTENTLOADED); String userAgent = (String) popup.evaluate("() => window['initialUserAgent']"); Server.Request request = requestPromise.get(); @@ -95,7 +96,7 @@ public class TestPopup { route.continue_(); intercepted[0] = true; }); - Deferred popup = context.waitForPage(); + Deferred> popup = context.waitForEvent(BrowserContext.EventType.PAGE); page.click("a"); popup.get(); @@ -137,10 +138,11 @@ public class TestPopup { .withHttpCredentials("user", "pass")); Page page = context.newPage(); page.navigate(server.EMPTY_PAGE); - Deferred popup = page.waitForPopup(); + Deferred> popupEvent = page.waitForEvent(POPUP); page.evaluate("url => window['_popup'] = window.open(url)", server.PREFIX + "/title.html"); - popup.get().waitForLoadState(DOMCONTENTLOADED); - assertEquals("Woof-Woof", popup.get().title()); + Page popup = (Page) popupEvent.get().data(); + popup.waitForLoadState(DOMCONTENTLOADED); + assertEquals("Woof-Woof", popup.title()); context.close(); } @@ -179,12 +181,12 @@ public class TestPopup { .withViewport(700, 700)); Page page = context.newPage(); page.navigate(server.EMPTY_PAGE); - Deferred popupEvent = page.waitForPopup(); + Deferred> popupEvent = page.waitForEvent(POPUP); Object size = page.evaluate("() => {\n" + " const win = window.open(window.location.href, 'Title', 'toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=600,height=300,top=0,left=0');\n" + " return { width: win.innerWidth, height: win.innerHeight };\n" + "}"); - Page popup = popupEvent.get(); + Page popup = (Page) popupEvent.get().data(); popup.setViewportSize(500, 400); popup.waitForLoadState(); Object resized = popup.evaluate("() => ({ width: window.innerWidth, height: window.innerHeight })"); @@ -203,7 +205,7 @@ public class TestPopup { route.continue_(); intercepted[0] = true; }); - Deferred popupEvent = page.waitForPopup(); + Deferred> popupEvent = page.waitForEvent(POPUP); page.evaluate("url => window['__popup'] = window.open(url)", server.EMPTY_PAGE); popupEvent.get(); assertTrue(intercepted[0]); @@ -230,9 +232,9 @@ public class TestPopup { context.addInitScript("() => window['injected'] = 123"); Page page = context.newPage(); page.navigate(server.EMPTY_PAGE); - Deferred popupEvent = page.waitForPopup(); + Deferred> popupEvent = page.waitForEvent(POPUP); page.evaluate("url => window.open(url)", server.CROSS_PROCESS_PREFIX + "/title.html"); - Page popup = popupEvent.get(); + Page popup = (Page) popupEvent.get().data(); assertEquals(123, popup.evaluate("injected")); popup.reload();