7 AppleScript
Cameron Kaiser edited this page 2019-04-01 01:31:01 +00:00

AppleScript support is experimental and may change in future versions of TenFourFox. This document refers to the AppleScript dictionary in the latest version of TenFourFox.

TenFourFox FPR13 and later can be controlled via AppleScript. Using AppleScript, you can open and close browser windows and tabs, read selected text content for further handling, visit URLs, and access plaintext and HTML versions of their contents. If you enable GUI scripting, you can even send click and keyboard events to webpages and many TenFourFox internal widgets and interact with them programmatically. However, because of Firefox/Gecko's cross-platform nature, TenFourFox's AppleScript support uses slightly different idioms. This page assumes you have basic knowledge of AppleScript commands and concepts.

TenFourFox's AppleScript dictionary defines an application, which contains windows and browser windows (with a 1:1 correspondence to each other). Each browser window contains a variable number of tabs. The dictionary is viewable from within Script Editor.app.

Because window and network operations are asynchronous in TenFourFox, synchronization properties are provided to let a script know when it is safe to manipulate a property or fetch data. Using these properties in your script is mandatory. If you don't, at best you'll get an error, or at worst events or manipulation may go to the wrong window or tab and cause difficult-to-predict effects.

In the examples below, substitute your TenFourFox application name for TheApp (such as TenFourFoxG5).

Example scripts

In the Event Log tab of Script Editor, this script shows every tab in every browser window, including its title (the name property) and URL (the URL property). Note that before querying each tab, it checks a synchronization property called busy. busy returns true when the tab is opening or currently downloading data. You should not change the tab's properties while it is busy. You can read properties while a tab is busy, but it may not be current, so this script will wait regardless even though it isn't changing anything.

tell application "TheApp"
	repeat with w in every browser window
		repeat with t in every tab of w
			repeat while (t is busy)
				delay 1
			end repeat
			get name of t
			get URL of t
		end repeat
	end repeat
end tell

This script iterates through browser window objects. In the TenFourFox object model, only browser windows (not just plain windows) contain tabs. Every browser window corresponds to a window at a 1:1 relationship, but only certain operations can be done on each one due to limitations necessary to remain compatible with Tiger.

