The Firefox integration script revisited

Dealing with Firefox I found that its integration script did not handle file URLs properly.
Why file URLs? Most people are probably not aware that you can use most browsers (among them iCab and especially Firefox) as one-stop-document-previewing-app.
I. e. open a blank window and drag and drop pictures, text files, videos, pdfs and many other file types on the big white void or on the browser’s icon in the dock to view them. [See my post on iCab in this forum.]

When I checked the script out I was astonished about its many shortcomings. I found
• completely irrelevant and not functional code
• experimental remnants
• completely outdated code
• hideous verbosity for the simplest things
• damaging code
• missing functionality
The code seems to have never been updated in recent years.

The details:
• irrelevant code
The following code is completely irrelevant, i. e. functionless:

property NSShiftKeyMask : a reference to 131072
property NSAlternateKeyMask : a reference to 524288
property NSControlKeyMask : a reference to 262144
property NSEvent : a reference to current application's NSEvent

set modifier_down to true
repeat while modifier_down
	set modifier_flags to NSEvent's modifierFlags()
	set option_down to ((modifier_flags div (get NSAlternateKeyMask)) mod 2) = 1
	set shift_down to ((modifier_flags div (get NSShiftKeyMask)) mod 2) = 1
	set control_down to ((modifier_flags div (get NSControlKeyMask)) mod 2) = 1
	set modifier_down to option_down or shift_down or control_down
end repeat

Astonishingly, the same code block is in various other integrations scripts and there also does nothing.

• this code is largely useless

	set firefoxVer to version
	considering numeric strings
		set is87 to firefoxVer ≥ "87"
		set is72 to firefoxVer ≥ "72"
	end considering
	if is72 and myName ends with " - Mozilla Firefox" then
		if length of myName > 18 then
			set myName to text 1 through -19 of myName
		else
			set myName to ""
		end if
	end if

It tried to cope with quirks of nowadays completely outdated versions of Firefox (and not in a particularly elegant way.) Firefox’s current v. 153 supports OS 10.15, the same as the current version of Hookmark, so people can and should update their browser (2026.07.23)
BTW: Hookmarks info.plist designates OS 10.13 as minimal OS, whereas its website states 10.15.

• The following code is incredibly tedious, a maintenance nightmare, but above all outdated (probably for years?) and no longer works:

try
		set theUrl to ""
		tell application "Firefox"
			tell application "System Events"
				if toolbar "Navigation" of first group of front window of application process "Firefox" exists then
					set theUrl to get value of UI element 1 of combo box 1 of toolbar "Navigation" of first group of front window of application process "Firefox"
				else if toolbar "导航" of first group of front window of application process "Firefox" exists then
					set theUrl to get value of UI element 1 of combo box 1 of toolbar "导航" of first group of front window of application process "Firefox"
					
				else if toolbar "Área de navegación" of first group of front window of application process "Firefox" exists then
					set theUrl to get value of UI element 1 of combo box 1 of toolbar "Área de navegación" of first group of front window of application process "Firefox"
					
				else if toolbar "Navigazione" of first group of front window of application process "Firefox" exists then
					set theUrl to get value of UI element 1 of combo box 1 of toolbar "Navigazione" of first group of front window of application process "Firefox"
					
				else if toolbar "ナビゲーション" of first group of front window of application process "Firefox" exists then
					set theUrl to get value of UI element 1 of combo box 1 of toolbar "ナビゲーション" of first group of front window of application process "Firefox"
					
				else if toolbar "導覽" of first group of front window of application process "Firefox" exists then
					set theUrl to get value of UI element 1 of combo box 1 of toolbar "導覽" of first group of front window of application process "Firefox"
					
				else if toolbar "Navegação" of first group of front window of application process "Firefox" exists then
					set theUrl to get value of UI element 1 of combo box 1 of toolbar "Navegação" of first group of front window of application process "Firefox"
					
					
				end if
				
				if theUrl is not "" then
					return ("[" & myName & "](" & theUrl) & ")"
				end if
				
				
			end tell
			
			
		end tell
		
		
	end try

This code could have been refactored into this more maintainable form:

set theUrl to ""
tell application "System Events"
	tell process "Firefox"
		set toolbarNames to {"Navigation", "导航", "Área de navegación", "Navigazione", "ナビゲーション", "導覽", "Navegação"}
		repeat with aName in toolbarNames
			try
				set theUrl to get value of UI element 1 of combo box 1 of toolbar aName of first group of front window
			end try
			if theUrl is not "" then return ("[" & myName & "](" & theUrl) & ")"
		end repeat
	end tell
end tell

39 lines of completely useless code since the UI element structure in Firefox has changed long ago, maybe years. Nobody ever realized this, since it fails silently. Even if it worked it would work only for the seven language localisations given, since the code chose to use labeled (and - courtesy of Mozilla - localised) references. To avoid this indexed references should and can be used (see my updated code at the end of this post.)

This code snippet

tell application "Firefox"
			tell application "System Events"

is completely meaningless here, clearly an experimental remnant which did not get cleared out. Instead UI references should be pointed specifically to Firefox (instead of some “front application”) whithin the “System Events” tell block for stability reason, like this:

tell application "System Events"
	tell process "Firefox"

Now what is left and what keeps the code working at all is fallback code highlighting and copying the URL from Firefoxes URL input field to the clipboard. This approach is forced to use the clipboard but does not even bother to save and restore the previous clipboard contents. Instead it rudely wipes out everything that is there. See my version of the script storing and restoring the previous clipboard content to make the user experience completely transparent.
Apart from that the code does not distinguish between possible entries in the URL entry field:
• Web URLs are handled, but the two code variants return URLs with and without the “https://” prefix.
• File URLs where not properly handled. I added support in my script below.
• Search strings for web searches should be relegated.

My version of the script, free to use (note the various inline comments):

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions


-- classes, constants, and enums used
property NSPasteboard : a reference to current application's NSPasteboard
property NSPasteboardItem : a reference to current application's NSPasteboardItem
property NSString : a reference to current application's NSString
property NSMutableCharacterSet : a reference to current application's NSMutableCharacterSet
property NSMutableArray : a reference to current application's NSMutableArray

