diff --git a/src/htmx.js b/src/htmx.js index 0ff735822..347d62c95 100644 --- a/src/htmx.js +++ b/src/htmx.js @@ -137,6 +137,9 @@ var htmx = (() => { #internalAPI; #Function = Function; #AsyncFunction = Object.getPrototypeOf(async function(){}).constructor; + // The names a function parameter can take: identifiers that aren't reserved words + #identifier = /^[$_\p{ID_Start}][$\u200C\u200D\p{ID_Continue}]*$/u; + #reservedWords = new Set(["await", "break", "case", "catch", "class", "const", "continue", "debugger", "default", "delete", "do", "else", "enum", "export", "extends", "false", "finally", "for", "function", "if", "import", "in", "instanceof", "new", "null", "return", "super", "switch", "this", "throw", "true", "try", "typeof", "var", "void", "while", "with"]); #ttPolicy = { createHTML: s => s, createScript: s => s }; #actionSelector #boostSelector = "a,form"; @@ -903,8 +906,15 @@ var htmx = (() => { code = detail.code; Object.assign(args, scope); Object.assign(args, obj) - let keys = Object.keys(args); - let values = Object.values(args); + let keys = []; + let values = []; + for (let [key, value] of Object.entries(args)) { + // Any other name would make the constructor throw, and code couldn't reference it by bare name anyway + if (this.#identifier.test(key) && !this.#reservedWords.has(key)) { + keys.push(key); + values.push(value); + } + } let FunctionConstructor = isAsync ? this.#AsyncFunction : this.#Function; let func = new FunctionConstructor(...keys, expression ? `return (${code})` : code); return compile ? () => func.call(thisArg, ...values) : func.call(thisArg, ...values); diff --git a/test/tests/attributes/hx-trigger.js b/test/tests/attributes/hx-trigger.js index 3ccbb5270..692219679 100644 --- a/test/tests/attributes/hx-trigger.js +++ b/test/tests/attributes/hx-trigger.js @@ -52,6 +52,18 @@ describe('hx-trigger attribute', function() { await forRequest() }) + it('filters properly when the event has a property that is not an identifier', async function() { + mockResponse('GET', '/test', 'Called!') + let form = createProcessedHTML('
Not Called
') + let event = new CustomEvent("evt"); + event.foo = true + event['not-an-identifier'] = {} + event.default = true + form.dispatchEvent(event) + fetchMock.calls.length.should.equal(1) + await forRequest() + }) + it('filters properly with true expression filter spec', async function() { mockResponse('GET', '/test', 'Called!') let form = createProcessedHTML('
Not Called
')