Release Notes

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

Contents:

Introduction

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

OSA Component

The JavaScript OSA component implements JavaScript for Automation. The component can be used from Script Editor, the global Script Menu, in the Run JavaScript Automator Action, applets/droplets, the osascript command-line tool, the NSUserScriptTask API, and everywhere else other OSA components, such as AppleScript, can be used. This includes Mail Rules, Folder Actions, Address Book Plugins, Calendar Alarms, and Message Triggers.

Scripting Dictionaries

Scripting dictionaries detail the object model of applications. Terminology in scripting dictionaries is mapped to valid JavaScript identifiers 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. You can open a scripting dictionary in Script Editor by using the File > Open Dictionary command or the Window > Library command.

Object Specifiers

Many of the 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. It is important to note that this object is not the actual value of the external entity's property; it is a reference to the object. To retrieve the value of a referenced property, an additional step is required, described in the Getting and Setting Properties section.

Accessing Applications

Applications can be accessed in these ways:

  1. Name

    • Application('Mail')
  2. Bundle ID

    • Application('com.apple.mail')
  3. Path

    • Application('/Applications/Mail.app')
  4. Process ID

    • Application(763)
  5. Getting the application that is running the script

    • Application.currentApplication()

Syntax Examples

These are examples of the syntax for interacting with objects:

  1. Accessing properties

    • Mail.name
  2. Accessing elements

    • Mail.outgoingMessages[0]
  3. Calling commands

    • Mail.open(...)
  4. Creating new objects

    • Mail.OutgoingMessage(...)

Getting and Setting Properties

Properties of scripting objects are accessed 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, you 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'

Element Arrays

Elements are accessed using square brackets. The returned values are object specifiers, with their own properties and elements, that refer to the array elements. They can be accessed by:

  1. Index

    • window = Mail.windows[0]
  2. Name

    • window = Mail.windows['New Message']
  3. ID

    • window = Mail.windows['#412']

Filtering Arrays

To filter arrays, returning only some of the elements, you can used the special whose command for arrays. You pass in an object of named property values you would like to match on the elements.

  • jsEmails = Mail.outgoingMessages.whose({subject:'JavaScript'})

Note that the matching is literal (meaning "JavaScript" will not match "JavaScript for 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 are passed as the first and only argument. When the direct parameter if optional, you can pass nothing to the command, or you can pass null as the first parameter if you will pass in named parameters.

  1. Command with no arguments

    • message.open()
  2. Command with direct parameter

    • Mail.open(message)
  3. Command with named parameters

    • response = message.reply({
    • replayAll: true,
    • openingWindow: false
    • })
  4. Command with direct parameter and named parameters

    • Safari.doJavaScript('alert("Hello world")', {
    • in: Safari.windows[0].tabs[0]
    • })

Creating Objects

You can create new objects by calling class constructors as functions. Optionally, you can pass in an object of properties to set on the newly created object. Once you have made the object, you must push it onto an array in the application for it to be created. Before the object is contained by an array within the application, it doesn't exist in the application.

  1. Creating a new object

    • message = Mail.OutgoingMessage()
    • Mail.outgoingMessages.push(message)
  2. Creating a new object with properties

    • message = Mail.OutgoingMessage({
    • subject: 'Hello world',
    • visible: true
    • })
    • Mail.outgoingMessages.push(message)

Once an object has been created in an application (pushed onto the appropriate array), it can be interacted with just like any of the already existing objects in the application.

  • message = Mail.OutgoingMessage()
  • Mail.outgoingMessages.push(message)
  • message.subject = 'Hello world'

Note: Class constructors are title cased.

Scripting Additions

Scripting additions (plugins for scripts) can be used to enhance the functionality of applications. The OS has a set of standard scripting additions available. These offer things like speaking text and user interaction dialogs. 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'
  • })

ObjectSpecifier

The ObjectSpecifier object can be used to check if an object is an object specifier:

  • Mail.inbox.messages[0] instanceof ObjectSpecifier == true

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)

Libraries

You can use scripts as libraries by storing them in ~/Library/Script Libraries/.

If you have a script library "toolbox.scpt":

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

a script can use the library like this:

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

Note: Any code outside of functions in a library will be executed when the library is instantiated.

Applets

You can make standalone, double-clickable applications (called applets) by writing a script in Script Editor and saving it as an application. Applets have a certain set of events that you can create handlers for:

  1. When the applet is run:

    • function run() {...}

    Note: Any code outside of functions in an applet will be executed when the applet is run.

  2. When the applet (droplet) is told to open documents (done by dropping documents onto the applet):

    • function openDocuments(docs) {...}
  3. When the applet is told to print documents:

    • function printDocuments(docs) {...}

    Note: The argument to the openDocuments and printDocuments handler will be an array of strings containing the file paths.

  4. The idle handler allows you to do periodic processing

    • function idle() {...}
  5. When the applet is reopened:

    • function reopen() {...}
  6. When the applet is quit:

    • function quit() {...}

    Note: Returning false from the quit handler is the only way to cause the applet not to quit. Having no quit handler or returning any value other than false (including not returning a value) will cause the applet to quit as it normally would.

UI Automation

You can script the interface of applications by using the System Events application. Explore the scripting dictionary of System Events in Script Editor (specifically the Processes Suite) to see what exactly can be interacted with.

Here is an example of using 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 offers powerful utility such as accessing the file system and building 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. You can make additional frameworks and libraries available using the ObjC.import() method. BridgeSupport files are consulted when importing frameworks. For instance, to use the NSBeep() function, which is not present in Foundation, you can import the Cocoa framework:

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

Data types

The primitive JavaScript data types are mapped to C data types. For instance, 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. Note: Longs will be converted to strings in JavaScript.

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 instance, a JS string will be converted to an NSString 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. For instance, this 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. For instance, this 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. For instance, the following returns the primitive JavaScript integer, 99.

  • $.NSNumber.numberWithInt(99).intValue

Accessing ObjC properties

ObjC properties are also accessed through JavaScript properties, similar to how argument-less methods are invoked.

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.

If a property is defined 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 readwrite). 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 instance, $("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 instance, $("foo").js returns "foo".

Constants and enums

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)

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 is different from JavaScript, which does not allow methods to be called on undefined.

In order to create a JavaScript object that represents nil, you just call the box 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 magically 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

Function binding

When frameworks are imported, only the C functions specified in the BridgeSupport 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. To do so, you must 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, and 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.

Note: Struct type names, such as NSRect, are allowed, but id must be used for objects, not actual class names.

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'
  • }
  • }
  • }
  • })

Note: DO NOT assign to the this variable or any of its properties in init methods. Assign to a different variable (_this is used above), use that variable to initialize properties, and then return it. In all other methods, the standard this variable should be used. In JavaScript, you are not allowed to reassign the this variable. Objective-C classes are allowed to return from their init methods a different object than that on which the method was called.