OS X 10.10 Release Notes

This article describes JavaScript for Automation, a new feature in OS X Yosemite.

Global Properties

JavaScript for Automation is a host environment for JavaScript that adds the following global properties:

Open Scripting Architecture (OSA) Component

The JavaScript OSA component implements JavaScript for Automation. The component can be used from Script Editor, the system-wide Script menu, the Run JavaScript Automator action, applets, droplets, the osascript command-line tool, the NSUserScriptTask API, and everywhere else that other OSA components, such as AppleScript, can be used. This includes Mail rules, Folder Actions, Address Book plug-ins, Calendar alarms, and message triggers.

Scripting Dictionaries

Scripting dictionaries detail the object model of applications. Terminology in scripting dictionaries maps to valid JavaScript identifiers by following a set of conventions. The Script Dictionary viewer in Script Editor has been updated to show terminology in AppleScript, JavaScript, and Objective-C (Scripting Bridge framework) formats. To open a scripting dictionary, launch Script Editor (in /Applications/Utilities/) and choose File > Open Dictionary or Window > Library.

Object Specifiers

Many objects in the JavaScript for Automation host environment refer to external entities, such as other applications, or windows or data inside of those other applications. When you access a JavaScript property of an application object, or of an element of an application object, a new object specifier is returned that refers to the specific property of that object.

Accessing Applications

You can access an application in a number of ways:

Syntax Examples

The following examples demonstrate the syntax for interacting with objects in an application:

Getting and Setting Properties

You access properties of scripting objects as JavaScript properties using dot notation. As described above, the returned object is an object specifier—a reference to the accessed property—rather than the actual value. To send the get event to the external entity and return its value, call the property as a function:

subject = Mail.inbox.messages[0].subject()

Similarly, setting a property sends the set event to the external entity with the data you want to set.

Mail.outgoingMessages[0].subject = 'Hello world'

