Google Chrome – How to Bring an Existing GMail Tab to the Front

Hello everyone,

After using Keyboard Maestro for the sole purpose of opening the Finder with a shortcut button for more than a year, I finally started to make use of the combining powers of Alfred and KM, and am blown away by it. I am not a coder but look at optimizing workflows in daily operations for product management.

I stumbled across this one and got it working for my specific case, where I embed the Script in another Macro. However, what do you have to change if you want the new Gmail tab to open in the same window and not in a new one? I tried to steal from other Scripts, but it did not do the trick in my case.

I am sure it is something embarrassingly easy in the last part of the script, where I want to replace the “newWin” for newTab parameters:

tell application "Google Chrome"
   set newTab to make new tab
   tell newTab to set URL of active tab to "NEWURL"
end tell

Thanks a lot!

You don't need a script for this. There are several KM Actions that will do this for your directly.

image

There are identical Actions for Safari, and FrontMost Browser.

Thanks for the fast response! I know about the “new tab/new window” KM actions, but looked at something like that:
Scans Chrome for an open tab with certain values

  • if tab is in place
    –> do x

  • if tab is not in place,
    –> create new tab
    –> open URL in new tab,
    –> do x

Hey @Jarko,

Yep; it’s dead simple – if you know how. If you don’t then it’s a headache.  :sunglasses:

Here’s my script above updated to do what you want.

-Chris

----------------------------------------------------
# Auth: Christopher Stone <scriptmeister@thestoneforge.com>
# dCre: 2015/05/14 12:30
# dMod: 2018/03/31 00:39
# Appl: Google Chrome, System Events
# Task: Bring Chrome Tab with Google Calendar to front if it exists - otherwise open Calendar in new tab.
# Libs: None
# Osax: None
# Tags: @Applescript, @Script, @Google_Chrome, @System_Events, @Tab
----------------------------------------------------

set googleServiceURL to "https://calendar.google.com/calendar/"

set _win to false
tell application "Google Chrome"
   set {idList, urlList} to {id, URL} of every tab of every window
end tell

set AppleScript's text item delimiters to return

set urlListText to urlList as text

if urlListText contains googleServiceURL = true then
   set theWin to 1
   repeat with i in urlList
      set theTab to 1
      repeat with n in i
         if n starts with googleServiceURL then
            set {_win, _tab} to {theWin, theTab}
         end if
         set theTab to theTab + 1
      end repeat
      set theWin to theWin + 1
   end repeat
end if

if _win ≠ false then
   tell application "System Events"
      if quit delay ≠ 0 then set quit delay to 0
      tell process "Google Chrome"
         perform action "AXRaise" of window _win
      end tell
   end tell
   tell application "Google Chrome"
      tell front window to set active tab index to _tab
   end tell
else
   tell application "Google Chrome"
      set newWin to make new tab at end of tabs of front window with properties {URL:googleServiceURL}
   end tell
end if

----------------------------------------------------

Chris, this is amazing. I just realized that the googleServiceURL is nothing but a placeholder in your script that is defined by the specific URL. Hence, I can use it for many different cases such as scanning for Jira, Intercom, Product-Tabs that are open, and embed the script in those workflows. Thanks a lot!

1 Like

Am I right in thinking that idList is unused in your code, Chris ?

Perhaps

tabs of every window where URL begins with "https://calendar.google.com/calendar/r"

or in JS

Application('Google Chrome')
  .windows.tabs
  .where({
    url: {
      _beginsWith: "https://calendar.google.com/calendar/r"
    }
  })

?

(but I’ve probably missed something … )

FWIW, another route to the same destination:

property serviceURL : "https://calendar.google.com/calendar/r"

on run
    tell application "Google Chrome"
        script
            on |λ|(w)
                set ts to tabs of w where URL begins with serviceURL
                if length of ts > 0 then
                    {w, item 1 of ts}
                else
                    {}
                end if
            end |λ|
        end script
        set winTab to my concatMap(result, windows)
        
        if length of winTab > 0 then
            
            set {oWin, oTab} to winTab
            tell application "System Events"
                if quit delay ≠ 0 then set quit delay to 0
                tell process "Google Chrome" to ¬
                    perform action "AXRaise" of window (index of oWin)
            end tell
            
            set idTab to id of oTab
            script
                on |λ|(x)
                    idTab = id of x
                end |λ|
            end script
            tell oWin to set active tab index to |Just| of (my findIndex(result, tabs of oWin))
            
        else
            make new tab at end of tabs of front window with properties {URL:serviceURL}
        end if
    end tell
    
end run

-- GENERIC FUNCTIONS -------------------------------------------------------------

-- Just :: a -> Just a
on Just(x)
    {type:"Maybe", Nothing:false, Just:x}
end Just

-- Nothing :: () -> Nothing
on Nothing()
    {type:"Maybe", Nothing:true}
end Nothing

-- concatMap :: (a -> [b]) -> [a] -> [b]
on concatMap(f, xs)
    tell mReturn(f)
        set lng to length of xs
        set acc to {}
        repeat with i from 1 to lng
            set acc to acc & |λ|(item i of xs, i, xs)
        end repeat
    end tell
    return acc
end concatMap

-- findIndex :: (a -> Bool) -> [a] -> Maybe Int
on findIndex(f, xs)
    tell mReturn(f)
        set lng to length of xs
        repeat with i from 1 to lng
            if |λ|(item i of xs) then return Just(i)
        end repeat
        return Nothing()
    end tell
