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

Handle cases when location.protocol is about: or data: #13226

Open
CNSeniorious000 opened this issue Dec 23, 2024 · 5 comments
Open

Handle cases when location.protocol is about: or data: #13226

CNSeniorious000 opened this issue Dec 23, 2024 · 5 comments
Labels
bug Something isn't working

Comments

@CNSeniorious000
Copy link

CNSeniorious000 commented Dec 23, 2024

Describe the problem

I am using svelte in several embedded webviews. I used to have to run a static file server before bundleStrategy: 'inline' became available yesterday.

Normally (if we click this html file) the url will start with file://, and everything works fine.

But In the webview implementation I use the url is a data url, and hydration always fails because of:

Uncaught TypeError: Failed to construct 'URL': Invalid URL
    at data:text/html;chars…8L2h0bWw+Cg==:16:13

In another case, I want to parse metadata from an url using an iframe, but sveltekit apps always fail to hydrate because of a similar error:

Uncaught TypeError: Failed to construct 'URL': Invalid URL
    at about:blank:16:13

Describe the proposed solution

Sveltekit always generates

  <body data-sveltekit-preload-data="hover">
    <div style="display: contents">
      <script>
        {
          __sveltekit_1bknoa9 = {
            base: new URL('.', location).pathname.slice(0, -1)
        };

where new URL('.', location) will fail when location.protocol is data:// or about://

Alternatives considered

No response

Importance

would make my life easier

Additional Information

No response

@CNSeniorious000 CNSeniorious000 changed the title Handle cases when location.protocal is about: or data: Handle cases when location.protocol is about: or data: Dec 23, 2024
@CNSeniorious000
Copy link
Author

CNSeniorious000 commented Dec 23, 2024

// resolve e.g. '../..' against current location, then remove trailing slash
base_expression = `new URL(${s(base)}, location).pathname.slice(0, -1)`;
if (!paths.assets || (paths.assets[0] === '/' && paths.assets !== SVELTE_KIT_ASSETS)) {
assets = base;
}
} else if (options.hash_routing) {
// we have to assume that we're in the right place
base_expression = "new URL('.', location).pathname.slice(0, -1)";
}

An ugly fix would be

- `new URL(${s(base)}, location).pathname.slice(0, -1)`
+ `["about:", "data:"].includes(location.protocol) ? `${s(base)} : new URL(${s(base)}, location).pathname.slice(0, -1)`

- new URL('.', location).pathname.slice(0, -1)
+ ["about:", "data:"].includes(location.protocol) ? "" : new URL('.', location).pathname.slice(0, -1)

@eltigerchino
Copy link
Member

eltigerchino commented Dec 23, 2024

You are welcome to open a pull request. Are there any other protocols we need to consider? Maybe we can create a set and search that.

@CNSeniorious000
Copy link
Author

I think maybe ftp:? But it doesn't disrupt the current implementation. The only issues seem to arise with data: and about:.

@eltigerchino
Copy link
Member

eltigerchino commented Jan 10, 2025

Did some investigation and there seems to be two ways of embedding SvelteKit through HTML text:

1. iframe srcdoc="..."

The document will have an embedded URL of about:srcdoc which is an invalid URL base.

2. iframe src="data:text/html;charset=utf-8,..."

The document will have an embedded URL of data:text/html... which is an invalid URL base.

Overall, there's a number of changes we need to make to avoid the "invalid base URL" error when constructing a new URL object to improve embedding SvelteKit apps as HTML text.

base_expression = `new URL(${s(base)}, location).pathname.slice(0, -1)`;

base_expression = "new URL('.', location).pathname.slice(0, -1)";

let baseURI = document.baseURI;
if (!baseURI) {
const baseTags = document.getElementsByTagName('base');
baseURI = baseTags.length ? baseTags[0].href : document.URL;
}
return new URL(url, baseURI);

url = new URL(a instanceof SVGAElement ? a.href.baseVal : a.href, document.baseURI);

const { href } = new URL(resource, location.href);

await native_navigation(new URL(error.location, location.href));

cc: @kran6a

Also, when using a hash router, it's possible for the page to infinitely refresh due to the embedded URL pathname not passing any of the conditions below (#13287):

if (hash_routing) {
if (url.pathname === base + '/') {
return false;
}
// be lenient if serving from filesystem
if (url.protocol === 'file:' && url.pathname.replace(/\/[^/]+\.html?$/, '') === base) {
return false;
}
return true;
}

Fortunately, we can check if location.url.origin === 'null' to detect non-http/https URLs or if the kit.config.embedded option is set and perhaps handle these situations more gracefully.

@eltigerchino eltigerchino added the bug Something isn't working label Jan 10, 2025
@kran6a
Copy link

kran6a commented Jan 20, 2025

I applied this patch on my node_modules and it works for my use-case. There are no invalid base URL errors and no infinite reloads.
Absolute links work, relative links won't ever work because I am using an iframe with srcdoc which makes relative URL take the parent base.

One strange thing I noticed that also broke when migrating widgets from raw svelte (bundling to a single .js file then inlining it into an HTML shell) to sveltekit is that previously I injected some classes into the iframe from the host website via

iframe_element.contentWindow.props = {some_class: Class}; //Class is a class definition, not an instance

These classes use runes and this approach worked on svelte but reactivity broke after transitioning to sveltekit.
Importing the classes from the widget code fixes reactivity but the point of injecting those classes is that widgets don't need to bundle them to reduce their size, dependencies and maintenance as they all will use the most recent version of those classes.
I am not attempting to achieve cross-document reactivity, classes are instanced and meant to be reactive only within the iframe.
I am a bit puzzled on why this does not work. The class state is updated correctly and if you trigger a reload somehow (e.g: navigating to another route and then back) you can see the updated state correctly but reactivity is still nonexistant.

diff --git a/node_modules/@sveltejs/kit/src/runtime/client/utils.js b/node_modules/@sveltejs/kit/src/runtime/client/utils.js
index 28a06d8..24b6ee7 100644
--- a/node_modules/@sveltejs/kit/src/runtime/client/utils.js
+++ b/node_modules/@sveltejs/kit/src/runtime/client/utils.js
@@ -311,6 +311,9 @@ export function is_external_url(url, base, hash_routing) {
 	}
 
 	if (hash_routing) {
+		if (url.protocol === "about:" || url.protocol === "data:"){
+			return false;
+		}
 		if (url.pathname === base + '/' || url.pathname === base + '/index.html') {
 			return false;
 		}
diff --git a/node_modules/@sveltejs/kit/src/runtime/server/page/render.js b/node_modules/@sveltejs/kit/src/runtime/server/page/render.js
index d8fbe32..82867e0 100644
--- a/node_modules/@sveltejs/kit/src/runtime/server/page/render.js
+++ b/node_modules/@sveltejs/kit/src/runtime/server/page/render.js
@@ -109,7 +109,7 @@ export async function render_response({
 			}
 		} else if (options.hash_routing) {
 			// we have to assume that we're in the right place
-			base_expression = "new URL('.', location).pathname.slice(0, -1)";
+			base_expression = "location.protocol === 'about:' || location.protocol === 'data:' ? '' : new URL('.', location.protocol).pathname.slice(0, -1)";
 		}
 	}
 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants