Merge pull request #5290 from node-red/5283-fix-ui-lockup-for-typed-arrays

Fix UI lock-up when typed arrays are expanded in debug window
This commit is contained in:
Nick O'Leary
2025-10-09 11:24:43 +01:00
committed by GitHub
4 changed files with 201 additions and 7 deletions

View File

@@ -15,6 +15,7 @@
**/
RED.utils = (function() {
const listOfTypedArrays = ['Int8Array','Uint8Array','Uint8ClampedArray','Int16Array','Uint16Array','Int32Array','Uint32Array','Float32Array','Float64Array','BigInt64Array','BigUint64Array'];
window._marked = window.marked;
window.marked = function(txt) {
@@ -170,6 +171,8 @@ RED.utils = (function() {
result = $('<span class="red-ui-debug-msg-object-value red-ui-debug-msg-type-number"></span>').text(value.data);
} else if (value.hasOwnProperty('type') && value.type === 'regexp') {
result = $('<span class="red-ui-debug-msg-object-value red-ui-debug-msg-type-string"></span>').text(value.data);
} else if (value.hasOwnProperty('type') && value.hasOwnProperty('data') && listOfTypedArrays.includes(value.type)) {
result = $('<span class="red-ui-debug-msg-object-value red-ui-debug-msg-type-meta"></span>').text(value.type + '['+value.length+']');
} else {
result = $('<span class="red-ui-debug-msg-object-value red-ui-debug-msg-type-meta">object</span>');
}
@@ -475,7 +478,7 @@ RED.utils = (function() {
var isArray = Array.isArray(obj);
var isArrayObject = false;
if (obj && typeof obj === 'object' && obj.hasOwnProperty('type') && obj.hasOwnProperty('data') && ((obj.__enc__ && obj.type === 'set') || (obj.__enc__ && obj.type === 'array') || obj.type === 'Buffer')) {
if (obj && typeof obj === 'object' && obj.hasOwnProperty('type') && obj.hasOwnProperty('data') && ((obj.__enc__ && obj.type === 'set') || (obj.__enc__ && obj.type === 'array') || (obj.__enc__ && listOfTypedArrays.includes(obj.type)) || obj.type === 'Buffer')) {
isArray = true;
isArrayObject = true;
}
@@ -1299,7 +1302,7 @@ RED.utils = (function() {
payload = Infinity;
} else if ((format === 'number') && (payload === "-Infinity")) {
payload = -Infinity;
} else if (format === 'Object' || /^(array|set|map)/.test(format) || format === 'boolean' || format === 'number' ) {
} else if (format === 'Object' || /^(array|set|map)/.test(format) || format === 'boolean' || format === 'number' || listOfTypedArrays.includes(format)) {
payload = JSON.parse(payload);
} else if (/error/i.test(format)) {
payload = JSON.parse(payload);

View File

@@ -437,6 +437,11 @@ RED.debug = (function() {
var property = sanitize(o.property?o.property:'');
var payload = o.msg;
var format = sanitize((o.format||"").toString());
var baseFormat = format
// If format is "array[12]"/"Float32Array[12]" then strip off the size portion [12] for the decodeObject
if (/\w+\[\d+\]$/.test(baseFormat)) {
baseFormat = format.replace(/\[\d+\]$/,'');
}
msg.attr("class", 'red-ui-debug-msg'+(o.level?(' red-ui-debug-msg-level-'+o.level):'')+
(sourceNode?(
" red-ui-debug-msg-node-"+sourceNode.id.replace(/\./g,"_")+
@@ -502,7 +507,7 @@ RED.debug = (function() {
$('<span class="red-ui-debug-msg-name">'+name+'</span>').appendTo(metaRow);
}
payload = RED.utils.decodeObject(payload,format);
payload = RED.utils.decodeObject(payload, baseFormat);
var el = $('<span class="red-ui-debug-msg-payload"></span>').appendTo(msg);
var path = o.property||'';

View File

@@ -877,14 +877,16 @@ function encodeObject(msg,opts) {
});
} else {
var isArray = Array.isArray(msg.msg);
var needsStringify = isArray;
if (isArray) {
msg.format = "array["+msg.msg.length+"]";
const isTypedArray = ArrayBuffer.isView(msg.msg);
var needsStringify = isArray || isTypedArray;
var typeName = isArray ? 'array' : constructorName(msg.msg);
if (isArray || isTypedArray) {
msg.format = typeName + "["+msg.msg.length+"]";
if (msg.msg.length > debuglength) {
// msg.msg = msg.msg.slice(0,debuglength);
msg.msg = {
__enc__: true,
type: "array",
type: typeName,
data: msg.msg.slice(0,debuglength),
length: msg.msg.length
}
@@ -948,6 +950,16 @@ function encodeObject(msg,opts) {
data: value.slice(0,debuglength),
length: value.length
}
} else if (ArrayBuffer.isView(value) && value.length > debuglength && !Buffer.isBuffer(value)) { // include typed arrays, exclude Buffer
// Note: Buffer is a subclass of Uint8Array
const typeName = constructorName(value);
/** @type {ArrayBufferView} */
value = {
__enc__: true,
type: typeName,
data: Array.from(value).slice(0,debuglength),
length: value.length
}
} else if (typeof value === 'string') {
if (value.length > debuglength) {
value = value.substring(0,debuglength)+"...";
@@ -978,6 +990,13 @@ function encodeObject(msg,opts) {
if (value.length > debuglength) {
value.data = value.data.slice(0,debuglength);
}
} else if (ArrayBuffer.isView(value)) {
value = {
__enc__: true,
type: "array",
data: Array.from(value),
length: value.length
}
} else if (constructorName(value) === "ServerResponse") {
value = "[internal]"
} else if (constructorName(value) === "Socket") {

View File

@@ -887,6 +887,145 @@ describe("@node-red/util/util", function() {
resultJson.should.have.property("length",2)
});
describe('encode typed arrays', function() {
it('encodes Int8Array', function () {
const arr = new Int8Array([1, 2, 3]);
const msg = { msg: arr };
const result = util.encodeObject(msg);
result.format.should.eql("Int8Array[3]");
const resultJson = JSON.parse(result.msg);
resultJson.should.have.property("__enc__", true);
resultJson.should.have.property("type", "array");
resultJson.should.have.property("data").which.eql([1, 2, 3]);
resultJson.should.have.property("length", 3);
});
it('encodes Uint8Array', function () {
const arr = new Uint8Array([4, 5, 6]);
const msg = { msg: arr };
const result = util.encodeObject(msg);
result.format.should.eql("Uint8Array[3]");
const resultJson = JSON.parse(result.msg);
resultJson.should.have.property("__enc__", true);
resultJson.should.have.property("type", "array");
resultJson.should.have.property("data").which.eql([4, 5, 6]);
resultJson.should.have.property("length", 3);
});
it('encodes Uint8ClampedArray', function () {
const arr = new Uint8ClampedArray([7, 8, 9]);
const msg = { msg: arr };
const result = util.encodeObject(msg);
result.format.should.eql("Uint8ClampedArray[3]");
const resultJson = JSON.parse(result.msg);
resultJson.should.have.property("__enc__", true);
resultJson.should.have.property("type", "array");
resultJson.should.have.property("data").which.eql([7, 8, 9]);
resultJson.should.have.property("length", 3);
});
it('encodes Int16Array', function () {
const arr = new Int16Array([10, 11, 12]);
const msg = { msg: arr };
const result = util.encodeObject(msg);
result.format.should.eql("Int16Array[3]");
const resultJson = JSON.parse(result.msg);
resultJson.should.have.property("__enc__", true);
resultJson.should.have.property("type", "array");
resultJson.should.have.property("data").which.eql([10, 11, 12]);
resultJson.should.have.property("length", 3);
});
it('encodes Uint16Array', function () {
const arr = new Uint16Array([13, 14, 15]);
const msg = { msg: arr };
const result = util.encodeObject(msg);
result.format.should.eql("Uint16Array[3]");
const resultJson = JSON.parse(result.msg);
resultJson.should.have.property("__enc__", true);
resultJson.should.have.property("type", "array");
resultJson.should.have.property("data").which.eql([13, 14, 15]);
resultJson.should.have.property("length", 3);
});
it('encodes Int32Array', function () {
const arr = new Int32Array([16, 17, 18]);
const msg = { msg: arr };
const result = util.encodeObject(msg);
result.format.should.eql("Int32Array[3]");
const resultJson = JSON.parse(result.msg);
resultJson.should.have.property("__enc__", true);
resultJson.should.have.property("type", "array");
resultJson.should.have.property("data").which.eql([16, 17, 18]);
resultJson.should.have.property("length", 3);
});
it('encodes Uint32Array', function () {
const arr = new Uint32Array([19, 20, 21]);
const msg = { msg: arr };
const result = util.encodeObject(msg);
result.format.should.eql("Uint32Array[3]");
const resultJson = JSON.parse(result.msg);
resultJson.should.have.property("__enc__", true);
resultJson.should.have.property("type", "array");
resultJson.should.have.property("data").which.eql([19, 20, 21]);
resultJson.should.have.property("length", 3);
});
it('encodes Float32Array', function () {
const arr = new Float32Array([22.1, 23.2, 24.3]);
const msg = { msg: arr };
const result = util.encodeObject(msg);
result.format.should.eql("Float32Array[3]");
const resultJson = JSON.parse(result.msg);
resultJson.should.have.property("__enc__", true);
resultJson.should.have.property("type", "array");
resultJson.should.have.property("data").which.be.an.Array();
resultJson.data[0].should.be.approximately(22.1, 0.00001);
resultJson.data[1].should.be.approximately(23.2, 0.00001);
resultJson.data[2].should.be.approximately(24.3, 0.00001);
resultJson.should.have.property("length", 3);
});
it('encodes Float64Array', function () {
const arr = new Float64Array([25.4, 26.5, 27.6]);
const msg = { msg: arr };
const result = util.encodeObject(msg);
result.format.should.eql("Float64Array[3]");
const resultJson = JSON.parse(result.msg);
resultJson.should.have.property("__enc__", true);
resultJson.should.have.property("type", "array");
resultJson.should.have.property("data").which.be.an.Array();
resultJson.data[0].should.be.approximately(25.4, 0.00001);
resultJson.data[1].should.be.approximately(26.5, 0.00001);
resultJson.data[2].should.be.approximately(27.6, 0.00001);
resultJson.should.have.property("length", 3);
});
it('encodes BigInt64Array', function () {
const arr = new BigInt64Array([BigInt(28), BigInt(29), BigInt(30)]);
const msg = { msg: arr };
const result = util.encodeObject(msg);
result.format.should.eql("BigInt64Array[3]");
const resultJson = JSON.parse(result.msg);
resultJson.should.have.property("__enc__", true);
resultJson.should.have.property("type", "array");
// BigInt arrays are stringified in JSON
resultJson.should.have.property("data").which.eql([
{ "__enc__": true, "data": "28", "type": "bigint" },
{ "__enc__": true, "data": "29", "type": "bigint" },
{ "__enc__": true, "data": "30", "type": "bigint" }
]);
resultJson.should.have.property("length", 3);
});
it('encodes BigUint64Array', function () {
const arr = new BigUint64Array([BigInt(31), BigInt(32), BigInt(33)]);
const msg = { msg: arr };
const result = util.encodeObject(msg);
result.format.should.eql("BigUint64Array[3]");
const resultJson = JSON.parse(result.msg);
resultJson.should.have.property("__enc__", true);
resultJson.should.have.property("type", "array");
// BigInt arrays are stringified in JSON
resultJson.should.have.property("data").which.eql([
{ "__enc__": true, "data": "31", "type": "bigint" },
{ "__enc__": true, "data": "32", "type": "bigint" },
{ "__enc__": true, "data": "33", "type": "bigint" }
]);
resultJson.should.have.property("length", 3);
});
});
describe('encode object', function() {
it('object', function() {
@@ -966,6 +1105,34 @@ describe("@node-red/util/util", function() {
resultJson.aSet.should.have.property("data",["a","b"]);
resultJson.aSet.should.have.property("length",2)
});
it('object with typed array property (Int8Array)', function() {
const arr = new Int8Array([1, 2, 3]);
const msg = { msg: { anArray: arr } };
const result = util.encodeObject(msg);
result.format.should.eql("Object");
const resultJson = JSON.parse(result.msg);
resultJson.should.have.property("anArray");
resultJson.anArray.should.have.property("__enc__", true);
resultJson.anArray.should.have.property("type", "array");
resultJson.anArray.should.have.property("data", [1, 2, 3]);
resultJson.anArray.should.have.property("length", 3);
});
it('object with typed array property (BigInt64Array)', function() {
const arr = new BigInt64Array([10n, 20n, 30n]);
const msg = { msg: { anArray: arr } };
const result = util.encodeObject(msg);
result.format.should.eql("Object");
const resultJson = JSON.parse(result.msg);
resultJson.should.have.property("anArray");
resultJson.anArray.should.have.property("__enc__", true);
resultJson.anArray.should.have.property("type", "array");
resultJson.anArray.should.have.property("data", [
{ __enc__: true, data: "10", type: "bigint" },
{ __enc__: true, data: "20", type: "bigint" },
{ __enc__: true, data: "30", type: "bigint" }
]);
resultJson.anArray.should.have.property("length", 3);
});
it('constructor of IncomingMessage', function() {
function IncomingMessage(){};
var msg = { msg:new IncomingMessage() };