diff --git a/functional-samples/tutorial.open-api-reference/api-list.js b/functional-samples/tutorial.open-api-reference/api-list.js new file mode 100644 index 0000000000..72554e9c8a --- /dev/null +++ b/functional-samples/tutorial.open-api-reference/api-list.js @@ -0,0 +1,81 @@ +export default [ + { + content: 'commands', + description: + 'Use the Commands API to add a keyboard shortcuts.' + }, + { + content: 'contextmenus', + description: + "Use the ContextMenus API to add a custom item to Chrome's context menu." + }, + { + content: 'declarativeNetRequest', + description: + 'Use the DeclarativeNetRequest API to block or modify network requests.' + }, + { + content: 'downloads', + description: + 'Use the Downloads API to programmatically manipulate downloads.' + }, + { + content: 'i18n', + description: 'Use the i18n API to localize your extension' + }, + { + content: 'identity', + description: + 'Use the Identity API to get OAuth2 access tokens.' + }, + { + content: 'notifications', + description: + 'Use the Notifications API show notifications to users in the system tray.' + }, + { + content: 'offscreen', + description: + 'Use the Offscreen API to create and manage offscreen documents.' + }, + { + content: 'omnibox', + description: + "Use the Omnibox API to register a keyword with Chrome's address bar." + }, + { + content: 'permissions', + description: + 'Use the Permissions API to request optional permissions at run time.' + }, + { + content: 'runtime', + description: + 'Use the Runtime API pass messages, manage extension lifecycle, and access other helper utils.' + }, + { + content: 'scripting', + description: + 'Use the Scripting API to execute scripts in different contexts.' + }, + { + content: 'storage', + description: + 'Use the Storage API to store, retrieve, and track changes to user data.' + }, + { + content: 'tabs', + description: + 'Use the Tabs API to create, update and manipulate tabs.' + }, + { + content: 'topSites', + description: + 'Use the TopSites API to access the most visited sites that are displayed on the new tab page.' + }, + { + content: 'webNavigation', + description: + 'Use the WebNavigation API to receive notifications about the status of navigation requests in-flight.' + } +]; diff --git a/functional-samples/tutorial.open-api-reference/content.js b/functional-samples/tutorial.open-api-reference/content.js new file mode 100644 index 0000000000..1a5f65d449 --- /dev/null +++ b/functional-samples/tutorial.open-api-reference/content.js @@ -0,0 +1,30 @@ +// Popover API https://chromestatus.com/feature/5463833265045504 + +(async () => { + const nav = document.querySelector('.navigation-rail__links'); + + const { tip } = await chrome.runtime.sendMessage({ greeting: 'tip' }); + + const tipWidget = createDomElement(` + + `); + + const popover = createDomElement( + `
${tip}
` + ); + + document.body.append(popover); + nav.append(tipWidget); +})(); + +function createDomElement(html) { + const dom = new DOMParser().parseFromString(html, 'text/html'); + return dom.body.firstElementChild; +} diff --git a/functional-samples/tutorial.open-api-reference/icon-128.png b/functional-samples/tutorial.open-api-reference/icon-128.png new file mode 100644 index 0000000000..8fb1368633 Binary files /dev/null and b/functional-samples/tutorial.open-api-reference/icon-128.png differ diff --git a/functional-samples/tutorial.open-api-reference/icon-16.png b/functional-samples/tutorial.open-api-reference/icon-16.png new file mode 100644 index 0000000000..4537f9d574 Binary files /dev/null and b/functional-samples/tutorial.open-api-reference/icon-16.png differ diff --git a/functional-samples/tutorial.open-api-reference/manifest.json b/functional-samples/tutorial.open-api-reference/manifest.json new file mode 100644 index 0000000000..e88d16a118 --- /dev/null +++ b/functional-samples/tutorial.open-api-reference/manifest.json @@ -0,0 +1,25 @@ +{ + "manifest_version": 3, + "name": "Open extension API reference", + "version": "1.0.0", + "icons": { + "16": "icon-16.png", + "128": "icon-128.png" + }, + "background": { + "service_worker": "service-worker.js", + "type": "module" + }, + "minimum_chrome_version": "102", + "omnibox": { + "keyword": "api" + }, + "permissions": ["alarms", "storage"], + "content_scripts": [ + { + "matches": ["https://developer.chrome.com/docs/extensions/reference/*"], + "js": ["content.js"] + } + ], + "host_permissions": ["https://extension-tips.glitch.me/*"] +} diff --git a/functional-samples/tutorial.open-api-reference/service-worker.js b/functional-samples/tutorial.open-api-reference/service-worker.js new file mode 100644 index 0000000000..1af54df209 --- /dev/null +++ b/functional-samples/tutorial.open-api-reference/service-worker.js @@ -0,0 +1,2 @@ +import './sw-omnibox.js'; +import './sw-tips.js'; diff --git a/functional-samples/tutorial.open-api-reference/sw-omnibox.js b/functional-samples/tutorial.open-api-reference/sw-omnibox.js new file mode 100644 index 0000000000..005869bf40 --- /dev/null +++ b/functional-samples/tutorial.open-api-reference/sw-omnibox.js @@ -0,0 +1,37 @@ +import { getApiSuggestions } from './sw-suggestions.js'; + +console.log('sw-omnibox.js'); + +// Initialize default API suggestions +chrome.runtime.onInstalled.addListener(({ reason }) => { + if (reason === 'install') { + chrome.storage.local.set({ + apiSuggestions: ['tabs', 'storage', 'scripting'] + }); + } +}); + +const URL_CHROME_EXTENSIONS_DOC = + 'https://developer.chrome.com/docs/extensions/reference/'; +const NUMBER_OF_PREVIOUS_SEARCHES = 4; + +// Displays the suggestions after user starts typing +chrome.omnibox.onInputChanged.addListener(async (input, suggest) => { + const { description, suggestions } = await getApiSuggestions(input); + await chrome.omnibox.setDefaultSuggestion({ description }); + suggest(suggestions); +}); + +// Opens the reference page of the chosen API +chrome.omnibox.onInputEntered.addListener((input) => { + chrome.tabs.create({ url: URL_CHROME_EXTENSIONS_DOC + input }); + // Saves the latest keyword + updateHistory(input); +}); + +async function updateHistory(input) { + const { apiSuggestions } = await chrome.storage.local.get('apiSuggestions'); + apiSuggestions.unshift(input); + apiSuggestions.splice(NUMBER_OF_PREVIOUS_SEARCHES); + await chrome.storage.local.set({ apiSuggestions }); +} diff --git a/functional-samples/tutorial.open-api-reference/sw-suggestions.js b/functional-samples/tutorial.open-api-reference/sw-suggestions.js new file mode 100644 index 0000000000..42dd477a9d --- /dev/null +++ b/functional-samples/tutorial.open-api-reference/sw-suggestions.js @@ -0,0 +1,23 @@ +import apiList from './api-list.js'; + +/** + * Returns a list of suggestions and a description for the default suggestion + */ +export async function getApiSuggestions(input) { + const suggestions = apiList.filter((api) => api.content.startsWith(input)); + + // return suggestions if any exist + if (suggestions.length) { + return { + description: 'Matching Chrome APIs', + suggestions: suggestions + }; + } + + // return past searches if no match was found + const { apiSuggestions } = await chrome.storage.local.get('apiSuggestions'); + return { + description: 'No matches found. Choose from past searches', + suggestions: apiList.filter((item) => apiSuggestions.includes(item.content)) + }; +} diff --git a/functional-samples/tutorial.open-api-reference/sw-tips.js b/functional-samples/tutorial.open-api-reference/sw-tips.js new file mode 100644 index 0000000000..912d236dd3 --- /dev/null +++ b/functional-samples/tutorial.open-api-reference/sw-tips.js @@ -0,0 +1,28 @@ +console.log('sw-tips.js'); + +// Fetch tip & save in storage +const updateTip = async () => { + const response = await fetch('https://extension-tips.glitch.me/tips.json'); + const tips = await response.json(); + const randomIndex = Math.floor(Math.random() * tips.length); + await chrome.storage.local.set({ tip: tips[randomIndex] }); +}; + +// Create a daily alarm and retrieves the first tip when extension is installed. +chrome.runtime.onInstalled.addListener(({ reason }) => { + if (reason === 'install') { + chrome.alarms.create({ delayInMinutes: 1, periodInMinutes: 1440 }); + updateTip(); + } +}); + +// Retrieve tip of the day +chrome.alarms.onAlarm.addListener(updateTip); + +// Send tip to content script via messaging +chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + if (message.greeting === 'tip') { + chrome.storage.local.get('tip').then(sendResponse); + return true; + } +});