I recently wanted to use a generator function inside of a macro to encapsulate some iteration logic on a spreadsheet, and make it easily copy-paste-able from one macro to another.
However, the macro did not work, after a closer inspection by opening the browser console in a web editor, I saw errors every time I ran the macro: “TypeError: next() function returned undefined”. Inspecting the generator object itself I saw the next method was modified and referred to an empty function in a file called sdkjs-all.min.js in an onlyoffice path.
The nature of the failure seems to indicate it was intentional, so the question is, to confirm: Is it intentional that generator functions do not work in macros ? If so what would be the reasons behind the decision ?
For additional more technical details:
The failure is that onlyoffice code overwrites the generator’s prototype’s next method with an empty function, which could look like something like this in code:
We have logged a bug for the issue related to the incorrect behavior of generator functions in macros. Unfortunately, we do not have a timeline for a fix at the moment. However, we will update you as soon as there is information about the release of a fix.
I’m here to share a workaround fix for anyone interested, it’s possible to fix the issue by using this code in the macro source:
// This is the main macro IIFE, which needs to pass down the this value
(function (globalThis) {
(/**
* Fix generators in only office macros.
*
* Global this value exposes a window as property 0 which has access to a
* DOM Document, which allows us to create a temporary iframe and access its
* Window to get an unaltered javascript context from which we can grab the
* builtins or builtin functions we need.
*
* usage in macro:
* ```js
* (function(globalThis){
*
* function fixGenerators(globalThis) {
* // -- snip --
* }
*
* fixGenerators(globalThis);
*
* })(this);
* ```
* @param {Window&{0:Window}} globalThis
*/
function fixGenerators(globalThis) {
const document = globalThis[0].document;
const frame = document.createElement('iframe');
document.body.appendChild(frame);
const contentWindow =
/** @type {Window&{Function:new(code:string)=>Function,getGeneratorFunction:()=>Function}|null} */ (
frame.contentWindow
);
if (contentWindow == null) {
throw new Error();
}
contentWindow.getGeneratorFunction = /**@type {()=>Function}*/ (
new contentWindow.Function('return (function*(){}).constructor;')
);
const BuiltinGeneratorFunction = contentWindow.getGeneratorFunction();
const GeneratorFunction = function* () {}.constructor;
GeneratorFunction.prototype.prototype.next =
BuiltinGeneratorFunction.prototype.prototype.next;
frame.remove();
})(globalThis)
// -- normal macro code using generators --
})(this)
This works by accessing a DOM document on the this object during macro execution to create an <iframe> element and get a pristine generator prototype from its window/JS execution context and reassign them in place of the broken one. It feels kind of like going around restrictions normally imposed by the macro plugin, but in the meantime this works. I’m a bit nervous sharing this here since I’d rather have this “exploit” only fixed if generators themselves get fixed.
I have also seen recently, on OnlyOffice version 9.0.4.50, that try {...} catch {...} blocks cause an error preventing macros containing them from executing at all. This renders the example file I provided useless, since the macro simply cannot be executed. Maybe this issue should also be reported in another post?
Has there been any news on this generator topic? (though I suspect this is pretty low priority)