Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: abort navigation only after frame navigated #2898

Merged
merged 1 commit into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 29 additions & 7 deletions src/bidiMapper/modules/context/NavigationTracker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ describe('NavigationTracker', () => {
});
});

it('aborted by script-initiated navigation', async () => {
it('canceled by script-initiated navigation', async () => {
const navigation = navigationTracker.createPendingNavigation(SOME_URL);
navigationTracker.frameStartedNavigating(ANOTHER_URL, LOADER_ID);

Expand All @@ -173,19 +173,41 @@ describe('NavigationTracker', () => {
navigationTracker.frameRequestedNavigation(YET_ANOTHER_URL);

assertNavigationEvent(
ChromiumBidi.BrowsingContext.EventNames.NavigationAborted,
ChromiumBidi.BrowsingContext.EventNames.NavigationFailed,
navigation.navigationId,
ANOTHER_URL,
);

assert.equal(
(await navigation.finished).eventName,
NavigationEventName.NavigationAborted,
NavigationEventName.NavigationFailed,
);
assert.equal(navigationTracker.currentNavigationId, initialNavigationId);
assert.equal(navigationTracker.url, INITIAL_URL);
});

it('aborted by script-initiated navigation', async () => {
const navigation = navigationTracker.createPendingNavigation(SOME_URL);
navigationTracker.frameStartedNavigating(ANOTHER_URL, LOADER_ID);
navigationTracker.frameNavigated(ANOTHER_URL, LOADER_ID);

eventManager.registerEvent.reset();

navigationTracker.frameRequestedNavigation(YET_ANOTHER_URL);
navigationTracker.frameNavigated(YET_ANOTHER_URL, ANOTHER_LOADER_ID);

assertNavigationEvent(
ChromiumBidi.BrowsingContext.EventNames.NavigationAborted,
navigation.navigationId,
ANOTHER_URL,
);

assert.equal(
(await navigation.finished).eventName,
NavigationEventName.NavigationAborted,
);
});

it('failed command', async () => {
const navigation = navigationTracker.createPendingNavigation(SOME_URL);
navigationTracker.frameStartedNavigating(ANOTHER_URL, LOADER_ID);
Expand Down Expand Up @@ -296,7 +318,7 @@ describe('NavigationTracker', () => {
assertNoNavigationEvents();
});

it('aborted by script-initiated navigation', () => {
it('canceled by script-initiated navigation', async () => {
navigationTracker.frameRequestedNavigation(SOME_URL);

assertNoNavigationEvents();
Expand All @@ -317,7 +339,7 @@ describe('NavigationTracker', () => {
navigationTracker.frameRequestedNavigation(YET_ANOTHER_URL);

assertNavigationEvent(
ChromiumBidi.BrowsingContext.EventNames.NavigationAborted,
ChromiumBidi.BrowsingContext.EventNames.NavigationFailed,
sinon.match.any,
ANOTHER_URL,
);
Expand All @@ -328,7 +350,7 @@ describe('NavigationTracker', () => {
assert.equal(navigationTracker.url, INITIAL_URL);
});

it('aborted by command navigation', () => {
it('canceled by command navigation', async () => {
navigationTracker.frameRequestedNavigation(SOME_URL);
navigationTracker.frameStartedNavigating(ANOTHER_URL, LOADER_ID);

Expand All @@ -341,7 +363,7 @@ describe('NavigationTracker', () => {
navigationTracker.createPendingNavigation(YET_ANOTHER_URL);

assertNavigationEvent(
ChromiumBidi.BrowsingContext.EventNames.NavigationAborted,
ChromiumBidi.BrowsingContext.EventNames.NavigationFailed,
sinon.match.any,
ANOTHER_URL,
);
Expand Down
91 changes: 40 additions & 51 deletions src/bidiMapper/modules/context/NavigationTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class NavigationState {
loaderId?: string;
#isInitial: boolean;
#eventManager: EventManager;
#navigated = false;

get finished(): Promise<NavigationResult> {
return this.#finished;
Expand Down Expand Up @@ -97,7 +98,7 @@ class NavigationState {
this.#started = true;
}

finish(navigationResult: NavigationResult) {
#finish(navigationResult: NavigationResult) {
this.#started = true;

if (
Expand All @@ -116,6 +117,30 @@ class NavigationState {
}
this.#finished.resolve(navigationResult);
}

frameNavigated() {
this.#navigated = true;
}

fragmentNavigated() {
this.#navigated = true;
this.#finish(new NavigationResult(NavigationEventName.FragmentNavigated));
}

load() {
this.#finish(new NavigationResult(NavigationEventName.Load));
}

fail(message: string) {
this.#finish(
new NavigationResult(
this.#navigated
? NavigationEventName.NavigationAborted
: NavigationEventName.NavigationFailed,
message,
),
);
}
}

/**
Expand Down Expand Up @@ -201,11 +226,8 @@ export class NavigationTracker {
this.#isInitialNavigation &&
urlMatchesAboutBlank(url);

this.#pendingNavigation?.finish(
new NavigationResult(
NavigationEventName.NavigationAborted,
'navigation canceled by concurrent navigation',
),
this.#pendingNavigation?.fail(
'navigation canceled by concurrent navigation',
);
const navigation = new NavigationState(
url,
Expand All @@ -218,20 +240,8 @@ export class NavigationTracker {
}

dispose() {
// TODO: check if it should be aborted or failed.
this.#pendingNavigation?.finish(
new NavigationResult(
NavigationEventName.NavigationFailed,
'navigation canceled by context disposal',
),
);
// TODO: check if it should be aborted or failed.
this.#currentNavigation.finish(
new NavigationResult(
NavigationEventName.NavigationFailed,
'navigation canceled by context disposal',
),
);
this.#pendingNavigation?.fail('navigation canceled by context disposal');
this.#currentNavigation.fail('navigation canceled by context disposal');
}

// Update the current url.
Expand Down Expand Up @@ -277,23 +287,16 @@ export class NavigationTracker {
this.createPendingNavigation(unreachableUrl, true);
navigation.url = unreachableUrl;
navigation.start();
navigation.finish(
new NavigationResult(
NavigationEventName.NavigationFailed,
'the requested url is unreachable',
),
);
navigation.fail('the requested url is unreachable');
return;
}

const navigation = this.#getNavigationForFrameNavigated(url, loaderId);
navigation.frameNavigated();

if (navigation !== this.#currentNavigation) {
this.#currentNavigation.finish(
new NavigationResult(
NavigationEventName.NavigationAborted,
'navigation canceled by concurrent navigation',
),
this.#currentNavigation.fail(
'navigation canceled by concurrent navigation',
);
}

Expand Down Expand Up @@ -340,9 +343,7 @@ export class NavigationTracker {
);

// Finish ongoing navigation.
fragmentNavigation.finish(
new NavigationResult(NavigationEventName.FragmentNavigated),
);
fragmentNavigation.fragmentNavigated();

if (fragmentNavigation === this.#pendingNavigation) {
this.#pendingNavigation = undefined;
Expand All @@ -365,19 +366,15 @@ export class NavigationTracker {
// Even if it was an initial navigation, it is finished.
this.#isInitialNavigation = false;

this.#loaderIdToNavigationsMap
.get(loaderId)
?.finish(new NavigationResult(NavigationEventName.Load));
this.#loaderIdToNavigationsMap.get(loaderId)?.load();
}

/**
* Fail navigation due to navigation command failed.
*/
failNavigation(navigation: NavigationState, errorText: string) {
this.#logger?.(LogType.debug, 'failCommandNavigation');
navigation.finish(
new NavigationResult(NavigationEventName.NavigationFailed, errorText),
);
navigation.fail(errorText);
}

/**
Expand All @@ -401,11 +398,8 @@ export class NavigationTracker {
return;
}

this.#currentNavigation.finish(
new NavigationResult(
NavigationEventName.NavigationAborted,
'navigation canceled by concurrent navigation',
),
this.#currentNavigation.fail(
'navigation canceled by concurrent navigation',
);

navigation.start();
Expand Down Expand Up @@ -462,11 +456,6 @@ export class NavigationTracker {
* that the navigation failed.
*/
networkLoadingFailed(loaderId: string, errorText: string) {
if (this.#loaderIdToNavigationsMap.has(loaderId)) {
const navigation = this.#loaderIdToNavigationsMap.get(loaderId)!;
navigation.finish(
new NavigationResult(NavigationEventName.NavigationFailed, errorText),
);
}
this.#loaderIdToNavigationsMap.get(loaderId)?.fail(errorText);
}
}
90 changes: 90 additions & 0 deletions tests/browsing_context/__snapshots__/test_navigate_events.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,96 @@
}),
])
# ---
# name: test_navigate_hang_navigate_again_checkEvents
list([
dict({
'method': 'network.beforeRequestSent',
'params': dict({
'context': 'stable_0',
'isBlocked': False,
'navigation': 'stable_1',
'redirectCount': 0,
'request': 'stable_4',
}),
'type': 'event',
}),
dict({
'method': 'browsingContext.navigationFailed',
'params': dict({
'context': 'stable_0',
'navigation': 'stable_1',
'url': 'stable_3',
}),
'type': 'event',
}),
dict({
'error': 'unknown error',
'id': 'stable_5',
'message': 'navigation canceled by concurrent navigation',
'type': 'error',
}),
dict({
'method': 'script.message',
'params': dict({
'channel': 'beforeunload_channel',
'data': dict({
'type': 'string',
'value': 'beforeunload',
}),
'source': dict({
'context': 'stable_0',
}),
}),
'type': 'event',
}),
dict({
'method': 'browsingContext.navigationStarted',
'params': dict({
'context': 'stable_0',
'navigation': 'stable_6',
'url': 'stable_7',
}),
'type': 'event',
}),
dict({
'method': 'network.beforeRequestSent',
'params': dict({
'context': 'stable_0',
'isBlocked': False,
'navigation': 'stable_6',
'redirectCount': 0,
'request': 'stable_9',
}),
'type': 'event',
}),
dict({
'method': 'browsingContext.domContentLoaded',
'params': dict({
'context': 'stable_0',
'navigation': 'stable_6',
'url': 'stable_7',
}),
'type': 'event',
}),
dict({
'method': 'browsingContext.load',
'params': dict({
'context': 'stable_0',
'navigation': 'stable_6',
'url': 'stable_7',
}),
'type': 'event',
}),
dict({
'id': 'stable_10',
'result': dict({
'navigation': 'stable_6',
'url': 'stable_7',
}),
'type': 'success',
}),
])
# ---
# name: test_reload_aboutBlank_checkEvents
list([
dict({
Expand Down
Loading
Loading