Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions api-samples/contextMenus/global_context_search/README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,30 @@
# chrome.contextMenus

This sample demonstrates the `chrome.contextMenus` API by letting a user switch between searching different countries' versions of Google via a `contextMenu`.
This sample demonstrates the `chrome.contextMenus` API by letting a user search Google with region-specific parameters via a context menu.

## Overview

The extension uses `chrome.contextMenus.create()` to populate the context menu with locale options based on an options menu in the popup. A `chrome.contextMenus.onClicked.addListener()` event will open a specific locale's Google homepage when one of the extension's context menu options are clicked.
Modern Google Search uses IP geolocation and query parameters for localization instead of domain-based routing (e.g., google.com.br). This sample reflects that reality.

The extension uses:
- `chrome.contextMenus.create()` to populate context menu with region options
- Query parameters: `cr` (country restriction) and `lr` (language restriction)
- A popup UI to enable/disable regions
- `chrome.contextMenus.onClicked.addListener()` to open searches in new tabs

## Key Implementation Details

- **Base URL**: All searches use `https://www.google.com/search`
- **Country Parameter (cr)**: Restricts results to a specific country (e.g., `countryCA` for Canada)
- **Language Parameter (lr)**: Restricts results to a specific language (e.g., `lang_en` for English)

While these parameters don't guarantee region-specific results (as IP geolocation is the primary factor), they're the modern, programmatic way to express regional preference to Google Search.

## Running this extension

