What is Best Method to Activate Google Chrome Tab for Existing URL?

Chris, thanks so much for pointing out this important design pattern (use lists instead of objects).

My revised script now runs in 0.28 seconds for 10 windows with a total of 20 tabs.

####Here are the key changes:

  set winIDList to id of every window
  
  -------------------------------------
  repeat with aWinID in winIDList -- search through every window ID
    -----------------------------------
    set tabIndex to 0
   
    set oWin to window id aWinID 
    set urlList to URL of tabs of oWin
    
    -------------------------------------
    repeat with aTabURL in urlList -- search through every tab URL
      ------------------------------------
      set tabIndex to tabIndex + 1
      set urlTab to aTabURL as text

. . .

###Script Revised to Use Lists instead of Objects

(*
  PURPOSE:  Show/Create Tab with Given URL
                  * If Tab with URL exists, just Activate it
                  * If not, create new Tab with URL
*)
property LF : linefeed
property defaultBrowser : "Google Chrome"

set scriptResults to "TBD"

######### JUST FOR TESTING ###########

set urlMatch to "EXACT"
set urlToFind to "https://forum.keyboardmaestro.com/"

--------------------------------------
tell application "Google Chrome"
  ------------------------------------
  --    activate
  
  set urlFound to false
  
  --- SEARCH THROUGH EVERY TAB OF EVERY WINDOW UNTIL A MATCH IS FOUND ---
  --    (If no match, open new Tab)
  
  set winIDList to id of every window
  set numWin to length of winIDList
  
  -------------------------------------
  repeat with aWinID in winIDList -- search through every window
    -----------------------------------
    set tabIndex to 0
    log aWinID
    
    
    ### ? Surely there must be a better way to get the tab index ###
    -- apparently the only way to get tab index is to count
    
    set oWin to window id aWinID --  (first window whose id is aWinID)
    set urlList to URL of tabs of oWin
    
    -------------------------------------
    repeat with aTabURL in urlList -- search through every tab
      ------------------------------------
      set tabIndex to tabIndex + 1
      set urlTab to aTabURL as text
      
      if (urlMatch = "CONTAINS") then -- also handles Domain Name match
        if (urlTab contains urlName) then
          set urlFound to true
        end if
        
      else if (urlMatch = "EXACT") then
        if (urlTab = urlToFind) then
          set urlFound to true
        end if
        
      else if (urlMatch = "ENDSWITH") then
        if ((urlTab ends with urlToFind) or (urlTab ends with (urlToFind & "/"))) then
          set urlFound to true
        end if
        
      end if
      
      --------------------
      if urlFound then
        ------------------
        
        set active tab index of oWin to tabIndex
        set index of oWin to 1
        set scriptResults to "OK" & LF & ¬
          "Existing Tab with URL was Activated." & LF & "Match Type: " & urlMatch & LF & urlToFind
        -----------------
        exit repeat
        -----------------
      end if
      
    end repeat
    
    ------------------------------
    if urlFound then exit repeat
    ------------------------------
    
  end repeat
  
  if (urlFound is false) then
    
    if (urlToFind does not start with "http") then set urlToFind to "http://" & urlToFind
    
    --- OPEN URL IN NEW TAB ---
    tell front window
      set oTab to make new tab at end of tabs with properties {URL:urlToFind}
    end tell
    
    set scriptResults to "OK" & LF & "NEW Tab with URL was Opened." & LF & "Match Type: " & urlMatch & LF & urlToFind
    
  end if -- urlFound is false
  
end tell

return scriptResults


--~~~~~~~~~~~~~~~~~~ END OF MAIN SCRIPT ~~~~~~~~~~~~~~~~~~


###New Idea: Create a Flat List of Tab Records
Chris (@ccstone), what do you think about this script which builds a list of records of all tabs of all windows? It creates a flat list with each item being a record with the following record keys:

  • WinID
  • TabIndex
  • Tab ID
  • TabURL
  • TabTitle

