@samitouri / QOS-React / commits / ea05b750a5

Allow Passing Blob/File/MediaSource/MediaStream to src of <img>, <video> and <audio> (#32828)

Behind the `enableSrcObject` flag. This is revisiting a variant of what was discussed in #11163. Instead of supporting the [`srcObject` property](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/srcObject) as a separate name, this adds an overload of `src` to allow objects to be passed. The DOM needs to add separate properties for the object forms since you read back but it doesn't make sense for React's write-only API to do that. Similar to how we'll like add an overload for `popoverTarget` instead of calling it `popoverTargetElement` and how `style` accepts an object and it's not `styleObject={{...}}`. There are a number of reason to revisit this. - It's just way more convenient to have this built-in and it makes conceptual sense. We typically support declarative APIs and polyfill them when necessary. - RSC supports Blobs and by having it built-in you don't need a Client Component wrapper to render it where as doing it with effects would require more complex wrappers. By picking Blobs over base64, client-navigations can use the more optimized binary encoding in the RSC protocol. - The timing aspect of coordinating it with Suspensey images and image decoding is a bit tricky to get right because if you set it in an effect it's too late because you've already rendered it. - SSR gets complicated when done in user space because you have to handle both branches. Likely with `useSyncExternalStore`. - By having it built-in we could optimize the payloads shared between RSC payloads embedded in the HTML and data URLs. This does not support objects for `<source src>` nor `<img srcset>`. Those don't really have equivalents in the DOM neither. They're mainly for picking an option when you don't know programmatically. However, for this use case you're really better off picking a variant before generating the blobs. We may support Response objects in the future too as per https://github.com/whatwg/fetch/issues/49

Sebastian Markbåge committed Apr 8, 2025 at 12:11 UTC ea05b750a5374458fc8c74ea0918059c818d1167
25 files changed +805 -38
fixtures/flight/package.json
+1
@@ -23,6 +23,7 @@
23 "browserslist": "^4.18.1",
24 "busboy": "^1.6.0",
25 "camelcase": "^6.2.1",
26 + "canvas": "^3.1.0",
27 "case-sensitive-paths-webpack-plugin": "^2.4.0",
28 "compression": "^1.7.4",
29 "concurrently": "^7.3.0",
fixtures/flight/src/App.js
+7 -1
@@ -15,6 +15,8 @@ import {Client} from './Client.js';
15
16 import {Note} from './cjs/Note.js';
17
18 +import {GenerateImage} from './GenerateImage.js';
19 +
20 import {like, greet, increment} from './actions.js';
21
22 import {getServerState} from './ServerState.js';
@@ -41,6 +43,7 @@ export default async function App({prerender}) {
43 const todos = await res.json();
44
45 const dedupedChild = <ServerComponent />;
46 + const message = getServerState();
47 return (
48 <html lang="en">
49 <head>
@@ -55,7 +58,7 @@ export default async function App({prerender}) {
58 ) : (
59 <meta content="when not prerendering we render this meta tag. When prerendering you will expect to see this tag and the one with data-testid=prerendered because we SSR one and hydrate the other" />
60 )}
58 - <h1>{getServerState()}</h1>
61 + <h1>{message}</h1>
62 <React.Suspense fallback={null}>
63 <div data-testid="promise-as-a-child-test">
64 Promise as a child hydrates without errors: {promisedText}
@@ -79,6 +82,9 @@ export default async function App({prerender}) {
82 <div>
83 loaded statically: <Dynamic />
84 </div>
85 + <div>
86 + <GenerateImage message={message} />
87 + </div>
88 <Client />
89 <Note />
90 <Foo>{dedupedChild}</Foo>
fixtures/flight/src/GenerateImage.js new
+19
@@ -0,0 +1,19 @@
1 +import * as React from 'react';
2 +
3 +import {createCanvas} from 'canvas';
4 +
5 +export async function GenerateImage({message}) {
6 + // Generate an image using an image library
7 + const canvas = createCanvas(200, 70);
8 + const ctx = canvas.getContext('2d');
9 + ctx.font = '20px Impact';
10 + ctx.rotate(-0.1);
11 + ctx.fillText(message, 10, 50);
12 +
13 + // Rasterize into a Blob with a mime type
14 + const type = 'image/png';
15 + const blob = new Blob([canvas.toBuffer(type)], {type});
16 +
17 + // Just pass it to React
18 + return <img src={blob} />;
19 +}
fixtures/flight/yarn.lock
+220 -30
@@ -3747,6 +3747,11 @@ balanced-match@^1.0.0:
3747 version "1.0.0"
3748 resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
3749
3750 +base64-js@^1.3.1:
3751 + version "1.5.1"
3752 + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
3753 + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
3754 +
3755 big.js@^5.2.2:
3756 version "5.2.2"
3757 resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
@@ -3757,6 +3762,15 @@ binary-extensions@^2.0.0:
3762 resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.1.0.tgz#30fa40c9e7fe07dbc895678cd287024dea241dd9"
3763 integrity sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==
3764
3765 +bl@^4.0.3:
3766 + version "4.1.0"
3767 + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
3768 + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==
3769 + dependencies:
3770 + buffer "^5.5.0"
3771 + inherits "^2.0.4"
3772 + readable-stream "^3.4.0"
3773 +
3774 body-parser@^1.20.1:
3775 version "1.20.1"
3776 resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668"
@@ -3853,6 +3867,14 @@ buffer-from@^1.0.0:
3867 version "1.1.1"
3868 resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
3869
3870 +buffer@^5.5.0:
3871 + version "5.7.1"
3872 + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
3873 + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
3874 + dependencies:
3875 + base64-js "^1.3.1"
3876 + ieee754 "^1.1.13"
3877 +
3878 busboy@^1.6.0:
3879 version "1.6.0"
3880 resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893"
@@ -3945,6 +3967,14 @@ caniuse-lite@^1.0.30001646:
3967 resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz#52de59529e8b02b1aedcaaf5c05d9e23c0c28138"
3968 integrity sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==
3969
3970 +canvas@^3.1.0:
3971 + version "3.1.0"
3972 + resolved "https://registry.yarnpkg.com/canvas/-/canvas-3.1.0.tgz#6cdf094b859fef8e39b0e2c386728a376f1727b2"
3973 + integrity sha512-tTj3CqqukVJ9NgSahykNwtGda7V33VLObwrHfzT0vqJXu7J4d4C/7kQQW3fOEGDfZZoILPut5H00gOjyttPGyg==
3974 + dependencies:
3975 + node-addon-api "^7.0.0"
3976 + prebuild-install "^7.1.1"
3977 +
3978 case-sensitive-paths-webpack-plugin@^2.4.0:
3979 version "2.4.0"
3980 resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz#db64066c6422eed2e08cc14b986ca43796dbc6d4"
@@ -4007,6 +4037,11 @@ chokidar@^3.4.2, chokidar@^3.5.2, chokidar@^3.5.3:
4037 optionalDependencies:
4038 fsevents "~2.3.2"
4039
4040 +chownr@^1.1.1:
4041 + version "1.1.4"
4042 + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
4043 + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
4044 +
4045 chrome-trace-event@^1.0.2:
4046 version "1.0.2"
4047 resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4"
@@ -4501,11 +4536,23 @@ decimal.js@^10.2.1:
4536 resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.0.tgz#97a7448873b01e92e5ff9117d89a7bca8e63e0fe"
4537 integrity sha512-Nv6ENEzyPQ6AItkGwLE2PGKinZZ9g59vSh2BeH6NqPu0OTKZ5ruJsVqh/orbAnqXc9pBbgXAIrc2EyaCj8NpGg==
4538
4539 +decompress-response@^6.0.0:
4540 + version "6.0.0"
4541 + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc"
4542 + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==
4543 + dependencies:
4544 + mimic-response "^3.1.0"
4545 +
4546 dedent@^0.7.0:
4547 version "0.7.0"
4548 resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
4549 integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=
4550
4551 +deep-extend@^0.6.0:
4552 + version "0.6.0"
4553 + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
4554 + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
4555 +
4556 deep-is@~0.1.3:
4557 version "0.1.3"
4558 resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
@@ -4559,6 +4606,11 @@ destroy@1.2.0:
4606 resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
4607 integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
4608
4609 +detect-libc@^2.0.0:
4610 + version "2.0.3"
4611 + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700"
4612 + integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==
4613 +
4614 detect-newline@^3.0.0:
4615 version "3.1.0"
4616 resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
@@ -4745,6 +4797,13 @@ emojis-list@^3.0.0:
4797 resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78"
4798 integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==
4799
4800 +end-of-stream@^1.1.0, end-of-stream@^1.4.1:
4801 + version "1.4.4"
4802 + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
4803 + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
4804 + dependencies:
4805 + once "^1.4.0"
4806 +
4807 enhanced-resolve@^5.17.0:
4808 version "5.17.1"
4809 resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15"
@@ -4989,6 +5048,11 @@ exit@^0.1.2:
5048 version "0.1.2"
5049 resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
5050
5051 +expand-template@^2.0.3:
5052 + version "2.0.3"
5053 + resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
5054 + integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==
5055 +
5056 expect@^27.5.1:
5057 version "27.5.1"
5058 resolved "https://registry.yarnpkg.com/expect/-/expect-27.5.1.tgz#83ce59f1e5bdf5f9d2b94b61d2050db48f3fef74"
@@ -5159,6 +5223,11 @@ fraction.js@^4.2.0:
5223 resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950"
5224 integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==
5225
5226 +fs-constants@^1.0.0:
5227 + version "1.0.0"
5228 + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
5229 + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
5230 +
5231 fs-extra@^10.0.0:
5232 version "10.1.0"
5233 resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf"
@@ -5265,6 +5334,11 @@ get-symbol-description@^1.0.2:
5334 es-errors "^1.3.0"
5335 get-intrinsic "^1.2.4"
5336
5337 +github-from-package@0.0.0:
5338 + version "0.0.0"
5339 + resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce"
5340 + integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==
5341 +
5342 glob-parent@^5.1.2, glob-parent@~5.1.2:
5343 version "5.1.2"
5344 resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
@@ -5572,6 +5646,11 @@ identity-obj-proxy@^3.0.0:
5646 dependencies:
5647 harmony-reflect "^1.4.6"
5648
5649 +ieee754@^1.1.13:
5650 + version "1.2.1"
5651 + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
5652 + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
5653 +
5654 ignore-by-default@^1.0.1:
5655 version "1.0.1"
5656 resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09"
@@ -5627,7 +5706,7 @@ inherits@2:
5706 version "2.0.3"
5707 resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
5708
5630 -inherits@2.0.4:
5709 +inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4:
5710 version "2.0.4"
5711 resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
5712 integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -5636,6 +5715,11 @@ ini@^1.3.5:
5715 version "1.3.5"
5716 resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
5717
5718 +ini@~1.3.0:
5719 + version "1.3.8"
5720 + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
5721 + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
5722 +
5723 internal-slot@^1.0.7:
5724 version "1.0.7"
5725 resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802"
@@ -6699,6 +6783,11 @@ mimic-fn@^2.1.0:
6783 version "2.1.0"
6784 resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
6785
6786 +mimic-response@^3.1.0:
6787 + version "3.1.0"
6788 + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9"
6789 + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==
6790 +
6791 min-indent@^1.0.0:
6792 version "1.0.1"
6793 resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
@@ -6735,11 +6824,21 @@ minimist@0.0.8:
6824 version "0.0.8"
6825 resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
6826
6827 +minimist@^1.2.0, minimist@^1.2.3:
6828 + version "1.2.8"
6829 + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
6830 + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
6831 +
6832 "minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2:
6833 version "7.1.2"
6834 resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707"
6835 integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
6836
6837 +mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3:
6838 + version "0.5.3"
6839 + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
6840 + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
6841 +
6842 mkdirp@~0.5.1:
6843 version "0.5.1"
6844 resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
@@ -6778,6 +6877,11 @@ nanoid@^3.3.7:
6877 resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
6878 integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
6879
6880 +napi-build-utils@^2.0.0:
6881 + version "2.0.0"
6882 + resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-2.0.0.tgz#13c22c0187fcfccce1461844136372a47ddc027e"
6883 + integrity sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==
6884 +
6885 natural-compare@^1.4.0:
6886 version "1.4.0"
6887 resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
@@ -6800,6 +6904,18 @@ no-case@^3.0.4:
6904 lower-case "^2.0.2"
6905 tslib "^2.0.3"
6906
6907 +node-abi@^3.3.0:
6908 + version "3.74.0"
6909 + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.74.0.tgz#5bfb4424264eaeb91432d2adb9da23c63a301ed0"
6910 + integrity sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==
6911 + dependencies:
6912 + semver "^7.3.5"
6913 +
6914 +node-addon-api@^7.0.0:
6915 + version "7.1.1"
6916 + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558"
6917 + integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==
6918 +
6919 node-int64@^0.4.0:
6920 version "0.4.0"
6921 resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
@@ -6959,7 +7075,7 @@ on-headers@~1.0.2:
7075 resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f"
7076 integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==
7077
6962 -once@^1.3.0:
7078 +once@^1.3.0, once@^1.3.1, once@^1.4.0:
7079 version "1.4.0"
7080 resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
7081 dependencies:
@@ -7781,6 +7897,24 @@ postcss@^8.4.23:
7897 picocolors "^1.0.1"
7898 source-map-js "^1.2.0"
7899
7900 +prebuild-install@^7.1.1:
7901 + version "7.1.3"
7902 + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.3.tgz#d630abad2b147443f20a212917beae68b8092eec"
7903 + integrity sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==
7904 + dependencies:
7905 + detect-libc "^2.0.0"
7906 + expand-template "^2.0.3"
7907 + github-from-package "0.0.0"
7908 + minimist "^1.2.3"
7909 + mkdirp-classic "^0.5.3"
7910 + napi-build-utils "^2.0.0"
7911 + node-abi "^3.3.0"
7912 + pump "^3.0.0"
7913 + rc "^1.2.7"
7914 + simple-get "^4.0.0"
7915 + tar-fs "^2.0.0"
7916 + tunnel-agent "^0.6.0"
7917 +
7918 prelude-ls@~1.1.2:
7919 version "1.1.2"
7920 resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
@@ -7837,6 +7971,14 @@ pstree.remy@^1.1.8:
7971 resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a"
7972 integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==
7973
7974 +pump@^3.0.0:
7975 + version "3.0.2"
7976 + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.2.tgz#836f3edd6bc2ee599256c924ffe0d88573ddcbf8"
7977 + integrity sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==
7978 + dependencies:
7979 + end-of-stream "^1.1.0"
7980 + once "^1.3.1"
7981 +
7982 punycode@^2.1.0:
7983 version "2.3.0"
7984 resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f"
@@ -7888,6 +8030,16 @@ raw-body@2.5.1:
8030 iconv-lite "0.4.24"
8031 unpipe "1.0.0"
8032
8033 +rc@^1.2.7:
8034 + version "1.2.8"
8035 + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
8036 + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
8037 + dependencies:
8038 + deep-extend "^0.6.0"
8039 + ini "~1.3.0"
8040 + minimist "^1.2.0"
8041 + strip-json-comments "~2.0.1"
8042 +
8043 react-dev-utils@^12.0.1:
8044 version "12.0.1"
8045 resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-12.0.1.tgz#ba92edb4a1f379bd46ccd6bcd4e7bc398df33e73"
@@ -7966,6 +8118,15 @@ read-cache@^1.0.0:
8118 dependencies:
8119 pify "^2.3.0"
8120
8121 +readable-stream@^3.1.1, readable-stream@^3.4.0:
8122 + version "3.6.2"
8123 + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967"
8124 + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
8125 + dependencies:
8126 + inherits "^2.0.3"
8127 + string_decoder "^1.1.1"
8128 + util-deprecate "^1.0.1"
8129 +
8130 readdirp@~3.6.0:
8131 version "3.6.0"
8132 resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
@@ -8191,7 +8352,7 @@ safe-buffer@5.1.2, safe-buffer@~5.1.1:
8352 resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
8353 integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
8354
8194 -safe-buffer@^5.1.0:
8355 +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0:
8356 version "5.2.1"
8357 resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
8358 integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
@@ -8368,6 +8529,20 @@ signal-exit@^4.0.1:
8529 resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04"
8530 integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==
8531
8532 +simple-concat@^1.0.0:
8533 + version "1.0.1"
8534 + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f"
8535 + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==
8536 +
8537 +simple-get@^4.0.0:
8538 + version "4.0.1"
8539 + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543"
8540 + integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==
8541 + dependencies:
8542 + decompress-response "^6.0.0"
8543 + once "^1.3.1"
8544 + simple-concat "^1.0.0"
8545 +
8546 simple-update-notifier@^1.0.7:
8547 version "1.1.0"
8548 resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz#67694c121de354af592b347cdba798463ed49c82"
@@ -8485,16 +8660,7 @@ string-length@^5.0.1:
8660 char-regex "^2.0.0"
8661 strip-ansi "^7.0.1"
8662
8488 -"string-width-cjs@npm:string-width@^4.2.0":
8489 - version "4.2.3"
8490 - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
8491 - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
8492 - dependencies:
8493 - emoji-regex "^8.0.0"
8494 - is-fullwidth-code-point "^3.0.0"
8495 - strip-ansi "^6.0.1"
8496 -
8497 -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
8663 +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
8664 version "4.2.3"
8665 resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
8666 integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -8558,14 +8724,14 @@ string.prototype.trimstart@^1.0.3, string.prototype.trimstart@^1.0.8:
8724 define-properties "^1.2.1"
8725 es-object-atoms "^1.0.0"
8726
8561 -"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
8562 - version "6.0.1"
8563 - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
8564 - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
8727 +string_decoder@^1.1.1:
8728 + version "1.3.0"
8729 + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
8730 + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
8731 dependencies:
8566 - ansi-regex "^5.0.1"
8732 + safe-buffer "~5.2.0"
8733
8568 -strip-ansi@^6.0.0, strip-ansi@^6.0.1:
8734 +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
8735 version "6.0.1"
8736 resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
8737 integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -8601,6 +8767,11 @@ strip-json-comments@^3.1.1:
8767 resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
8768 integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
8769
8770 +strip-json-comments@~2.0.1:
8771 + version "2.0.1"
8772 + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
8773 + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==
8774 +
8775 style-loader@^3.3.1:
8776 version "3.3.4"
8777 resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.4.tgz#f30f786c36db03a45cbd55b6a70d930c479090e7"
@@ -8741,6 +8912,27 @@ tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0:
8912 resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"
8913 integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==
8914
8915 +tar-fs@^2.0.0:
8916 + version "2.1.2"
8917 + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.2.tgz#425f154f3404cb16cb8ff6e671d45ab2ed9596c5"
8918 + integrity sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==
8919 + dependencies:
8920 + chownr "^1.1.1"
8921 + mkdirp-classic "^0.5.2"
8922 + pump "^3.0.0"
8923 + tar-stream "^2.1.4"
8924 +
8925 +tar-stream@^2.1.4:
8926 + version "2.2.0"
8927 + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287"
8928 + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==
8929 + dependencies:
8930 + bl "^4.0.3"
8931 + end-of-stream "^1.4.1"
8932 + fs-constants "^1.0.0"
8933 + inherits "^2.0.3"
8934 + readable-stream "^3.1.1"
8935 +
8936 terminal-link@^2.0.0:
8937 version "2.1.1"
8938 resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994"
@@ -8867,6 +9059,13 @@ tslib@^2.0.3, tslib@^2.1.0:
9059 resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0"
9060 integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==
9061
9062 +tunnel-agent@^0.6.0:
9063 + version "0.6.0"
9064 + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
9065 + integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==
9066 + dependencies:
9067 + safe-buffer "^5.0.1"
9068 +
9069 type-check@~0.3.2:
9070 version "0.3.2"
9071 resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"
@@ -9041,7 +9240,7 @@ url-parse@^1.5.3:
9240 querystringify "^2.1.1"
9241 requires-port "^1.0.0"
9242
9044 -util-deprecate@^1.0.2:
9243 +util-deprecate@^1.0.1, util-deprecate@^1.0.2:
9244 version "1.0.2"
9245 resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
9246 integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
@@ -9247,16 +9446,7 @@ wordwrap@~1.0.0:
9446 resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
9447 integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==
9448
9250 -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
9251 - version "7.0.0"
9252 - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
9253 - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
9254 - dependencies:
9255 - ansi-styles "^4.0.0"
9256 - string-width "^4.1.0"
9257 - strip-ansi "^6.0.0"
9258 -
9259 -wrap-ansi@^7.0.0:
9449 +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
9450 version "7.0.0"
9451 resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
9452 integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
packages/react-dom-bindings/src/client/ReactDOMComponent.js
+108 -2
@@ -47,6 +47,7 @@ import {
47 updateTextarea,
48 restoreControlledTextareaState,
49 } from './ReactDOMTextarea';
50 +import {setSrcObject} from './ReactDOMSrcObject';
51 import {validateTextNesting} from './validateDOMNesting';
52 import {track} from './inputValueTracking';
53 import setTextContent from './setTextContent';
@@ -67,6 +68,7 @@ import {trackHostMutation} from 'react-reconciler/src/ReactFiberMutationTracking
68
69 import {
70 enableScrollEndPolyfill,
71 + enableSrcObject,
72 enableTrustedTypesIntegration,
73 } from 'shared/ReactFeatureFlags';
74 import {
@@ -402,7 +404,40 @@ function setProp(
404 break;
405 }
406 // fallthrough
405 - case 'src':
407 + case 'src': {
408 + if (enableSrcObject && typeof value === 'object' && value !== null) {
409 + // Some tags support object sources like Blob, File, MediaSource and MediaStream.
410 + if (tag === 'img' || tag === 'video' || tag === 'audio') {
411 + try {
412 + setSrcObject(domElement, tag, value);
413 + break;
414 + } catch (x) {
415 + // If URL.createObjectURL() errors, it was probably some other object type
416 + // that should be toString:ed instead, so we just fall-through to the normal
417 + // path.
418 + }
419 + } else {
420 + if (__DEV__) {
421 + try {
422 + // This should always error.
423 + URL.revokeObjectURL(URL.createObjectURL((value: any)));
424 + if (tag === 'source') {
425 + console.error(
426 + 'Passing Blob, MediaSource or MediaStream to <source src> is not supported. ' +
427 + 'Pass it directly to <img src>, <video src> or <audio src> instead.',
428 + );
429 + } else {
430 + console.error(
431 + 'Passing Blob, MediaSource or MediaStream to <%s src> is not supported.',
432 + tag,
433 + );
434 + }
435 + } catch (x) {}
436 + }
437 + }
438 + }
439 + // Fallthrough
440 + }
441 case 'href': {
442 if (
443 value === '' &&
@@ -2301,6 +2336,39 @@ function hydrateSanitizedAttribute(
2336 warnForPropDifference(propKey, serverValue, value, serverDifferences);
2337 }
2338
2339 +function hydrateSrcObjectAttribute(
2340 + domElement: Element,
2341 + value: Blob,
2342 + extraAttributes: Set<string>,
2343 + serverDifferences: {[propName: string]: mixed},
2344 +): void {
2345 + const attributeName = 'src';
2346 + extraAttributes.delete(attributeName);
2347 + const serverValue = domElement.getAttribute(attributeName);
2348 + if (serverValue != null && value != null) {
2349 + const size = value.size;
2350 + const type = value.type;
2351 + if (typeof size === 'number' && typeof type === 'string') {
2352 + if (serverValue.indexOf('data:' + type + ';base64,') === 0) {
2353 + // For Blobs we don't bother reading the actual data but just diff by checking if
2354 + // the byte length size of the Blob maches the length of the data url.
2355 + const prefixLength = 5 + type.length + 8;
2356 + let byteLength = ((serverValue.length - prefixLength) / 4) * 3;
2357 + if (serverValue[serverValue.length - 1] === '=') {
2358 + byteLength--;
2359 + }
2360 + if (serverValue[serverValue.length - 2] === '=') {
2361 + byteLength--;
2362 + }
2363 + if (byteLength === size) {
2364 + return;
2365 + }
2366 + }
2367 + }
2368 + }
2369 + warnForPropDifference('src', serverValue, value, serverDifferences);
2370 +}
2371 +
2372 function diffHydratedCustomComponent(
2373 domElement: Element,
2374 tag: string,
@@ -2547,7 +2615,45 @@ function diffHydratedGenericElement(
2615 continue;
2616 }
2617 // fallthrough
2550 - case 'src':
2618 + case 'src': {
2619 + if (enableSrcObject && typeof value === 'object' && value !== null) {
2620 + // Some tags support object sources like Blob, File, MediaSource and MediaStream.
2621 + if (tag === 'img' || tag === 'video' || tag === 'audio') {
2622 + try {
2623 + // Test if this is a compatible object
2624 + URL.revokeObjectURL(URL.createObjectURL((value: any)));
2625 + hydrateSrcObjectAttribute(
2626 + domElement,
2627 + value,
2628 + extraAttributes,
2629 + serverDifferences,
2630 + );
2631 + continue;
2632 + } catch (x) {
2633 + // If not, just fall through to the normal toString flow.
2634 + }
2635 + } else {
2636 + if (__DEV__) {
2637 + try {
2638 + // This should always error.
2639 + URL.revokeObjectURL(URL.createObjectURL((value: any)));
2640 + if (tag === 'source') {
2641 + console.error(
2642 + 'Passing Blob, MediaSource or MediaStream to <source src> is not supported. ' +
2643 + 'Pass it directly to <img src>, <video src> or <audio src> instead.',
2644 + );
2645 + } else {
2646 + console.error(
2647 + 'Passing Blob, MediaSource or MediaStream to <%s src> is not supported.',
2648 + tag,
2649 + );
2650 + }
2651 + } catch (x) {}
2652 + }
2653 + }
2654 + }
2655 + // Fallthrough
2656 + }
2657 case 'href':
2658 if (
2659 value === '' &&
packages/react-dom-bindings/src/client/ReactDOMSrcObject.js new
+25
@@ -0,0 +1,25 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +export function setSrcObject(domElement: Element, tag: string, value: any) {
11 + // We optimistically create the URL regardless of object type. This lets us
12 + // support cross-realms and any type that the browser supports like new types.
13 + const url = URL.createObjectURL((value: any));
14 + const loadEvent = tag === 'img' ? 'load' : 'loadstart';
15 + const cleanUp = () => {
16 + // Once the object has started loading, then it's already collected by the
17 + // browser and it won't refer to it by the URL anymore so we can now revoke it.
18 + URL.revokeObjectURL(url);
19 + domElement.removeEventListener(loadEvent, cleanUp);
20 + domElement.removeEventListener('error', cleanUp);
21 + };
22 + domElement.addEventListener(loadEvent, cleanUp);
23 + domElement.addEventListener('error', cleanUp);
24 + domElement.setAttribute('src', url);
25 +}
packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js
+21 -2
@@ -29,6 +29,8 @@ import type {TransitionTypes} from 'react/src/ReactTransitionType';
29
30 import {NotPending} from '../shared/ReactDOMFormActions';
31
32 +import {setSrcObject} from './ReactDOMSrcObject';
33 +
34 import {getCurrentRootHostContainer} from 'react-reconciler/src/ReactFiberHostContext';
35 import {runWithFiberInDEV} from 'react-reconciler/src/ReactCurrentFiber';
36
@@ -104,6 +106,7 @@ import {
106 enableMoveBefore,
107 disableCommentsAsDOMContainers,
108 enableSuspenseyImages,
109 + enableSrcObject,
110 } from 'shared/ReactFeatureFlags';
111 import {
112 HostComponent,
@@ -151,7 +154,7 @@ export type Props = {
154 is?: string,
155 size?: number,
156 multiple?: boolean,
154 - src?: string,
157 + src?: string | Blob | MediaSource | MediaStream, // TODO: Response
158 srcSet?: string,
159 loading?: 'eager' | 'lazy',
160 onLoad?: (event: any) => void,
@@ -780,7 +783,23 @@ export function commitMount(
783 // is already a noop regardless of which properties are assigned. We should revisit if browsers update
784 // this heuristic in the future.
785 if (newProps.src) {
783 - ((domElement: any): HTMLImageElement).src = (newProps: any).src;
786 + const src = (newProps: any).src;
787 + if (enableSrcObject && typeof src === 'object') {
788 + // For object src, we can't just set the src again to the same blob URL because it might have
789 + // already revoked if it loaded before this. However, we can create a new blob URL and set that.
790 + // This is relatively cheap since the blob is already in memory but this might cause some
791 + // duplicated work.
792 + // TODO: We could maybe detect if load hasn't fired yet and if so reuse the URL.
793 + try {
794 + setSrcObject(domElement, type, src);
795 + return;
796 + } catch (x) {
797 + // If URL.createObjectURL() errors, it was probably some other object type
798 + // that should be toString:ed instead, so we just fall-through to the normal
799 + // path.
800 + }
801 + }
802 + ((domElement: any): HTMLImageElement).src = src;
803 } else if (newProps.srcSet) {
804 ((domElement: any): HTMLImageElement).srcset = (newProps: any).srcSet;
805 }
packages/react-dom-bindings/src/server/ReactDOMLegacyServerStreamConfig.js
+11
@@ -80,3 +80,14 @@ export function closeWithError(destination: Destination, error: mixed): void {
80 }
81
82 export {createFastHashJS as createFastHash} from 'react-server/src/createFastHashJS';
83 +
84 +export function readAsDataURL(blob: Blob): Promise<string> {
85 + return blob.arrayBuffer().then(arrayBuffer => {
86 + const encoded =
87 + typeof Buffer === 'function' && typeof Buffer.from === 'function'
88 + ? Buffer.from(arrayBuffer).toString('base64')
89 + : btoa(String.fromCharCode.apply(String, new Uint8Array(arrayBuffer)));
90 + const mimeType = blob.type || 'application/octet-stream';
91 + return 'data:' + mimeType + ';base64,' + encoded;
92 + });
93 +}
packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js
+60 -3
@@ -7,7 +7,11 @@
7 * @flow
8 */
9
10 -import type {ReactNodeList, ReactCustomFormAction} from 'shared/ReactTypes';
10 +import type {
11 + ReactNodeList,
12 + ReactCustomFormAction,
13 + Thenable,
14 +} from 'shared/ReactTypes';
15 import type {
16 CrossOriginEnum,
17 PreloadImplOptions,
@@ -27,7 +31,10 @@ import {
31
32 import {Children} from 'react';
33
30 -import {enableFizzExternalRuntime} from 'shared/ReactFeatureFlags';
34 +import {
35 + enableFizzExternalRuntime,
36 + enableSrcObject,
37 +} from 'shared/ReactFeatureFlags';
38
39 import type {
40 Destination,
@@ -42,6 +49,7 @@ import {
49 writeChunkAndReturn,
50 stringToChunk,
51 stringToPrecomputedChunk,
52 + readAsDataURL,
53 } from 'react-server/src/ReactServerStreamConfig';
54 import {
55 resolveRequest,
@@ -1214,6 +1222,47 @@ function pushFormActionAttribute(
1222 return formData;
1223 }
1224
1225 +let blobCache: null | WeakMap<Blob, Thenable<string>> = null;
1226 +
1227 +function pushSrcObjectAttribute(
1228 + target: Array<Chunk | PrecomputedChunk>,
1229 + blob: Blob,
1230 +): void {
1231 + // Throwing a Promise style suspense read of the Blob content.
1232 + if (blobCache === null) {
1233 + blobCache = new WeakMap();
1234 + }
1235 + const suspenseCache: WeakMap<Blob, Thenable<string>> = blobCache;
1236 + let thenable = suspenseCache.get(blob);
1237 + if (thenable === undefined) {
1238 + thenable = ((readAsDataURL(blob): any): Thenable<string>);
1239 + thenable.then(
1240 + result => {
1241 + (thenable: any).status = 'fulfilled';
1242 + (thenable: any).value = result;
1243 + },
1244 + error => {
1245 + (thenable: any).status = 'rejected';
1246 + (thenable: any).reason = error;
1247 + },
1248 + );
1249 + suspenseCache.set(blob, thenable);
1250 + }
1251 + if (thenable.status === 'rejected') {
1252 + throw thenable.reason;
1253 + } else if (thenable.status !== 'fulfilled') {
1254 + throw thenable;
1255 + }
1256 + const url = thenable.value;
1257 + target.push(
1258 + attributeSeparator,
1259 + stringToChunk('src'),
1260 + attributeAssign,
1261 + stringToChunk(escapeTextForBrowser(url)),
1262 + attributeEnd,
1263 + );
1264 +}
1265 +
1266 function pushAttribute(
1267 target: Array<Chunk | PrecomputedChunk>,
1268 name: string,
@@ -1243,7 +1292,15 @@ function pushAttribute(
1292 pushStyleAttribute(target, value);
1293 return;
1294 }
1246 - case 'src':
1295 + case 'src': {
1296 + if (enableSrcObject && typeof value === 'object' && value !== null) {
1297 + if (typeof Blob === 'function' && value instanceof Blob) {
1298 + pushSrcObjectAttribute(target, value);
1299 + return;
1300 + }
1301 + }
1302 + // Fallthrough to general urls
1303 + }
1304 case 'href': {
1305 if (value === '') {
1306 if (__DEV__) {
packages/react-dom/src/__tests__/ReactDOMImageLoad-test.internal.js
+62
@@ -598,4 +598,66 @@ describe('ReactDOMImageLoad', () => {
598 expect(renderSrcProperty).toBe(commitSrcProperty);
599 expect(renderSrcAttr).toBe(commitSrcAttr);
600 });
601 +
602 + it('captures the load event for Blob sources if it happens before commit phase', async function () {
603 + const container = document.createElement('div');
604 + const root = ReactDOMClient.createRoot(container);
605 +
606 + const blob = new Blob();
607 +
608 + React.startTransition(() =>
609 + root.render(
610 + <PhaseMarkers>
611 + <Img src={blob} onLoad={onLoadSpy} />
612 + <Yield />
613 + <Text text={'a'} />
614 + </PhaseMarkers>,
615 + ),
616 + );
617 +
618 + await waitFor(['render start', 'Img [object Blob]', 'Yield']);
619 + const img = last(images);
620 + loadImage(img);
621 + assertLog([
622 + 'actualLoadSpy [[object Blob]]',
623 + // no onLoadSpy since we have not completed render
624 + ]);
625 + await waitForAll(['a', 'load triggered', 'last layout', 'last passive']);
626 + expect(img.__needsDispatch).toBe(true);
627 + loadImage(img);
628 + assertLog([
629 + 'actualLoadSpy [[object Blob]]', // the browser reloading of the image causes this to yield again
630 + 'onLoadSpy [[object Blob]]',
631 + ]);
632 + expect(onLoadSpy).toHaveBeenCalled();
633 + });
634 +
635 + it('captures the load event for Blob sources if it happens after commit phase and replays it', async function () {
636 + const container = document.createElement('div');
637 + const root = ReactDOMClient.createRoot(container);
638 +
639 + const blob = new Blob();
640 +
641 + React.startTransition(() =>
642 + root.render(
643 + <PhaseMarkers>
644 + <Img src={blob} onLoad={onLoadSpy} />
645 + </PhaseMarkers>,
646 + ),
647 + );
648 +
649 + await waitFor([
650 + 'render start',
651 + 'Img [object Blob]',
652 + 'load triggered',
653 + 'last layout',
654 + ]);
655 + Scheduler.unstable_requestPaint();
656 + const img = last(images);
657 + loadImage(img);
658 + assertLog(['actualLoadSpy [[object Blob]]', 'onLoadSpy [[object Blob]]']);
659 + await waitForAll(['last passive']);
660 + expect(img.__needsDispatch).toBe(false);
661 + expect(onLoadSpy).toHaveBeenCalledTimes(1);
662 + });
663 });
packages/react-dom/src/__tests__/ReactDOMSrcObject-test.js new
+217
@@ -0,0 +1,217 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @emails react-core
8 + * @jest-environment ./scripts/jest/ReactDOMServerIntegrationEnvironment
9 + */
10 +
11 +'use strict';
12 +
13 +// Polyfills for test environment
14 +global.ReadableStream =
15 + require('web-streams-polyfill/ponyfill/es6').ReadableStream;
16 +global.TextEncoder = require('util').TextEncoder;
17 +
18 +describe('ReactDOMSrcObject', () => {
19 + let React;
20 + let ReactDOMClient;
21 + let ReactDOMFizzServer;
22 + let act;
23 + let container;
24 + let assertConsoleErrorDev;
25 +
26 + beforeEach(() => {
27 + jest.resetModules();
28 +
29 + React = require('react');
30 + ReactDOMClient = require('react-dom/client');
31 + ReactDOMFizzServer = require('react-dom/server.edge');
32 + act = require('internal-test-utils').act;
33 +
34 + assertConsoleErrorDev =
35 + require('internal-test-utils').assertConsoleErrorDev;
36 +
37 + container = document.createElement('div');
38 + document.body.appendChild(container);
39 + });
40 +
41 + afterEach(() => {
42 + document.body.removeChild(container);
43 + jest.restoreAllMocks();
44 + });
45 +
46 + // @gate enableSrcObject
47 + it('can render a Blob as an img src', async () => {
48 + const root = ReactDOMClient.createRoot(container);
49 + const ref = React.createRef();
50 +
51 + const blob = new Blob();
52 + await act(() => {
53 + root.render(<img src={blob} ref={ref} />);
54 + });
55 +
56 + expect(ref.current.src).toMatch(/^blob:/);
57 + });
58 +
59 + // @gate enableSrcObject
60 + it('can render a Blob as a picture img src', async () => {
61 + const root = ReactDOMClient.createRoot(container);
62 + const ref = React.createRef();
63 +
64 + const blob = new Blob();
65 + await act(() => {
66 + root.render(
67 + <picture>
68 + <img src={blob} ref={ref} />
69 + </picture>,
70 + );
71 + });
72 +
73 + expect(ref.current.src).toMatch(/^blob:/);
74 + });
75 +
76 + // @gate enableSrcObject
77 + it('can render a Blob as a video and audio src', async () => {
78 + const root = ReactDOMClient.createRoot(container);
79 + const videoRef = React.createRef();
80 + const audioRef = React.createRef();
81 +
82 + const blob = new Blob();
83 + await act(() => {
84 + root.render(
85 + <>
86 + <video src={blob} ref={videoRef} />
87 + <audio src={blob} ref={audioRef} />
88 + </>,
89 + );
90 + });
91 +
92 + expect(videoRef.current.src).toMatch(/^blob:/);
93 + expect(audioRef.current.src).toMatch(/^blob:/);
94 + });
95 +
96 + // @gate enableSrcObject || !__DEV__
97 + it('warn when rendering a Blob as a source src of a video, audio or picture element', async () => {
98 + const root = ReactDOMClient.createRoot(container);
99 + const videoRef = React.createRef();
100 + const audioRef = React.createRef();
101 + const pictureRef = React.createRef();
102 +
103 + const blob = new Blob();
104 + await act(() => {
105 + root.render(
106 + <>
107 + <video ref={videoRef}>
108 + <source src={blob} />
109 + </video>
110 + <audio ref={audioRef}>
111 + <source src={blob} />
112 + </audio>
113 + <picture ref={pictureRef}>
114 + <source src={blob} />
115 + <img />
116 + </picture>
117 + </>,
118 + );
119 + });
120 +
121 + assertConsoleErrorDev([
122 + 'Passing Blob, MediaSource or MediaStream to <source src> is not supported. ' +
123 + 'Pass it directly to <img src>, <video src> or <audio src> instead.',
124 + 'Passing Blob, MediaSource or MediaStream to <source src> is not supported. ' +
125 + 'Pass it directly to <img src>, <video src> or <audio src> instead.',
126 + 'Passing Blob, MediaSource or MediaStream to <source src> is not supported. ' +
127 + 'Pass it directly to <img src>, <video src> or <audio src> instead.',
128 + ]);
129 + expect(videoRef.current.firstChild.src).not.toMatch(/^blob:/);
130 + expect(videoRef.current.firstChild.src).toContain('[object%20Blob]'); // toString:ed
131 + expect(audioRef.current.firstChild.src).not.toMatch(/^blob:/);
132 + expect(audioRef.current.firstChild.src).toContain('[object%20Blob]'); // toString:ed
133 + expect(pictureRef.current.firstChild.src).not.toMatch(/^blob:/);
134 + expect(pictureRef.current.firstChild.src).toContain('[object%20Blob]'); // toString:ed
135 + });
136 +
137 + async function readContent(stream) {
138 + const reader = stream.getReader();
139 + let content = '';
140 + while (true) {
141 + const {done, value} = await reader.read();
142 + if (done) {
143 + return content;
144 + }
145 + content += Buffer.from(value).toString('utf8');
146 + }
147 + }
148 +
149 + // @gate enableSrcObject
150 + it('can SSR a Blob as an img src', async () => {
151 + const blob = new Blob([new Uint8Array([69, 230, 156, 181, 68, 75])], {
152 + type: 'image/jpeg',
153 + });
154 +
155 + const ref = React.createRef();
156 +
157 + function App() {
158 + return <img src={blob} ref={ref} />;
159 + }
160 +
161 + const stream = await ReactDOMFizzServer.renderToReadableStream(<App />);
162 + container.innerHTML = await readContent(stream);
163 +
164 + expect(container.firstChild.src).toBe('data:image/jpeg;base64,ReactURL');
165 +
166 + await act(() => {
167 + ReactDOMClient.hydrateRoot(container, <App />);
168 + });
169 +
170 + expect(container.firstChild.src).toBe('data:image/jpeg;base64,ReactURL');
171 + });
172 +
173 + // @gate enableSrcObject
174 + it('errors in DEV when mismatching a Blob during hydration', async () => {
175 + const blob = new Blob([new Uint8Array([69, 230, 156, 181, 68, 75])], {
176 + type: 'image/jpeg',
177 + });
178 +
179 + const ref = React.createRef();
180 +
181 + const stream = await ReactDOMFizzServer.renderToReadableStream(
182 + <img src={blob} ref={ref} />,
183 + );
184 + container.innerHTML = await readContent(stream);
185 +
186 + expect(container.firstChild.src).toBe('data:image/jpeg;base64,ReactURL');
187 +
188 + const clientBlob = new Blob([new Uint8Array([69, 230, 156, 181, 68])], {
189 + type: 'image/jpeg',
190 + });
191 +
192 + await act(() => {
193 + ReactDOMClient.hydrateRoot(container, <img src={clientBlob} ref={ref} />);
194 + });
195 +
196 + assertConsoleErrorDev([
197 + "A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. " +
198 + "This won't be patched up. This can happen if a SSR-ed Client Component used:\n\n" +
199 + "- A server/client branch `if (typeof window !== 'undefined')`.\n" +
200 + "- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.\n" +
201 + "- Date formatting in a user's locale which doesn't match the server.\n" +
202 + '- External changing data without sending a snapshot of it along with the HTML.\n' +
203 + '- Invalid HTML tag nesting.\n\n' +
204 + 'It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.\n\n' +
205 + 'https://react.dev/link/hydration-mismatch\n\n' +
206 + ' <img\n' +
207 + '+ src={Blob:image/jpeg}\n' +
208 + '- src="data:image/jpeg;base64,ReactURL"\n' +
209 + ' ref={{current:null}}\n' +
210 + ' >\n' +
211 + '\n in img (at **)',
212 + ]);
213 +
214 + // The original URL left in place.
215 + expect(container.firstChild.src).toBe('data:image/jpeg;base64,ReactURL');
216 + });
217 +});
packages/react-reconciler/src/ReactFiberHydrationDiffs.js
+4
@@ -23,6 +23,8 @@ import {
23 HostText,
24 } from './ReactWorkTags';
25
26 +import {enableSrcObject} from 'shared/ReactFeatureFlags';
27 +
28 import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';
29 import assign from 'shared/assign';
30 import getComponentNameFromType from 'shared/getComponentNameFromType';
@@ -222,6 +224,8 @@ function describeValue(value: mixed, maxLength: number): string {
224 (properties === '' ? '' : ',') + propName + ':' + propValue;
225 }
226 return '{' + properties + '}';
227 + } else if (enableSrcObject && (name === 'Blob' || name === 'File')) {
228 + return name + ':' + (value: any).type;
229 }
230 return name;
231 }
packages/react-server/src/ReactServerStreamConfigBrowser.js
+10
@@ -192,3 +192,13 @@ export function closeWithError(destination: Destination, error: mixed): void {
192 }
193
194 export {createFastHashJS as createFastHash} from 'react-server/src/createFastHashJS';
195 +
196 +export function readAsDataURL(blob: Blob): Promise<string> {
197 + return new Promise((resolve, reject) => {
198 + const reader = new FileReader();
199 + // $FlowFixMe[incompatible-call]: We always expect a string result with readAsDataURL.
200 + reader.onloadend = () => resolve(reader.result);
201 + reader.onerror = reject;
202 + reader.readAsDataURL(blob);
203 + });
204 +}
packages/react-server/src/ReactServerStreamConfigBun.js
+8
@@ -103,3 +103,11 @@ export function closeWithError(destination: Destination, error: mixed): void {
103 export function createFastHash(input: string): string | number {
104 return Bun.hash(input);
105 }
106 +
107 +export function readAsDataURL(blob: Blob): Promise<string> {
108 + return blob.arrayBuffer().then(arrayBuffer => {
109 + const encoded = Buffer.from(arrayBuffer).toString('base64');
110 + const mimeType = blob.type || 'application/octet-stream';
111 + return 'data:' + mimeType + ';base64,' + encoded;
112 + });
113 +}
packages/react-server/src/ReactServerStreamConfigEdge.js
+11
@@ -182,3 +182,14 @@ export function closeWithError(destination: Destination, error: mixed): void {
182 }
183
184 export {createFastHashJS as createFastHash} from 'react-server/src/createFastHashJS';
185 +
186 +export function readAsDataURL(blob: Blob): Promise<string> {
187 + return blob.arrayBuffer().then(arrayBuffer => {
188 + const encoded =
189 + typeof Buffer === 'function' && typeof Buffer.from === 'function'
190 + ? Buffer.from(arrayBuffer).toString('base64')
191 + : btoa(String.fromCharCode.apply(String, new Uint8Array(arrayBuffer)));
192 + const mimeType = blob.type || 'application/octet-stream';
193 + return 'data:' + mimeType + ';base64,' + encoded;
194 + });
195 +}
packages/react-server/src/ReactServerStreamConfigFB.js
+4
@@ -75,3 +75,7 @@ export function closeWithError(destination: Destination, error: mixed): void {
75 }
76
77 export {createFastHashJS as createFastHash} from './createFastHashJS';
78 +
79 +export function readAsDataURL(blob: Blob): Promise<string> {
80 + throw new Error('Not implemented.');
81 +}
packages/react-server/src/ReactServerStreamConfigNode.js
+8
@@ -236,3 +236,11 @@ export function createFastHash(input: string): string | number {
236 hash.update(input);
237 return hash.digest('hex');
238 }
239 +
240 +export function readAsDataURL(blob: Blob): Promise<string> {
241 + return blob.arrayBuffer().then(arrayBuffer => {
242 + const encoded = Buffer.from(arrayBuffer).toString('base64');
243 + const mimeType = blob.type || 'application/octet-stream';
244 + return 'data:' + mimeType + ';base64,' + encoded;
245 + });
246 +}
packages/react-server/src/forks/ReactServerStreamConfig.custom.js
+1
@@ -45,3 +45,4 @@ export const typedArrayToBinaryChunk = $$$config.typedArrayToBinaryChunk;
45 export const byteLengthOfChunk = $$$config.byteLengthOfChunk;
46 export const byteLengthOfBinaryChunk = $$$config.byteLengthOfBinaryChunk;
47 export const createFastHash = $$$config.createFastHash;
48 +export const readAsDataURL = $$$config.readAsDataURL;
packages/shared/ReactFeatureFlags.js
+2
@@ -98,6 +98,8 @@ export const enableScrollEndPolyfill = __EXPERIMENTAL__;
98
99 export const enableSuspenseyImages = __EXPERIMENTAL__;
100
101 +export const enableSrcObject = __EXPERIMENTAL__;
102 +
103 /**
104 * Switches the Fabric API from doing layout in commit work instead of complete work.
105 */
packages/shared/forks/ReactFeatureFlags.native-fb.js
+1
@@ -83,6 +83,7 @@ export const enableViewTransition = false;
83 export const enableGestureTransition = false;
84 export const enableScrollEndPolyfill = true;
85 export const enableSuspenseyImages = false;
86 +export const enableSrcObject = false;
87 export const enableFragmentRefs = false;
88 export const ownerStackLimit = 1e4;
89
packages/shared/forks/ReactFeatureFlags.native-oss.js
+1
@@ -75,6 +75,7 @@ export const enableFastAddPropertiesInDiffing = false;
75 export const enableLazyPublicInstanceInFabric = false;
76 export const enableScrollEndPolyfill = true;
77 export const enableSuspenseyImages = false;
78 +export const enableSrcObject = false;
79 export const ownerStackLimit = 1e4;
80
81 export const enableFragmentRefs = false;
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+1
@@ -74,6 +74,7 @@ export const enableFastAddPropertiesInDiffing = true;
74 export const enableLazyPublicInstanceInFabric = false;
75 export const enableScrollEndPolyfill = true;
76 export const enableSuspenseyImages = false;
77 +export const enableSrcObject = false;
78 export const ownerStackLimit = 1e4;
79
80 export const enableFragmentRefs = false;
packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js
+1
@@ -71,6 +71,7 @@ export const enableFastAddPropertiesInDiffing = false;
71 export const enableLazyPublicInstanceInFabric = false;
72 export const enableScrollEndPolyfill = true;
73 export const enableSuspenseyImages = false;
74 +export const enableSrcObject = false;
75 export const enableFragmentRefs = false;
76 export const ownerStackLimit = 1e4;
77
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+1
@@ -85,6 +85,7 @@ export const enableFastAddPropertiesInDiffing = false;
85 export const enableLazyPublicInstanceInFabric = false;
86 export const enableScrollEndPolyfill = true;
87 export const enableSuspenseyImages = false;
88 +export const enableSrcObject = false;
89
90 export const enableFragmentRefs = false;
91 export const ownerStackLimit = 1e4;
packages/shared/forks/ReactFeatureFlags.www.js
+1
@@ -114,6 +114,7 @@ export const enableLazyPublicInstanceInFabric = false;
114 export const enableGestureTransition = false;
115
116 export const enableSuspenseyImages = false;
117 +export const enableSrcObject = false;
118
119 export const ownerStackLimit = 1e4;
120