Initial commit
This commit is contained in:
885
skills/skill/references/chrome-api.md
Normal file
885
skills/skill/references/chrome-api.md
Normal file
@@ -0,0 +1,885 @@
|
||||
# Chrome Extension API Reference
|
||||
|
||||
Comprehensive guide to Chrome Extension APIs with WXT. Based on official Chrome Extension documentation at https://developer.chrome.com/docs/extensions.
|
||||
|
||||
## Core APIs
|
||||
|
||||
### chrome.action (Manifest V3)
|
||||
|
||||
Control the extension's toolbar icon.
|
||||
|
||||
**Official Docs:** https://developer.chrome.com/docs/extensions/reference/api/action
|
||||
|
||||
```typescript
|
||||
// Set badge text (shows number on icon)
|
||||
await browser.action.setBadgeText({ text: '5' });
|
||||
|
||||
// Set badge background color
|
||||
await browser.action.setBadgeBackgroundColor({ color: '#FF0000' });
|
||||
|
||||
// Set icon
|
||||
await browser.action.setIcon({
|
||||
path: {
|
||||
16: '/icon/16.png',
|
||||
32: '/icon/32.png',
|
||||
}
|
||||
});
|
||||
|
||||
// Set title (tooltip)
|
||||
await browser.action.setTitle({ title: 'Extension tooltip' });
|
||||
|
||||
// Enable/disable for specific tabs
|
||||
await browser.action.enable(tabId);
|
||||
await browser.action.disable(tabId);
|
||||
|
||||
// Listen for icon clicks
|
||||
browser.action.onClicked.addListener((tab) => {
|
||||
console.log('Extension icon clicked in tab:', tab.id);
|
||||
});
|
||||
|
||||
// Set popup programmatically
|
||||
await browser.action.setPopup({ popup: 'popup.html' });
|
||||
```
|
||||
|
||||
### chrome.tabs
|
||||
|
||||
Interact with browser tabs.
|
||||
|
||||
**Official Docs:** https://developer.chrome.com/docs/extensions/reference/api/tabs
|
||||
|
||||
```typescript
|
||||
// Query tabs
|
||||
const tabs = await browser.tabs.query({
|
||||
active: true,
|
||||
currentWindow: true
|
||||
});
|
||||
|
||||
// Get specific tab
|
||||
const tab = await browser.tabs.get(tabId);
|
||||
|
||||
// Create new tab
|
||||
const newTab = await browser.tabs.create({
|
||||
url: 'https://example.com',
|
||||
active: true,
|
||||
pinned: false,
|
||||
});
|
||||
|
||||
// Update tab
|
||||
await browser.tabs.update(tabId, {
|
||||
url: 'https://example.com',
|
||||
active: true,
|
||||
});
|
||||
|
||||
// Close tab
|
||||
await browser.tabs.remove(tabId);
|
||||
|
||||
// Duplicate tab
|
||||
await browser.tabs.duplicate(tabId);
|
||||
|
||||
// Send message to content script
|
||||
const response = await browser.tabs.sendMessage(tabId, {
|
||||
type: 'getMessage',
|
||||
data: 'hello'
|
||||
});
|
||||
|
||||
// Note: tabs.executeScript and tabs.insertCSS are deprecated in MV3
|
||||
// Use chrome.scripting API instead (see scripting section below)
|
||||
|
||||
// Tab events
|
||||
browser.tabs.onCreated.addListener((tab) => {
|
||||
console.log('Tab created:', tab.id);
|
||||
});
|
||||
|
||||
browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
||||
if (changeInfo.status === 'complete') {
|
||||
console.log('Tab loaded:', tab.url);
|
||||
}
|
||||
});
|
||||
|
||||
browser.tabs.onRemoved.addListener((tabId, removeInfo) => {
|
||||
console.log('Tab closed:', tabId);
|
||||
});
|
||||
|
||||
browser.tabs.onActivated.addListener((activeInfo) => {
|
||||
console.log('Tab activated:', activeInfo.tabId);
|
||||
});
|
||||
```
|
||||
|
||||
### chrome.runtime
|
||||
|
||||
Access extension runtime information and communicate between components.
|
||||
|
||||
**Official Docs:** https://developer.chrome.com/docs/extensions/reference/api/runtime
|
||||
|
||||
```typescript
|
||||
// Get extension ID
|
||||
const extensionId = browser.runtime.id;
|
||||
|
||||
// Get manifest
|
||||
const manifest = browser.runtime.getManifest();
|
||||
console.log('Version:', manifest.version);
|
||||
|
||||
// Get URL of extension resource
|
||||
const iconUrl = browser.runtime.getURL('icon/128.png');
|
||||
|
||||
// Send message to background
|
||||
const response = await browser.runtime.sendMessage({
|
||||
type: 'getData',
|
||||
payload: { key: 'value' }
|
||||
});
|
||||
|
||||
// Listen for messages
|
||||
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
console.log('Message from:', sender.tab?.url || 'extension');
|
||||
|
||||
if (message.type === 'getData') {
|
||||
// Handle async with Promise
|
||||
(async () => {
|
||||
const result = await fetchData(message.payload);
|
||||
sendResponse(result);
|
||||
})();
|
||||
|
||||
return true; // Keep channel open for async
|
||||
}
|
||||
});
|
||||
|
||||
// Connect for long-lived connections
|
||||
const port = browser.runtime.connect({ name: 'my-channel' });
|
||||
port.postMessage({ data: 'hello' });
|
||||
port.onMessage.addListener((msg) => {
|
||||
console.log('Received:', msg);
|
||||
});
|
||||
|
||||
// Listen for connection
|
||||
browser.runtime.onConnect.addListener((port) => {
|
||||
console.log('Connected:', port.name);
|
||||
|
||||
port.onMessage.addListener((msg) => {
|
||||
console.log('Message:', msg);
|
||||
port.postMessage({ response: 'received' });
|
||||
});
|
||||
});
|
||||
|
||||
// Install/update events
|
||||
browser.runtime.onInstalled.addListener((details) => {
|
||||
if (details.reason === 'install') {
|
||||
console.log('Extension installed');
|
||||
} else if (details.reason === 'update') {
|
||||
console.log('Extension updated to version:', manifest.version);
|
||||
}
|
||||
});
|
||||
|
||||
// Extension suspend warning
|
||||
browser.runtime.onSuspend.addListener(() => {
|
||||
console.log('Service worker about to suspend');
|
||||
// Clean up resources
|
||||
});
|
||||
```
|
||||
|
||||
### chrome.storage
|
||||
|
||||
Store and sync data.
|
||||
|
||||
**Official Docs:** https://developer.chrome.com/docs/extensions/reference/api/storage
|
||||
|
||||
```typescript
|
||||
// Local storage (not synced)
|
||||
await browser.storage.local.set({ key: 'value' });
|
||||
const result = await browser.storage.local.get('key');
|
||||
console.log(result.key); // 'value'
|
||||
|
||||
// Sync storage (synced across devices)
|
||||
await browser.storage.sync.set({ settings: { theme: 'dark' } });
|
||||
const settings = await browser.storage.sync.get('settings');
|
||||
|
||||
// Get multiple items
|
||||
const data = await browser.storage.local.get(['key1', 'key2']);
|
||||
console.log(data.key1, data.key2);
|
||||
|
||||
// Get all items
|
||||
const all = await browser.storage.local.get(null);
|
||||
|
||||
// Remove items
|
||||
await browser.storage.local.remove('key');
|
||||
await browser.storage.local.remove(['key1', 'key2']);
|
||||
|
||||
// Clear all
|
||||
await browser.storage.local.clear();
|
||||
|
||||
// Get bytes in use
|
||||
const bytes = await browser.storage.local.getBytesInUse('key');
|
||||
|
||||
// Listen for changes
|
||||
browser.storage.onChanged.addListener((changes, area) => {
|
||||
console.log('Storage area:', area); // 'local' or 'sync'
|
||||
|
||||
for (const [key, { oldValue, newValue }] of Object.entries(changes)) {
|
||||
console.log(`${key} changed from ${oldValue} to ${newValue}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Storage limits
|
||||
// local: ~10MB
|
||||
// sync: 100KB total, 8KB per item
|
||||
```
|
||||
|
||||
### chrome.alarms
|
||||
|
||||
Schedule periodic tasks.
|
||||
|
||||
**Official Docs:** https://developer.chrome.com/docs/extensions/reference/api/alarms
|
||||
|
||||
```typescript
|
||||
// Create alarm that fires once
|
||||
await browser.alarms.create('reminder', {
|
||||
delayInMinutes: 1,
|
||||
});
|
||||
|
||||
// Create periodic alarm
|
||||
await browser.alarms.create('daily-sync', {
|
||||
periodInMinutes: 1440, // 24 hours
|
||||
});
|
||||
|
||||
// Create alarm at specific time
|
||||
await browser.alarms.create('scheduled', {
|
||||
when: Date.now() + 60000, // 1 minute from now
|
||||
});
|
||||
|
||||
// Get alarm
|
||||
const alarm = await browser.alarms.get('reminder');
|
||||
|
||||
// Get all alarms
|
||||
const alarms = await browser.alarms.getAll();
|
||||
|
||||
// Clear alarm
|
||||
await browser.alarms.clear('reminder');
|
||||
|
||||
// Clear all alarms
|
||||
await browser.alarms.clearAll();
|
||||
|
||||
// Listen for alarms
|
||||
browser.alarms.onAlarm.addListener((alarm) => {
|
||||
console.log('Alarm fired:', alarm.name);
|
||||
|
||||
if (alarm.name === 'daily-sync') {
|
||||
performDailySync();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### chrome.notifications
|
||||
|
||||
Display system notifications.
|
||||
|
||||
**Official Docs:** https://developer.chrome.com/docs/extensions/reference/api/notifications
|
||||
|
||||
```typescript
|
||||
// Basic notification
|
||||
await browser.notifications.create({
|
||||
type: 'basic',
|
||||
iconUrl: '/icon/128.png',
|
||||
title: 'Notification Title',
|
||||
message: 'This is the notification message',
|
||||
priority: 2,
|
||||
});
|
||||
|
||||
// Notification with buttons
|
||||
await browser.notifications.create('my-notification-id', {
|
||||
type: 'basic',
|
||||
iconUrl: '/icon/128.png',
|
||||
title: 'Action Required',
|
||||
message: 'Click a button to respond',
|
||||
buttons: [
|
||||
{ title: 'Accept' },
|
||||
{ title: 'Decline' }
|
||||
],
|
||||
requireInteraction: true, // Don't auto-dismiss
|
||||
});
|
||||
|
||||
// Progress notification
|
||||
await browser.notifications.create({
|
||||
type: 'progress',
|
||||
iconUrl: '/icon/128.png',
|
||||
title: 'Downloading...',
|
||||
message: 'File download in progress',
|
||||
progress: 50,
|
||||
});
|
||||
|
||||
// List notification
|
||||
await browser.notifications.create({
|
||||
type: 'list',
|
||||
iconUrl: '/icon/128.png',
|
||||
title: 'Multiple Items',
|
||||
message: 'Summary message',
|
||||
items: [
|
||||
{ title: 'Item 1', message: 'First item' },
|
||||
{ title: 'Item 2', message: 'Second item' },
|
||||
],
|
||||
});
|
||||
|
||||
// Image notification
|
||||
await browser.notifications.create({
|
||||
type: 'image',
|
||||
iconUrl: '/icon/128.png',
|
||||
title: 'Image Notification',
|
||||
message: 'Notification with image',
|
||||
imageUrl: '/images/preview.png',
|
||||
});
|
||||
|
||||
// Update notification
|
||||
await browser.notifications.update('my-notification-id', {
|
||||
progress: 75,
|
||||
});
|
||||
|
||||
// Clear notification
|
||||
await browser.notifications.clear('my-notification-id');
|
||||
|
||||
// Notification events
|
||||
browser.notifications.onClicked.addListener((notificationId) => {
|
||||
console.log('Notification clicked:', notificationId);
|
||||
});
|
||||
|
||||
browser.notifications.onButtonClicked.addListener((notificationId, buttonIndex) => {
|
||||
console.log(`Button ${buttonIndex} clicked on ${notificationId}`);
|
||||
});
|
||||
|
||||
browser.notifications.onClosed.addListener((notificationId, byUser) => {
|
||||
console.log(`Notification ${notificationId} closed by user: ${byUser}`);
|
||||
});
|
||||
```
|
||||
|
||||
### chrome.contextMenus
|
||||
|
||||
Add items to browser context menu (right-click menu).
|
||||
|
||||
**Official Docs:** https://developer.chrome.com/docs/extensions/reference/api/contextMenus
|
||||
|
||||
```typescript
|
||||
// Create context menu in background script
|
||||
browser.runtime.onInstalled.addListener(() => {
|
||||
// Simple menu item
|
||||
browser.contextMenus.create({
|
||||
id: 'search-selection',
|
||||
title: 'Search "%s"',
|
||||
contexts: ['selection'],
|
||||
});
|
||||
|
||||
// Menu with submenu
|
||||
browser.contextMenus.create({
|
||||
id: 'parent',
|
||||
title: 'Extension Actions',
|
||||
contexts: ['page', 'selection'],
|
||||
});
|
||||
|
||||
browser.contextMenus.create({
|
||||
id: 'child1',
|
||||
parentId: 'parent',
|
||||
title: 'Action 1',
|
||||
contexts: ['page'],
|
||||
});
|
||||
|
||||
browser.contextMenus.create({
|
||||
id: 'child2',
|
||||
parentId: 'parent',
|
||||
title: 'Action 2',
|
||||
contexts: ['page'],
|
||||
});
|
||||
|
||||
// Menu for specific URL patterns
|
||||
browser.contextMenus.create({
|
||||
id: 'github-actions',
|
||||
title: 'GitHub Actions',
|
||||
contexts: ['page'],
|
||||
documentUrlPatterns: ['*://github.com/*'],
|
||||
});
|
||||
});
|
||||
|
||||
// Listen for clicks
|
||||
browser.contextMenus.onClicked.addListener((info, tab) => {
|
||||
if (info.menuItemId === 'search-selection') {
|
||||
const query = info.selectionText;
|
||||
browser.tabs.create({
|
||||
url: `https://www.google.com/search?q=${encodeURIComponent(query)}`,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Context types
|
||||
// 'all', 'page', 'selection', 'link', 'editable', 'image', 'video', 'audio'
|
||||
```
|
||||
|
||||
### chrome.webRequest
|
||||
|
||||
Intercept and modify network requests.
|
||||
|
||||
**Official Docs:** https://developer.chrome.com/docs/extensions/reference/api/webRequest
|
||||
|
||||
**Requires permission:** `"webRequest"` and host permissions
|
||||
|
||||
```typescript
|
||||
// Block requests
|
||||
browser.webRequest.onBeforeRequest.addListener(
|
||||
(details) => {
|
||||
// Block requests to certain URLs
|
||||
if (details.url.includes('ads.com')) {
|
||||
return { cancel: true };
|
||||
}
|
||||
},
|
||||
{ urls: ['<all_urls>'] },
|
||||
['blocking']
|
||||
);
|
||||
|
||||
// Modify request headers
|
||||
browser.webRequest.onBeforeSendHeaders.addListener(
|
||||
(details) => {
|
||||
const headers = details.requestHeaders || [];
|
||||
|
||||
// Add custom header
|
||||
headers.push({
|
||||
name: 'X-Custom-Header',
|
||||
value: 'my-value',
|
||||
});
|
||||
|
||||
// Remove header
|
||||
const filtered = headers.filter(h => h.name !== 'User-Agent');
|
||||
|
||||
return { requestHeaders: filtered };
|
||||
},
|
||||
{ urls: ['*://*.example.com/*'] },
|
||||
['blocking', 'requestHeaders']
|
||||
);
|
||||
|
||||
// Modify response headers
|
||||
browser.webRequest.onHeadersReceived.addListener(
|
||||
(details) => {
|
||||
const headers = details.responseHeaders || [];
|
||||
|
||||
// Modify CORS headers
|
||||
headers.push({
|
||||
name: 'Access-Control-Allow-Origin',
|
||||
value: '*',
|
||||
});
|
||||
|
||||
return { responseHeaders: headers };
|
||||
},
|
||||
{ urls: ['*://*.api.com/*'] },
|
||||
['blocking', 'responseHeaders']
|
||||
);
|
||||
|
||||
// Redirect requests
|
||||
browser.webRequest.onBeforeRequest.addListener(
|
||||
(details) => {
|
||||
return { redirectUrl: 'https://alternative.com' };
|
||||
},
|
||||
{ urls: ['*://blocked.com/*'] },
|
||||
['blocking']
|
||||
);
|
||||
```
|
||||
|
||||
### chrome.cookies
|
||||
|
||||
Manage browser cookies.
|
||||
|
||||
**Official Docs:** https://developer.chrome.com/docs/extensions/reference/api/cookies
|
||||
|
||||
```typescript
|
||||
// Get cookie
|
||||
const cookie = await browser.cookies.get({
|
||||
url: 'https://example.com',
|
||||
name: 'session',
|
||||
});
|
||||
|
||||
// Get all cookies for URL
|
||||
const cookies = await browser.cookies.getAll({
|
||||
url: 'https://example.com',
|
||||
});
|
||||
|
||||
// Set cookie
|
||||
await browser.cookies.set({
|
||||
url: 'https://example.com',
|
||||
name: 'session',
|
||||
value: 'abc123',
|
||||
expirationDate: Date.now() / 1000 + 3600, // 1 hour
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: 'lax',
|
||||
});
|
||||
|
||||
// Remove cookie
|
||||
await browser.cookies.remove({
|
||||
url: 'https://example.com',
|
||||
name: 'session',
|
||||
});
|
||||
|
||||
// Listen for cookie changes
|
||||
browser.cookies.onChanged.addListener((changeInfo) => {
|
||||
console.log('Cookie changed:', changeInfo.cookie.name);
|
||||
console.log('Removed:', changeInfo.removed);
|
||||
});
|
||||
```
|
||||
|
||||
### chrome.downloads
|
||||
|
||||
Manage file downloads.
|
||||
|
||||
**Official Docs:** https://developer.chrome.com/docs/extensions/reference/api/downloads
|
||||
|
||||
```typescript
|
||||
// Download file
|
||||
const downloadId = await browser.downloads.download({
|
||||
url: 'https://example.com/file.pdf',
|
||||
filename: 'downloaded-file.pdf',
|
||||
saveAs: true, // Show save dialog
|
||||
});
|
||||
|
||||
// Search downloads
|
||||
const downloads = await browser.downloads.search({
|
||||
query: ['pdf'],
|
||||
orderBy: ['-startTime'],
|
||||
limit: 10,
|
||||
});
|
||||
|
||||
// Pause download
|
||||
await browser.downloads.pause(downloadId);
|
||||
|
||||
// Resume download
|
||||
await browser.downloads.resume(downloadId);
|
||||
|
||||
// Cancel download
|
||||
await browser.downloads.cancel(downloadId);
|
||||
|
||||
// Show download in folder
|
||||
await browser.downloads.show(downloadId);
|
||||
|
||||
// Open downloaded file
|
||||
await browser.downloads.open(downloadId);
|
||||
|
||||
// Listen for download changes
|
||||
browser.downloads.onChanged.addListener((delta) => {
|
||||
if (delta.state?.current === 'complete') {
|
||||
console.log('Download complete:', delta.id);
|
||||
}
|
||||
});
|
||||
|
||||
browser.downloads.onCreated.addListener((item) => {
|
||||
console.log('Download started:', item.filename);
|
||||
});
|
||||
```
|
||||
|
||||
### chrome.bookmarks
|
||||
|
||||
Access and modify bookmarks.
|
||||
|
||||
**Official Docs:** https://developer.chrome.com/docs/extensions/reference/api/bookmarks
|
||||
|
||||
```typescript
|
||||
// Get bookmarks
|
||||
const bookmarks = await browser.bookmarks.getTree();
|
||||
|
||||
// Search bookmarks
|
||||
const results = await browser.bookmarks.search('github');
|
||||
|
||||
// Create bookmark
|
||||
const bookmark = await browser.bookmarks.create({
|
||||
parentId: '1',
|
||||
title: 'GitHub',
|
||||
url: 'https://github.com',
|
||||
});
|
||||
|
||||
// Create folder
|
||||
const folder = await browser.bookmarks.create({
|
||||
parentId: '1',
|
||||
title: 'My Folder',
|
||||
});
|
||||
|
||||
// Update bookmark
|
||||
await browser.bookmarks.update(bookmark.id, {
|
||||
title: 'GitHub - Updated',
|
||||
url: 'https://github.com/explore',
|
||||
});
|
||||
|
||||
// Move bookmark
|
||||
await browser.bookmarks.move(bookmark.id, {
|
||||
parentId: folder.id,
|
||||
index: 0,
|
||||
});
|
||||
|
||||
// Remove bookmark
|
||||
await browser.bookmarks.remove(bookmark.id);
|
||||
|
||||
// Remove folder recursively
|
||||
await browser.bookmarks.removeTree(folder.id);
|
||||
|
||||
// Listen for changes
|
||||
browser.bookmarks.onCreated.addListener((id, bookmark) => {
|
||||
console.log('Bookmark created:', bookmark.title);
|
||||
});
|
||||
|
||||
browser.bookmarks.onRemoved.addListener((id, removeInfo) => {
|
||||
console.log('Bookmark removed:', id);
|
||||
});
|
||||
```
|
||||
|
||||
### chrome.scripting
|
||||
|
||||
Inject JavaScript and CSS into web pages (replaces deprecated tabs.executeScript/insertCSS).
|
||||
|
||||
**Official Docs:** https://developer.chrome.com/docs/extensions/reference/api/scripting
|
||||
|
||||
**Required permission:** `"scripting"`
|
||||
|
||||
```typescript
|
||||
// Execute script in tab
|
||||
await chrome.scripting.executeScript({
|
||||
target: { tabId: tabId },
|
||||
files: ['content.js'],
|
||||
});
|
||||
|
||||
// Execute inline function
|
||||
await chrome.scripting.executeScript({
|
||||
target: { tabId: tabId },
|
||||
func: () => {
|
||||
console.log('Hello from injected script');
|
||||
},
|
||||
});
|
||||
|
||||
// Execute with arguments
|
||||
await chrome.scripting.executeScript({
|
||||
target: { tabId: tabId },
|
||||
func: (color) => {
|
||||
document.body.style.backgroundColor = color;
|
||||
},
|
||||
args: ['red'],
|
||||
});
|
||||
|
||||
// Inject CSS file
|
||||
await chrome.scripting.insertCSS({
|
||||
target: { tabId: tabId },
|
||||
files: ['styles.css'],
|
||||
});
|
||||
|
||||
// Inject inline CSS
|
||||
await chrome.scripting.insertCSS({
|
||||
target: { tabId: tabId },
|
||||
css: 'body { background: red; }',
|
||||
});
|
||||
|
||||
// Remove CSS
|
||||
await chrome.scripting.removeCSS({
|
||||
target: { tabId: tabId },
|
||||
css: 'body { background: red; }',
|
||||
});
|
||||
|
||||
// Register content scripts dynamically
|
||||
await chrome.scripting.registerContentScripts([{
|
||||
id: 'my-script',
|
||||
matches: ['*://example.com/*'],
|
||||
js: ['content.js'],
|
||||
runAt: 'document_idle',
|
||||
}]);
|
||||
|
||||
// Get registered scripts
|
||||
const scripts = await chrome.scripting.getRegisteredContentScripts();
|
||||
|
||||
// Unregister scripts
|
||||
await chrome.scripting.unregisterContentScripts({
|
||||
ids: ['my-script'],
|
||||
});
|
||||
|
||||
// Update existing scripts
|
||||
await chrome.scripting.updateContentScripts([{
|
||||
id: 'my-script',
|
||||
matches: ['*://example.com/*', '*://example.org/*'],
|
||||
}]);
|
||||
```
|
||||
|
||||
### chrome.history
|
||||
|
||||
Access browser history.
|
||||
|
||||
**Official Docs:** https://developer.chrome.com/docs/extensions/reference/api/history
|
||||
|
||||
```typescript
|
||||
// Search history
|
||||
const history = await browser.history.search({
|
||||
text: 'github',
|
||||
startTime: Date.now() - 7 * 24 * 60 * 60 * 1000, // Last 7 days
|
||||
maxResults: 100,
|
||||
});
|
||||
|
||||
// Get visits for URL
|
||||
const visits = await browser.history.getVisits({
|
||||
url: 'https://github.com',
|
||||
});
|
||||
|
||||
// Add URL to history
|
||||
await browser.history.addUrl({
|
||||
url: 'https://example.com',
|
||||
title: 'Example Domain',
|
||||
});
|
||||
|
||||
// Remove URL from history
|
||||
await browser.history.deleteUrl({
|
||||
url: 'https://example.com',
|
||||
});
|
||||
|
||||
// Remove all history in time range
|
||||
await browser.history.deleteRange({
|
||||
startTime: Date.now() - 24 * 60 * 60 * 1000, // Last 24 hours
|
||||
endTime: Date.now(),
|
||||
});
|
||||
|
||||
// Delete all history
|
||||
await browser.history.deleteAll();
|
||||
|
||||
// Listen for history changes
|
||||
browser.history.onVisited.addListener((result) => {
|
||||
console.log('Page visited:', result.url);
|
||||
});
|
||||
```
|
||||
|
||||
## Chrome 140+ Features (September 2025)
|
||||
|
||||
### sidePanel.getLayout()
|
||||
|
||||
New in Chrome 140 - determine side panel side (left or right).
|
||||
|
||||
**Official Docs:** https://developer.chrome.com/docs/extensions/reference/api/sidePanel#method-getLayout
|
||||
|
||||
```typescript
|
||||
// Get side panel layout
|
||||
const layout = await chrome.sidePanel.getLayout();
|
||||
console.log('Side panel side:', layout.side); // 'left' or 'right'
|
||||
|
||||
// Useful for RTL language support
|
||||
if (layout.side === 'right') {
|
||||
// Apply RTL-specific styling or behavior
|
||||
}
|
||||
```
|
||||
|
||||
**Use Cases:**
|
||||
- Adapting UI for RTL languages
|
||||
- Adjusting panel content based on side
|
||||
- Optimizing user experience based on panel location
|
||||
|
||||
**Browser Support:** Chrome 140+ (September 2025)
|
||||
|
||||
## Permission Patterns
|
||||
|
||||
### Required Permissions
|
||||
|
||||
**manifest.json:**
|
||||
```json
|
||||
{
|
||||
"permissions": [
|
||||
"storage",
|
||||
"tabs",
|
||||
"activeTab",
|
||||
"alarms",
|
||||
"notifications",
|
||||
"contextMenus"
|
||||
],
|
||||
"host_permissions": [
|
||||
"*://example.com/*",
|
||||
"*://api.example.com/*"
|
||||
],
|
||||
"optional_permissions": [
|
||||
"downloads",
|
||||
"bookmarks",
|
||||
"history"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Request Optional Permissions
|
||||
|
||||
```typescript
|
||||
// Check if permission granted
|
||||
const hasPermission = await browser.permissions.contains({
|
||||
permissions: ['downloads'],
|
||||
origins: ['*://downloads.example.com/*'],
|
||||
});
|
||||
|
||||
// Request permission
|
||||
const granted = await browser.permissions.request({
|
||||
permissions: ['downloads'],
|
||||
origins: ['*://downloads.example.com/*'],
|
||||
});
|
||||
|
||||
if (granted) {
|
||||
// Permission granted, use the API
|
||||
await browser.downloads.download({ url: 'https://example.com/file.pdf' });
|
||||
}
|
||||
|
||||
// Remove permission
|
||||
await browser.permissions.remove({
|
||||
permissions: ['downloads'],
|
||||
});
|
||||
|
||||
// Listen for permission changes
|
||||
browser.permissions.onAdded.addListener((permissions) => {
|
||||
console.log('Permissions added:', permissions);
|
||||
});
|
||||
|
||||
browser.permissions.onRemoved.addListener((permissions) => {
|
||||
console.log('Permissions removed:', permissions);
|
||||
});
|
||||
```
|
||||
|
||||
## Content Script Communication
|
||||
|
||||
### Sending Messages
|
||||
|
||||
```typescript
|
||||
// Content script → Background
|
||||
const response = await browser.runtime.sendMessage({
|
||||
type: 'getData',
|
||||
payload: { key: 'value' },
|
||||
});
|
||||
|
||||
// Background → Content script
|
||||
const response = await browser.tabs.sendMessage(tabId, {
|
||||
type: 'updateUI',
|
||||
payload: { theme: 'dark' },
|
||||
});
|
||||
```
|
||||
|
||||
### Long-Lived Connections
|
||||
|
||||
```typescript
|
||||
// Content script
|
||||
const port = browser.runtime.connect({ name: 'my-channel' });
|
||||
|
||||
port.postMessage({ type: 'init' });
|
||||
|
||||
port.onMessage.addListener((msg) => {
|
||||
console.log('Received:', msg);
|
||||
});
|
||||
|
||||
port.onDisconnect.addListener(() => {
|
||||
console.log('Disconnected');
|
||||
});
|
||||
|
||||
// Background script
|
||||
browser.runtime.onConnect.addListener((port) => {
|
||||
if (port.name === 'my-channel') {
|
||||
port.onMessage.addListener((msg) => {
|
||||
// Handle message
|
||||
port.postMessage({ response: 'acknowledged' });
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Official Documentation Links
|
||||
|
||||
- **Get Started:** https://developer.chrome.com/docs/extensions/get-started
|
||||
- **API Reference:** https://developer.chrome.com/docs/extensions/reference/api
|
||||
- **Manifest V3:** https://developer.chrome.com/docs/extensions/develop/migrate/what-is-mv3
|
||||
- **Content Scripts:** https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts
|
||||
- **Service Workers:** https://developer.chrome.com/docs/extensions/develop/concepts/service-workers
|
||||
- **Message Passing:** https://developer.chrome.com/docs/extensions/develop/concepts/messaging
|
||||
- **Storage:** https://developer.chrome.com/docs/extensions/reference/api/storage
|
||||
- **Permissions:** https://developer.chrome.com/docs/extensions/develop/concepts/declare-permissions
|
||||
- **Publishing:** https://developer.chrome.com/docs/webstore/publish
|
||||
- **Best Practices:** https://developer.chrome.com/docs/extensions/develop/concepts/best-practices
|
||||
Reference in New Issue
Block a user