Writing simple Chrome extension
Recently, Google removed clickable map links from search results in the EU. Let's see how we can write a Chrome extension to fix it.
So recently, Google removed clickable map links from search results in the EU. Over time (as short as it was) I got so annoyed by it, that I decided I had to fix it by writing an extension. Only when I was finished did I notice, that someone beat me to it so if you are just after the extension to fix this issue you can go and install their extension here.
However, since I already wrote it I decided to share how to do it for anyone who wants to try to write their extension.
Creating new project
Writing a basic extension like this is pretty straightforward. Create a folder to house your files and in total, we will
need
just two: manifest.json
and content.js
. The manifest.json
contains information about our extension and
the content.js
contains the logic that should be executed.
Manifest.json
I will start by showing you the entire manifest.json
file and then we will take a look at it in greater detail.
{
"manifest_version": 3,
"version": "1.0.0",
"name": "Return Google Map Links",
"description": "This extension restores the functionality of clickable map results within Google for users located in the European Union.",
"content_scripts": [
{
"matches": [
"*://www.google.com/search*",
"*://google.com/search*"
],
"js": [
"content.js"
]
}
]
}
By manifest version, we are specifying the version of the manifest we are using (duh). Next, we need to specify the
version of the extension. We will start with 1.0.0
and in case of any change or fix we can update the version
accordingly. The name and description are self-explanatory.
The interesting part is content_scripts
. Here we can specify which JavaScript to load every time a page, that matches
a URL pattern is opened. So if we quickly inspect Google search results we can see, that for us the URL
is https://www.google.com/search
. The *
at the beginning and the end is a wildcard, that matches anything. So we
will match both http
as well as https
protocols and anything that comes after /search
. We also added one pattern
with www
prefix. If any of these patterns match the opened URL we will load scripts inside js
array. In our
case, it is content.js
.
Testing the extension
Before we start to code the extension let's test whether it works. Chrome enables us to load our extension manually for
development purposes. Let's start by adding the following line console.log("It works!")
to our content.js
file.
Next, open Google Chrome go to the Manage Extensions
page, turn on Developer mode
in the top right corner and
finally click the Load unpacked
button in the top left corner. Now find the folder in which you have your project and
select it.
This should load the extension and if you open up your developer console and search for something in Google you should
see It works!
printed out in the console. Hurray, we have a working extension.
One thing to keep in mind is, that whenever you make changes to your extension you need to go to the
Manage extensions
tab and reload it using the arrow button.
The script
There are a lot of ways to make this work, but let's try to keep it simple. If we search for a few places we can see, that we have two possibilities where the map is located. Either a large rectangle above the results or in the case of a business it is a small rectangle in the top right corner of the page. So let's inspect the source.
Regular place
Let's search for O'Brien Street Sydney
. We get the large rectangle which is a div with an id of lu_map
. To
get the query we will need to use for opening the map we can use for example this selector b > div
. This returns the
div containing the address below the map.
Business
Now let's search for Makuto Bondi Beach
. We get the small rectangle which is an img tag id of lu_map
. To get the URL
we can use this selector div[data-place]
which has a data-place
attribute which contains the necessary URL.
Putting it together
Now that we know all the necessary information all we need to do is to check whether there is a map on the search
results page and if so insert <a>
with the corresponding href
. I will paste the entire code below:
(async () => {
const mapElement = document.getElementById('lu_map');
if (!mapElement) return;
if (isAlreadyClickable(mapElement)) return;
const isBusiness = mapElement.tagName.toLowerCase() === 'img'
getHref(isBusiness ? 300 : 0).then(href => {
createLink(isBusiness, mapElement, href)
})
// In case the user is outisde the EU we don't need to make it clickable
function isAlreadyClickable(mapElement) {
return mapElement.parentElement.getAttribute('href')?.startsWith('/maps/place/')
}
function getHref(delay) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const dataPlace = document.querySelector('[data-place]');
if (dataPlace) {
resolve('https://google.com' + dataPlace.getAttribute('data-place'));
return
}
const query = document.querySelector('b > div')
?.innerText
?.replace("\n", " ")
if (query) {
resolve('https://google.com/maps/place/' + encodeURIComponent(query))
return
}
reject()
}, delay)
})
}
function createLink(isBusiness, element, href) {
if (!href) return;
if (isBusiness) {
// The img tag for business has a parent which already is <a> tag.
// So we will just set the `href` attribute
element.parentElement.setAttribute('href', href)
return
}
const anchorElement = document.createElement('a');
anchorElement.innerHTML = element.innerHTML;
anchorElement.href = href;
element.parentNode.replaceChild(anchorElement, element);
}
})()
Summary
So there you have it - the working MVP of such an extension. It is obviously far from perfect and could use a lot of attention, but this was the proof of concept I quickly put together before I noticed another extension already exists.
I hope this was helpful and if you want to know more, the best place to go is the official docs.
Until next time,
Tony.