Here is an example showing how to get a property from every element of an array (in this case, the subject of every message in Mail's inbox).

subjects = Mail.inbox.messages.subject()

Element Arrays

You access elements within an array by calling specific element retrieval methods on the array, or by using square brackets and specifying the name or index of the element you want to retrieve. The returned values are object specifiers, with their own properties and elements, that refer to the array elements. They can be accessed by:

Filtering Arrays

To filter arrays, returning only some of the elements, use the whose method. Here is the general syntax.

someElementArray.whose({...})

Pass in an object containing the query's criteria—element property and value to use for filtering. You can use this technique to check for:

You can also combine queries in order to determine whether multiple criteria evaluate to true.

The _and, _or, and _not keys require arrays as their value.

Here is an example of a whose clause to find all of the messages in your inbox that contain the word JavaScript or the word Automation in the subject.

Mail = Application('Mail')
Mail.inbox.messages.whose({
    _or: [
        { subject: { _contains: "JavaScript" } },
        { subject: { _contains: "Automation" } }
    ]
})

Calling Commands

Commands are called as functions. Some commands take a direct parameter, which is passed as the first argument to a command. Some commands can take named parameters, accepting an object of keyed values for the named parameters. If the command takes a direct parameter, you pass the object of named parameters as a second argument. If the command has no direct parameter, the object of named parameters is passed as the first and only argument. When the direct parameter is optional, you can pass nothing to the command, or you can pass null as the first parameter if you will pass in named parameters.

Creating Objects

You can create new objects by calling class constructors as functions and optionally providing properties and data. When making an object, you must either call the make method on the object, or call the push method on an application object array for the object to actually be created. Until you call one of these methods, the object doesn't actually exist in the application.

Once you create a new object in an application (by calling make or push), you can interact with it like any existing application objects.

message = Mail.OutgoingMessage().make()
message.subject = 'Hello world'

Scripting Additions

Use scripting additions (plug-ins for scripts) to enhance the functionality of applications. The OS has a set of standard scripting additions that provide the ability to speak text, display user interaction dialogs, and more. To use them, an application must explicitly set the includeStandardAdditions flag to true.

app = Application.currentApplication()
app.includeStandardAdditions = true
app.say('Hello world')
app.displayDialog('Please enter your email address', {
    withTitle: 'Email',
    defaultAnswer: 'your_email@site.com'
})

Strict Flags

You can affect how strictly JavaScript for Automation treats application objects when you:

By default, applications have strictPropertyScope and strictCommandScope set to false and strictParameterType set to true.

Passing Event Modifiers

You can pass event modifiers when getting and setting properties, calling commands on objects, creating new objects in applications, and so on. The event modifiers should be passed as the last argument to whatever function you are calling. Here are some examples:

You can consider or ignore:

ObjectSpecifier

You can use the ObjectSpecifier object to:

Paths

When you need to interact with files, such as a document in TextEdit, you will need a path object, not just a string with a path in it. You can use the Path constructor to instantiate paths.

TextEdit = Application('TextEdit')
path = Path('/Users/username/Desktop/foo.rtf')
TextEdit.open(path)

Progress

You can interact with a script's Progress object to affect the UI that details the script's progress. You can set the object’s:

Libraries

To use scripts as libraries, store them in ~/Library/Script Libraries/.

Suppose you have a script library named toolbox.scpt, which contains the following code:

function log(message) {
    TextEdit = Application('TextEdit')
    doc = TextEdit.documents['Log.rtf']
    doc.text = message
}

Another script can target methods in your library using the following syntax.

toolbox = Library('toolbox')
toolbox.log('Hello world')

Applets

You can make standalone, double-clickable applications (called applets) by writing a script in Script Editor and saving it as an application. Applets support the following event handlers:

UI Automation

You can automate the user interfaces of applications by scripting the System Events application. Explore the scripting dictionary of System Events in Script Editor—specifically the Processes Suite—to see a list of application interface elements that support this type of automation.

The following example uses UI scripting to create a new note in Notes.

Notes = Application('Notes')
Notes.activate()
 
delay(1)
SystemEvents = Application('System Events')
Notes = SystemEvents.processes['Notes']
 
Notes.windows[0].splitterGroups[0].groups[1].groups[0].buttons[0].click()

Objective-C Bridge

JavaScript for Automation has a built-in Objective-C bridge that enables you to access the file system and build Cocoa applications.

The primary access points for the Objective-C bridge are the global properties ObjC and $.

Frameworks

The symbols from the Foundation framework are available by default in JavaScript for Automation. You can make additional frameworks and libraries available using the ObjC.import() method. For example, to use the NSBeep() function, which is not present in Foundation, you can import the Cocoa framework.

ObjC.import('Cocoa')
$.NSBeep()

In addition to system frameworks, functionality from some of the system libraries has been exposed. This functionality can be imported by referring to the name of the header file (without the .h) that declares that functionality, such as arpa/inet, asl, copyfile, dispatch, dyld, errno, getopt, glob, grp, ifaddrs, launch, membership, netdb, netinet/in, notify, objc, paths, pwd, readline, removefile, signal, spawn, sqlite3, stdio, stdlib, string, sys/fcntl, sys/file, sys/ioctl, sys/mount, sys/param, sys/resource, sys/socket, sys/stat, sys/sysctl, sys/time, sys/times, sys/types, sys/wait, sys/xattr, syslog, time, unistd, uuid/uuid, vImage, vecLib, vmnet, xpc, and zlib.

When you import frameworks, the system consults bridge support files. In addition to built-in frameworks and libraries, you can import any framework that has bridge support by passing the full path to the framework, as in the following example:

ObjC.import('/Library/Frameworks/Awesome.framework')

Data Types

The primitive JavaScript data types are mapped to C data types. For example, a JavaScript string maps to char *, while a JavaScript integer maps to int. When using an ObjC API that returns a char *, you'll get a JS string.

Primitive JavaScript data types will be automatically converted to ObjC object types when passed as an argument to an ObjC method that is expecting an object type. For example, a JS string will be converted to an NSString object if that is what the method signature says the argument should be typed as.

Note, however, that ObjC object types that are returned by ObjC methods are never automatically converted to primitive JavaScript data types.

Instantiating Classes and Invoking Methods

All classes are defined as properties of the $ object. Methods of ObjC objects are invoked in one of two ways, depending on whether the method takes arguments.

If the ObjC method does not take arguments, then you invoke it by accessing the JavaScript property with that name. This example instantiates an empty mutable string.

str = $.NSMutableString.alloc.init

If the ObjC method does take arguments, then you invoke it by calling the JavaScript method (function-typed property) named according to the JSExport convention; the letter following each ":" is capitalized, and then the ":"s are removed. This example instantiates an NSString from a JavaScript string and writes it to a file.

str = $.NSString.alloc.initWithUTF8String('foo')
str.writeToFileAtomically('/tmp/foo', true)

If you call a method, such as -intValue, that returns a C data type rather than an object, then you'll get back a primitive JavaScript data type. This example returns the primitive JavaScript integer, 99.

$.NSNumber.numberWithInt(99).intValue

Accessing ObjC Properties

ObjC properties are also accessed through JavaScript properties, similar to how you invoke methods that don’t have arguments.

When a property of a bridged object is accessed, the list of ObjC properties is first consulted, and if a property with that name exists, then the appropriate getter or setter selector for that property is used. If an ObjC property with that name does not exist on the class, then the property name is used as the method selector.

To define a property using a custom getter name, you can use either the property name or the getter name, and get the same result.

task = $.NSTask.alloc.init
task.running == task.isRunning

Also, unlike argument-less methods, bridged object properties that map to ObjC properties can also be set to (assuming the property is read/write). The following two lines are equivalent because launchPath is defined as an ObjC property.

task.launchPath = '/bin/sleep'
task.setLaunchPath('/bin/sleep')

Wrapping and Unwrapping

The ObjC.wrap() method can be used convert a primitive JavaScript data type into an ObjC object type, and then wrap that ObjC object in a JavaScript wrapper.

The ObjC.unwrap() method can be used to convert a wrapped ObjC object into a primitive JavaScript data type.

The ObjC.deepUnwrap() method can be used to recursively convert a wrapped ObjC object into a primitive JavaScript data type.

The $() operator is a convenient alias for ObjC.wrap(), and is meant to be similar to the @() boxing operator in ObjC. For example, $("foo") returns a wrapped NSString instance, and $(1) returns a wrapped NSNumber instance.

The .js property of wrapped ObjC objects is a convenient alias for ObjC.unwrap(). For example, $("foo").js returns "foo".

Constants and Enumerations

Constants and enums are defined as properties of the $ object.

$.NSNotFound
$.NSFileReadNoSuchFileError
$.NSZeroRect

Functions

Functions are also defined as properties of the $ object.

$.NSBeep()
$.NSMakeSize(33, 55)

Variadics

When you use functions and methods that accept variadic arguments, every variable argument passed in must be the same type as the last declared parameter. For example, printf must receive %s arguments after the initial format string.

ObjC.import('stdio')
$.printf('%s %s %s', 'JavaScript', 'for', 'Automation')

NSLog has a final declared parameter type of %@ and must receive %@ arguments after the initial format string.

$.NSLog('%@ %@ %@', $('JavaScript'), $('for'), $('Automation'))

When you create an NSArray using arrayWithObjects, nil termination is handled for you. Don’t try to terminate it yourself.

$.NSArray.arrayWithObjects($('JavaScript'), $('for'), $('Automation'))

Structs

Structs are represented as generic JavaScript objects, where the struct field names are the property names.

$.NSMakeRect(0, 0, 1024, 768)

Result: {"origin":{"x":0,"y":0},"size":{"width":1024,"height":768}}

Nil

In ObjC, nil is a valid message target. Sending a message to a nil object returns nil again. This behavior is different in JavaScript, which does not allow methods to be called on undefined.

To create a JavaScript object that represents nil, call the $() function with no arguments.

nil = $()

To determine if a bridged object is actually wrapping a nil pointer, you can call the isNil() method.

if (o.isNil()) { ... }

Implicit Pass-by-Reference

This boxed nil can be passed as an argument to an ObjC method that expects an argument passed by reference; the runtime will automatically replace the inner nil pointer with the pointer returned by reference.

error = $()
fm = $.NSFileManager.defaultManager
fm.attributesOfItemAtPathError('/DOES_NOT_EXIST', error)
if (error.code == $.NSFileReadNoSuchFileError) {
    // ...
}

Explicit Pass-by-Reference

The Ref class can be used to pass arguments that ObjC expects to be passed by reference. The referenced value can be retrieved by accessing the 0 property of the Ref.

function testExplicitPassByReference(path) {
    ref = Ref()
    fm = $.NSFileManager.defaultManager
    exists = fm.fileExistsAtPathIsDirectory(path, ref)
    if (exists) {
        isDirectory = ref[0]
        return (path + ' is ' + (isDirectory ? '' : 'not ') + 'a directory; ')
    }
    else {
        return (path + ' does not exist\n')
    }
}
 
output = testExplicitPassByReference('/System')
       + testExplicitPassByReference('/mach_kernel')
       + testExplicitPassByReference('/foo')

Result: /System is a directory; /mach_kernel is not a directory; /foo does not exist

Ref

To access the byte values of Ref objects, index them into like arrays.

ref = Ref('id*')
ref[0] = $('foo')
if (ref[0] == $('foo')) { ... }

When you need to pass an opaque pointer and check whether you got that opaque pointer back, you instantiate a void Ref and use the equals method.

CONTEXT = Ref('void')
 
ObjC.registerSubclass({
    name: 'MyObject',
    properties: { foo: 'id' },
    methods: {
        'startObserving': {
            types: ['void', []],
            implementation: function (obj) {
                this.addObserverForKeyPathOptionsContext(
                    this,
                    'foo',
                    ($.NSKeyValueObservingOptionNew | $.NSKeyValueObservingOptionOld),
                    CONTEXT
                );
            },
        },
        'observeValueForKeyPath:ofObject:change:context:': {
            types: ['void', ['id', 'id', 'id', 'void *']],
            implementation: function(keyPath, observedObject, change, context) {
                $.NSLog(Automation.getDisplayString(Ref.equals(context, CONTEXT)))
            }
        }
    }
})

Function Binding

When frameworks are imported, only the C functions specified in the bridge support files are made available. To access other C functions that are linked into the host application, a script may bind them into the JavaScript environment. Call the bindFunction() method and specify the return and argument types of the function.

ObjC.bindFunction('pow', ['double', ['double', 'double']])
$.pow(2,10)

Result: 1024

Types

Here are some of the allowed strings for specifying types of properties, method and function arguments, and return values: void, bool, float, double, unsigned char, char, unsigned short, short, unsigned int, int, unsigned long, long, long double, class, selector, string, pointer, block, function, and id.

Subclassing

Subclasses of Objective-C objects can be registered from JavaScript.

ObjC.registerSubclass({
    name: 'AppDelegate',
    superclass: 'NSObject',
    protocols: ['NSApplicationDelegate'],
    properties: {
        window: 'id'
    },
    methods: {
        'applicationDidFinishLaunching:': function (notification) {
            $.NSLog('Application finished launching');
        }
    }
})

If no superclass is given, the object will implicitly subclass NSObject. When you override a method, specifying the method types is optional, but if the types are present, they must match those of the superclass's method.

Here is an example of a custom init method and a custom method with its types declared.

ObjC.registerSubclass({
    name: 'MyClass',
    properties: {
        foo: 'id'
    },
    methods: {
        init: function () {
            var _this = ObjC.super(this).init;
            if (_this != undefined) {
                _this.foo = 'bar'
            }
            return _this
        },
        baz: {
            types: ['void', []],
            implementation: function () {
                this.foo = 'quux'
            }
        }
    }
})

Automation.getDisplayString()

The Automation object has a getDisplayString method that allows you to pass in an object and print it as source text or a human-readable display string.

foo = $('bar')
Automation.getDisplayString(foo)

Passing in true as the second parameter to getDisplayString will print the object as a display string instead of source text.

Automation.getDisplayString(foo, true)