Skip to content

Commit

Permalink
support crossOrigin for preconnect
Browse files Browse the repository at this point in the history
  • Loading branch information
gnoff committed Feb 24, 2023
1 parent 7ae8685 commit 5fc3a28
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 32 deletions.
52 changes: 40 additions & 12 deletions packages/react-dom-bindings/src/client/ReactDOMFloatClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,16 +156,23 @@ function getDocumentFromRoot(root: HoistableRoot): Document {
return root.ownerDocument || root;
}

function preconnectAs(rel: 'preconnect' | 'dns-prefetch', href: string) {
function preconnectAs(
rel: 'preconnect' | 'dns-prefetch',
crossOrigin: null | '' | 'use-credentials',
href: string,
) {
const ownerDocument = getDocumentForPreloads();
if (typeof href === 'string' && href && ownerDocument) {
const limitedEscapedHref =
escapeSelectorAttributeValueInsideDoubleQuotes(href);
const key = `link[rel="${rel}"][href="${limitedEscapedHref}"]`;
let key = `link[rel="${rel}"][href="${limitedEscapedHref}"]`;
if (typeof crossOrigin === 'string') {
key += `[crossorigin="${crossOrigin}"]`;
}
if (!preconnectsSet.has(key)) {
preconnectsSet.add(key);

const preconnectProps = {rel, href};
const preconnectProps = {rel, crossOrigin, href};
if (null === ownerDocument.querySelector(key)) {
const preloadInstance = createElement(
'link',
Expand All @@ -192,33 +199,54 @@ function prefetchDNS(href: string, options?: mixed) {
getValueDescriptorExpectingObjectForWarning(href),
);
} else if (options != null) {
console.error(
'ReactDOM.prefetchDNS(): Expected only one argument (href) but encountered a second argument, %s, instead. This argument is reserved for future options and is currently disallowed. Try calling ReactDOM.prefetchDNS() with just a single string argument, `href`.',
getValueDescriptorExpectingEnumForWarning(options),
);
if (
typeof options === 'object' &&
options.hasOwnProperty('crossOrigin')
) {
console.error(
'ReactDOM.prefetchDNS(): Expected only one argument, `href`, but encountered %s as a second argument instead. This argument is reserved for future options and is currently disallowed. It looks like the you are attempting to set a crossOrigin property for this DNS lookup hint. Browsers do not perform DNS queries using CORS and setting this attribute on the resource hint has no effect. Try calling ReactDOM.prefetchDNS() with just a single string argument, `href`.',
getValueDescriptorExpectingEnumForWarning(options),
);
} else {
console.error(
'ReactDOM.prefetchDNS(): Expected only one argument, `href`, but encountered %s as a second argument instead. This argument is reserved for future options and is currently disallowed. Try calling ReactDOM.prefetchDNS() with just a single string argument, `href`.',
getValueDescriptorExpectingEnumForWarning(options),
);
}
}
}
preconnectAs('dns-prefetch', href);
preconnectAs('dns-prefetch', null, href);
}

// --------------------------------------
// ReactDOM.preconnect
// --------------------------------------
function preconnect(href: string, options?: mixed) {
function preconnect(href: string, options?: {crossOrigin?: string}) {
if (__DEV__) {
if (typeof href !== 'string' || !href) {
console.error(
'ReactDOM.preconnect(): Expected the `href` argument (first) to be a non-empty string but encountered %s instead.',
getValueDescriptorExpectingObjectForWarning(href),
);
} else if (options != null) {
} else if (options != null && typeof options !== 'object') {
console.error(
'ReactDOM.preconnect(): Expected only one argument (href) but encountered a second argument, %s, instead. This argument is reserved for future options and is currently disallowed. Try calling ReactDOM.preconnect() with just a single string argument, `href`.',
'ReactDOM.preconnect(): Expected the `options` argument (second) to be an object but encountered %s instead. The only supported option at this time is `crossOrigin` which accepts a string.',
getValueDescriptorExpectingEnumForWarning(options),
);
} else if (options != null && typeof options.crossOrigin !== 'string') {
console.error(
'ReactDOM.preconnect(): Expected the `crossOrigin` option (second argument) to be a string but encountered %s instead. Try removing this option or passing a string value instead.',
getValueDescriptorExpectingObjectForWarning(options.crossOrigin),
);
}
}
preconnectAs('preconnect', href);
const crossOrigin =
options == null || typeof options.crossOrigin !== 'string'
? null
: options.crossOrigin === 'use-credentials'
? 'use-credentials'
: '';
preconnectAs('preconnect', crossOrigin, href);
}

// --------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4243,10 +4243,20 @@ export function prefetchDNS(href: string, options?: mixed) {
getValueDescriptorExpectingObjectForWarning(href),
);
} else if (options != null) {
console.error(
'ReactDOM.prefetchDNS(): Expected only one argument (href) but encountered a second argument, %s, instead. This argument is reserved for future options and is currently disallowed. Try calling ReactDOM.prefetchDNS() with just a single string argument, `href`.',
getValueDescriptorExpectingEnumForWarning(options),
);
if (
typeof options === 'object' &&
options.hasOwnProperty('crossOrigin')
) {
console.error(
'ReactDOM.prefetchDNS(): Expected only one argument, `href`, but encountered %s as a second argument instead. This argument is reserved for future options and is currently disallowed. It looks like the you are attempting to set a crossOrigin property for this DNS lookup hint. Browsers do not perform DNS queries using CORS and setting this attribute on the resource hint has no effect. Try calling ReactDOM.prefetchDNS() with just a single string argument, `href`.',
getValueDescriptorExpectingEnumForWarning(options),
);
} else {
console.error(
'ReactDOM.prefetchDNS(): Expected only one argument, `href`, but encountered %s as a second argument instead. This argument is reserved for future options and is currently disallowed. Try calling ReactDOM.prefetchDNS() with just a single string argument, `href`.',
getValueDescriptorExpectingEnumForWarning(options),
);
}
}
}

Expand All @@ -4270,7 +4280,7 @@ export function prefetchDNS(href: string, options?: mixed) {
}
}

