diff --git a/.changeset/famous-monkeys-sort.md b/.changeset/famous-monkeys-sort.md new file mode 100644 index 000000000000..0c42c9c9119c --- /dev/null +++ b/.changeset/famous-monkeys-sort.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': minor +--- + +feat: warn on external goto navigation in preparation for SvelteKit 2 diff --git a/packages/kit/src/runtime/app/navigation.js b/packages/kit/src/runtime/app/navigation.js index 0da8f93e1599..f0256c5b466e 100644 --- a/packages/kit/src/runtime/app/navigation.js +++ b/packages/kit/src/runtime/app/navigation.js @@ -16,6 +16,7 @@ export const disableScrollHandling = /* @__PURE__ */ client_method('disable_scro * noScroll?: boolean; * keepFocus?: boolean; * invalidateAll?: boolean; + * external?: boolean; * state?: any * }) => Promise} * @param {string | URL} url Where to navigate to. Note that if you've set [`config.kit.paths.base`](https://kit.svelte.dev/docs/configuration#paths) and the URL is root-relative, you need to prepend the base path if you want to navigate within the app. @@ -23,7 +24,8 @@ export const disableScrollHandling = /* @__PURE__ */ client_method('disable_scro * @param {boolean} [opts.replaceState] If `true`, will replace the current `history` entry rather than creating a new one with `pushState` * @param {boolean} [opts.noScroll] If `true`, the browser will maintain its scroll position rather than scrolling to the top of the page after navigation * @param {boolean} [opts.keepFocus] If `true`, the currently focused element will retain focus after navigation. Otherwise, focus will be reset to the body - * @param {boolean} [invalidateAll] If `true`, all `load` functions of the page will be rerun. See https://kit.svelte.dev/docs/load#rerunning-load-functions for more info on invalidation. + * @param {boolean} [opts.invalidateAll] If `true`, all `load` functions of the page will be rerun. See https://kit.svelte.dev/docs/load#rerunning-load-functions for more info on invalidation. + * @param {boolean} [opts.external] By default, `goto` will warn on navigation to external URLs for consistency and security reasons (in SvelteKit 2, this will be an error). Set this to `true` to allow navigating to external URLs. * @param {any} [opts.state] The state of the new/updated history entry * @returns {Promise} */ diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index 08465d6b5342..9b820c70a9d5 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -1376,6 +1376,17 @@ export function create_client(app, target) { }, goto: (href, opts = {}) => { + if (DEV) { + if (typeof href === 'string') { + href = new URL(href, get_base_uri(document)); + } + if (!opts.external && href.origin !== origin) { + console.warn( + 'Navigating to an external URL using `goto` will be an error in SvelteKit 2 unless the `external` option is set' + ); + } + } + return goto(href, opts, 0); }, diff --git a/packages/kit/test/apps/basics/test/cross-platform/client.test.js b/packages/kit/test/apps/basics/test/cross-platform/client.test.js index 598c6e4c356e..1b52aab21002 100644 --- a/packages/kit/test/apps/basics/test/cross-platform/client.test.js +++ b/packages/kit/test/apps/basics/test/cross-platform/client.test.js @@ -147,7 +147,7 @@ test.describe('Navigation lifecycle functions', () => { baseURL }) => { await page.goto('/navigation-lifecycle/before-navigate/prevent-navigation'); - await app.goto('https://google.de'); + await app.goto('https://google.de', { external: true }); expect(page.url()).toBe(baseURL + '/navigation-lifecycle/before-navigate/prevent-navigation'); expect(await page.innerHTML('pre')).toBe('1 true goto'); }); @@ -216,7 +216,7 @@ test.describe('Navigation lifecycle functions', () => { await page.goto('/navigation-lifecycle/before-navigate/prevent-navigation'); await page.click('h1'); // The browsers block attempts to prevent navigation on a frame that's never had a user gesture. - await app.goto('https://google.de'); + await app.goto('https://google.de', { external: true }); await app.goto('/navigation-lifecycle/before-navigate/prevent-navigation?x=1'); expect(await page.innerHTML('pre')).toBe('2 false goto'); diff --git a/packages/kit/test/utils.d.ts b/packages/kit/test/utils.d.ts index 6ba3c0b8d89f..5ffab4b42b43 100644 --- a/packages/kit/test/utils.d.ts +++ b/packages/kit/test/utils.d.ts @@ -13,7 +13,7 @@ export const test: TestType< PlaywrightTestArgs & PlaywrightTestOptions & { app: { - goto(url: string, opts?: { replaceState?: boolean }): Promise; + goto(url: string, opts?: { replaceState?: boolean; external?: boolean }): Promise; invalidate(url: string): Promise; beforeNavigate(url: URL): void | boolean; afterNavigate(url: URL): void;