1
0
mirror of synced 2026-08-31 19:55:37 +00:00

fix: asynchronous route handling (#1137)

This commit is contained in:
Yury Semikhatsky
2022-11-30 14:59:41 -08:00
committed by GitHub
parent afa20b91ae
commit 048bca9d59
5 changed files with 97 additions and 10 deletions
@@ -539,10 +539,10 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
void handleRoute(RouteImpl route) {
Router.HandleResult handled = routes.handle(route);
if (handled == Router.HandleResult.FoundMatchingHandler) {
if (handled != Router.HandleResult.NoMatchingHandler) {
maybeDisableNetworkInterception();
}
if (!route.isHandled()){
if (handled == Router.HandleResult.NoMatchingHandler || handled == Router.HandleResult.Fallback) {
route.resume();
}
}
@@ -188,10 +188,10 @@ public class PageImpl extends ChannelOwner implements Page {
} else if ("route".equals(event)) {
RouteImpl route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
Router.HandleResult handled = routes.handle(route);
if (handled == Router.HandleResult.FoundMatchingHandler) {
if (handled != Router.HandleResult.NoMatchingHandler) {
maybeDisableNetworkInterception();
}
if (!route.isHandled()) {
if (handled == Router.HandleResult.NoMatchingHandler || handled == Router.HandleResult.Fallback) {
browserContext.handleRoute(route);
}
} else if ("video".equals(event)) {
@@ -34,6 +34,9 @@ public class RouteImpl extends ChannelOwner implements Route {
private final RequestImpl request;
private boolean handled;
boolean fallbackCalled;
boolean shouldResumeIfFallbackIsCalled;
public RouteImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
request = connection.getExistingObject(initializer.getAsJsonObject("request").get("guid").getAsString());
@@ -62,10 +65,14 @@ public class RouteImpl extends ChannelOwner implements Route {
@Override
public void fallback(FallbackOptions options) {
fallbackCalled = true;
if (handled) {
throw new PlaywrightException("Route is already handled!");
}
applyOverrides(options);
if (shouldResumeIfFallbackIsCalled) {
resume();
}
}
private void applyOverrides(FallbackOptions options) {
@@ -65,7 +65,7 @@ class Router {
return routes.size();
}
enum HandleResult { NoMatchingHandler, FoundMatchingHandler}
enum HandleResult { NoMatchingHandler, Handled, Fallback, PendingHandler }
HandleResult handle(RouteImpl route) {
HandleResult result = HandleResult.NoMatchingHandler;
for (Iterator<RouteInfo> it = routes.iterator(); it.hasNext();) {
@@ -76,11 +76,19 @@ class Router {
if (info.decrementRemainingCallCount()) {
it.remove();
}
result = HandleResult.FoundMatchingHandler;
route.fallbackCalled = false;
info.handle(route);
if (route.isHandled()) {
break;
return HandleResult.Handled;
}
// Not immediately handled and fallback() was not called => the route
// must be handled asynchronously.
if (!route.fallbackCalled) {
route.shouldResumeIfFallbackIsCalled = true;
return HandleResult.PendingHandler;
}
// Fallback was called, continue to the remaining handlers.
result = HandleResult.Fallback;
}
return result;
}
@@ -469,7 +469,7 @@ public class TestPageRoute extends TestBase {
}
@Test
void shouldThrowIfResumeIsCalledAfterRouteHandlerFinished() {
void shouldNotThrowIfResumeIsCalledAfterRouteHandlerFinished() {
page.setContent("<iframe></iframe>");
Route[] route = {null};
page.route("**/*", r -> route[0] = r);
@@ -477,8 +477,7 @@ public class TestPageRoute extends TestBase {
page.waitForRequest("**", () -> page.evalOnSelector("iframe", "(frame, url) => frame.src = url", server.EMPTY_PAGE));
// Delete frame to cause request to be canceled.
page.evalOnSelector("iframe", "frame => frame.remove()");
PlaywrightException e = assertThrows(PlaywrightException.class, () -> route[0].resume());
assertTrue(e.getMessage().contains("Route is already handled!"), e.getMessage());
route[0].resume();
}
@Test
@@ -731,4 +730,77 @@ public class TestPageRoute extends TestBase {
page.navigate(server.EMPTY_PAGE);
assertEquals(asList(3, 2, 1), intercepted);
}
@Test
void shouldAllowToCallRouteAsynchronously() {
page.navigate(server.EMPTY_PAGE);
Route[] route = new Route[] { null };
page.route("**/cars", r -> {
route[0] = r;
});
page.evaluate("async () => {\n" +
" window.didReceiveResponse = false;\n" +
" window.pendingFetch = fetch('/cars', {\n" +
" method: 'POST',\n" +
" headers: { 'Content-Type': 'application/json' },\n" +
" mode: 'cors',\n" +
" body: JSON.stringify({ 'number': 1 })\n" +
" }).then(r => { window.didReceiveResponse = true; return r; });\n" +
" }");
while (route[0] == null) {
page.waitForTimeout(10);
}
assertNotNull(route[0]);
page.waitForTimeout(1000); // Allow some time for didReceiveResponse to be updated.
assertEquals(false, page.evaluate("window.didReceiveResponse"));
route[0].fulfill(new Route.FulfillOptions()
.setContentType("text/plain")
.setStatus(200)
.setBody("Hi!"));
Object response = page.evaluate("async () => (await pendingFetch).text()\n");
assertEquals("Hi!", response);
}
@Test
void shouldResumeIfFallbackIsCalledAsynchronously() {
page.navigate(server.EMPTY_PAGE);
Route[] route = new Route[] { null };
page.route("**/simple.json", r -> {
route[0] = r;
});
page.evaluate("async () => {\n" +
" window.didReceiveResponse = false;\n" +
" window.pendingFetch = fetch('" + server.PREFIX + "/simple.json', {\n" +
" method: 'POST',\n" +
" headers: { 'Content-Type': 'application/json' },\n" +
" mode: 'cors',\n" +
" body: JSON.stringify({ 'number': 1 })\n" +
" }).then(r => { window.didReceiveResponse = true; return r; });\n" +
" }");
while (route[0] == null) {
page.waitForTimeout(10);
}
assertNotNull(route[0]);
page.waitForTimeout(1000); // Allow some time for didReceiveResponse to be updated.
assertEquals(false, page.evaluate("window.didReceiveResponse"));
route[0].fallback();
Object response = page.evaluate("async () => (await pendingFetch).text()\n");
assertEquals("{\"foo\": \"bar\"}\n", response);
}
@Test
void shouldContinueIfAllHandlersCalledFallback() {
List<Integer> intercepted = new ArrayList<>();
Predicate<String> predicate = r -> true;
page.route(predicate, route -> {
intercepted.add(1);
route.fallback();
});
context.route(predicate, route -> {
intercepted.add(2);
route.fallback();
});
page.navigate(server.EMPTY_PAGE);
assertEquals(asList(1, 2), intercepted);
}
}