@samitouri / QOS-React-1 / commits / 306a01b4e0

Add credentialless as a recognized boolean attribute for iframes (#36148)

## Summary The `credentialless` attribute is a boolean HTML attribute for `<iframe>` elements that loads the iframe in a new, ephemeral context without access to the parent's credentials (cookies, client certificates, etc.). This change adds it to all boolean attribute switch/case lists in React DOM so it is properly handled as a boolean (set when true, removed when false) rather than being treated as an unknown string attribute. Per the [Anonymous iframe spec (WICG)](https://wicg.github.io/anonymous-iframe/): > The credentialless attribute enables loading documents hosted by the iframe with a new and ephemeral storage partition. It is a boolean value. The default is false. ``` partial interface HTMLIFrameElement { attribute boolean credentialless; }; ``` Changes: - ReactDOMComponent.js: Added to both `setProp` and `diffHydratedGenericElement` - ReactFizzConfigDOM.js: Added to `pushAttribute` for server-side rendering - ReactDOMUnknownPropertyHook.js: Added to both validation switch/case lists ## Test plan - Added unit test in DOMPropertyOperations-test.js verifying `credentialless={true}` sets the attribute to `''` and `credentialless={false}` removes it - All tests pass in source and www channels (590 tests each) - Flow type checking passes (dom-node renderer) - Prettier and lint pass

vmx906 committed Apr 20, 2026 at 18:37 UTC 306a01b4e0242e9379ba971c8925670651f16818
5 files changed +45
packages/react-dom-bindings/src/client/ReactDOMComponent.js
+2
@@ -756,6 +756,7 @@ function setProp(
756 case 'async':
757 case 'autoPlay':
758 case 'controls':
759 + case 'credentialless':
760 case 'default':
761 case 'defer':
762 case 'disabled':
@@ -2849,6 +2850,7 @@ function diffHydratedGenericElement(
2850 case 'async':
2851 case 'autoPlay':
2852 case 'controls':
2853 + case 'credentialless':
2854 case 'default':
2855 case 'defer':
2856 case 'disabled':
packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js
+1
@@ -1695,6 +1695,7 @@ function pushAttribute(
1695 case 'async':
1696 case 'autoPlay':
1697 case 'controls':
1698 + case 'credentialless':
1699 case 'default':
1700 case 'defer':
1701 case 'disabled':
packages/react-dom-bindings/src/shared/ReactDOMUnknownPropertyHook.js
+2
@@ -208,6 +208,7 @@ function validateProperty(tagName, name, value, eventRegistry) {
208 case 'async':
209 case 'autoPlay':
210 case 'controls':
211 + case 'credentialless':
212 case 'default':
213 case 'defer':
214 case 'disabled':
@@ -287,6 +288,7 @@ function validateProperty(tagName, name, value, eventRegistry) {
288 case 'async':
289 case 'autoPlay':
290 case 'controls':
291 + case 'credentialless':
292 case 'default':
293 case 'defer':
294 case 'disabled':
packages/react-dom/src/__tests__/DOMPropertyOperations-test.js
+28
@@ -160,6 +160,34 @@ describe('DOMPropertyOperations', () => {
160 expect(container.firstChild.hasAttribute('allowFullScreen')).toBe(false);
161 });
162
163 + it('should set credentialless boolean attribute on iframes', async () => {
164 + const container = document.createElement('div');
165 + const root = ReactDOMClient.createRoot(container);
166 + await act(() => {
167 + root.render(<iframe credentialless={true} />);
168 + });
169 + expect(container.firstChild.getAttribute('credentialless')).toBe('');
170 + await act(() => {
171 + root.render(<iframe credentialless={false} />);
172 + });
173 + expect(container.firstChild.hasAttribute('credentialless')).toBe(false);
174 + });
175 +
176 + it('should set credentialless attribute when passed a string and warn', async () => {
177 + const container = document.createElement('div');
178 + const root = ReactDOMClient.createRoot(container);
179 + await act(() => {
180 + root.render(<iframe credentialless="true" />);
181 + });
182 + assertConsoleErrorDev([
183 + 'Received the string `true` for the boolean attribute `credentialless`. ' +
184 + 'Although this works, it will not work as expected if you pass the string "false". ' +
185 + 'Did you mean credentialless={true}?\n' +
186 + ' in iframe (at **)',
187 + ]);
188 + expect(container.firstChild.getAttribute('credentialless')).toBe('');
189 + });
190 +
191 it('should remove when setting custom attr to null', async () => {
192 const container = document.createElement('div');
193 const root = ReactDOMClient.createRoot(container);
packages/react-dom/src/__tests__/ReactDOMServerIntegrationAttributes-test.js
+12
@@ -187,6 +187,18 @@ describe('ReactDOMServerIntegration', () => {
187 });
188 });
189
190 + describe('credentialless property', function () {
191 + itRenders('credentialless prop with true value', async render => {
192 + const e = await render(<iframe credentialless={true} />);
193 + expect(e.getAttribute('credentialless')).toBe('');
194 + });
195 +
196 + itRenders('credentialless prop with false value', async render => {
197 + const e = await render(<iframe credentialless={false} />);
198 + expect(e.hasAttribute('credentialless')).toBe(false);
199 + });
200 + });
201 +
202 describe('download property (combined boolean/string attribute)', function () {
203 itRenders('download prop with true value', async render => {
204 const e = await render(<a download={true} />);