fix: asynchronous route handling (#1137)
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user