omniJS crashes OG7 with script-created documents

At the moment OG7 documents/canvases created by script:

  1. Crash OG7 instantly and reproducibly when evaluating omniJS code
  2. Sometimes lose access to content (visible in outline panel, but not printable or displayable in outline panel) if the only non-scripted canvas is deleted before a document also containing an omniJS script-created and script-populated canvas is saved. (The jump to 6,400% Zoom on ‘Fit in Window’ also appears in these cases).

The second of these was reported as a bug, with a sample document or two, a month or two ago, and still intermittently turns up, but the first I have only understood this morning.

My guess is that if 1 can be tracked down and fixed, then 2 will vanish with it :-)

JXA code to create a document like this:

    // ogFrontDoc :: {useExisting : Bool} -> OG.Document
    const ogFrontDoc = ({
        useExisting: bool
    }) => {
        const
            og = Application('OmniGraffle'),
            ds = og.documents,
            d = bool && ds.length > 0 ? (
                ds.at(0)
            ) : (() => {
                return (
                    ds.push(og.Document()),
                    //ds.at(0).canvases.push(og.Canvas()),
                    ds.at(0)
                );
            })();
        return (
            og.activate(),
            d
        );
    };

Invoked with:

    const d = ogFrontDoc({
        useExisting: false
    });

If omniJS attempts to create and populate a new canvas in a document created like this with JavaScript for Automation or AppleScript, then OG7 instantly crashes. (Multiply reproduced without fail this morning :-)

The shortest omniJS script that reproducibly crashes OG7 each time (if evaluated in a script-created OG document), turns out to be the following one-liner:

    addCanvas()