Can it be improved? Instead of a list of records, would you use a list of lists?
Can it be used as the basis for a number of derivative scripts?
How would we search for a matching URL, or Title?

tell application "Google Chrome"
  
  set winIDList to id of windows
  set {tabIDList, tabURLList, tabTitleList} to {id, URL, title} of tabs of windows
  
  set tabList to {} -- Flat list of all tabs in all windows
  
  set numWin to length of tabIDList
  
  repeat with iWin from 1 to numWin
    set winIDofTab to item iWin of winIDList
    set numTabs to length of item iWin of tabIDList
    
    repeat with iTab from 1 to numTabs
      
      set end of tabList to ¬
        {WinID:winIDofTab, TabIndex:iTab, TabID:item iTab of item iWin of tabIDList, TabURL:item iTab of item iWin of tabURLList, TabTitle:item iTab of item iWin of tabTitleList ¬
          } ¬
          
    end repeat
  end repeat
  
end tell

@ShaneStanley, I hope your vacation trip is going well. Any pics you’d like to share?

If you are interested, and have a few moments, I’d love to know your thoughts about this.
Can we use ASObjC Array Dictionary tools to help us with this use case?
Given a list of records, how can we quickly search for records that match a specific key, like:

  • TabURL is urlToFind
  • TabURL contains urlToFind
  • TabTitle contains textToFind

Actually I should have started with, what is the best way to present the user with list to choose from, and then will activate the tab that matches? I’m thinking Myriad Tables, right?

What I’d love to do is present the user with a table of the following to select from:
Win#, Tab#, Domain Name, TabTitle

and then activate the tab the user selected.

Any/all suggestions/guidance is much appreciated.

Hey JM,

It’s not a bad idea at all.

The reason I wrote my Safari-Tabs script using vanilla AppleScript and loops was so people would at least try it (and hopefully use it).

But I’ve done many projects similarly to what you propose, except I use fully-flat-text-records and usually tab-delimit them.

Then I use the Satimage.osax to search the result.

Here’s your script adapted to this method and used to bring a given tab to the front.

------------------------------------------------------------------------------
# Auth: Christopher Stone (building on work of Jim Underwood)
# dCre: 2017/05/14 12:12
# dMod: 2017/05/14 12:30 
# Appl: Google Chrome
# Task: Compile a flat, tab-delimited text list of tab-info-records and search it to bring the found tab frontmost.
# Libs: None
# Osax: Satimage.osax
# Tags: @Applescript, @Script, @Satimage.osax, @Google_Chrome, @System_Events, @Compile, @Flat, @Tab-delimited, @Text, @List, @Info-Records, @Search, @Found, @Tab, @Frontmost
------------------------------------------------------------------------------

set tabInfoList to {} -- Flat text list of all tabs in all windows

tell application "Google Chrome"
   set winIDList to id of windows
   set {tabIDList, tabURLList, tabTitleList} to {id, URL, title} of tabs of windows
   set numWin to length of tabIDList
   
   repeat with iWin from 1 to numWin
      set winIDofTab to item iWin of winIDList
      set numTabs to length of item iWin of tabIDList
      set AppleScript's text item delimiters to {"	"}
      repeat with iTab from 1 to numTabs
         set end of tabInfoList to items 1 thru -2 of {¬
            item iTab of item iWin of tabURLList, ¬
            item iTab of item iWin of tabTitleList, ¬
            winIDofTab, ¬
            item iTab of item iWin of tabIDList, ¬
            iTab, ¬
            ""} as text
      end repeat
   end repeat
   
end tell

set tabInfoList to join (sortlist tabInfoList) using linefeed

set foundRec to fnd("^https?://forum.latenightsw.com.+", tabInfoList, true, true) of me