The next example opens a new window, opens a second tab within that window, makes it active, browses to the TenFourFox main page and displays the plaintext version of it (the tab's plaintext property) in the Event Log tab of the Script Editor. It then closes the window.

Notice that after commanding the browser to create a new browser window, the script checks another synchronization property called opening. opening returns true while the window is still in the process of being initialized. You should not manipulate the new window until opening is false. opening is at the application level, not the browser window level.

tell application "TheApp"
	activate
	make new browser window
	repeat while (opening)
		delay 1
	end repeat
	tell front browser window
		set t to make new tab
		repeat while (t is busy)
			delay 1
		end repeat
		set current tab to t
		set URL of t to "https://www.floodgap.com/software/tenfourfox/"
		repeat while (t is busy)
			delay 1
		end repeat
		get plaintext of t
		close
	end tell
end tell

The plaintext property returns the formatted plaintext of the rendered contents of that tab. If you want the actual HTML contents of the tab, then read the HTML property.

The close command has special powers: even if multiple tabs are open and you have the TenFourFox preference set to warn you if you are closing a window with multiple tabs, it will still close instantly from within a script. We do assume, after all, that you know what you're doing.

The next example opens a New York city traffic webcam image (the westbound Long Island Expressway) in a new browser, makes the window fullscreen, and reloads it every 15 seconds. Since the current tab is assumed, you can use the shorthand to set the URL shown here. We don't care about the contents of the tab, so we can just reload when the timer is up.

tell application "TheApp"
	activate
	make new browser window
	repeat while (opening)
		delay 1
	end repeat
	tell front browser window
		set fullscreen to true
		set URL to "https://511ny.org/map/Cctv/428834--20"
		repeat
			delay 15
			reload current tab
		end repeat
	end tell
end tell

A script like this could be handy to display a changing web page in a separate display, or for a non-interactive information kiosk. It could be modified to make it effectively display a programmed slideshow of sites by changing the URL as well.

To stop this script, Alt-Tab to the Script Editor and click Stop, then return to TenFourFox and press Command-W to close the window.

You can also get what the user currently has selected in the window which your script can act on further. This script displays whatever text you have selected/highlighted in a dialogue box.

tell application "TheApp"
	repeat while (current tab of front browser window is busy)
		delay 1
	end repeat
	display dialog ("" & selected of current tab of front browser window)
end tell

This could be the basis of things you put in the Scripts menu for interactive operations on web page text. If images are selected, then their "alt texts" are used instead.

AppleScript-JavaScript bridging

TenFourFox's AppleScript support contains an AppleScript-to-JavaScript "bridge." Tabs can be told to execute embedded JavaScript in your AppleScript (run JavaScript), and the embedded scripts can return values back to the main AppleScript. This allows you to manipulate the page in the tab and query or change the DOM. Consider the following script:

tell application "TheApp"
	tell front browser window
		set URL of current tab to "https://www.google.com/"
		repeat while (current tab is busy)
			delay 1
		end repeat
		tell current tab
			run JavaScript "let f = document.getElementById('tsf');f.q.value='tenfourfox';f.submit();"
		end tell
		repeat while (current tab is busy)
			delay 1
		end repeat
		tell current tab
			run JavaScript "return document.getElementsByTagName('h3')[0].innerText + ' ' + document.getElementsByTagName('cite')[0].innerText"
		end tell
	end tell
end tell

This script goes to Google and searches for tenfourfox, then returns the name and location of the first result. You should check busy if you make any changes to the state of the page before continuing.

Embedded JavaScript runs at chrome level, and within a Function context. There is no global object, so you must explicitly refer to document and window (e.g., it's not alert(), it's window.alert()). However, any supported document or window function can be run by the bridge. Embedded JavaScript may return at most one value.

While you can embed subfunctions within your code, you should not attempt to make the state of your script persist between calls to run JavaScript, nor attempt to make any objects you create permanent in the DOM; this is not supported, and probably won't work reliably.

GUI scripting

When GUI scripting is enabled (make sure Enable access for assistive devices is checked in System Preferences and/or check Enable GUI Scripting in AppleScript Utility.app), you can manipulate some TenFourFox widgets and send clicks and keyboard events to the application. There is one irregularity in that the System Events target is TenFourFox always, not the filename of the actual browser (don't change that in these examples, just TheApp as before).

For example, this script will open the Downloads window. Note that the menu manipulation is just generically sent to TenFourFox, no matter what the name of the browser application is.

tell application "TheApp"
	activate
end tell
tell application "System Events"
	tell process "TenFourFox"
		tell menu bar 1
			tell menu bar item "Tools"
				tell menu "Tools"
					click menu item "Downloads"
				end tell
			end tell
		end tell
	end tell
end tell

Any click is forwarded to the application or underlying web page. However, since click events are global, you need a predictable location so you know what to click on. You can do that by enabling fullscreen mode as above, or you can move the window to a predicted location like this:

tell application "TheApp"
	activate
	make new browser window
	repeat while (opening)
		delay 1
	end repeat
	tell front window
		set s to bounds
		set the bounds to {0, 22, (item 3 of s) - (item 1 of s), (item 4 of s) - (item 2 of s)}
	end tell
end tell
tell application "System Events"
	tell process "TenFourFox"
		click at {300, 100}
	end tell
end tell

Notice that we create a new browser window, but it's the underlying window itself that we move by changing the bounds. Alternatively, now that we have the bounds coordinates, you could simply recalculate the click's coordinates instead.

Don't do these things

Browser window indexes are by Z-order, not the order they were opened. You should avoid holding references to windows, especially ones you just made (e.g., "set w to make new browser window" will cause problems, because AppleScript will cache the incompletely built window), and you should avoid referring to them by index. For best results, simply say front browser window and you'll be guaranted to get the one in front.

Tabs can be referred to by index, but the index may change when the tab is closed, so you should avoid holding references to them as well. For example, don't do this:

(* this acts weird *)
repeat with t in every tab of front browser window
	close t
end repeat

You'd do better simply closing the front browser window, which will close all tabs at once and is more efficient anyway. In a like fashion, because the browser window indexes are variable, trying to do a repeat with w in every browser window followed by close to close all browser windows will probably cause an error.