-- courtesy Shane Stanley https://www.macscripter.net/t/styled-text-lib/69970/2
on fetchStorableClipboard()
	set aMutableArray to NSMutableArray's array() -- used to store contents
	-- get the pasteboard and then its pasteboard items
	set thePasteboard to NSPasteboard's generalPasteboard()
	set theItems to thePasteboard's pasteboardItems()
	-- loop through pasteboard items
	repeat with i from 1 to count of theItems
		-- make a new pasteboard item to store existing item's stuff
		set newPBItem to NSPasteboardItem's alloc()'s init()
		-- get the types of data stored on the pasteboard item
		set theTypes to (item i of theItems)'s |types|()
		-- for each type, get the corresponding data and store it all in the new pasteboard item
		repeat with j from 1 to count of theTypes
			set theData to ((item i of theItems)'s dataForType:(item j of theTypes))'s mutableCopy()
			if theData is not missing value then
				(newPBItem's setData:theData forType:(item j of theTypes))
			end if
		end repeat
		-- add new pasteboard item to array
		(aMutableArray's addObject:newPBItem)
	end repeat
	return aMutableArray
end fetchStorableClipboard

on putOnClipboard:theArray
	-- get pasteboard
	set thePasteboard to NSPasteboard's generalPasteboard()
	-- clear it, then write new contents
	thePasteboard's clearContents()
	thePasteboard's writeObjects:theArray
end putOnClipboard:

-- copied from Michael Tsai's Eagle Filer integration script
on urlEncode(input)
	tell NSString to set rawUrl to stringWithString_(input)
	set charset to NSMutableCharacterSet's URLQueryAllowedCharacterSet's mutableCopyWithZone:(missing value)
	charset's removeCharactersInString:"?"
	set theEncodedURL to rawUrl's stringByAddingPercentEncodingWithAllowedCharacters:charset
	return theEncodedURL as Unicode text
end urlEncode

-- main
tell application "Firefox"
	activate
	delay 0.05 -- is this needed? shouldn't be, no UI scripting here; and 5/100s?? Shouldn't achieve anything meaningful, just vodoo
	set myName to get name of front window
	
	-- bail out if script (accidentally) got executed without a window being displayed
	if myName is "" then return --  --nothing; "" will be the name string if no window is open
	
	set firefoxVer to version -- used for notification on UI scripting failure
	
	-- the rest down to "end tell" seems to be practically obsolete (attempting to support antique versions of Firefox)
	-- the current FF version 153 supports OS 10.15, the same as the current version of Hookmark, so people can and should update their browser (2026.07.23) 
	--considering numeric strings
	--	--set is87 to firefoxVer ≥ "87" -- not needed
	--	set is72 to (firefoxVer ≥ "72" and firefoxVer < "87")
	--end considering
	--if is72 and myName ends with " - Mozilla Firefox" then
	--	if length of myName > 18 then
	--		set myName to text 1 through -19 of myName
	--	else
	--		set myName to ""
	--	end if
	--end if
end tell

set myURL to ""

-- UI scripting: fast but application version dependent
tell application "System Events"
	tell process "Firefox"
		try -- valid for v. 153 and very likely several earlier versions
			tell group 2 of group 1 of toolbar 2 of group 1 of window 1
				-- combo box index changes on the fly according to the type and/or number of open tabs in front window etc.
				try
					set myURL to get value of combo box 2
				on error
					try
						set myURL to get value of combo box 1
					end try
				end try
			end tell
		end try
	end tell
end tell

-- any web URL if retrieved at this point does not begin with "https://"! If copy-via-clipboard is used below, it does; file URLs, however, properly begin with "file://" but "Mozilla Firefox" will be the default name for any tab displaying a file URL; ...

if myURL is "" then
	--  debugging helper
	tell application "Finder" to display notification "Hookmark UI Scripting is broken for Firefox version " & firefoxVer & ". Please inform the developer providing your Firefox version number. Using copy-via-clipboard approach instead."
	
	-- from here, get URL via clipboard: this approach works at least for any Firefox version since 2014 and probably earlier but is slower and possibly shakey
	
	-- backup clipboard contents for later restore
	set theClip to my fetchStorableClipboard()
	
	set the clipboard to "" -- empty clipboard avoiding error on ‘clipboard as string'
	tell application "System Events"
		tell process "Firefox"
			keystroke "lc" using {command down} -- highlight URL and copy combined into a single event, so no shakey delay needed in-between
		end tell
		delay 0.3
		keystroke (key code 53) -- esc to close the URL history popup menu and thus allow Firefox to transfer the copied URL to the system clipboard for retrival by this script
	end tell
	
	repeat 10 times -- poll clipboard for 5 seconds; should not be necessary with clipboard containing "" and after esc closed the popup menu
		-- give the code a chance not to fail at all, even if it takes half a second
		delay 0.5 -- why hog the CPU with delays of 0.1 when you are prepared to wait 5s?
		try
			set myURL to (the clipboard as string) -- this may error after 'set clipboard to {}', i. e. if clipboard is (still) empty, but not after 'set clipboard to ""', clipboard contains empty string
			if myURL is not equal to "" then
				exit repeat
			end if
		end try
	end repeat
	
	--set myURL to (the clipboard as string)
	my putOnClipboard:theClip -- restore backuped clipboard
end if

if myURL is "" then
	return -- nothing
else if myURL begins with "file://" then -- in this case myName will be "Mozilla Firefox" for any file
	set AppleScript's text item delimiters to "/"
	set myName to last item of (get text items of myURL) -- get the full file name instead
	set myURL to my urlEncode(myURL) -- the file URL may contain spaces etc. (?!), so it has to be encoded
	return ("[" & myName & "](" & myURL) & ")"
else if myURL does not contain " " and myURL contains "." then -- heuristical check that myURL is really a URL and not a search string for a web search
	if myURL does not contain "://" then set myURL to "https://" & myURL -- heuristical "repair"
	return ("[" & myName & "](" & myURL) & ")"
end if

This code is to make sure that the user releases all modifier keys before the get URL script is running. Otherwise, those keys might interfere with the script.

You are right the code is not elegant.

Some of our users still have very old computers. Not sure we can do that. Perhaps next year we will do some cleanup.

Hookmark does restore the clipboard after running the script, but not doing it in AppleScript.

Thank you very much for your script. We will integrate it with Hookmark.

OK… I stand corrected. Forced to do a lot of UI scripting I thought I now know every possible pitfall. Also never read about that. But you are absolutely right: the script tells the system to use cmd only, but Apple decides to honor the user’s paw position and cause havoc. Thanks Apple, for a hideous implementation.

Still, I dare to propose improved code. First thing coming to mind is that the repeat loop, on my old and sluggish machine gets called about 800 to 1200 times per second, a huge CPU hog because it is not tempered by a delay. A delay of 0.1s already brings things down to under 20 loops, 0.2 to under 10 - which clearly is sufficent and fast enough - the user decides when to move his paw anyway.
And since interference within the script at hand will “only” happen with the keystroke commands, why not wrap it into a handler and call it only when needed, and right before those. Stability is everything here.
I repost my revised code at the end of this post. Feel free, of course, to use what fits you.

I initially thought that this is what should happen. But does it work? On the other hand, it should be difficult to explain why it does not.

But do you support any of those old boxes with new releases of Hookmark? I think there is no need to support browsers for machines that Hookmark even doesn’t support. If Hookmark supports OS 10.15, the current version of Firefox is supposed to work. And browsers are critical software, both in terms of security and technological innovation. So supporting outdated software is not really a “favor” one should do to people.

BTW, I’d like to offer some “user support”: Should you leave something like my notification about informing the developer about an incompatibility with a newer or older browser AND someone really contacts you (the script will work anyway with the clipboard method), I’d be happy to provide the UI scripting code. But this only makes sense when a incompatibility really becomes positively known - without something like that nobody will ever know when the code brakes, which is, IMHO, quite a bit dissatisfactory. It’s practically impossible to check out each and every Firefox version. I tried a couple but some of them don’t even run any longer on newer OSes.

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions


-- classes, constants, and enums used
property NSPasteboard : a reference to current application's NSPasteboard
property NSPasteboardItem : a reference to current application's NSPasteboardItem
property NSString : a reference to current application's NSString
property NSMutableCharacterSet : a reference to current application's NSMutableCharacterSet
property NSMutableArray : a reference to current application's NSMutableArray

property NSShiftKeyMask : a reference to 131072
property NSAlternateKeyMask : a reference to 524288
property NSControlKeyMask : a reference to 262144
property NSEvent : a reference to current application's NSEvent

on waitForModifierRelease()
	repeat
		set modifier_flags to NSEvent's modifierFlags()
		set option_down to ((modifier_flags div (get NSAlternateKeyMask)) mod 2) = 1
		set shift_down to ((modifier_flags div (get NSShiftKeyMask)) mod 2) = 1
		set control_down to ((modifier_flags div (get NSControlKeyMask)) mod 2) = 1
		if (option_down or shift_down or control_down) then
			delay 0.2
		else
			exit repeat
		end if
	end repeat
end waitForModifierRelease

-- courtesy Shane Stanley https://www.macscripter.net/t/styled-text-lib/69970/2
on fetchStorableClipboard()
	set aMutableArray to NSMutableArray's array() -- used to store contents
	-- get the pasteboard and then its pasteboard items
	set thePasteboard to NSPasteboard's generalPasteboard()
	set theItems to thePasteboard's pasteboardItems()
	-- loop through pasteboard items
	repeat with i from 1 to count of theItems
		-- make a new pasteboard item to store existing item's stuff
		set newPBItem to NSPasteboardItem's alloc()'s init()
		-- get the types of data stored on the pasteboard item
		set theTypes to (item i of theItems)'s |types|()
		-- for each type, get the corresponding data and store it all in the new pasteboard item
		repeat with j from 1 to count of theTypes
			set theData to ((item i of theItems)'s dataForType:(item j of theTypes))'s mutableCopy()
			if theData is not missing value then
				(newPBItem's setData:theData forType:(item j of theTypes))
			end if
		end repeat
		-- add new pasteboard item to array
		(aMutableArray's addObject:newPBItem)
	end repeat
	return aMutableArray
end fetchStorableClipboard

on putOnClipboard:theArray
	-- get pasteboard
	set thePasteboard to NSPasteboard's generalPasteboard()
	-- clear it, then write new contents
	thePasteboard's clearContents()
	thePasteboard's writeObjects:theArray
end putOnClipboard:

-- copied from Michael Tsai's Eagle Filer integration script
on urlEncode(input)
	tell NSString to set rawUrl to stringWithString_(input)
	set charset to NSMutableCharacterSet's URLQueryAllowedCharacterSet's mutableCopyWithZone:(missing value)
	charset's removeCharactersInString:"?"
	set theEncodedURL to rawUrl's stringByAddingPercentEncodingWithAllowedCharacters:charset
	return theEncodedURL as Unicode text
end urlEncode

-- main
tell application "Firefox"
	activate
	delay 0.05 -- is this needed? shouldn't be, no UI scripting here; and 5/100s?? Shouldn't achieve anything meaningful, just vodoo
	set myName to get name of front window
	
	-- bail out if script (accidentall) got executed without a window being displayed
	if myName is "" then return --  --nothing; "" will be the name string if no window is open
	
	set firefoxVer to version -- used for notification on UI scripting failure
	
	-- the rest down to "end tell" seems to be practically obsolete (attempting to support antique versions of Firefox)
	-- the current FF version 153 supports OS 10.15, the same as the current version of Hookmark, so people can and should update their browser (2026.07.23) 
	--considering numeric strings
	--	--set is87 to firefoxVer ≥ "87" -- not needed
	--	set is72 to (firefoxVer ≥ "72" and firefoxVer < "87")
	--end considering
	--if is72 and myName ends with " - Mozilla Firefox" then
	--	if length of myName > 18 then
	--		set myName to text 1 through -19 of myName
	--	else
	--		set myName to ""
	--	end if
	--end if
end tell

set myURL to ""

-- UI scripting: fast but application version dependent
tell application "System Events"
	tell process "Firefox"
		try -- valid for v. 153 and very likely several earlier versions
			tell group 2 of group 1 of toolbar 2 of group 1 of window 1
				-- combo box index changes on the fly according to the type and/or number of open tabs in front window etc.
				try
					set myURL to get value of combo box 2
				on error
					try
						set myURL to get value of combo box 1
					end try
				end try
			end tell
		end try
	end tell
end tell

-- any web URL if retrieved at this point does not begin with "https://"! If copy-via-clipboard is used below, it does; file URLs, however, properly begin with "file://" but "Mozilla Firefox" will be the default name for any tab displaying a file URL; ...

if myURL is "" then
	--  debugging helper
	tell application "Finder" to display notification "Hookmark UI Scripting is broken for Firefox version " & firefoxVer & ". Please inform the developer providing your Firefox version number. Using copy-via-clipboard approach instead."
	
	-- from here, get URL via clipboard: this approach works at least for any Firefox version since 2014 and probably earlier but is slower and possibly shakey
	
	-- backup clipboard contents for later restore
	set theClip to my fetchStorableClipboard()
	
	set the clipboard to "" -- empty clipboard avoiding error on ‘clipboard as string'
	tell application "System Events"
		tell process "Firefox"
			my waitForModifierRelease()
			keystroke "lc" using {command down} -- highlight URL and copy combined into a single event, so no shakey delay needed in-between
		end tell
		delay 0.3
		my waitForModifierRelease()
		keystroke (key code 53) -- esc to close the URL history popup menu and thus allow Firefox to transfer the copied URL to the system clipboard for retrival by this script
	end tell
	
	repeat 10 times -- poll clipboard for 5 seconds; should not be necessary with clipboard containing "" and after esc closed the popup menu
		-- give the code a chance not to fail at all, even if it takes half a second
		delay 0.5 -- why hog the CPU with delays of 0.1 when you are prepared to wait 5s?
		try
			set myURL to (the clipboard as string) -- this may error after 'set clipboard to {}', i. e. if clipboard is (still) empty, but not after 'set clipboard to ""', clipboard contains empty string
			if myURL is not equal to "" then
				exit repeat
			end if
		end try
	end repeat
	
	--set myURL to (the clipboard as string)
	my putOnClipboard:theClip -- restore backuped clipboard
end if

if myURL is "" then
	return -- nothing
else if myURL begins with "file://" then -- in this case myName will be "Mozilla Firefox" for any file
	set AppleScript's text item delimiters to "/"
	set myName to last item of (get text items of myURL) -- get the full file name instead
	set myURL to my urlEncode(myURL) -- the file URL may contain spaces etc. (?!), so it has to be encoded
	return ("[" & myName & "](" & myURL) & ")"
else if myURL does not contain " " and myURL contains "." then -- heuristic check that myURL is really a URL and not a search string for a web search
	if myURL does not contain "://" then set myURL to "https://" & myURL -- heuristic "repair"
	return ("[" & myName & "](" & myURL) & ")"
end if

Thank you very much for your feedback and the script, @hookmeupscotty . Truly appreciated.

This is absolutely right. We need to clean it up.

Thank you