if length of foundRec = 1 then
   
   set AppleScript's text item delimiters to "	"
   set foundRec to text items of item 1 of foundRec
   
   tell application "Google Chrome"
      tell window id (item 3 of foundRec)
         set index to 1
         set active tab index to (item 5 of foundRec)
      end tell
   end tell
   
   raiseWindowOne()
   
end if

------------------------------------------------------------------------------
--» HANDLERS
------------------------------------------------------------------------------
on fnd(_find, _data, _all, strRslt)
   try
      find text _find in _data all occurrences _all string result strRslt with regexp without case sensitive
   on error
      return false
   end try
end fnd
------------------------------------------------------------------------------
on raiseWindowOne()
   tell application "System Events"
      tell application process "Google Chrome"
         tell window 1
            perform action "AXRaise"
         end tell
      end tell
   end tell
end raiseWindowOne
------------------------------------------------------------------------------

-Chris

I meant to say that AppleScript records are poor at just about everything — and slooow.

Of course AppleScriptObjC brings great new tools to the table with dictionaries and predicates.

I’m still not able to use those at will — I have to dig through my library of AppleScriptObjC and painfully assemble them from example code I have.

They are so powerful though, that I really need to get them into my working vocabulary.

-Chris

This is how I would do it, seems pretty quick on my mac, but I haven’t timed it outside of SD, where it took .08 seconds to find the right tab out of 35 tabs in 3 windows.