1. Clone this repository.
2. Load this directory in Chrome as an [unpacked extension](https://developer.chrome.com/docs/extensions/mv3/getstarted/development-basics/#load-unpacked).
3. Pin the extension to the taskbar to access the action button.
4. Open the extension popup by clicking the action button and interact with the UI.
5. Select the text you want to search and right-click within the selection to view and interact with the context menu.
4. Open the extension popup by clicking the action button to enable/disable regions.
5. Select text on any webpage and right-click to access the context menu with region options.
6. Click a region to search for your selected text with that region's parameters.
77 changes: 47 additions & 30 deletions api-samples/contextMenus/global_context_search/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,54 +3,71 @@
// found in the LICENSE file.

// When you specify "type": "module" in the manifest background,
// you can include the service worker as an ES Module,
import { tldLocales } from './locales.js';
// you can include the service worker as an ES Module.
import { regions } from './locales.js';

// Add a listener to create the initial context menu items,
// context menu items only need to be created at runtime.onInstalled
/**
* Creates context menu items when the extension is installed.
* Each region gets its own context menu option for searching.
*/
chrome.runtime.onInstalled.addListener(async () => {
for (const [tld, locale] of Object.entries(tldLocales)) {
for (const [regionId, regionConfig] of Object.entries(regions)) {
chrome.contextMenus.create({
id: tld,
title: locale,
id: regionId,
title: regionConfig.display,
type: 'normal',
contexts: ['selection']
});
}
});

// Open a new search tab when the user clicks a context menu
/**
* Performs a Google Search with region-specific parameters when menu item is clicked.
*
* Uses the base URL https://www.google.com/search with query parameters:
* - q: the search query (user selection)
* - cr: country restriction parameter
* - lr: language restriction parameter
*/
chrome.contextMenus.onClicked.addListener((item, tab) => {
const tld = item.menuItemId;
const url = new URL(`https://google.${tld}/search`);
const regionId = item.menuItemId;
const regionConfig = regions[regionId];

// Build the search URL with region parameters
const url = new URL('https://www.google.com/search');
url.searchParams.set('q', item.selectionText);
url.searchParams.set('cr', regionConfig.country);
url.searchParams.set('lr', regionConfig.language);

chrome.tabs.create({ url: url.href, index: tab.index + 1 });
});

// Add or removes the locale from context menu
// when the user checks or unchecks the locale in the popup
chrome.storage.onChanged.addListener(({ enabledTlds }) => {
if (typeof enabledTlds === 'undefined') return;

const allTlds = Object.keys(tldLocales);
const currentTlds = new Set(enabledTlds.newValue);
const oldTlds = new Set(enabledTlds.oldValue ?? allTlds);
const changes = allTlds.map((tld) => ({
tld,
added: currentTlds.has(tld) && !oldTlds.has(tld),
removed: !currentTlds.has(tld) && oldTlds.has(tld)
}));

for (const { tld, added, removed } of changes) {
if (added) {
/**
* Updates the context menu when the user toggles regions in the popup.
* Adds or removes menu items based on user preferences.
*/
chrome.storage.onChanged.addListener(({ enabledRegions }) => {
if (typeof enabledRegions === 'undefined') return;

const allRegionIds = Object.keys(regions);
const currentRegions = new Set(enabledRegions.newValue);
const oldRegions = new Set(enabledRegions.oldValue ?? allRegionIds);

for (const regionId of allRegionIds) {
const wasEnabled = oldRegions.has(regionId);
const isEnabled = currentRegions.has(regionId);

if (isEnabled && !wasEnabled) {
// Region was enabled, add menu item
chrome.contextMenus.create({
id: tld,
title: tldLocales[tld],
id: regionId,
title: regions[regionId].display,
type: 'normal',
contexts: ['selection']
});
} else if (removed) {
chrome.contextMenus.remove(tld);
} else if (!isEnabled && wasEnabled) {
// Region was disabled, remove menu item
chrome.contextMenus.remove(regionId);
}
}
});
90 changes: 76 additions & 14 deletions api-samples/contextMenus/global_context_search/locales.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,80 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// TLD: top level domain; the "com" in "google.com"
export const tldLocales = {
'com.au': 'Australia',
'com.br': 'Brazil',
ca: 'Canada',
cn: 'China',
fr: 'France',
it: 'Italy',
'co.in': 'India',
'co.jp': 'Japan',
'com.ms': 'Mexico',
ru: 'Russia',
'co.za': 'South Africa',
'co.uk': 'United Kingdom'
/**
* Region configurations for Google Search.
*
* Historically, Google Search results were localized by domain (e.g., google.com.br).
* However, modern Google Search uses IP geolocation for most localization.
*
* To simulate region-specific searches, we now use query parameters:
* - cr (country restriction): Restricts results to a specific country
* - lr (language restriction): Restricts results to a specific language
*
* This demonstrates how to customize Google Search programmatically while
* reflecting modern Google behavior.
*
* @type {Object.<string, {country: string, language: string, display: string}>}
*/
export const regions = {
'au': {
country: 'countryAU',
language: 'lang_en',
display: 'Australia'
},
'br': {
country: 'countryBR',
language: 'lang_pt',
display: 'Brazil'
},
'ca': {
country: 'countryCA',
language: 'lang_en',
display: 'Canada'
},
'cn': {
country: 'countryCN',
language: 'lang_zh-CN',
display: 'China'
},
'fr': {
country: 'countryFR',
language: 'lang_fr',
display: 'France'
},
'it': {
country: 'countryIT',
language: 'lang_it',
display: 'Italy'
},
'in': {
country: 'countryIN',
language: 'lang_en',
display: 'India'
},
'jp': {
country: 'countryJP',
language: 'lang_ja',
display: 'Japan'
},
'mx': {
country: 'countryMX',
language: 'lang_es',
display: 'Mexico'
},
'ru': {
country: 'countryRU',
language: 'lang_ru',
display: 'Russia'
},
'za': {
country: 'countryZA',
language: 'lang_en',
display: 'South Africa'
},
'uk': {
country: 'countryGB',
language: 'lang_en',
display: 'United Kingdom'
}
};
4 changes: 2 additions & 2 deletions api-samples/contextMenus/global_context_search/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "Global Google Search",
"description": "Uses the context menu to search a different country's Google",
"version": "1.1",
"description": "Search Google with region-specific parameters using the context menu",
"version": "2.0",
"manifest_version": 3,
"permissions": ["contextMenus", "storage"],
"background": {
Expand Down
41 changes: 25 additions & 16 deletions api-samples/contextMenus/global_context_search/popup.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,31 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// TLD: top level domain; the "com" in "google.com"
import { tldLocales } from './locales.js';
import { regions } from './locales.js';

createForm().catch(console.error);

/**
* Creates the popup form with region checkboxes.
* Users can enable or disable regions to customize context menu options.
*/
async function createForm() {
const { enabledTlds = Object.keys(tldLocales) } =
await chrome.storage.sync.get('enabledTlds');
const checked = new Set(enabledTlds);
const { enabledRegions = Object.keys(regions) } =
await chrome.storage.sync.get('enabledRegions');
const checked = new Set(enabledRegions);

const form = document.getElementById('form');
for (const [tld, locale] of Object.entries(tldLocales)) {
for (const [regionId, regionConfig] of Object.entries(regions)) {
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.checked = checked.has(tld);
checkbox.name = tld;
checkbox.checked = checked.has(regionId);
checkbox.name = regionId;
checkbox.addEventListener('click', (event) => {
handleCheckboxClick(event).catch(console.error);
});

const span = document.createElement('span');
span.textContent = locale;
span.textContent = regionConfig.display;

const div = document.createElement('div');
div.appendChild(checkbox);
Expand All @@ -33,17 +36,23 @@ async function createForm() {
}
}

/**
* Handles checkbox state changes and updates storage.
*/
async function handleCheckboxClick(event) {
const checkbox = event.target;
const tld = checkbox.name;
const regionId = checkbox.name;
const enabled = checkbox.checked;

const { enabledTlds = Object.keys(tldLocales) } =
await chrome.storage.sync.get('enabledTlds');
const tldSet = new Set(enabledTlds);
const { enabledRegions = Object.keys(regions) } =
await chrome.storage.sync.get('enabledRegions');
const regionSet = new Set(enabledRegions);

if (enabled) tldSet.add(tld);
else tldSet.delete(tld);
if (enabled) {
regionSet.add(regionId);
} else {
regionSet.delete(regionId);
}

await chrome.storage.sync.set({ enabledTlds: [...tldSet] });
await chrome.storage.sync.set({ enabledRegions: [...regionSet] });
}