1
0
mirror of synced 2026-08-05 15:06:54 +00:00

feat: implement Page.route/unroute

This commit is contained in:
Yury Semikhatsky
2020-10-07 15:00:38 -07:00
parent d35b1b74e1
commit 660960387c
9 changed files with 405 additions and 41 deletions
@@ -297,10 +297,37 @@ class Method extends Element {
tsToJavaMethodName.put("goto", "navigate");
}
private static Map<String, String> customSignature = new HashMap<>();
private static Map<String, String[]> customSignature = new HashMap<>();
static {
customSignature.put("Page.setViewportSize", "void setViewportSize(int width, int height);");
customSignature.put("BrowserContext.setHTTPCredentials", "void setHTTPCredentials(String username, String password);");
customSignature.put("Page.setViewportSize", new String[]{"void setViewportSize(int width, int height);"});
customSignature.put("BrowserContext.setHTTPCredentials", new String[]{
"void setHTTPCredentials(String username, String password);"});
customSignature.put("BrowserContext.route", new String[]{
"void route(String url, BiConsumer<Route, Request> handler);",
"void route(Pattern url, BiConsumer<Route, Request> handler);",
"void route(Predicate<String> url, BiConsumer<Route, Request> handler);",
});
customSignature.put("Page.route", new String[]{
"void route(String url, BiConsumer<Route, Request> handler);",
"void route(Pattern url, BiConsumer<Route, Request> handler);",
"void route(Predicate<String> url, BiConsumer<Route, Request> handler);",
});
customSignature.put("BrowserContext.unroute", new String[]{
"default void unroute(String url) { unroute(url, null); }",
"default void unroute(Pattern url) { unroute(url, null); }",
"default void unroute(Predicate<String> url) { unroute(url, null); }",
"void unroute(String url, BiConsumer<Route, Request> handler);",
"void unroute(Pattern url, BiConsumer<Route, Request> handler);",
"void unroute(Predicate<String> url, BiConsumer<Route, Request> handler);",
});
customSignature.put("Page.unroute", new String[]{
"default void unroute(String url) { unroute(url, null); }",
"default void unroute(Pattern url) { unroute(url, null); }",
"default void unroute(Predicate<String> url) { unroute(url, null); }",
"void unroute(String url, BiConsumer<Route, Request> handler);",
"void unroute(Pattern url, BiConsumer<Route, Request> handler);",
"void unroute(Predicate<String> url, BiConsumer<Route, Request> handler);",
});
}
Method(TypeDefinition parent, JsonObject jsonElement) {
@@ -327,7 +354,9 @@ class Method extends Element {
void writeTo(List<String> output, String offset) {
if (customSignature.containsKey(jsonPath)) {
output.add(offset + customSignature.get(jsonPath));
for (String signature : customSignature.get(jsonPath)) {
output.add(offset + signature);
}
return;
}
for (int i = params.size() - 1; i >= 0; i--) {
@@ -375,6 +404,9 @@ class Param extends Element {
Param(Method method, JsonObject jsonElement) {
super(method, jsonElement);
type = new TypeRef(this, jsonElement.get("type").getAsJsonObject());
if (jsonPath.contains(".route")) {
System.out.println(jsonPath + ": " + type.jsonName);
}
}
boolean isOptional() {
@@ -490,12 +522,10 @@ class Interface extends TypeDefinition {
void writeTo(List<String> output, String offset) {
output.add(header);
output.add("import java.util.*;");
if (jsonName.equals("Page")) {
if (Arrays.asList("Page", "BrowserContext").contains(jsonName)) {
output.add("import java.util.function.BiConsumer;");
output.add("import java.util.function.Predicate;");
output.add("import java.util.regex.Pattern;");
} else if (jsonName.equals("BrowserContext")) {
output.add("import java.util.function.BiConsumer;");
}
output.add("");
@@ -18,6 +18,8 @@ package com.microsoft.playwright;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
import java.util.regex.Pattern;
public interface BrowserContext {
class HTTPCredentials {
@@ -87,16 +89,20 @@ public interface BrowserContext {
Page newPage();
List<Page> pages();
void route(String url, BiConsumer<Route, Request> handler);
void route(Pattern url, BiConsumer<Route, Request> handler);
void route(Predicate<String> url, BiConsumer<Route, Request> handler);
void setDefaultNavigationTimeout(int timeout);
void setDefaultTimeout(int timeout);
void setExtraHTTPHeaders(Map<String, String> headers);
void setGeolocation(Geolocation geolocation);
void setHTTPCredentials(String username, String password);
void setOffline(boolean offline);
default void unroute(String url) {
unroute(url, null);
}
default void unroute(String url) { unroute(url, null); }
default void unroute(Pattern url) { unroute(url, null); }
default void unroute(Predicate<String> url) { unroute(url, null); }
void unroute(String url, BiConsumer<Route, Request> handler);
void unroute(Pattern url, BiConsumer<Route, Request> handler);
void unroute(Predicate<String> url, BiConsumer<Route, Request> handler);
default Object waitForEvent(String event) {
return waitForEvent(event, null);
}
@@ -889,6 +889,8 @@ public interface Page {
}
Response reload(ReloadOptions options);
void route(String url, BiConsumer<Route, Request> handler);
void route(Pattern url, BiConsumer<Route, Request> handler);
void route(Predicate<String> url, BiConsumer<Route, Request> handler);
default byte[] screenshot() {
return screenshot(null);
}
@@ -922,10 +924,12 @@ public interface Page {
uncheck(selector, null);
}
void uncheck(String selector, UncheckOptions options);
default void unroute(String url) {
unroute(url, null);
}
default void unroute(String url) { unroute(url, null); }
default void unroute(Pattern url) { unroute(url, null); }
default void unroute(Predicate<String> url) { unroute(url, null); }
void unroute(String url, BiConsumer<Route, Request> handler);
void unroute(Pattern url, BiConsumer<Route, Request> handler);
void unroute(Predicate<String> url, BiConsumer<Route, Request> handler);
String url();
Viewport viewportSize();
default Object waitForEvent(String event) {
@@ -26,33 +26,19 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static com.microsoft.playwright.impl.Utils.globToRegex;
import static com.microsoft.playwright.impl.Utils.isFunctionBody;
class BrowserContextImpl extends ChannelOwner implements BrowserContext {
private final BrowserImpl browser;
final List<PageImpl> pages = new ArrayList<>();
private List<RouteInfo> routes = new ArrayList<>();
final Router routes = new Router();
private boolean isClosedOrClosing;
final Map<String, Page.Binding> bindings = new HashMap<String, Page.Binding>();
PageImpl ownerPage;
private class RouteInfo {
private String url;
private BiConsumer<Route, Request> handler;
private final Pattern pattern;
public RouteInfo(String url, BiConsumer<Route, Request> handler) {
this.url = url;
this.handler = handler;
pattern = Pattern.compile(globToRegex(url));
}
}
protected BrowserContextImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
browser = (BrowserImpl) parent;
@@ -148,7 +134,21 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override
public void route(String url, BiConsumer<Route, Request> handler) {
routes.add(new RouteInfo(url, handler));
route(new UrlMatcher(url), handler);
}
@Override
public void route(Pattern url, BiConsumer<Route, Request> handler) {
route(new UrlMatcher(url), handler);
}
@Override
public void route(Predicate<String> url, BiConsumer<Route, Request> handler) {
route(new UrlMatcher(url), handler);
}
private void route(UrlMatcher matcher, BiConsumer<Route, Request> handler) {
routes.add(matcher, handler);
if (routes.size() == 1) {
JsonObject params = new JsonObject();
params.addProperty("enabled", true);
@@ -190,10 +190,22 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override
public void unroute(String url, BiConsumer<Route, Request> handler) {
routes = routes.stream()
.filter(info -> !info.url.equals(url) || (handler != null && info.handler != handler))
.collect(Collectors.toList());
if (routes.isEmpty()) {
unroute(new UrlMatcher(url), handler);
}
@Override
public void unroute(Pattern url, BiConsumer<Route, Request> handler) {
unroute(new UrlMatcher(url), handler);
}
@Override
public void unroute(Predicate<String> url, BiConsumer<Route, Request> handler) {
unroute(new UrlMatcher(url), handler);
}
private void unroute(UrlMatcher matcher, BiConsumer<Route, Request> handler) {
routes.remove(matcher, handler);
if (routes.size() == 0) {
JsonObject params = new JsonObject();
params.addProperty("enabled", false);
sendMessage("setNetworkInterceptionEnabled", params);
@@ -220,12 +232,10 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
if ("route".equals(event)) {
Route route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
Request request = connection.getExistingObject(params.getAsJsonObject("request").get("guid").getAsString());
for (RouteInfo info : routes) {
if (info.pattern.matcher(request.url()).find()) {
info.handler.accept(route, request);
}
boolean handled = routes.handle(route, request);
if (!handled) {
route.continue_();
}
route.continue_();
} else if ("page".equals(event)) {
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
pages.add(page);
@@ -24,6 +24,8 @@ import com.microsoft.playwright.*;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.Utils.convertViaJson;
@@ -34,6 +36,7 @@ public class PageImpl extends ChannelOwner implements Page {
private final KeyboardImpl keyboard;
private final MouseImpl mouse;
private Viewport viewport;
private final Router routes = new Router();
// TODO: do not rely on the frame order in the tests
private final Set<FrameImpl> frames = new LinkedHashSet<>();
private final List<Listener<ConsoleMessage>> consoleListeners = new ArrayList<>();
@@ -127,6 +130,16 @@ public class PageImpl extends ChannelOwner implements Page {
if (frame.parentFrame != null) {
frame.parentFrame.childFrames.remove(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());
boolean handled = routes.handle(route, request);
if (!handled) {
handled = browserContext.routes.handle(route, request);
}
if (!handled) {
route.continue_();
}
} else if ("close".equals(event)) {
isClosed = true;
browserContext.pages.remove(this);
@@ -274,7 +287,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public Frame frame(FrameOptions options) {
if (options == null) {
throw new IllegalArgumentException("Frame criteria should be cpecified");
throw new IllegalArgumentException("Frame criteria should be specified");
}
for (Frame frame : frames) {
if (options.name != null && options.name.equals(frame.name())) {
@@ -379,7 +392,26 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void route(String url, BiConsumer<Route, Request> handler) {
route(new UrlMatcher(url), handler);
}
@Override
public void route(Pattern url, BiConsumer<Route, Request> handler) {
route(new UrlMatcher(url), handler);
}
@Override
public void route(Predicate<String> url, BiConsumer<Route, Request> handler) {
route(new UrlMatcher(url), handler);
}
private void route(UrlMatcher matcher, BiConsumer<Route, Request> handler) {
routes.add(matcher, handler);
if (routes.size() == 1) {
JsonObject params = new JsonObject();
params.addProperty("enabled", true);
sendMessage("setNetworkInterceptionEnabled", params);
}
}
@Override
@@ -447,7 +479,26 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void unroute(String url, BiConsumer<Route, Request> handler) {
unroute(new UrlMatcher(url), handler);
}
@Override
public void unroute(Pattern url, BiConsumer<Route, Request> handler) {
unroute(new UrlMatcher(url), handler);
}
@Override
public void unroute(Predicate<String> url, BiConsumer<Route, Request> handler) {
unroute(new UrlMatcher(url), handler);
}
private void unroute(UrlMatcher matcher, BiConsumer<Route, Request> handler) {
routes.remove(matcher, handler);
if (routes.size() == 0) {
JsonObject params = new JsonObject();
params.addProperty("enabled", false);
sendMessage("setNetworkInterceptionEnabled", params);
}
}
@Override
@@ -16,16 +16,20 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Frame;
import com.microsoft.playwright.Request;
import com.microsoft.playwright.Response;
import java.util.HashMap;
import java.util.Map;
public class RequestImpl extends ChannelOwner implements Request {
private RequestImpl redirectedFrom;
private RequestImpl redirectedTo;
private final Map<String, String> headers = new HashMap<>();
RequestImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
@@ -33,6 +37,10 @@ public class RequestImpl extends ChannelOwner implements Request {
redirectedFrom = connection.getExistingObject(initializer.getAsJsonObject("redirectedFrom").get("guid").getAsString());
redirectedFrom.redirectedTo = this;
}
for (JsonElement e : initializer.getAsJsonArray("headers")) {
JsonObject item = e.getAsJsonObject();
headers.put(item.get("name").getAsString().toLowerCase(), item.get("value").getAsString());
}
}
@Override
@@ -47,7 +55,7 @@ public class RequestImpl extends ChannelOwner implements Request {
@Override
public Map<String, String> headers() {
return null;
return headers;
}
@Override
@@ -0,0 +1,63 @@
/**
* 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.Request;
import com.microsoft.playwright.Route;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
class Router {
private List<RouteInfo> routes = new ArrayList<>();
private static class RouteInfo {
final UrlMatcher matcher;
final BiConsumer<Route, Request> handler;
RouteInfo(UrlMatcher matcher, BiConsumer<Route, Request> handler) {
this.matcher = matcher;
this.handler = handler;
}
}
void add(UrlMatcher matcher, BiConsumer<Route, Request> handler) {
routes.add(new RouteInfo(matcher, handler));
}
void remove(UrlMatcher matcher, BiConsumer<Route, Request> handler) {
routes = routes.stream()
.filter(info -> !info.matcher.equals(matcher) || (handler != null && info.handler != handler))
.collect(Collectors.toList());
}
int size() {
return routes.size();
}
boolean handle(Route route, Request request) {
for (RouteInfo info : routes) {
if (info.matcher.test(request.url())) {
info.handler.accept(route, request);
return true;
}
}
return false;
}
}
@@ -0,0 +1,65 @@
/**
* 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 java.util.Objects;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.Utils.globToRegex;
class UrlMatcher {
private final Object rawSource;
private final Predicate<String> predicate;
private static Predicate<String> toPridcate(Pattern pattern) {
return s -> pattern.matcher(s).find();
}
UrlMatcher(String url) {
this(url, toPridcate(Pattern.compile(globToRegex(url))));
}
UrlMatcher(Pattern pattern) {
this(pattern, toPridcate(pattern));
}
UrlMatcher(Predicate<String> predicate) {
this(predicate, predicate);
}
private UrlMatcher(Object rawSource, Predicate<String> predicate) {
this.rawSource = rawSource;
this.predicate = predicate;
}
boolean test(String value) {
return predicate.test(value);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UrlMatcher that = (UrlMatcher) o;
return Objects.equals(rawSource, that.rawSource);
}
@Override
public int hashCode() {
return Objects.hash(rawSource);
}
}
@@ -0,0 +1,127 @@
/**
* 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;
import org.junit.jupiter.api.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.BiConsumer;
import static org.junit.jupiter.api.Assertions.*;
public class TestPageRoute {
private static Server server;
private static Browser browser;
private static boolean isChromium;
private static boolean isWebKit;
private static boolean isFirefox;
private BrowserContext context;
private Page page;
@BeforeAll
static void launchBrowser() {
Playwright playwright = Playwright.create();
BrowserType.LaunchOptions options = new BrowserType.LaunchOptions();
browser = playwright.chromium().launch(options);
isChromium = true;
}
@BeforeAll
static void startServer() throws IOException {
server = new Server(8907);
}
@AfterAll
static void stopServer() throws IOException {
browser.close();
server.stop();
server = null;
}
@BeforeEach
void setUp() {
context = browser.newContext();
page = context.newPage();
}
@AfterEach
void tearDown() {
context.close();
context = null;
page = null;
}
@Test
void shouldIntercept() {
boolean[] intercepted = {false};
page.route("**/empty.html", (route, request) -> {
assertEquals(request, route.request());
assertTrue(request.url().contains("empty.html"));
assertNotNull(request.headers().get("user-agent"));
assertEquals("GET", request.method());
assertNull(request.postData());
assertTrue(request.isNavigationRequest());
assertEquals("document", request.resourceType());
assertTrue(request.frame() == page.mainFrame());
assertEquals("about:blank", request.frame().url());
route.continue_();
intercepted[0] = true;
});
Response response = page.navigate(server.EMPTY_PAGE);
assertTrue(response.ok());
assertTrue(intercepted[0]);
}
@Test
void shouldUnroute() {
List<Integer> intercepted = new ArrayList<>();
BiConsumer<Route, Request> handler1 = (route, request) -> {
intercepted.add(1);
route.continue_();
};
page.route("**/empty.html", handler1);
page.route("**/empty.html", (route, request) -> {
intercepted.add(2);
route.continue_();
});
page.route("**/empty.html", (route, request) -> {
intercepted.add(3);
route.continue_();
});
page.route("**/*", (route, request) -> {
intercepted.add(4);
route.continue_();
});
page.navigate(server.EMPTY_PAGE);
assertEquals(Arrays.asList(1), intercepted);
intercepted.clear();
page.unroute("**/empty.html", handler1);
page.navigate(server.EMPTY_PAGE);
assertEquals(Arrays.asList(2), intercepted);
intercepted.clear();
page.unroute("**/empty.html");
page.navigate(server.EMPTY_PAGE);
assertEquals(Arrays.asList(4), intercepted);
}
}