(Of course SD slows execution down a lot, and you can’t count that to be consistent. (A script slower than another script when both are run in SD could be quicker than the other script when both run at RT.)

 tell application "Google Chrome"
	set windowTabList to URL of tabs of every window
	set targetURL to "http://macscripter.net/"
	set found to false
	set windowIndex to 1
	repeat with thisWindowsTabs in windowTabList
		set TabIndex to 1
		repeat with TabURL in thisWindowsTabs
			
			if TabURL as text = targetURL then
				set index of window windowIndex to 1
				set active tab index of window 1 to TabIndex
				set found to true
				exit repeat
			end if
			set TabIndex to TabIndex + 1
		end repeat
		if found then exit repeat
		set windowIndex to windowIndex + 1
	end repeat
	if not found then
		make new tab at window 1 with properties {URL:targetURL}
		
	end if
end tell

Whoops posted wrong version…

This version is even faster, sends only GC commands to GC

tell application "Google Chrome" to set windowTabList to URL of tabs of every window

set targetURL to "http://macscripter.net/"
set found to false
set windowIndex to 1
repeat with thisWindowsTabs in windowTabList
	set TabIndex to 1
	repeat with TabURL in thisWindowsTabs
		if TabURL as text = targetURL then
			
			tell application "Google Chrome"
				set index of window windowIndex to 1
				set active tab index of window 1 to TabIndex
			end tell
			
			set found to true
			exit repeat
		end if
		set TabIndex to TabIndex + 1
	end repeat
	if found then exit repeat
	set windowIndex to windowIndex + 1
end repeat
if not found then
	tell application "Google Chrome" to make new tab at window 1 with properties {URL:targetURL}
end if
2 Likes

Yep, I’ve been very frustrated with the limited use of AppleScript Records, especially when you compare them to JavaScript Objects/Arrays.

I had the same thought. I’m hoping that Shane can point us in the right direction to making Records useful. I really like being able to reference an object’s value by its key.

Congratulations Ed! Your script is the fastest by 2X when run against my standard test from above (10 win with 20 total tabs):

@JMichaelTX: 0.39 @ccstone: 0.38 @estocky: 0.17

I don’t see any change in time on my iMac-27.

I think the key is you ask Chrome only ONE time for tab/win data:

set windowTabList to URL of tabs of every window

All of the rest of your script is processing a list of strings, until you find the matching URL, or have to create a new tab.

And you just count the items to get the window index instead using the window “id” in some manner.


@ccstone and @estockly: 
This has been an interesting exercise.  Thanks for sharing your solutions and insights.

:slight_smile: I found it an interesting thread. Google Chrome is an incomplete mess when it comes to AppleScripting, but in some ways it’s better than Safari and more stable.

FWIW, I threw this little ditty to set the stage for testing. I’m curious if your results are the same after running this.

It closes all Google Chrome windows, then opens ten new ones, each with ten tabs, then sets the last tab of the last window to the target tab, so any testing has to go through every tab.

tell application "Google Chrome" to close windows

set targetURL to "http://macscripter.net/"


set newURLList to {"http://macscripter.net/index.php", ¬
	"http://macscripter.net/register-pp.php", ¬
	"http://macscripter.net/guidelines.php", ¬
	"http://macscripter.net/page.php?id=1", ¬
	"http://macscripter.net/login.php", ¬
	"http://macscripter.net/viewforum.php?id=2", ¬
	"http://macscripter.net/viewforum.php?id=31", ¬
	"http://macscripter.net/viewforum.php?id=63", ¬
	"http://macscripter.net/viewtopic.php?pid=190102#p190102"}
tell application "Google Chrome"
	repeat 10 times
		make new window
		set URL of tab 1 of window 1 to some item of newURLList
		repeat 9 times
			make new tab at beginning of window 1
			set URL of tab 1 of window 1 to some item of newURLList
		end repeat
	end repeat
	set the URL of the last tab of the last window to targetURL
end tell

I already have a similar test set, saved/restored using the Chrome extension Session Buddy.

Also, I make sure the target URL is NOT opened in any tab. This will force the script to loop through all tabs in all windows.

I’ve actually tested this on two different Macs now, with the results being within 0.01 of each other.

1 Like

Hey Ed,

You did a great job optimizing the list-based version.  :smiley:

I tried pretty hard to beat it and couldn’t, although I didn’t resort to script-objects.

(I think Nigel Garvey has a way to make a script-object iterate through lists faster than a plain script, but I’m not going to go there.)

While I was experimenting I had one more go at using whose clauses.

-Chris

------------------------------------------------------------------------------
# Auth: Christopher Stone
# dCre: 2017/05/15 15:45
# dMod: 2017/05/15 17:14
# Appl: Google Chrome
# Task: Get Tabs whose URL starts with a given string - whose clause version.
# Libs: None
# Osax: None
# Tags: @Applescript, @Script, @Google_Chrome, @Get, @Tabs, @Whose, @URL, @Starts_With, @String
------------------------------------------------------------------------------

set targetURL to "https://www.macupdate.com/"

tell application "Google Chrome"
   tell windows
      set tabList to tabs where its URL starts with targetURL
   end tell
end tell

try
   tabList / 0
on error e
   set AppleScript's text item delimiters to {"Can’t make {", "} into type real.", "{", "}", ", ", "«class asDB» id (application \"Google Chrome\") of "}
   set tabList to text items of e
   repeat with theTab in tabList
      if contents of theTab = "" then set contents of theTab to 0
   end repeat
   
   set tabList to text of tabList
   
   if length of tabList > 0 then
      set theTab to item 1 of tabList
      set AppleScript's text item delimiters to {"id ", " of"}
      set {tabID, winID} to {text item 2, text item 4} of theTab
      set tabID to tabID as integer
      
      tell application "Google Chrome" to set tabIDList to id of tabs of window id winID
      
      repeat with i from 1 to length of tabIDList
         if (item i of tabIDList) = tabID then
            set tabIndex to i
            exit repeat
         end if
      end repeat
      
      tell application "Google Chrome"
         tell window id winID
            set index to 1
            set active tab index to tabIndex
         end tell
      end tell
      
      raiseWindowOne() of me
      
   else
      
      tell application "Google Chrome" to make new tab at window 1 with properties {URL:targetURL}
      
   end if
   
end try

------------------------------------------------------------------------------
--» HANDLERS
------------------------------------------------------------------------------
on raiseWindowOne()
   tell application "System Events"
      tell application process "Google Chrome"
         tell window 1
            perform action "AXRaise"
         end tell
      end tell
   end tell
end raiseWindowOne
------------------------------------------------------------------------------

And, we have a new record: 0.14 seconds

TEST: Search 10 windows with a total of 20 tabs, and open new tab
@ccstone: 0.14 (ver 2) @estocky: 0.17 @ccstone: 0.38 @JMichaelTX: 0.39

Great job Chris! :+1:

Given that all of these are << 0.5 sec, it seems to me that this is mostly an academic test, unless you are one of those that like 10’s of windows with hundreds of tabs (not me).

Chris, I know you love to shave hundredths of a second off a script/process, but I am not nearly that driven. :wink: I routinely use a delay of 0.20 sec in my KM Macros, and they all seem fast to me (but maybe not to others).

As one of our colleagues likes to point out, sometimes programmer’s time is more valuable than execution time.

Still, it is good to know what techniques are the fastest. Now that I know that, there is no reason not to use that technique unless I have a compelling reason to do otherwise.

Chris, how do you explain this?

I thought I saw an opportunity to yet further optimize your script by checking for an empty “tabList”, meaning the the URL is not found in any of the tabs.
Seems like it should be faster, but it also runs in 0.14 seconds.

Tested with Script Debugger 6.0.4 (6A198) on macOS 10.11.6.

###My Mod to Chris’ Script
This just shows the changes.

tell application "Google Chrome"
  tell windows
    set tabList to tabs where its URL starts with targetURL
  end tell
end tell

### MOD by JMichaelTX ###
set actionStr to "TBD"
if tabList as text = "" then
  set actionStr to "** JUST OPEN URL IN NEW TAB"
  tell application "Google Chrome" to make new tab at window 1 with properties {URL:targetURL}
  return
end if
### End of MOD ###

try
  tabList / 0
on error e

BTW, is there a better method for testing for an empty list of lists than:
if tabList as text = "" then

where tabList = {{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}

Hey JM,

There’s not much overhead in my code, so we’re probably talking a few milliseconds.

Not with vanilla AppleScript.

Of course that code will throw an error if there’s any content in the nested lists, but catching it with a try-block is very fast.

-Chris

1 Like

Yep, you’re right, as usual!

This simple handler seems to work fast enough:


tell application "Google Chrome"
  tell windows
    set tabList to tabs
  end tell
end tell

set testTabs to my isEmpty(tabList)

set testListEmpty to my isEmpty({{}, {}, {}})
set testListNotEmpty to my isEmpty({{}, {"not empty"}, {}})
set testTextNotEmpty to my isEmpty("not empty")
set testTextEmpty to my isEmpty("")

on isEmpty(pObject)
  
  try
    if (pObject as text = "") then
      return true
    else
      return false
    end if
  on error
    return false
  end try
  
end isEmpty

Many thanks to all in this thread for creating and posting scripts for this task. This is a life saver for me and something I would be unlikely to have created with my beginner Applescript skills. I’ve estimated this applescript – triggered using Keyboard Maestro – will save me between 3 to 6 hours per year of tedious tab hunting in my management role.

Thank you all!

1 Like

Hi, I aspire to achieve

  1. Copy text in "Active"chrome tab
  2. Paste in another chrome tab "DropBox Paper
  3. The Back to "Active"chrome tab

I have tried setting a variable A to id / tab object to restore the active tab after using the above script (post 16) to activate the tab

set A to the front tab of the front window
or
set A to the id of the active tab of the front window

Run Script from post 16

tell application “System Events” to keystroke (the clipboard as text)
delay 3
set active tab index of front window to A
Errors
If set A to the id of the active tab of the front window
=Google Chrome got an error: Invalid tab index entered." number 9
If set A to the front tab of the front window, then
=error “Google Chrome got an error: Can’t make tab id 714 of window id 98 into type integer.” number -1700 from tab id 714 of window id 98 to integer

This fails… Any suggestions
Cheers

Hi, I have managed to get a working solution for I wanted to do
Its a dirty workaround from brilliant work from Masters of applescript art

wud be grateful to suggest changes to look like a proper work

use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "AppKit"
--https://apple.stackexchange.com/questions/271161/how-to-get-the-selected-text-into-an-applescript-without-copying-the-text-to-th
--https://mjtsai.com/blog/2017/01/30/processing-the-selected-text-via-script/#comment-2669464
-- Back up clipboard contents:
set savedClipboard to my fetchStorableClipboard()
-- Copy selected text to clipboard:
set thePasteboard to current application's NSPasteboard's generalPasteboard()
set theCount to thePasteboard's changeCount()
-- Copy selected text to clipboard:
tell application "System Events" to keystroke "c" using {command down}
-- Check for changed clipboard:
repeat 20 times
	if thePasteboard's changeCount() is not theCount then exit repeat
	delay 0.1
end repeat
-- Set the selected to ClipBoard, Ready to paste
set theSelectedText to the clipboard
-- Block for doing Copy & paste
on fetchStorableClipboard()
	set aMutableArray to current application's NSMutableArray's array() -- used to store contents
	-- get the pasteboard and then its pasteboard items
	set thePasteboard to current application's NSPasteboard's generalPasteboard()
	-- loop through pasteboard items
	repeat with anItem in thePasteboard's pasteboardItems()
		-- make a new pasteboard item to store existing item's stuff
		set newPBItem to current application's NSPasteboardItem's alloc()'s init()
		-- get the types of data stored on the pasteboard item
		set theTypes to anItem's types()
		-- for each type, get the corresponding data and store it all in the new pasteboard item
		repeat with aType in theTypes
			set theData to (anItem's dataForType:aType)'s mutableCopy()
			if theData is not missing value then
				(newPBItem's setData:theData forType:aType)
			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 current application's NSPasteboard's generalPasteboard()
	-- clear it, then write new contents
	thePasteboard's clearContents()
	thePasteboard's writeObjects:theArray
end putOnClipboard:
-- Google Chrome
tell application "Google Chrome"
		set restoreURL to URL of active tab of front window as text
	--https://forum.latenightsw.com/t/what-is-best-method-to-activate-google-chrome-tab-for-existing-url/600/16
	-- Find paper DropBox URL, and set it active to paste
	set windowTabList to URL of tabs of every window
	set targetURL to "paper.dropbox.com"
	set found to false
	set windowIndex to 1
	repeat with thisWindowsTabs in windowTabList
		set TabIndex to 1
		repeat with TabURL in thisWindowsTabs
			if (TabURL as text) contains targetURL then
				
				tell application "Google Chrome"
					set index of window windowIndex to 1
					set active tab index of window 1 to TabIndex
				end tell
				
				set found to true
				exit repeat
			end if
			set TabIndex to TabIndex + 1
		end repeat
		if found then exit repeat
		set windowIndex to windowIndex + 1
	end repeat
	delay 1
	-- Block to paste the clipBoard & restore to Saved one
	tell application "System Events" to keystroke "v" using {command down}
	tell application "System Events" to keystroke {return}
	delay 0.1 -- Without this delay, may restore clipboard before pasting.
	-- Restore clipboard:
	(my putOnClipboard:savedClipboard)
	delay 1
	-- Block to restore the URL
	set found to false
	set windowIndex to 1
	repeat with thisWindowsTabs in windowTabList
		set TabIndex to 1
		repeat with TabURL in thisWindowsTabs
			if (TabURL as text) = restoreURL then
				
				tell application "Google Chrome"
					set index of window windowIndex to 1
					set active tab index of window 1 to TabIndex
				end tell
				
				set found to true
				exit repeat
			end if
			set TabIndex to TabIndex + 1
		end repeat
		if found then exit repeat
		set windowIndex to windowIndex + 1
	end repeat
end tell