Embedding in a test framework for experimentation (using ES6 Sierra Javascript – for back-conversion to pre-Sierra ES5, paste into the REPL at babel JS https://babeljs.io )

(() => {
    'use strict';

    // ES6 (Sierra) Javascript (JXA + omniJS)

    // Javascript for Automation script

    // DEMONSTRATES CRASHING OF OG 7 BY SIMPLE OMNIJS SCRIPT

    // See TEST at bottom

    // OMNIJS SIMPLE TEST FUNCTION -------------------------------------------
    // (Crashes when run in script-generated new doc)

    // simpleTest :: () -> OG7.Crash
    const simpleTest = () => {
        // omniJS for creation of additional canvas
        var cnv = addCanvas();

        console.log("If you see this the doc was not script-created");
        // See test at bottom and setting of .useExisting :: Bool in
        // ogFrontDoc() options.
    };

    // GENERIC JS FUNCTIONS --------------------------------------------------

    // A list of functions applied to a list of arguments
    // <*> :: [(a -> b)] -> [a] -> [b]
    const ap = (fs, xs) => //
        [].concat.apply([], fs.map(f => //
            [].concat.apply([], xs.map(x => [f(x)]))));

    // An integer prepended to the argument list yields an indentation level
    // show :: a -> String
    const show = (...x) =>
        JSON.stringify.apply(
            null, x.length > 1 ? [x[0], null, x[1]] : x
        );

    // JXA FUNCTIONS FOR: ----------------------------------------------------

    // 1. Creating a new OG7 doc
    // 2. Running an omniJS script (via URL opening)
    // 3. Viewing the console

    // ogFrontDoc :: {useExisting : Bool} -> OG.Document
    const ogFrontDoc = ({
        useExisting: bool
    }) => {
        const
            og = Application('OmniGraffle'),
            ds = og.documents,
            d = bool && ds.length > 0 ? (
                ds.at(0)
            ) : (() => {
                return (
                    ds.push(og.Document()),
                    ds.at(0)
                );
            })();

        return (
            og.activate(),
            d
        );
    };

    // evaluateOmniJS :: (Options -> ()) -> { KeyValues ..}
    const evaluateOmniJS = (f, dctOptions) => {
        const
            a = Application.currentApplication(),
            sa = (a.includeStandardAdditions = true, a);

        ap([sa.openLocation, sa.setTheClipboardTo], //
            ['omnigraffle:///omnijs-run?script=' +
                encodeURIComponent(
                    '(' + f.toString() + ')(' +
                    (dctOptions && Object.keys(dctOptions)
                        .length > 0 ? show(dctOptions) : '') + ')'
                )
            ]);
    };

    // menuItemClick :: String -> [String] -> UI ()
    const menuItemClick = (strAppName, lstMenuPath) => {
        const intMenuPath = lstMenuPath.length;

        if (intMenuPath > 1) {
            const appProcs = Application('System Events')
                .processes.where({
                    name: strAppName
                }),
                procApp = appProcs.length ? appProcs[0] : undefined;

            if (procApp) {
                Application(strAppName)
                    .activate();

                lstMenuPath.slice(1, -1)
                    .reduce((a, x) => a.menuItems[x].menus[x],
                        procApp.menuBars[0].menus.byName(lstMenuPath[0]))
                    .menuItems[lstMenuPath[intMenuPath - 1]].click();
            }
        }
    };


    // TEST ------------------------------------------------------------------

    const d = ogFrontDoc({
        useExisting: false // To avoid the crash use 'true' with gui-created doc
    });

    // Create and 'open' an omniJS URL to execute the simpleTest function
    // in OG 7
    evaluateOmniJS(simpleTest, {});

    // Look at the omniJS console.
    menuItemClick('OmniGraffle', ['Automation', 'Show Console']);
})();
1 Like

Incidentally the script creation of the doc in which to evaluate omniJS addCanvas() can be either in AppleScript or JavaScript for Automation if we want to see the crash.

i.e. whatever omniJS addCanvas() is relying on another canvas to provide, is generally absent with script-created canvases – nothing specific to JXA here …

If omniJS addCanvas() is evaluated in the result of this:

tell application "OmniGraffle"
    make new document
end tell

we still get this:

1 Like

The answer seem to be that the script interfaces to templates are not working at the moment (see Scripting access to templates failing/absent across board ? (OG7 test))

It turns out, after further experimentation, that we can script-create a document in which omniJS addCanvas() can be evaluated without a crash by specifying a template at document creation time.

(That part of the interface to templates is working)

A difficulty remains with AppleScript, which is not at the moment able to tell us the names of the items in the available templates property of the application (it yields only Apple Event errors), but in JXA we seem to be able prepare a useable fresh document for omniJS by specifying a known template name, possibly retrieved from the head of

Application('OmniGraffle').availableTemplates()

SUMMARY

The upstream source of the problem seems related to the fact that AppleScript and JXA are, in current OG7 test builds, only getting Apple Event Errors in response to requests for a document’s template

(and AppleScript is also unable to get a template name string from the application’s available templates at the moment.

To script the creation of a document which omniJS will not instantly crash on a call to addCanvas(), we need then to update our JXA ogFrontDocument function to specify a known template name, or a default retrieved from the og.availableTemplates() list:

    // elem :: Eq a => a -> [a] -> Bool
    const elem = (x, xs) => xs.indexOf(x) !== -1;

    // ogFrontDoc :: {useExisting : Bool} -> OG.Document
    const ogFrontDoc = (dctOptions) => {
        const
            options = dctOptions || {},
            og = Application('OmniGraffle'),
            optTemplate = options.templateName,
            xs = og.availableTemplates(),
            strTemplate = (optTemplate && elem(optTemplate, xs)) ? (
                optTemplate
            ) : xs[0],
            ds = og.documents,
            d = options.useExisting && ds.length > 0 ? (
                ds.at(0)
            ) : (() => {
                return (
                    ds.push(og.Document({template: strTemplate})),
                    ds.at(0)
                );
            })();

        return (
            og.activate(),
            d
        );
    };

Followed by a call in a pattern like:

    const d = ogFrontDoc({
        useExisting: false,
        template: 'Blank'
    });

Thanks for the bug report!

What is happening here is that the AppleScript/JXA path of creating a document without a template is now subtly broken. It creates a first canvas whose sizing is set inconsistently. Then when you addCanvas(), we try to make the new canvas have the same sizing as the last existing one, and hit an assertion in that code because it’s an invalid size.

In short, this is an AppleScript bug instead of an OmniJS bug, and as you discovered, using a template for creating the new document avoids it.

1 Like

Got it.

Many thanks for taking the time to track that down.