Safari

Safari

JavaScript is one of the core technologies of the Web, and you can run JavaScript code in Chrome and Safari browsers from JXA scripts. This allows you to extract parts of HTML documents or to modify them programmatically.

Windows and tabs#

As usual, you get the application object by calling the Application method with the name of the application:

const SafariApp = Application('Safari');

Both browsers use a similar object hierarchy with windows and tabs. And they also need to know in which tab to execute the code you send to them. To get the first tab of the first window, you can use

const tab = SafariApp.windows[0].tabs[0];

Or to get the currently active tab,
const tab = SafariApp.windows[0].currentTab;

Running code in the browser#

To execute JavaScript code in Safari, you call the method doJavaScript passing it the code as a string and the tab: SafariApp.doJavaScript(`the code`, {in: SafariTab}).

In a simple example, JavaScript is used to reload whatever web page is currently displayed in the active tab:

// Running JavaScript code in Safari
const Safari = Application('Safari');
const tab = Safari.windows[0].currentTab();
Safari.doJavaScript('window.location.reload()', {in: tab});

You can programmatically search with Google:

// Search Google programmatically with Safari
const Safari = Application('Safari');
const tab = Safari.windows[0].currentTab();
SafariTab.url = 'https://google.com/';
Safari.doJavaScript(`document.querySelector('textarea').value = "JXA";
document.querySelector('form').submit();`{in: tab})

This code sets the content of the only textarea element on the web page to “JXA” and than submits the only form.

You can also use JavaScript to modify a web page. If, for example, a page displays many irritating images, you can turn them off like this:

// Turn off images in a web document in Safari
Safari.doJavaScript(`document.querySelectorAll('img').forEach(i => i.display = "none")`)

Returning values from Safari#

To return a value from Safari to the script using doJavaScript, you have to make this value the last statement of the code passed to Safari.

// Return simple value from JavaScript executed in Safari
Safari.doJavaScript('5+4', {in: tab});

Will return the number 9 to the calling script.

Do not use a return statement to return values to the calling script. Instead, ensure the value of the last statement executed is what you want to return.

If, for example, you wanted to extract the URLs from all links on a web page, you could run this JavaScript code in Safari:

[...document.querySelectorAll('a')].map(a => a.href);

That will build an Array of Strings. But how do you get these strings back into the calling script? The only data types which can be passed back from Safari to the calling script are strings and numbers. Therefore, you have to convert the Array to a String using `JSON.stringify``:

JSON.stringify([...document.querySelectorAll('a')].map(a => a.href));

In your calling script, use JSON.parse to convert the string back to an Array:

// Return a compound object from JavaScript run in Safari
const result = Safari.doJavaScript(`JSON.stringify([...document.querySelectorAll('a')].map(h2 => h2.href))`, {in: tab});
const arrayOfURLs = JSON.parse(result);

Convert HTML to Markdown in Safari#

With the help of a third-party JavaScript library, you can convert a web document in Safari to Markdown. The library is turndown and available on GitHub where you’ll also find documentation on the parameters you can use to customize the conversion.

The obvious choice to handle the conversion would be to inject the library into the web document using a script element. This does, however, only work in some cases: As soon as the author of the document as defined a strict content security policy (CSP), Safari will refuse to load the library.

Therefore, the script below takes another approach. It downloads the entire script first from a public URL into a variable (turndownSrc). After that, it concatenates this string with code to parametrize turndown and run the conversion. This JavaScript code is then injected into Safari with doJavaScript and the conversion result returned as explained before.

At the beginning of the code, you’ll see three lines downloading the turndown library. They make use of the Objective-C framework. You’ll find in-depth details about interfacing JavaScript with Objective-C in another section.

// Convert content of current Safari tab to Markdown

(() => {
  const Safari = Application('Safari');
  const tab = Safari.windows[0].currentTab();

  const turndownURL = "https://unpkg.com/turndown/dist/turndown.js";
  const nsURL = $.NSURL.URLWithString($(turndownURL));
  const data = $.NSData.dataWithContentsOfURL(nsURL);
  const turndownSrc = $.NSString.alloc.initWithDataEncoding(data, $.NSUTF8StringEncoding).js;

  const scriptSrc = turndownSrc + 
    `
  var turndownService = new TurndownService({
     headingStyle: 'atx',
     codeBlockStyle: 'fenced',
     bullet: '-'
     });
     /* Ignore 'script' and 'nav' elements to keep 
        markdown cleaner */
     turndownService.remove('script');
     turndownService.remove('nav');
     turndownService.turndown(document.body);
`;
 const result = Safari.doJavaScript(`${scriptSrc}`, {in: tab});
// Do whatever you want with the Markdown text in 'result'
})()

The example skips over script and nav elements in the HTML document. You might want to exclude other elements as well, depending on the HTML document you’re converting.