end findIndex

-- Lift 2nd class handler function into 1st class script wrapper
-- mReturn :: First-class m => (a -> b) -> m (a -> b)
on mReturn(f)
    if class of f is script then
        f
    else
        script
            property |λ| : f
        end script
    end if
end mReturn


Hey Rob,

It’s been a while since I messed with that…

I believe you’re right about the unused idList.

You can do something like this:

set googleServiceURL to "https://calendar.google.com/calendar/"

tell application "Google Chrome"
   set tabList to tabs of windows whose URL starts with googleServiceURL
   repeat with i in tabList
      if contents of i = {} then
         set contents of i to 0
      end if
   end repeat
   
   set tabList to lists of tabList
   set theTab to item 1 of item 1 of tabList
   
   try
      theTab / 0
   on error errMsg
      set AppleScript's text item delimiters to {"window id ", " of application"}
      set winID to text item 2 of errMsg
   end try
   
   set index of window id winID to 1
   
end tell

The trouble is that setting the index doesn’t’ work properly due to a nearly decade-old bug in the macOS.

In any case this task is not as easy as it should be.

-Chris

2 Likes

A post was split to a new topic: Problems Opening Reddit URLs in Safari Using AppleScript

This is awesome Chris. I have one question/request - how can we add/specify the page title to target specific Google accounts. Many of us utilize multiple Gmail and/or GSuite accounts. Each account includes its unique email address in the title.

By adding a title parameter, we could specify activating our Personal vs. Business inbox.

Full disclosure: I'm certain I "borrowed" this code from someone here, but I can't remember who. I am dirt.

This is a subroutine I use for that and more. You can pass it a regular expression (or just a substring) of the tab title you want to focus on.

var kme = Application("Keyboard Maestro Engine")
kme.includeStandardAdditions = true
var app = Application("Google Chrome")
app.includeStandardAdditions = true


function run(argv) {

// Get KM local/instance var
var theRegExp = kme.getvariable("RegExp");

	let theTabExp = new RegExp(theRegExp)
	let found = 0
	for (let winIdx = 0; winIdx < app.windows().length; winIdx++) {
		if (found) break
		for (let tabIdx = 0; tabIdx < app.windows()[winIdx].tabs().length; tabIdx++) {
			if (app.windows()[winIdx].tabs()[tabIdx].title().match(theTabExp)) {
    				app.activate()
    				app.windows[winIdx].visible = true
    				app.windows[winIdx].activeTabIndex = tabIdx + 1 // Focus on tab
    				app.windows[winIdx].index = 1 // Focus on this specific Chrome window
			found = 1
			break
			}
		}
	}
}

Thank you - but I am unclear as to where I would add the tab element I desire (specifically: myemail@gmail.com - Gmail)

I am not one with the Regex Force :grinning:

Hey Joe,

This is a subroutine. That's so that you can find pretty much any Chome tab you want from another macro that passes a pattern to match to a Chrome tab. (Any Chrome tab, any user profile)

Does not have to be regexp, a substring will do. Regex is a steep hill to climb, but from the top you can see and do everything.

regexp

1 Like

Hi Devoy - if it's not too much trouble, please provide an export of the complete workflow that I may download and enter my email address - Thanks in Advance

Hmm. I'm not even sure how to document and upload a "workflow". But really, this is very easy.

The advantage of the subroutine is that you can call it with any number of tab-title patterns without reproducing the main code. (You'd mentioned have multiple Chrome profiles and wanting to focus on the inbox for each.) That last picture shows how to call it with a search pattern.

I was finally able to get your code to work - Thanks Again. After jumping up with excitement, I realized it only works for activating a Gmail tab and could not work with activating Google Calendar, Drive, and Docs. For such a use case, we would need to use the URL for each service, as Google does not utilize the users email address as a unique identifier in the page title for its calendar, drive, and docs.

It appears Google applies a sequential number to the URL of each account - I believe the number correlates to the order in which one logs into the respective account. Here are the URLs for the 4 Google accounts I am currently logged into:

https://drive.google.com/drive/u/0/my-drive ‪—> 0 (prime account)
https://drive.google.com/drive/u/1/my-drive ‪—> 1
https://drive.google.com/drive/u/2/my-drive ‪—> 2
https://drive.google.com/drive/u/3/my-drive ‪—> 3

Now that I know about subroutines, I would love to modify the code provided by @ccstone and replace the URL with a variable for a master workflow and utilize subroutines to specify the URL for each service and account:

set googleServiceURL to "https://drive.google.com/drive/u/0/my-drive"

I tried replacing the URL with %TriggerValue% but to no avail:

set googleServiceURL to %TriggerValue%

I'm pretty sure (based on baseless speculation) that the integer there references the order in which the profiles were created.

Here's something you could try, precede everything by activating Chrome and then triggering the profile you want from the "People menu". Based on a single test, this seems to work.

I absolute love what this script is trying to do but I couldn't get it to work (nothing happens whatsoever). I cut-and-pasted the script into the attached macro. Any thoughts on why it doesn't work or how I can debug it? Thanks!

Activate gmail tab.kmmacros (2.7 KB)

Your regular expression is off..it's got to be the window (tab) title, not the URL

Try 'Inbox.+' You need to be more specific if you have multiple profiles running gmail. You can use %WindowName%1% to get an idea what you're looking for.