export function preconnect(href: string, options?: mixed) {
export function preconnect(href: string, options?: {crossOrigin?: string}) {
if (!currentResources) {
// While we expect that preconnect calls are primarily going to be observed
// during render because effects and events don't run on the server it is
Expand All @@ -4287,16 +4297,30 @@ export function preconnect(href: string, options?: mixed) {
'ReactDOM.preconnect(): Expected the `href` argument (first) to be a non-empty string but encountered %s instead.',
getValueDescriptorExpectingObjectForWarning(href),
);
} else if (options != null) {
} else if (options != null && typeof options !== 'object') {
console.error(
'ReactDOM.preconnect(): Expected only one argument (href) but encountered a second argument, %s, instead. This argument is reserved for future options and is currently disallowed. Try calling ReactDOM.preconnect() with just a single string argument, `href`.',
'ReactDOM.preconnect(): Expected the `options` argument (second) to be an object but encountered %s instead. The only supported option at this time is `crossOrigin` which accepts a string.',
getValueDescriptorExpectingEnumForWarning(options),
);
} else if (options != null && typeof options.crossOrigin !== 'string') {
console.error(
'ReactDOM.preconnect(): Expected the `crossOrigin` option (second argument) to be a string but encountered %s instead. Try removing this option or passing a string value instead.',
getValueDescriptorExpectingObjectForWarning(options.crossOrigin),
);
}
}

if (typeof href === 'string' && href) {
const key = getResourceKey('preconnect', href);
const crossOrigin =
options == null || typeof options.crossOrigin !== 'string'
? null
: options.crossOrigin === 'use-credentials'
? 'use-credentials'
: '';

const key = `[preconnect][${
crossOrigin === null ? 'null' : crossOrigin
}]${href}`;
let resource = resources.preconnectsMap.get(key);
if (!resource) {
resource = {
Expand All @@ -4308,7 +4332,7 @@ export function preconnect(href: string, options?: mixed) {
resources.preconnectsMap.set(key, resource);
pushLinkImpl(
resource.chunks,
({href, rel: 'preconnect'}: PreconnectProps),
({rel: 'preconnect', href, crossOrigin}: PreconnectProps),
);
}
resources.preconnects.add(resource);
Expand Down
63 changes: 52 additions & 11 deletions packages/react-dom/src/__tests__/ReactDOMFloat-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2300,16 +2300,23 @@ body {
function App({url}) {
ReactDOM.prefetchDNS(url);
ReactDOM.prefetchDNS(url);
ReactDOM.prefetchDNS(url, {});
ReactDOM.prefetchDNS(url, {crossOrigin: 'use-credentials'});
return (
<html>
<body>hello world</body>
</html>
);
}

await actIntoEmptyDocument(() => {
renderToPipeableStream(<App url="foo" />).pipe(writable);
});
await expect(async () => {
await actIntoEmptyDocument(() => {
renderToPipeableStream(<App url="foo" />).pipe(writable);
});
}).toErrorDev([
'ReactDOM.prefetchDNS(): Expected only one argument, `href`, but encountered something with type "object" as a second argument instead. This argument is reserved for future options and is currently disallowed. Try calling ReactDOM.prefetchDNS() with just a single string argument, `href`.',
'ReactDOM.prefetchDNS(): Expected only one argument, `href`, but encountered something with type "object" as a second argument instead. This argument is reserved for future options and is currently disallowed. It looks like the you are attempting to set a crossOrigin property for this DNS lookup hint. Browsers do not perform DNS queries using CORS and setting this attribute on the resource hint has no effect. Try calling ReactDOM.prefetchDNS() with just a single string argument, `href`.',
]);

expect(getMeaningfulChildren(document)).toEqual(
<html>
Expand All @@ -2321,7 +2328,12 @@ body {
);

const root = ReactDOMClient.hydrateRoot(document, <App url="foo" />);
expect(Scheduler).toFlushWithoutYielding();
expect(() => {
expect(Scheduler).toFlushWithoutYielding();
}).toErrorDev([
'ReactDOM.prefetchDNS(): Expected only one argument, `href`, but encountered something with type "object" as a second argument instead. This argument is reserved for future options and is currently disallowed. Try calling ReactDOM.prefetchDNS() with just a single string argument, `href`.',
'ReactDOM.prefetchDNS(): Expected only one argument, `href`, but encountered something with type "object" as a second argument instead. This argument is reserved for future options and is currently disallowed. It looks like the you are attempting to set a crossOrigin property for this DNS lookup hint. Browsers do not perform DNS queries using CORS and setting this attribute on the resource hint has no effect. Try calling ReactDOM.prefetchDNS() with just a single string argument, `href`.',
]);
expect(getMeaningfulChildren(document)).toEqual(
<html>
<head>
Expand All @@ -2332,7 +2344,12 @@ body {
);

root.render(<App url="bar" />);
expect(Scheduler).toFlushWithoutYielding();
expect(() => {
expect(Scheduler).toFlushWithoutYielding();
}).toErrorDev([
'ReactDOM.prefetchDNS(): Expected only one argument, `href`, but encountered something with type "object" as a second argument instead. This argument is reserved for future options and is currently disallowed. Try calling ReactDOM.prefetchDNS() with just a single string argument, `href`.',
'ReactDOM.prefetchDNS(): Expected only one argument, `href`, but encountered something with type "object" as a second argument instead. This argument is reserved for future options and is currently disallowed. It looks like the you are attempting to set a crossOrigin property for this DNS lookup hint. Browsers do not perform DNS queries using CORS and setting this attribute on the resource hint has no effect. Try calling ReactDOM.prefetchDNS() with just a single string argument, `href`.',
]);
expect(getMeaningfulChildren(document)).toEqual(
<html>
<head>
Expand All @@ -2345,49 +2362,73 @@ body {
});
});

describe('ReactDOM.preconnect(href)', () => {
describe('ReactDOM.preconnect(href, { crossOrigin })', () => {
it('creates a preconnect resource when called', async () => {
function App({url}) {
ReactDOM.preconnect(url);
ReactDOM.preconnect(url);
ReactDOM.preconnect(url, {crossOrigin: true});
ReactDOM.preconnect(url, {crossOrigin: ''});
ReactDOM.preconnect(url, {crossOrigin: 'anonymous'});
ReactDOM.preconnect(url, {crossOrigin: 'use-credentials'});
return (
<html>
<body>hello world</body>
</html>
);
}

await actIntoEmptyDocument(() => {
renderToPipeableStream(<App url="foo" />).pipe(writable);
});
await expect(async () => {
await actIntoEmptyDocument(() => {
renderToPipeableStream(<App url="foo" />).pipe(writable);
});
}).toErrorDev(
'ReactDOM.preconnect(): Expected the `crossOrigin` option (second argument) to be a string but encountered something with type "boolean" instead. Try removing this option or passing a string value instead.',
);

expect(getMeaningfulChildren(document)).toEqual(
<html>
<head>
<link rel="preconnect" href="foo" />
<link rel="preconnect" href="foo" crossorigin="" />
<link rel="preconnect" href="foo" crossorigin="use-credentials" />
</head>
<body>hello world</body>
</html>,
);

const root = ReactDOMClient.hydrateRoot(document, <App url="foo" />);
expect(Scheduler).toFlushWithoutYielding();
expect(() => {
expect(Scheduler).toFlushWithoutYielding();
}).toErrorDev(
'ReactDOM.preconnect(): Expected the `crossOrigin` option (second argument) to be a string but encountered something with type "boolean" instead. Try removing this option or passing a string value instead.',
);
expect(getMeaningfulChildren(document)).toEqual(
<html>
<head>
<link rel="preconnect" href="foo" />
<link rel="preconnect" href="foo" crossorigin="" />
<link rel="preconnect" href="foo" crossorigin="use-credentials" />
</head>
<body>hello world</body>
</html>,
);

root.render(<App url="bar" />);
expect(Scheduler).toFlushWithoutYielding();
expect(() => {
expect(Scheduler).toFlushWithoutYielding();
}).toErrorDev(
'ReactDOM.preconnect(): Expected the `crossOrigin` option (second argument) to be a string but encountered something with type "boolean" instead. Try removing this option or passing a string value instead.',
);
expect(getMeaningfulChildren(document)).toEqual(
<html>
<head>
<link rel="preconnect" href="foo" />
<link rel="preconnect" href="foo" crossorigin="" />
<link rel="preconnect" href="foo" crossorigin="use-credentials" />
<link rel="preconnect" href="bar" />
<link rel="preconnect" href="bar" crossorigin="" />
<link rel="preconnect" href="bar" crossorigin="use-credentials" />
</head>
<body>hello world</body>
</html>,
Expand Down

0 comments on commit 5fc3a28

Please sign in to comment.