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 disabled preload config in firefox #7106

Merged
merged 4 commits into from
Aug 8, 2023
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
5 changes: 5 additions & 0 deletions .changeset/disabled-link-preload.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@remix-run/react": patch
---

Add `<link rel="preload">` timeout counter and disabling logic in case preloading is disabled by the user in Firefox. This prevents us from hanging on client-side navigations when we try to preload stylesheets and never receive a `load`/`error` event on the `link` tag.
49 changes: 41 additions & 8 deletions packages/remix-react/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,12 +221,42 @@ export function getLinksForMatches(
return dedupe(descriptors, preloads);
}

let stylesheetPreloadTimeouts = 0;
let isPreloadDisabled = false;

export async function prefetchStyleLinks(
routeModule: RouteModule
): Promise<void> {
if (!routeModule.links) return;
let descriptors = routeModule.links();
if (!descriptors) return;
if (isPreloadDisabled) return;

// If we've hit our timeout 3 times, we may be in firefox with the
// `network.preload` config disabled and we'll _never_ get onload/onerror
// callbacks. Let's try to confirm this with a totally invalid link preload
// which should immediately throw the onerror
if (stylesheetPreloadTimeouts >= 3) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commented on the Firefox bug, but you really want document.createElement("link").relList.supports("preload") or so.

In any case, I wouldn't bother dealing with it unless you want to also deal with browsers that don't support <link rel=preload> (which is fine, but can be simpler as per the above).

let linkLoadedOrErrored = await prefetchStyleLink({
rel: "preload",
as: "style",
href: "__remix-preload-detection-404.css",
});
if (linkLoadedOrErrored) {
// If this processed correctly, then our previous timeouts were probably
// legit, reset the counter.
stylesheetPreloadTimeouts = 0;
} else {
// If this bogus preload also times out without an onerror then it's safe
// to assume preloading is disabled and let's just stop trying. This
// _will_ cause FOUC on destination pages but there's nothing we can
// really do there if preloading is disabled since client-side injected
// scripts aren't render blocking. Maybe eventually React's client side
// async component stuff will provide an easier solution here
console.warn("Disabling preload due to lack of browser support");
isPreloadDisabled = true;
}
}

let styleLinks: HtmlLinkDescriptor[] = [];
for (let descriptor of descriptors) {
Expand All @@ -246,13 +276,12 @@ export async function prefetchStyleLinks(
(!link.media || window.matchMedia(link.media).matches) &&
!document.querySelector(`link[rel="stylesheet"][href="${link.href}"]`)
);

await Promise.all(matchingLinks.map(prefetchStyleLink));
}

async function prefetchStyleLink(
descriptor: HtmlLinkDescriptor
): Promise<void> {
): Promise<boolean> {
return new Promise((resolve) => {
let link = document.createElement("link");
Object.assign(link, descriptor);
Expand All @@ -266,16 +295,20 @@ async function prefetchStyleLink(
}
}

link.onload = () => {
// Allow 3s for the link preload to timeout
let timeoutId = setTimeout(() => {
stylesheetPreloadTimeouts++;
removeLink();
resolve();
};
resolve(false);
}, 3_000);

link.onerror = () => {
let done = () => {
clearTimeout(timeoutId);
removeLink();
resolve();
resolve(true);
};

link.onload = done;
link.onerror = done;
document.head.appendChild(link);
});
}
Expand Down