Commit 3025aa3

Browse files
authored
[Flight] Don't serialize toJSON in Debug path and omit wide arrays (#34759)
There's a couple of issues with serializing Buffer in the debug renders. For one, the Node.js Buffer has a `toJSON` on it which turns the binary data into a JSON array which is very inefficient to serialize compared to the real buffer. For debug info we never really want to resolve these and unlike the regular render we can't error. So this uses the trick where we read the original value. It's still unfortunate that this intermediate gets created at all but at least now we're not serializing it. Second, we have a limit on depth of objects but we didn't have a limit on width like large arrays or typed arrays. This omits large arrays from the payload when possible and make them deferred when there's a debug channel.
1 parent a4eb2df commit 3025aa3

3 files changed

Lines changed: 529 additions & 291 deletions

File tree

‎packages/react-server/src/ReactFlightServer.js‎

Lines changed: 74 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,49 @@ export function resolveRequest(): null | Request {
849849
return null;
850850
}
851851

852+
functionisTypedArray(value: any): boolean{
853+
if(valueinstanceofArrayBuffer){
854+
returntrue;
855+
}
856+
if (value instanceof Int8Array) {
857+
returntrue;
858+
}
859+
if (value instanceof Uint8Array) {
860+
returntrue;
861+
}
862+
if (value instanceof Uint8ClampedArray) {
863+
returntrue;
864+
}
865+
if (value instanceof Int16Array) {
866+
returntrue;
867+
}
868+
if (value instanceof Uint16Array) {
869+
returntrue;
870+
}
871+
if (value instanceof Int32Array) {
872+
returntrue;
873+
}
874+
if (value instanceof Uint32Array) {
875+
returntrue;
876+
}
877+
if (value instanceof Float32Array) {
878+
returntrue;
879+
}
880+
if (value instanceof Float64Array) {
881+
returntrue;
882+
}
883+
if (value instanceof BigInt64Array) {
884+
returntrue;
885+
}
886+
if (value instanceof BigUint64Array) {
887+
returntrue;
888+
}
889+
if (value instanceof DataView) {
890+
returntrue;
891+
}
892+
return false;
893+
}
894+
852895
functionserializeDebugThenable(
853896
request: Request,
854897
counter: {objectLimit: number},
@@ -906,6 +949,17 @@ function serializeDebugThenable(
906949
enqueueFlush(request);
907950
return;
908951
}
952+
if(
953+
(isArray(value)&&value.length>200)||
954+
(isTypedArray(value)&&value.byteLength>1000)
955+
){
956+
// If this should be deferred, but we don't have a debug channel installed
957+
// it would get omitted. We can't omit outlined models but we can avoid
958+
// resolving the Promise at all by halting it.
959+
emitDebugHaltChunk(request,id);
960+
enqueueFlush(request);
961+
return;
962+
}
909963
emitOutlinedDebugModelChunk(request,id,counter,value);
910964
enqueueFlush(request);
911965
},
@@ -3066,6 +3120,10 @@ function serializeDebugTypedArray(
30663120
tag: string,
30673121
typedArray: $ArrayBufferView,
30683122
): string{
3123+
if(typedArray.byteLength>1000&&!doNotLimit.has(typedArray)){
3124+
// Defer large typed arrays.
3125+
returnserializeDeferredObject(request,typedArray);
3126+
}
30693127
request.pendingDebugChunks++;
30703128
constbufferId=request.nextChunkId++;
30713129
emitTypedArrayChunk(request,bufferId,tag,typedArray,true);
@@ -4820,9 +4878,17 @@ function renderDebugModel(
48204878
}
48214879

48224880
if (isArray(value)) {
4881+
if(value.length>200&&!doNotLimit.has(value)){
4882+
// Defer large arrays. They're heavy to serialize.
4883+
// TODO: Consider doing the same for objects with many properties too.
4884+
returnserializeDeferredObject(request,value);
4885+
}
48234886
return value;
48244887
}
48254888

4889+
if(valueinstanceofDate){
4890+
returnserializeDate(value);
4891+
}
48264892
if (value instanceof Map) {
48274893
returnserializeDebugMap(request,counter,value);
48284894
}
@@ -4930,15 +4996,6 @@ function renderDebugModel(
49304996
}
49314997

49324998
if(typeofvalue=== 'string'){
4933-
if(value[value.length-1]==='Z'){
4934-
// Possibly a Date, whose toJSON automatically calls toISOString
4935-
// Make sure that `parent[parentPropertyName]` wasn't JSONified before `value` was passed to us
4936-
// $FlowFixMe[incompatible-use]
4937-
constoriginalValue=parent[parentPropertyName];
4938-
if(originalValueinstanceofDate){
4939-
returnserializeDateFromDateJSON(value);
4940-
}
4941-
}
49424999
if(value.length>=1024){
49435000
// Large strings are counted towards the object limit.
49445001
if(counter.objectLimit<=0){
@@ -5036,10 +5093,6 @@ function renderDebugModel(
50365093
returnserializeBigInt(value);
50375094
}
50385095

5039-
if(valueinstanceofDate){
5040-
returnserializeDate(value);
5041-
}
5042-
50435096
return 'unknown type ' + typeof value;
50445097
}
50455098

@@ -5058,12 +5111,15 @@ function serializeDebugModel(
50585111
value: ReactClientValue,
50595112
): ReactJSONValue {
50605113
try{
5114+
// By-pass toJSON and use the original value.
5115+
// $FlowFixMe[incompatible-use]
5116+
const originalValue =this[parentPropertyName];
50615117
returnrenderDebugModel(
50625118
request,
50635119
counter,
50645120
this,
50655121
parentPropertyName,
5066-
value,
5122+
originalValue,
50675123
);
50685124
}catch(x){
50695125
return(
@@ -5114,12 +5170,15 @@ function emitOutlinedDebugModelChunk(
51145170
value: ReactClientValue,
51155171
): ReactJSONValue {
51165172
try{
5173+
// By-pass toJSON and use the original value.
5174+
// $FlowFixMe[incompatible-use]
5175+
const originalValue =this[parentPropertyName];
51175176
returnrenderDebugModel(
51185177
request,
51195178
counter,
51205179
this,
51215180
parentPropertyName,
5122-
value,
5181+
originalValue,
51235182
);
51245183
}catch(x){
51255184
return(

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Commit 3025aa3

Browse files
authored
[Flight] Don't serialize toJSON in Debug path and omit wide arrays (#34759)
There's a couple of issues with serializing Buffer in the debug renders. For one, the Node.js Buffer has a `toJSON` on it which turns the binary data into a JSON array which is very inefficient to serialize compared to the real buffer. For debug info we never really want to resolve these and unlike the regular render we can't error. So this uses the trick where we read the original value. It's still unfortunate that this intermediate gets created at all but at least now we're not serializing it. Second, we have a limit on depth of objects but we didn't have a limit on width like large arrays or typed arrays. This omits large arrays from the payload when possible and make them deferred when there's a debug channel.
1 parent a4eb2df commit 3025aa3

3 files changed

Lines changed: 529 additions & 291 deletions

File tree

‎packages/react-server/src/ReactFlightServer.js‎

Lines changed: 74 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,49 @@ export function resolveRequest(): null | Request {
849849
return null;
850850
}
851851

852+
functionisTypedArray(value: any): boolean{
853+
if(valueinstanceofArrayBuffer){
854+
returntrue;
855+
}
856+
if (value instanceof Int8Array) {
857+
returntrue;
858+
}
859+
if (value instanceof Uint8Array) {
860+
returntrue;
861+
}
862+
if (value instanceof Uint8ClampedArray) {
863+
returntrue;
864+
}
865+
if (value instanceof Int16Array) {
866+
returntrue;
867+
}
868+
if (value instanceof Uint16Array) {
869+
returntrue;
870+
}
871+
if (value instanceof Int32Array) {
872+
returntrue;
873+
}
874+
if (value instanceof Uint32Array) {
875+
returntrue;
876+
}
877+
if (value instanceof Float32Array) {
878+
returntrue;
879+
}
880+
if (value instanceof Float64Array) {
881+
returntrue;
882+
}
883+
if (value instanceof BigInt64Array) {
884+
returntrue;
885+
}
886+
if (value instanceof BigUint64Array) {
887+
returntrue;
888+
}
889+
if (value instanceof DataView) {
890+
returntrue;
891+
}
892+
return false;
893+
}
894+
852895
functionserializeDebugThenable(
853896
request: Request,
854897
counter: {objectLimit: number},
@@ -906,6 +949,17 @@ function serializeDebugThenable(
906949
enqueueFlush(request);
907950
return;
908951
}
952+
if(
953+
(isArray(value)&&value.length>200)||
954+
(isTypedArray(value)&&value.byteLength>1000)
955+
){
956+
// If this should be deferred, but we don't have a debug channel installed
957+
// it would get omitted. We can't omit outlined models but we can avoid
958+
// resolving the Promise at all by halting it.
959+
emitDebugHaltChunk(request,id);
960+
enqueueFlush(request);
961+
return;
962+
}
909963
emitOutlinedDebugModelChunk(request,id,counter,value);
910964
enqueueFlush(request);
911965
},
@@ -3066,6 +3120,10 @@ function serializeDebugTypedArray(
30663120
tag: string,
30673121
typedArray: $ArrayBufferView,
30683122
): string{
3123+
if(typedArray.byteLength>1000&&!doNotLimit.has(typedArray)){
3124+
// Defer large typed arrays.
3125+
returnserializeDeferredObject(request,typedArray);
3126+
}
30693127
request.pendingDebugChunks++;
30703128
constbufferId=request.nextChunkId++;
30713129
emitTypedArrayChunk(request,bufferId,tag,typedArray,true);
@@ -4820,9 +4878,17 @@ function renderDebugModel(
48204878
}
48214879

48224880
if (isArray(value)) {
4881+
if(value.length>200&&!doNotLimit.has(value)){
4882+
// Defer large arrays. They're heavy to serialize.
4883+
// TODO: Consider doing the same for objects with many properties too.
4884+
returnserializeDeferredObject(request,value);
4885+
}
48234886
return value;
48244887
}
48254888

4889+
if(valueinstanceofDate){
4890+
returnserializeDate(value);
4891+
}
48264892
if (value instanceof Map) {
48274893
returnserializeDebugMap(request,counter,value);
48284894
}
@@ -4930,15 +4996,6 @@ function renderDebugModel(
49304996
}
49314997

49324998
if(typeofvalue=== 'string'){
4933-
if(value[value.length-1]==='Z'){
4934-
// Possibly a Date, whose toJSON automatically calls toISOString
4935-
// Make sure that `parent[parentPropertyName]` wasn't JSONified before `value` was passed to us
4936-
// $FlowFixMe[incompatible-use]
4937-
constoriginalValue=parent[parentPropertyName];
4938-
if(originalValueinstanceofDate){
4939-
returnserializeDateFromDateJSON(value);
4940-
}
4941-
}
49424999
if(value.length>=1024){
49435000
// Large strings are counted towards the object limit.
49445001
if(counter.objectLimit<=0){
@@ -5036,10 +5093,6 @@ function renderDebugModel(
50365093
returnserializeBigInt(value);
50375094
}
50385095

5039-
if(valueinstanceofDate){
5040-
returnserializeDate(value);
5041-
}
5042-
50435096
return 'unknown type ' + typeof value;
50445097
}
50455098

@@ -5058,12 +5111,15 @@ function serializeDebugModel(
50585111
value: ReactClientValue,
50595112
): ReactJSONValue {
50605113
try{
5114+
// By-pass toJSON and use the original value.
5115+
// $FlowFixMe[incompatible-use]
5116+
const originalValue =this[parentPropertyName];
50615117
returnrenderDebugModel(
50625118
request,
50635119
counter,
50645120
this,
50655121
parentPropertyName,
5066-
value,
5122+
originalValue,
50675123
);
50685124
}catch(x){
50695125
return(
@@ -5114,12 +5170,15 @@ function emitOutlinedDebugModelChunk(
51145170
value: ReactClientValue,
51155171
): ReactJSONValue {
51165172
try{
5173+
// By-pass toJSON and use the original value.
5174+
// $FlowFixMe[incompatible-use]
5175+
const originalValue =this[parentPropertyName];
51175176
returnrenderDebugModel(
51185177
request,
51195178
counter,
51205179
this,
51215180
parentPropertyName,
5122-
value,
5181+
originalValue,
51235182
);
51245183
}catch(x){
51255184
return(

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 3025aa3

Browse files
authored
[Flight] Don't serialize toJSON in Debug path and omit wide arrays (#34759)
There's a couple of issues with serializing Buffer in the debug renders. For one, the Node.js Buffer has a `toJSON` on it which turns the binary data into a JSON array which is very inefficient to serialize compared to the real buffer. For debug info we never really want to resolve these and unlike the regular render we can't error. So this uses the trick where we read the original value. It's still unfortunate that this intermediate gets created at all but at least now we're not serializing it. Second, we have a limit on depth of objects but we didn't have a limit on width like large arrays or typed arrays. This omits large arrays from the payload when possible and make them deferred when there's a debug channel.
1 parent a4eb2df commit 3025aa3

3 files changed

Lines changed: 529 additions & 291 deletions

File tree

‎packages/react-server/src/ReactFlightServer.js‎

Lines changed: 74 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,49 @@ export function resolveRequest(): null | Request {
849849
return null;
850850
}
851851

852+
functionisTypedArray(value: any): boolean{
853+
if(valueinstanceofArrayBuffer){
854+
returntrue;
855+
}
856+
if (value instanceof Int8Array) {
857+
returntrue;
858+
}
859+
if (value instanceof Uint8Array) {
860+
returntrue;
861+
}
862+
if (value instanceof Uint8ClampedArray) {
863+
returntrue;
864+
}
865+
if (value instanceof Int16Array) {
866+
returntrue;
867+
}
868+
if (value instanceof Uint16Array) {
869+
returntrue;
870+
}
871+
if (value instanceof Int32Array) {
872+
returntrue;
873+
}
874+
if (value instanceof Uint32Array) {
875+
returntrue;
876+
}
877+
if (value instanceof Float32Array) {
878+
returntrue;
879+
}
880+
if (value instanceof Float64Array) {
881+
returntrue;
882+
}
883+
if (value instanceof BigInt64Array) {
884+
returntrue;
885+
}
886+
if (value instanceof BigUint64Array) {
887+
returntrue;
888+
}
889+
if (value instanceof DataView) {
890+
returntrue;
891+
}
892+
return false;
893+
}
894+
852895
functionserializeDebugThenable(
853896
request: Request,
854897
counter: {objectLimit: number},
@@ -906,6 +949,17 @@ function serializeDebugThenable(
906949
enqueueFlush(request);
907950
return;
908951
}
952+
if(
953+
(isArray(value)&&value.length>200)||
954+
(isTypedArray(value)&&value.byteLength>1000)
955+
){
956+
// If this should be deferred, but we don't have a debug channel installed
957+
// it would get omitted. We can't omit outlined models but we can avoid
958+
// resolving the Promise at all by halting it.
959+
emitDebugHaltChunk(request,id);
960+
enqueueFlush(request);
961+
return;
962+
}
909963
emitOutlinedDebugModelChunk(request,id,counter,value);
910964
enqueueFlush(request);
911965
},
@@ -3066,6 +3120,10 @@ function serializeDebugTypedArray(
30663120
tag: string,
30673121
typedArray: $ArrayBufferView,
30683122
): string{
3123+
if(typedArray.byteLength>1000&&!doNotLimit.has(typedArray)){
3124+
// Defer large typed arrays.
3125+
returnserializeDeferredObject(request,typedArray);
3126+
}
30693127
request.pendingDebugChunks++;
30703128
constbufferId=request.nextChunkId++;
30713129
emitTypedArrayChunk(request,bufferId,tag,typedArray,true);
@@ -4820,9 +4878,17 @@ function renderDebugModel(
48204878
}
48214879

48224880
if (isArray(value)) {
4881+
if(value.length>200&&!doNotLimit.has(value)){
4882+
// Defer large arrays. They're heavy to serialize.
4883+
// TODO: Consider doing the same for objects with many properties too.
4884+
returnserializeDeferredObject(request,value);
4885+
}
48234886
return value;
48244887
}
48254888

4889+
if(valueinstanceofDate){
4890+
returnserializeDate(value);
4891+
}
48264892
if (value instanceof Map) {
48274893
returnserializeDebugMap(request,counter,value);
48284894
}
@@ -4930,15 +4996,6 @@ function renderDebugModel(
49304996
}
49314997

49324998
if(typeofvalue=== 'string'){
4933-
if(value[value.length-1]==='Z'){
4934-
// Possibly a Date, whose toJSON automatically calls toISOString
4935-
// Make sure that `parent[parentPropertyName]` wasn't JSONified before `value` was passed to us
4936-
// $FlowFixMe[incompatible-use]
4937-
constoriginalValue=parent[parentPropertyName];
4938-
if(originalValueinstanceofDate){
4939-
returnserializeDateFromDateJSON(value);
4940-
}
4941-
}
49424999
if(value.length>=1024){
49435000
// Large strings are counted towards the object limit.
49445001
if(counter.objectLimit<=0){
@@ -5036,10 +5093,6 @@ function renderDebugModel(
50365093
returnserializeBigInt(value);
50375094
}
50385095

5039-
if(valueinstanceofDate){
5040-
returnserializeDate(value);
5041-
}
5042-
50435096
return 'unknown type ' + typeof value;
50445097
}
50455098

@@ -5058,12 +5111,15 @@ function serializeDebugModel(
50585111
value: ReactClientValue,
50595112
): ReactJSONValue {
50605113
try{
5114+
// By-pass toJSON and use the original value.
5115+
// $FlowFixMe[incompatible-use]
5116+
const originalValue =this[parentPropertyName];
50615117
returnrenderDebugModel(
50625118
request,
50635119
counter,
50645120
this,
50655121
parentPropertyName,
5066-
value,
5122+
originalValue,
50675123
);
50685124
}catch(x){
50695125
return(
@@ -5114,12 +5170,15 @@ function emitOutlinedDebugModelChunk(
51145170
value: ReactClientValue,
51155171
): ReactJSONValue {
51165172
try{
5173+
// By-pass toJSON and use the original value.
5174+
// $FlowFixMe[incompatible-use]
5175+
const originalValue =this[parentPropertyName];
51175176
returnrenderDebugModel(
51185177
request,
51195178
counter,
51205179
this,
51215180
parentPropertyName,
5122-
value,
5181+
originalValue,
51235182
);
51245183
}catch(x){
51255184
return(

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 3025aa3

Browse files
authored
[Flight] Don't serialize toJSON in Debug path and omit wide arrays (#34759)
There's a couple of issues with serializing Buffer in the debug renders. For one, the Node.js Buffer has a `toJSON` on it which turns the binary data into a JSON array which is very inefficient to serialize compared to the real buffer. For debug info we never really want to resolve these and unlike the regular render we can't error. So this uses the trick where we read the original value. It's still unfortunate that this intermediate gets created at all but at least now we're not serializing it. Second, we have a limit on depth of objects but we didn't have a limit on width like large arrays or typed arrays. This omits large arrays from the payload when possible and make them deferred when there's a debug channel.
1 parent a4eb2df commit 3025aa3

3 files changed

Lines changed: 529 additions & 291 deletions

File tree

‎packages/react-server/src/ReactFlightServer.js‎

Lines changed: 74 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,49 @@ export function resolveRequest(): null | Request {
849849
return null;
850850
}
851851

852+
functionisTypedArray(value: any): boolean{
853+
if(valueinstanceofArrayBuffer){
854+
returntrue;
855+
}
856+
if (value instanceof Int8Array) {
857+
returntrue;
858+
}
859+
if (value instanceof Uint8Array) {
860+
returntrue;
861+
}
862+
if (value instanceof Uint8ClampedArray) {
863+
returntrue;
864+
}
865+
if (value instanceof Int16Array) {
866+
returntrue;
867+
}
868+
if (value instanceof Uint16Array) {
869+
returntrue;
870+
}
871+
if (value instanceof Int32Array) {
872+
returntrue;
873+
}
874+
if (value instanceof Uint32Array) {
875+
returntrue;
876+
}
877+
if (value instanceof Float32Array) {
878+
returntrue;
879+
}
880+
if (value instanceof Float64Array) {
881+
returntrue;
882+
}
883+
if (value instanceof BigInt64Array) {
884+
returntrue;
885+
}
886+
if (value instanceof BigUint64Array) {
887+
returntrue;
888+
}
889+
if (value instanceof DataView) {
890+
returntrue;
891+
}
892+
return false;
893+
}
894+
852895
functionserializeDebugThenable(
853896
request: Request,
854897
counter: {objectLimit: number},
@@ -906,6 +949,17 @@ function serializeDebugThenable(
906949
enqueueFlush(request);
907950
return;
908951
}
952+
if(
953+
(isArray(value)&&value.length>200)||
954+
(isTypedArray(value)&&value.byteLength>1000)
955+
){
956+
// If this should be deferred, but we don't have a debug channel installed
957+
// it would get omitted. We can't omit outlined models but we can avoid
958+
// resolving the Promise at all by halting it.
959+
emitDebugHaltChunk(request,id);
960+
enqueueFlush(request);
961+
return;
962+
}
909963
emitOutlinedDebugModelChunk(request,id,counter,value);
910964
enqueueFlush(request);
911965
},
@@ -3066,6 +3120,10 @@ function serializeDebugTypedArray(
30663120
tag: string,
30673121
typedArray: $ArrayBufferView,
30683122
): string{
3123+
if(typedArray.byteLength>1000&&!doNotLimit.has(typedArray)){
3124+
// Defer large typed arrays.
3125+
returnserializeDeferredObject(request,typedArray);
3126+
}
30693127
request.pendingDebugChunks++;
30703128
constbufferId=request.nextChunkId++;
30713129
emitTypedArrayChunk(request,bufferId,tag,typedArray,true);
@@ -4820,9 +4878,17 @@ function renderDebugModel(
48204878
}
48214879

48224880
if (isArray(value)) {
4881+
if(value.length>200&&!doNotLimit.has(value)){
4882+
// Defer large arrays. They're heavy to serialize.
4883+
// TODO: Consider doing the same for objects with many properties too.
4884+
returnserializeDeferredObject(request,value);
4885+
}
48234886
return value;
48244887
}
48254888

4889+
if(valueinstanceofDate){
4890+
returnserializeDate(value);
4891+
}
48264892
if (value instanceof Map) {
48274893
returnserializeDebugMap(request,counter,value);
48284894
}
@@ -4930,15 +4996,6 @@ function renderDebugModel(
49304996
}
49314997

49324998
if(typeofvalue=== 'string'){
4933-
if(value[value.length-1]==='Z'){
4934-
// Possibly a Date, whose toJSON automatically calls toISOString
4935-
// Make sure that `parent[parentPropertyName]` wasn't JSONified before `value` was passed to us
4936-
// $FlowFixMe[incompatible-use]
4937-
constoriginalValue=parent[parentPropertyName];
4938-
if(originalValueinstanceofDate){
4939-
returnserializeDateFromDateJSON(value);
4940-
}
4941-
}
49424999
if(value.length>=1024){
49435000
// Large strings are counted towards the object limit.
49445001
if(counter.objectLimit<=0){
@@ -5036,10 +5093,6 @@ function renderDebugModel(
50365093
returnserializeBigInt(value);
50375094
}
50385095

5039-
if(valueinstanceofDate){
5040-
returnserializeDate(value);
5041-
}
5042-
50435096
return 'unknown type ' + typeof value;
50445097
}
50455098

@@ -5058,12 +5111,15 @@ function serializeDebugModel(
50585111
value: ReactClientValue,
50595112
): ReactJSONValue {
50605113
try{
5114+
// By-pass toJSON and use the original value.
5115+
// $FlowFixMe[incompatible-use]
5116+
const originalValue =this[parentPropertyName];
50615117
returnrenderDebugModel(
50625118
request,
50635119
counter,
50645120
this,
50655121
parentPropertyName,
5066-
value,
5122+
originalValue,
50675123
);
50685124
}catch(x){
50695125
return(
@@ -5114,12 +5170,15 @@ function emitOutlinedDebugModelChunk(
51145170
value: ReactClientValue,
51155171
): ReactJSONValue {
51165172
try{
5173+
// By-pass toJSON and use the original value.
5174+
// $FlowFixMe[incompatible-use]
5175+
const originalValue =this[parentPropertyName];
51175176
returnrenderDebugModel(
51185177
request,
51195178
counter,
51205179
this,
51215180
parentPropertyName,
5122-
value,
5181+
originalValue,
51235182
);
51245183
}catch(x){
51255184
return(

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Commit 3025aa3

Browse files
authored
[Flight] Don't serialize toJSON in Debug path and omit wide arrays (#34759)
There's a couple of issues with serializing Buffer in the debug renders. For one, the Node.js Buffer has a `toJSON` on it which turns the binary data into a JSON array which is very inefficient to serialize compared to the real buffer. For debug info we never really want to resolve these and unlike the regular render we can't error. So this uses the trick where we read the original value. It's still unfortunate that this intermediate gets created at all but at least now we're not serializing it. Second, we have a limit on depth of objects but we didn't have a limit on width like large arrays or typed arrays. This omits large arrays from the payload when possible and make them deferred when there's a debug channel.
1 parent a4eb2df commit 3025aa3

3 files changed

Lines changed: 529 additions & 291 deletions

File tree

‎packages/react-server/src/ReactFlightServer.js‎

Lines changed: 74 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,49 @@ export function resolveRequest(): null | Request {
849849
return null;
850850
}
851851

852+
functionisTypedArray(value: any): boolean{
853+
if(valueinstanceofArrayBuffer){
854+
returntrue;
855+
}
856+
if (value instanceof Int8Array) {
857+
returntrue;
858+
}
859+
if (value instanceof Uint8Array) {
860+
returntrue;
861+
}
862+
if (value instanceof Uint8ClampedArray) {
863+
returntrue;
864+
}
865+
if (value instanceof Int16Array) {
866+
returntrue;
867+
}
868+
if (value instanceof Uint16Array) {
869+
returntrue;
870+
}
871+
if (value instanceof Int32Array) {
872+
returntrue;
873+
}
874+
if (value instanceof Uint32Array) {
875+
returntrue;
876+
}
877+
if (value instanceof Float32Array) {
878+
returntrue;
879+
}
880+
if (value instanceof Float64Array) {
881+
returntrue;
882+
}
883+
if (value instanceof BigInt64Array) {
884+
returntrue;
885+
}
886+
if (value instanceof BigUint64Array) {
887+
returntrue;
888+
}
889+
if (value instanceof DataView) {
890+
returntrue;
891+
}
892+
return false;
893+
}
894+
852895
functionserializeDebugThenable(
853896
request: Request,
854897
counter: {objectLimit: number},
@@ -906,6 +949,17 @@ function serializeDebugThenable(
906949
enqueueFlush(request);
907950
return;
908951
}
952+
if(
953+
(isArray(value)&&value.length>200)||
954+
(isTypedArray(value)&&value.byteLength>1000)
955+
){
956+
// If this should be deferred, but we don't have a debug channel installed
957+
// it would get omitted. We can't omit outlined models but we can avoid
958+
// resolving the Promise at all by halting it.
959+
emitDebugHaltChunk(request,id);
960+
enqueueFlush(request);
961+
return;
962+
}
909963
emitOutlinedDebugModelChunk(request,id,counter,value);
910964
enqueueFlush(request);
911965
},
@@ -3066,6 +3120,10 @@ function serializeDebugTypedArray(
30663120
tag: string,
30673121
typedArray: $ArrayBufferView,
30683122
): string{
3123+
if(typedArray.byteLength>1000&&!doNotLimit.has(typedArray)){
3124+
// Defer large typed arrays.
3125+
returnserializeDeferredObject(request,typedArray);
3126+
}
30693127
request.pendingDebugChunks++;
30703128
constbufferId=request.nextChunkId++;
30713129
emitTypedArrayChunk(request,bufferId,tag,typedArray,true);
@@ -4820,9 +4878,17 @@ function renderDebugModel(
48204878
}
48214879

48224880
if (isArray(value)) {
4881+
if(value.length>200&&!doNotLimit.has(value)){
4882+
// Defer large arrays. They're heavy to serialize.
4883+
// TODO: Consider doing the same for objects with many properties too.
4884+
returnserializeDeferredObject(request,value);
4885+
}
48234886
return value;
48244887
}
48254888

4889+
if(valueinstanceofDate){
4890+
returnserializeDate(value);
4891+
}
48264892
if (value instanceof Map) {
48274893
returnserializeDebugMap(request,counter,value);
48284894
}
@@ -4930,15 +4996,6 @@ function renderDebugModel(
49304996
}
49314997

49324998
if(typeofvalue=== 'string'){
4933-
if(value[value.length-1]==='Z'){
4934-
// Possibly a Date, whose toJSON automatically calls toISOString
4935-
// Make sure that `parent[parentPropertyName]` wasn't JSONified before `value` was passed to us
4936-
// $FlowFixMe[incompatible-use]
4937-
constoriginalValue=parent[parentPropertyName];
4938-
if(originalValueinstanceofDate){
4939-
returnserializeDateFromDateJSON(value);
4940-
}
4941-
}
49424999
if(value.length>=1024){
49435000
// Large strings are counted towards the object limit.
49445001
if(counter.objectLimit<=0){
@@ -5036,10 +5093,6 @@ function renderDebugModel(
50365093
returnserializeBigInt(value);
50375094
}
50385095

5039-
if(valueinstanceofDate){
5040-
returnserializeDate(value);
5041-
}
5042-
50435096
return 'unknown type ' + typeof value;
50445097
}
50455098

@@ -5058,12 +5111,15 @@ function serializeDebugModel(
50585111
value: ReactClientValue,
50595112
): ReactJSONValue {
50605113
try{
5114+
// By-pass toJSON and use the original value.
5115+
// $FlowFixMe[incompatible-use]
5116+
const originalValue =this[parentPropertyName];
50615117
returnrenderDebugModel(
50625118
request,
50635119
counter,
50645120
this,
50655121
parentPropertyName,
5066-
value,
5122+
originalValue,
50675123
);
50685124
}catch(x){
50695125
return(
@@ -5114,12 +5170,15 @@ function emitOutlinedDebugModelChunk(
51145170
value: ReactClientValue,
51155171
): ReactJSONValue {
51165172
try{
5173+
// By-pass toJSON and use the original value.
5174+
// $FlowFixMe[incompatible-use]
5175+
const originalValue =this[parentPropertyName];
51175176
returnrenderDebugModel(
51185177
request,
51195178
counter,
51205179
this,
51215180
parentPropertyName,
5122-
value,
5181+
originalValue,
51235182
);
51245183
}catch(x){
51255184
return(

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 3025aa3

Browse files
authored
[Flight] Don't serialize toJSON in Debug path and omit wide arrays (#34759)
There's a couple of issues with serializing Buffer in the debug renders. For one, the Node.js Buffer has a `toJSON` on it which turns the binary data into a JSON array which is very inefficient to serialize compared to the real buffer. For debug info we never really want to resolve these and unlike the regular render we can't error. So this uses the trick where we read the original value. It's still unfortunate that this intermediate gets created at all but at least now we're not serializing it. Second, we have a limit on depth of objects but we didn't have a limit on width like large arrays or typed arrays. This omits large arrays from the payload when possible and make them deferred when there's a debug channel.
1 parent a4eb2df commit 3025aa3

3 files changed

Lines changed: 529 additions & 291 deletions

File tree

‎packages/react-server/src/ReactFlightServer.js‎

Lines changed: 74 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,49 @@ export function resolveRequest(): null | Request {
849849
return null;
850850
}
851851

852+
functionisTypedArray(value: any): boolean{
853+
if(valueinstanceofArrayBuffer){
854+
returntrue;
855+
}
856+
if (value instanceof Int8Array) {
857+
returntrue;
858+
}
859+
if (value instanceof Uint8Array) {
860+
returntrue;
861+
}
862+
if (value instanceof Uint8ClampedArray) {
863+
returntrue;
864+
}
865+
if (value instanceof Int16Array) {
866+
returntrue;
867+
}
868+
if (value instanceof Uint16Array) {
869+
returntrue;
870+
}
871+
if (value instanceof Int32Array) {
872+
returntrue;
873+
}
874+
if (value instanceof Uint32Array) {
875+
returntrue;
876+
}
877+
if (value instanceof Float32Array) {
878+
returntrue;
879+
}
880+
if (value instanceof Float64Array) {
881+
returntrue;
882+
}
883+
if (value instanceof BigInt64Array) {
884+
returntrue;
885+
}
886+
if (value instanceof BigUint64Array) {
887+
returntrue;
888+
}
889+
if (value instanceof DataView) {
890+
returntrue;
891+
}
892+
return false;
893+
}
894+
852895
functionserializeDebugThenable(
853896
request: Request,
854897
counter: {objectLimit: number},
@@ -906,6 +949,17 @@ function serializeDebugThenable(
906949
enqueueFlush(request);
907950
return;
908951
}
952+
if(
953+
(isArray(value)&&value.length>200)||
954+
(isTypedArray(value)&&value.byteLength>1000)
955+
){
956+
// If this should be deferred, but we don't have a debug channel installed
957+
// it would get omitted. We can't omit outlined models but we can avoid
958+
// resolving the Promise at all by halting it.
959+
emitDebugHaltChunk(request,id);
960+
enqueueFlush(request);
961+
return;
962+
}
909963
emitOutlinedDebugModelChunk(request,id,counter,value);
910964
enqueueFlush(request);
911965
},
@@ -3066,6 +3120,10 @@ function serializeDebugTypedArray(
30663120
tag: string,
30673121
typedArray: $ArrayBufferView,
30683122
): string{
3123+
if(typedArray.byteLength>1000&&!doNotLimit.has(typedArray)){
3124+
// Defer large typed arrays.
3125+
returnserializeDeferredObject(request,typedArray);
3126+
}
30693127
request.pendingDebugChunks++;
30703128
constbufferId=request.nextChunkId++;
30713129
emitTypedArrayChunk(request,bufferId,tag,typedArray,true);
@@ -4820,9 +4878,17 @@ function renderDebugModel(
48204878
}
48214879

48224880
if (isArray(value)) {
4881+
if(value.length>200&&!doNotLimit.has(value)){
4882+
// Defer large arrays. They're heavy to serialize.
4883+
// TODO: Consider doing the same for objects with many properties too.
4884+
returnserializeDeferredObject(request,value);
4885+
}
48234886
return value;
48244887
}
48254888

4889+
if(valueinstanceofDate){
4890+
returnserializeDate(value);
4891+
}
48264892
if (value instanceof Map) {
48274893
returnserializeDebugMap(request,counter,value);
48284894
}
@@ -4930,15 +4996,6 @@ function renderDebugModel(
49304996
}
49314997

49324998
if(typeofvalue=== 'string'){
4933-
if(value[value.length-1]==='Z'){
4934-
// Possibly a Date, whose toJSON automatically calls toISOString
4935-
// Make sure that `parent[parentPropertyName]` wasn't JSONified before `value` was passed to us
4936-
// $FlowFixMe[incompatible-use]
4937-
constoriginalValue=parent[parentPropertyName];
4938-
if(originalValueinstanceofDate){
4939-
returnserializeDateFromDateJSON(value);
4940-
}
4941-
}
49424999
if(value.length>=1024){
49435000
// Large strings are counted towards the object limit.
49445001
if(counter.objectLimit<=0){
@@ -5036,10 +5093,6 @@ function renderDebugModel(
50365093
returnserializeBigInt(value);
50375094
}
50385095

5039-
if(valueinstanceofDate){
5040-
returnserializeDate(value);
5041-
}
5042-
50435096
return 'unknown type ' + typeof value;
50445097
}
50455098

@@ -5058,12 +5111,15 @@ function serializeDebugModel(
50585111
value: ReactClientValue,
50595112
): ReactJSONValue {
50605113
try{
5114+
// By-pass toJSON and use the original value.
5115+
// $FlowFixMe[incompatible-use]
5116+
const originalValue =this[parentPropertyName];
50615117
returnrenderDebugModel(
50625118
request,
50635119
counter,
50645120
this,
50655121
parentPropertyName,
5066-
value,
5122+
originalValue,
50675123
);
50685124
}catch(x){
50695125
return(
@@ -5114,12 +5170,15 @@ function emitOutlinedDebugModelChunk(
51145170
value: ReactClientValue,
51155171
): ReactJSONValue {
51165172
try{
5173+
// By-pass toJSON and use the original value.
5174+
// $FlowFixMe[incompatible-use]
5175+
const originalValue =this[parentPropertyName];
51175176
returnrenderDebugModel(
51185177
request,
51195178
counter,
51205179
this,
51215180
parentPropertyName,
5122-
value,
5181+
originalValue,
51235182
);
51245183
}catch(x){
51255184
return(

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 3025aa3

Browse files
authored
[Flight] Don't serialize toJSON in Debug path and omit wide arrays (#34759)
There's a couple of issues with serializing Buffer in the debug renders. For one, the Node.js Buffer has a `toJSON` on it which turns the binary data into a JSON array which is very inefficient to serialize compared to the real buffer. For debug info we never really want to resolve these and unlike the regular render we can't error. So this uses the trick where we read the original value. It's still unfortunate that this intermediate gets created at all but at least now we're not serializing it. Second, we have a limit on depth of objects but we didn't have a limit on width like large arrays or typed arrays. This omits large arrays from the payload when possible and make them deferred when there's a debug channel.
1 parent a4eb2df commit 3025aa3

3 files changed

Lines changed: 529 additions & 291 deletions

File tree

‎packages/react-server/src/ReactFlightServer.js‎

Lines changed: 74 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,49 @@ export function resolveRequest(): null | Request {
849849
return null;
850850
}
851851

852+
functionisTypedArray(value: any): boolean{
853+
if(valueinstanceofArrayBuffer){
854+
returntrue;
855+
}
856+
if (value instanceof Int8Array) {
857+
returntrue;
858+
}
859+
if (value instanceof Uint8Array) {
860+
returntrue;
861+
}
862+
if (value instanceof Uint8ClampedArray) {
863+
returntrue;
864+
}
865+
if (value instanceof Int16Array) {
866+
returntrue;
867+
}
868+
if (value instanceof Uint16Array) {
869+
returntrue;
870+
}
871+
if (value instanceof Int32Array) {
872+
returntrue;
873+
}
874+
if (value instanceof Uint32Array) {
875+
returntrue;
876+
}
877+
if (value instanceof Float32Array) {
878+
returntrue;
879+
}
880+
if (value instanceof Float64Array) {
881+
returntrue;
882+
}
883+
if (value instanceof BigInt64Array) {
884+
returntrue;
885+
}
886+
if (value instanceof BigUint64Array) {
887+
returntrue;
888+
}
889+
if (value instanceof DataView) {
890+
returntrue;
891+
}
892+
return false;
893+
}
894+
852895
functionserializeDebugThenable(
853896
request: Request,
854897
counter: {objectLimit: number},
@@ -906,6 +949,17 @@ function serializeDebugThenable(
906949
enqueueFlush(request);
907950
return;
908951
}
952+
if(
953+
(isArray(value)&&value.length>200)||
954+
(isTypedArray(value)&&value.byteLength>1000)
955+
){
956+
// If this should be deferred, but we don't have a debug channel installed
957+
// it would get omitted. We can't omit outlined models but we can avoid
958+
// resolving the Promise at all by halting it.
959+
emitDebugHaltChunk(request,id);
960+
enqueueFlush(request);
961+
return;
962+
}
909963
emitOutlinedDebugModelChunk(request,id,counter,value);
910964
enqueueFlush(request);
911965
},
@@ -3066,6 +3120,10 @@ function serializeDebugTypedArray(
30663120
tag: string,
30673121
typedArray: $ArrayBufferView,
30683122
): string{
3123+
if(typedArray.byteLength>1000&&!doNotLimit.has(typedArray)){
3124+
// Defer large typed arrays.
3125+
returnserializeDeferredObject(request,typedArray);
3126+
}
30693127
request.pendingDebugChunks++;
30703128
constbufferId=request.nextChunkId++;
30713129
emitTypedArrayChunk(request,bufferId,tag,typedArray,true);
@@ -4820,9 +4878,17 @@ function renderDebugModel(
48204878
}
48214879

48224880
if (isArray(value)) {
4881+
if(value.length>200&&!doNotLimit.has(value)){
4882+
// Defer large arrays. They're heavy to serialize.
4883+
// TODO: Consider doing the same for objects with many properties too.
4884+
returnserializeDeferredObject(request,value);
4885+
}
48234886
return value;
48244887
}
48254888

4889+
if(valueinstanceofDate){
4890+
returnserializeDate(value);
4891+
}
48264892
if (value instanceof Map) {
48274893
returnserializeDebugMap(request,counter,value);
48284894
}
@@ -4930,15 +4996,6 @@ function renderDebugModel(
49304996
}
49314997

49324998
if(typeofvalue=== 'string'){
4933-
if(value[value.length-1]==='Z'){
4934-
// Possibly a Date, whose toJSON automatically calls toISOString
4935-
// Make sure that `parent[parentPropertyName]` wasn't JSONified before `value` was passed to us
4936-
// $FlowFixMe[incompatible-use]
4937-
constoriginalValue=parent[parentPropertyName];
4938-
if(originalValueinstanceofDate){
4939-
returnserializeDateFromDateJSON(value);
4940-
}
4941-
}
49424999
if(value.length>=1024){
49435000
// Large strings are counted towards the object limit.
49445001
if(counter.objectLimit<=0){
@@ -5036,10 +5093,6 @@ function renderDebugModel(
50365093
returnserializeBigInt(value);
50375094
}
50385095

5039-
if(valueinstanceofDate){
5040-
returnserializeDate(value);
5041-
}
5042-
50435096
return 'unknown type ' + typeof value;
50445097
}
50455098

@@ -5058,12 +5111,15 @@ function serializeDebugModel(
50585111
value: ReactClientValue,
50595112
): ReactJSONValue {
50605113
try{
5114+
// By-pass toJSON and use the original value.
5115+
// $FlowFixMe[incompatible-use]
5116+
const originalValue =this[parentPropertyName];
50615117
returnrenderDebugModel(
50625118
request,
50635119
counter,
50645120
this,
50655121
parentPropertyName,
5066-
value,
5122+
originalValue,
50675123
);
50685124
}catch(x){
50695125
return(
@@ -5114,12 +5170,15 @@ function emitOutlinedDebugModelChunk(
51145170
value: ReactClientValue,
51155171
): ReactJSONValue {
51165172
try{
5173+
// By-pass toJSON and use the original value.
5174+
// $FlowFixMe[incompatible-use]
5175+
const originalValue =this[parentPropertyName];
51175176
returnrenderDebugModel(
51185177
request,
51195178
counter,
51205179
this,
51215180
parentPropertyName,
5122-
value,
5181+
originalValue,
51235182
);
51245183
}catch(x){
51255184
return(

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Commit 3025aa3

Browse files
authored
[Flight] Don't serialize toJSON in Debug path and omit wide arrays (#34759)
There's a couple of issues with serializing Buffer in the debug renders. For one, the Node.js Buffer has a `toJSON` on it which turns the binary data into a JSON array which is very inefficient to serialize compared to the real buffer. For debug info we never really want to resolve these and unlike the regular render we can't error. So this uses the trick where we read the original value. It's still unfortunate that this intermediate gets created at all but at least now we're not serializing it. Second, we have a limit on depth of objects but we didn't have a limit on width like large arrays or typed arrays. This omits large arrays from the payload when possible and make them deferred when there's a debug channel.
1 parent a4eb2df commit 3025aa3

3 files changed

Lines changed: 529 additions & 291 deletions

File tree

‎packages/react-server/src/ReactFlightServer.js‎

Lines changed: 74 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,49 @@ export function resolveRequest(): null | Request {
849849
return null;
850850
}
851851

852+
functionisTypedArray(value: any): boolean{
853+
if(valueinstanceofArrayBuffer){
854+
returntrue;
855+
}
856+
if (value instanceof Int8Array) {
857+
returntrue;
858+
}
859+
if (value instanceof Uint8Array) {
860+
returntrue;
861+
}
862+
if (value instanceof Uint8ClampedArray) {
863+
returntrue;
864+
}
865+
if (value instanceof Int16Array) {
866+
returntrue;
867+
}
868+
if (value instanceof Uint16Array) {
869+
returntrue;
870+
}
871+
if (value instanceof Int32Array) {
872+
returntrue;
873+
}
874+
if (value instanceof Uint32Array) {
875+
returntrue;
876+
}
877+
if (value instanceof Float32Array) {
878+
returntrue;
879+
}
880+
if (value instanceof Float64Array) {
881+
returntrue;
882+
}
883+
if (value instanceof BigInt64Array) {
884+
returntrue;
885+
}
886+
if (value instanceof BigUint64Array) {
887+
returntrue;
888+
}
889+
if (value instanceof DataView) {
890+
returntrue;
891+
}
892+
return false;
893+
}
894+
852895
functionserializeDebugThenable(
853896
request: Request,
854897
counter: {objectLimit: number},
@@ -906,6 +949,17 @@ function serializeDebugThenable(
906949
enqueueFlush(request);
907950
return;
908951
}
952+
if(
953+
(isArray(value)&&value.length>200)||
954+
(isTypedArray(value)&&value.byteLength>1000)
955+
){
956+
// If this should be deferred, but we don't have a debug channel installed
957+
// it would get omitted. We can't omit outlined models but we can avoid
958+
// resolving the Promise at all by halting it.
959+
emitDebugHaltChunk(request,id);
960+
enqueueFlush(request);
961+
return;
962+
}
909963
emitOutlinedDebugModelChunk(request,id,counter,value);
910964
enqueueFlush(request);
911965
},
@@ -3066,6 +3120,10 @@ function serializeDebugTypedArray(
30663120
tag: string,
30673121
typedArray: $ArrayBufferView,
30683122
): string{
3123+
if(typedArray.byteLength>1000&&!doNotLimit.has(typedArray)){
3124+
// Defer large typed arrays.
3125+
returnserializeDeferredObject(request,typedArray);
3126+
}
30693127
request.pendingDebugChunks++;
30703128
constbufferId=request.nextChunkId++;
30713129
emitTypedArrayChunk(request,bufferId,tag,typedArray,true);
@@ -4820,9 +4878,17 @@ function renderDebugModel(
48204878
}
48214879

48224880
if (isArray(value)) {
4881+
if(value.length>200&&!doNotLimit.has(value)){
4882+
// Defer large arrays. They're heavy to serialize.
4883+
// TODO: Consider doing the same for objects with many properties too.
4884+
returnserializeDeferredObject(request,value);
4885+
}
48234886
return value;
48244887
}
48254888

4889+
if(valueinstanceofDate){
4890+
returnserializeDate(value);
4891+
}
48264892
if (value instanceof Map) {
48274893
returnserializeDebugMap(request,counter,value);
48284894
}
@@ -4930,15 +4996,6 @@ function renderDebugModel(
49304996
}
49314997

49324998
if(typeofvalue=== 'string'){
4933-
if(value[value.length-1]==='Z'){
4934-
// Possibly a Date, whose toJSON automatically calls toISOString
4935-
// Make sure that `parent[parentPropertyName]` wasn't JSONified before `value` was passed to us
4936-
// $FlowFixMe[incompatible-use]
4937-
constoriginalValue=parent[parentPropertyName];
4938-
if(originalValueinstanceofDate){
4939-
returnserializeDateFromDateJSON(value);
4940-
}
4941-
}
49424999
if(value.length>=1024){
49435000
// Large strings are counted towards the object limit.
49445001
if(counter.objectLimit<=0){
@@ -5036,10 +5093,6 @@ function renderDebugModel(
50365093
returnserializeBigInt(value);
50375094
}
50385095

5039-
if(valueinstanceofDate){
5040-
returnserializeDate(value);
5041-
}
5042-
50435096
return 'unknown type ' + typeof value;
50445097
}
50455098

@@ -5058,12 +5111,15 @@ function serializeDebugModel(
50585111
value: ReactClientValue,
50595112
): ReactJSONValue {
50605113
try{
5114+
// By-pass toJSON and use the original value.
5115+
// $FlowFixMe[incompatible-use]
5116+
const originalValue =this[parentPropertyName];
50615117
returnrenderDebugModel(
50625118
request,
50635119
counter,
50645120
this,
50655121
parentPropertyName,
5066-
value,
5122+
originalValue,
50675123
);
50685124
}catch(x){
50695125
return(
@@ -5114,12 +5170,15 @@ function emitOutlinedDebugModelChunk(
51145170
value: ReactClientValue,
51155171
): ReactJSONValue {
51165172
try{
5173+
// By-pass toJSON and use the original value.
5174+
// $FlowFixMe[incompatible-use]
5175+
const originalValue =this[parentPropertyName];
51175176
returnrenderDebugModel(
51185177
request,
51195178
counter,
51205179
this,
51215180
parentPropertyName,
5122-
value,
5181+
originalValue,
51235182
);
51245183
}catch(x){
51255184
return(

0 commit comments

Comments
 (0)