Chapter 04 Content Scripts
Chapter 4: Content Scripts
- Understand the role and execution environment of Content Scripts
- Master methods for interacting with web page DOM
- Learn script injection timing control
Knowledge Summary
Content Scripts Overview
Content Scripts are JavaScript files that run in the context of web pages, able to read and modify the web page’s DOM. They execute in an isolated environment, have full access to the web page DOM, but are isolated from the web page’s JavaScript environment.
Execution Environment Architecture
Content Scripts Basics
Environment Isolation Mechanism
Content Script Isolated Environment Example
// content-script.js
// 1. Content Script has its own global scope
let myVariable = 'content script variable';
// 2. Cannot directly access page JavaScript variables
console.log(typeof window.pageVariable); // undefined
// 3. But can access and modify DOM
document.body.style.backgroundColor = '#f0f0f0';
// 4. Shares DOM but not JavaScript environment
const button = document.createElement('button');
button.textContent = 'Extension Button';
button.onclick = function() {
// This function executes in content script environment
console.log('Button clicked in content script');
};
document.body.appendChild(button);
// 5. Communication with page requires DOM events or postMessage
// Send message to page
window.postMessage({
type: 'FROM_CONTENT_SCRIPT',
data: 'Hello from extension'
}, '*');
// Listen for messages from page
window.addEventListener('message', (event) => {
if (event.data.type === 'FROM_PAGE') {
console.log('Received from page:', event.data);
}
});
Web Page Script Example
// Web page JavaScript
// Page has its own variables
window.pageVariable = 'page variable';
// Listen for messages from content script
window.addEventListener('message', (event) => {
if (event.data.type === 'FROM_CONTENT_SCRIPT') {
console.log('Received from extension:', event.data);
// Reply with message
window.postMessage({
type: 'FROM_PAGE',
data: 'Hello from page'
}, '*');
}
});
Shared DOM Access Example
// content-script.js - DOM manipulation example
class DOMManipulator {
constructor() {
this.init();
}
init() {
// Wait for DOM to load
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => this.setup());
} else {
this.setup();
}
}
setup() {
// 1. Find elements
this.findAndModifyElements();
// 2. Observe DOM changes
this.observeDOM();
// 3. Inject custom elements
this.injectCustomElements();
// 4. Modify styles
this.modifyStyles();
}
findAndModifyElements() {
// Find all links
const links = document.querySelectorAll('a[href]');
links.forEach(link => {
// Add icon to external links
if (link.hostname !== window.location.hostname) {
link.classList.add('external-link');
link.setAttribute('target', '_blank');
link.setAttribute('rel', 'noopener noreferrer');
}
});
// Find and highlight specific text
this.highlightText('important', '#ffff00');
}
highlightText(keyword, color) {
const walker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_TEXT,
null,
false
);
const textNodes = [];
let node;
while (node = walker.nextNode()) {
if (node.nodeValue.toLowerCase().includes(keyword)) {
textNodes.push(node);
}
}
textNodes.forEach(node => {
const span = document.createElement('span');
span.style.backgroundColor = color;
span.textContent = node.nodeValue;
node.parentNode.replaceChild(span, node);
});
}
observeDOM() {
// Observe DOM changes
const observer = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach(node => {
if (node.nodeType === 1) { // Element node
console.log('New element added:', node.tagName);
this.processNewElement(node);
}
});
}
});
});
// Configure observation options
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeOldValue: true
});
}
processNewElement(element) {
// Process newly added element
if (element.tagName === 'IMG') {
// Add lazy loading
element.loading = 'lazy';
}
}
injectCustomElements() {
// Create floating toolbar
const toolbar = document.createElement('div');
toolbar.id = 'extension-toolbar';
toolbar.innerHTML = `
<button id="ext-btn-highlight">Highlight</button>
<button id="ext-btn-export">Export</button>
<button id="ext-btn-settings">Settings</button>
`;
toolbar.style.cssText = `
position: fixed;
top: 10px;
right: 10px;
z-index: 10000;
background: white;
border: 1px solid #ccc;
padding: 10px;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
`;
document.body.appendChild(toolbar);
// Bind events
this.bindToolbarEvents(toolbar);
}
bindToolbarEvents(toolbar) {
toolbar.addEventListener('click', (e) => {
if (e.target.tagName === 'BUTTON') {
switch (e.target.id) {
case 'ext-btn-highlight':
this.toggleHighlight();
break;
case 'ext-btn-export':
this.exportPageContent();
break;
case 'ext-btn-settings':
this.openSettings();
break;
}
}
});
}
modifyStyles() {
// Inject custom CSS
const style = document.createElement('style');
style.textContent = `
.external-link::after {
content: ' ↗';
font-size: 0.8em;
vertical-align: super;
}
.extension-highlight {
background-color: yellow !important;
color: black !important;
}
#extension-toolbar button {
margin: 0 5px;
padding: 5px 10px;
cursor: pointer;
background: #4CAF50;
color: white;
border: none;
border-radius: 3px;
}
#extension-toolbar button:hover {
background: #45a049;
}
`;
document.head.appendChild(style);
}
toggleHighlight() {
document.body.classList.toggle('extension-highlight-mode');
}
exportPageContent() {
const content = document.body.innerText;
// Send to background script for processing
chrome.runtime.sendMessage({
action: 'export',
data: content
});
}
openSettings() {
chrome.runtime.sendMessage({action: 'openSettings'});
}
}
// Initialize
new DOMManipulator();
Injection Timing Control
document_start
Description: Before any DOM construction, before CSS loading Use Case: Need to prevent page script execution or modify initial DOM
// manifest.json
{
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["content.js"],
"run_at": "document_start"
}]
}
// content.js
console.log('document_start: DOM not yet constructed');
// Can set early event listeners
document.addEventListener('DOMContentLoaded', () => {
console.log('DOM loaded');
});
// Inject script into page context (before DOM construction)
const script = document.createElement('script');
script.textContent = `
// This code executes in page context
console.log('Injected at document_start');
// Can override page global functions
window.alert = function(msg) {
console.log('Alert blocked:', msg);
};
`;
(document.head || document.documentElement).appendChild(script);
script.remove();
document_end
Description: DOM construction complete, but images and other resources may still be loading Use Case: Default choice for most DOM operations
// content.js - document_end
console.log('document_end: DOM construction complete');
// Can safely access DOM
const allLinks = document.querySelectorAll('a');
console.log(`Found ${allLinks.length} links`);
// Modify DOM
document.body.insertAdjacentHTML('afterbegin',
'<div id="extension-banner">Extension Loaded</div>'
);
// But images may not be loaded yet
const images = document.querySelectorAll('img');
images.forEach(img => {
if (!img.complete) {
img.addEventListener('load', () => {
console.log('Image loaded:', img.src);
});
}
});
document_idle
Description: When DOM is idle or after window.onload Use Case: Non-urgent initialization, performance optimization
// content.js - document_idle
console.log('document_idle: Page fully rendered or idle');
// Execute non-urgent initialization
function initializeExtensionFeatures() {
// Analyze page content
analyzePageContent();
// Prefetch resources
prefetchResources();
// Setup performance monitoring
setupPerformanceMonitoring();
}
function analyzePageContent() {
const wordCount = document.body.innerText.split(/\s+/).length;
const readingTime = Math.ceil(wordCount / 200); // Assume 200 words per minute
console.log(`Page stats: ${wordCount} words, ~${readingTime} min read`);
}
function prefetchResources() {
// Prefetch resources extension may need
const link = document.createElement('link');
link.rel = 'prefetch';
link.href = chrome.runtime.getURL('assets/data.json');
document.head.appendChild(link);
}
function setupPerformanceMonitoring() {
// Monitor page performance
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log('Performance entry:', entry.name, entry.duration);
}
});
observer.observe({entryTypes: ['measure', 'navigation']});
}
initializeExtensionFeatures();
Dynamic Injection Example
// background.js - Dynamically inject content script
// 1. Programmatically inject file
chrome.action.onClicked.addListener((tab) => {
chrome.scripting.executeScript({
target: {tabId: tab.id},
files: ['content-script.js']
});
});
// 2. Inject function
function injectedFunction(color) {
document.body.style.backgroundColor = color;
return document.title;
}
chrome.tabs.query({active: true, currentWindow: true}, (tabs) => {
chrome.scripting.executeScript({
target: {tabId: tabs[0].id},
func: injectedFunction,
args: ['#e8f5e9']
}, (results) => {
console.log('Page title:', results[0].result);
});
});
// 3. Inject CSS
chrome.scripting.insertCSS({
target: {tabId: tab.id},
css: 'body { border: 5px solid red !important; }'
});
// Or inject CSS file
chrome.scripting.insertCSS({
target: {tabId: tab.id},
files: ['styles/content.css']
});
// 4. Conditional injection
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (changeInfo.status === 'complete' && tab.url) {
if (tab.url.includes('github.com')) {
chrome.scripting.executeScript({
target: {tabId: tabId},
files: ['github-enhancer.js']
});
}
}
});
// 5. Inject in all frames
chrome.scripting.executeScript({
target: {
tabId: tab.id,
allFrames: true // Inject into all frames
},
func: () => {
console.log('Injected in frame:', window.location.href);
}
});
// 6. Inject in specific frame
chrome.scripting.executeScript({
target: {
tabId: tab.id,
frameIds: [0] // Only inject into main frame
},
files: ['content.js']
});
Interacting with Pages
Page Script Injection
// content-script.js
// Method 1: Inject via script tag creation
function injectScript(file, node) {
const script = document.createElement('script');
script.setAttribute('type', 'text/javascript');
script.setAttribute('src', file);
node.appendChild(script);
}
// Inject script file from extension
injectScript(chrome.runtime.getURL('injected.js'), document.head);
// Method 2: Inline code injection
function injectInlineScript(code) {
const script = document.createElement('script');
script.textContent = code;
(document.head || document.documentElement).appendChild(script);
script.remove(); // Remove tag after injection
}
// Inject inline code
injectInlineScript(`
// This code executes in page context
console.log('Page context:', window);
// Can access page global variables
if (typeof jQuery !== 'undefined') {
console.log('jQuery version:', jQuery.fn.jquery);
}
// Override page functions
const originalFetch = window.fetch;
window.fetch = function(...args) {
console.log('Fetch intercepted:', args[0]);
return originalFetch.apply(this, args);
};
// Add global function for page use
window.extensionAPI = {
version: '1.0.0',
sendMessage: function(data) {
window.postMessage({
source: 'extension-api',
payload: data
}, '*');
}
};
`);
// Method 3: Use Web Accessible Resources
// manifest.json
{
"web_accessible_resources": [{
"resources": ["injected.js", "styles/*.css"],
"matches": ["<all_urls>"]
}]
}
// Then inject in content script
const script = document.createElement('script');
script.src = chrome.runtime.getURL('injected.js');
script.onload = function() {
this.remove();
};
(document.head || document.documentElement).appendChild(script);
Bi-directional Communication Implementation
// content-script.js
// Communication setup on Content Script side
class PageBridge {
constructor() {
this.setupMessageChannel();
}
setupMessageChannel() {
// Listen for messages from page
window.addEventListener('message', (event) => {
// Verify source
if (event.source !== window) return;
if (event.data.source === 'page-script') {
this.handlePageMessage(event.data);
}
});
// Listen for messages from extension
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === 'forward-to-page') {
this.sendToPage(request.data);
}
});
}
handlePageMessage(data) {
console.log('Message from page:', data);
// Handle based on message type
switch(data.type) {
case 'GET_EXTENSION_DATA':
this.sendToPage({
type: 'EXTENSION_DATA',
data: this.getExtensionData()
});
break;
case 'SEND_TO_BACKGROUND':
// Forward to background script
chrome.runtime.sendMessage({
action: 'from-page',
data: data.payload
}, response => {
this.sendToPage({
type: 'BACKGROUND_RESPONSE',
data: response
});
});
break;
}
}
sendToPage(data) {
window.postMessage({
source: 'content-script',
...data
}, '*');
}
getExtensionData() {
return {
version: chrome.runtime.getManifest().version,
id: chrome.runtime.id
};
}
}
// Initialize communication bridge
const bridge = new PageBridge();
// injected.js
// Script in page context
(function() {
'use strict';
class ExtensionInterface {
constructor() {
this.setupCommunication();
}
setupCommunication() {
// Listen for messages from content script
window.addEventListener('message', (event) => {
if (event.data.source === 'content-script') {
this.handleExtensionMessage(event.data);
}
});
}
handleExtensionMessage(data) {
console.log('Message from extension:', data);
// Trigger custom event for page to listen
const customEvent = new CustomEvent('extension-message', {
detail: data
});
window.dispatchEvent(customEvent);
}
sendToExtension(type, payload) {
window.postMessage({
source: 'page-script',
type: type,
payload: payload
}, '*');
}
// Public API for page use
requestExtensionData() {
this.sendToExtension('GET_EXTENSION_DATA');
}
sendToBackground(data) {
this.sendToExtension('SEND_TO_BACKGROUND', data);
}
}
// Expose interface to page
window.ExtensionInterface = new ExtensionInterface();
// Page can use like:
// window.ExtensionInterface.requestExtensionData();
// window.ExtensionInterface.sendToBackground({action: 'save', data: {}});
})();
Advanced DOM Operations
Performance Optimization Techniques
// content-script-optimized.js
class OptimizedContentScript {
constructor() {
this.cache = new Map();
this.observers = [];
this.init();
}
init() {
// Use requestIdleCallback to defer non-critical initialization
if ('requestIdleCallback' in window) {
requestIdleCallback(() => this.lazyInit());
} else {
setTimeout(() => this.lazyInit(), 1);
}
// Critical initialization
this.criticalInit();
}
criticalInit() {
// Only execute necessary initialization
this.injectCriticalStyles();
}
lazyInit() {
// Deferred initialization
this.setupObservers();
this.bindEvents();
this.processExistingElements();
}
// 1. Batch DOM operations
batchDOMUpdates(updates) {
// Use DocumentFragment for batch insertion
const fragment = document.createDocumentFragment();
updates.forEach(update => {
const element = this.createElement(update);
fragment.appendChild(element);
});
// Insert all elements at once
document.body.appendChild(fragment);
}
// 2. Debounce and throttle
debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// 3. Virtual scrolling implementation
setupVirtualScroll(container, items, itemHeight) {
const totalHeight = items.length * itemHeight;
const viewportHeight = container.clientHeight;
const visibleCount = Math.ceil(viewportHeight / itemHeight);
// Create spacer element
const spacer = document.createElement('div');
spacer.style.height = `${totalHeight}px`;
container.appendChild(spacer);
// Render visible items
const renderVisibleItems = this.throttle(() => {
const scrollTop = container.scrollTop;
const startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = Math.min(
startIndex + visibleCount + 1,
items.length
);
// Clear old elements
container.querySelectorAll('.item').forEach(el => el.remove());
// Render new elements
for (let i = startIndex; i < endIndex; i++) {
const item = this.renderItem(items[i], i);
item.style.position = 'absolute';
item.style.top = `${i * itemHeight}px`;
container.appendChild(item);
}
}, 100);
container.addEventListener('scroll', renderVisibleItems);
renderVisibleItems();
}
// 4. Cache DOM queries
querySelector(selector, forceRefresh = false) {
if (!this.cache.has(selector) || forceRefresh) {
this.cache.set(selector, document.querySelector(selector));
}
return this.cache.get(selector);
}
querySelectorAll(selector, forceRefresh = false) {
const key = `all_${selector}`;
if (!this.cache.has(key) || forceRefresh) {
this.cache.set(key, [...document.querySelectorAll(selector)]);
}
return this.cache.get(key);
}
// 5. Smart observers
setupObservers() {
// Intersection Observer for lazy loading
const lazyImageObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.add('loaded');
lazyImageObserver.unobserve(img);
}
});
});
// Observe all lazy-load images
document.querySelectorAll('img[data-src]').forEach(img => {
lazyImageObserver.observe(img);
});
this.observers.push(lazyImageObserver);
// Resize Observer for responsive elements
const resizeObserver = new ResizeObserver(
this.debounce((entries) => {
entries.forEach(entry => {
this.handleResize(entry.target, entry.contentRect);
});
}, 250)
);
// Observe elements needing responsive handling
const responsiveElements = document.querySelectorAll('.responsive');
responsiveElements.forEach(el => resizeObserver.observe(el));
this.observers.push(resizeObserver);
}
// 6. Event delegation
bindEvents() {
// Use event delegation instead of binding to each element
document.body.addEventListener('click', (e) => {
const target = e.target;
// Handle different types of clicks
if (target.matches('.button-class')) {
this.handleButtonClick(target);
} else if (target.matches('a.special-link')) {
this.handleSpecialLink(target, e);
} else if (target.closest('.widget')) {
this.handleWidgetClick(target.closest('.widget'));
}
});
// Optimize scroll events
let ticking = false;
const updateOnScroll = () => {
if (!ticking) {
window.requestAnimationFrame(() => {
this.handleScroll();
ticking = false;
});
ticking = true;
}
};
window.addEventListener('scroll', updateOnScroll, {passive: true});
}
// 7. Web Workers for heavy computation
setupWorker() {
const workerCode = `
self.onmessage = function(e) {
const result = heavyComputation(e.data);
self.postMessage(result);
};
function heavyComputation(data) {
// Execute time-consuming calculations
let result = 0;
for (let i = 0; i < data.length; i++) {
result += complexCalculation(data[i]);
}
return result;
}
function complexCalculation(item) {
// Complex calculation logic
return item * 2;
}
`;
const blob = new Blob([workerCode], {type: 'application/javascript'});
const workerUrl = URL.createObjectURL(blob);
this.worker = new Worker(workerUrl);
this.worker.onmessage = (e) => {
console.log('Worker result:', e.data);
};
}
// 8. Cleanup function
cleanup() {
// Disconnect all observers
this.observers.forEach(observer => observer.disconnect());
// Clear cache
this.cache.clear();
// Terminate Worker
if (this.worker) {
this.worker.terminate();
}
// Remove event listeners
// ... cleanup event listeners
}
}
// Initialize optimized content script
const optimizedScript = new OptimizedContentScript();
// Cleanup on page unload
window.addEventListener('unload', () => {
optimizedScript.cleanup();
});
Security Best Practices
Security Considerations
// secure-content-script.js
class SecureContentScript {
constructor() {
this.trustedTypes = this.setupTrustedTypes();
this.init();
}
// 1. Setup Trusted Types (if available)
setupTrustedTypes() {
if (typeof trustedTypes !== 'undefined') {
const policy = trustedTypes.createPolicy('extension-policy', {
createHTML: (string) => {
// Sanitize HTML input
return DOMPurify.sanitize(string);
},
createScriptURL: (string) => {
// Validate script URL
if (this.isValidScriptURL(string)) {
return string;
}
throw new Error('Invalid script URL');
}
});
return policy;
}
return null;
}
// 2. Input validation and sanitization
sanitizeInput(input) {
// Remove potentially dangerous characters
return input
.replace(/<script[^>]*>.*?<\/script>/gi, '')
.replace(/on\w+="[^"]*"/gi, '')
.replace(/javascript:/gi, '');
}
// 3. Safe DOM operations
safeSetInnerHTML(element, html) {
if (this.trustedTypes) {
element.innerHTML = this.trustedTypes.createHTML(html);
} else {
// Use textContent instead of innerHTML when possible
element.textContent = '';
const temp = document.createElement('div');
temp.innerHTML = this.sanitizeInput(html);
// Remove all script tags
temp.querySelectorAll('script').forEach(s => s.remove());
// Remove all inline event handlers
temp.querySelectorAll('*').forEach(el => {
Array.from(el.attributes).forEach(attr => {
if (attr.name.startsWith('on')) {
el.removeAttribute(attr.name);
}
});
});
// Clone nodes to target element
while (temp.firstChild) {
element.appendChild(temp.firstChild);
}
}
}
// 4. Secure message passing
sendSecureMessage(data) {
// Validate data
if (!this.validateMessageData(data)) {
throw new Error('Invalid message data');
}
// Add nonce for verification
const nonce = this.generateNonce();
const message = {
source: 'content-script',
nonce: nonce,
timestamp: Date.now(),
data: data
};
// Sign message
message.signature = this.signMessage(message);
chrome.runtime.sendMessage(message);
}
validateMessageData(data) {
// Validate data structure and type
if (typeof data !== 'object') return false;
if (data.constructor !== Object && !Array.isArray(data)) return false;
// Check for dangerous properties
const dangerous = ['__proto__', 'constructor', 'prototype'];
for (const key of dangerous) {
if (key in data) return false;
}
return true;
}
generateNonce() {
const array = new Uint8Array(16);
crypto.getRandomValues(array);
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
}
signMessage(message) {
// Simple signature example (use stronger algorithm in production)
const str = JSON.stringify(message.data) + message.nonce + message.timestamp;
return this.simpleHash(str);
}
simpleHash(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return hash.toString(36);
}
// 5. Prevent XSS attacks
preventXSS() {
// Use CSP meta tag
const meta = document.createElement('meta');
meta.httpEquiv = 'Content-Security-Policy';
meta.content = "default-src 'self'; script-src 'self' 'unsafe-inline'";
document.head.appendChild(meta);
// Monitor and block dangerous DOM changes
const observer = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.nodeName === 'SCRIPT') {
// Check and potentially remove unauthorized scripts
if (!this.isAuthorizedScript(node)) {
node.remove();
console.warn('Unauthorized script blocked');
}
}
});
});
});
observer.observe(document.documentElement, {
childList: true,
subtree: true
});
}
isAuthorizedScript(scriptNode) {
// Check if script is from trusted source
const src = scriptNode.src;
if (!src) return false;
const trustedDomains = [
chrome.runtime.getURL(''),
'https://trusted-cdn.com/'
];
return trustedDomains.some(domain => src.startsWith(domain));
}
// 6. Secure data storage
async secureStore(key, value) {
// Encrypt sensitive data
const encrypted = await this.encrypt(JSON.stringify(value));
chrome.storage.local.set({
[key]: {
data: encrypted,
timestamp: Date.now(),
version: chrome.runtime.getManifest().version
}
});
}
async secureRetrieve(key) {
return new Promise((resolve) => {
chrome.storage.local.get(key, async (result) => {
if (!result[key]) {
resolve(null);
return;
}
try {
const decrypted = await this.decrypt(result[key].data);
resolve(JSON.parse(decrypted));
} catch (error) {
console.error('Decryption failed:', error);
resolve(null);
}
});
});
}
async encrypt(text) {
// Simple encryption example (use Web Crypto API in production)
const encoder = new TextEncoder();
const data = encoder.encode(text);
// Should use actual encryption algorithm here
return btoa(String.fromCharCode(...data));
}
async decrypt(encrypted) {
// Simple decryption example
const data = Uint8Array.from(atob(encrypted), c => c.charCodeAt(0));
const decoder = new TextDecoder();
return decoder.decode(data);
}
// 7. Rate limiting API calls
rateLimiter(func, limit, window) {
const calls = [];
return function(...args) {
const now = Date.now();
calls.push(now);
// Remove expired call records
while (calls.length > 0 && calls[0] <= now - window) {
calls.shift();
}
if (calls.length <= limit) {
return func.apply(this, args);
} else {
console.warn('Rate limit exceeded');
return null;
}
};
}
}
// Initialize secure content script
const secureScript = new SecureContentScript();
- Never trust user input: Always validate and sanitize all input
- Avoid eval(): Don’t use eval() or new Function() to execute string code
- Use HTTPS: Only run sensitive operations on secure HTTPS pages
- Least Privilege: Only request necessary permissions and access
- Regular Updates: Update dependency libraries and security patches promptly
Chapter Summary
This chapter provided in-depth coverage of Chrome Extension Content Scripts development:
- Execution Environment: Understood Content Script’s isolated environment and DOM access mechanism
- Injection Timing: Mastered characteristics and use cases of different injection timings
- Page Interaction: Learned communication methods with page scripts
- Performance Optimization: Understood various performance optimization techniques
- Security Practices: Mastered security development best practices
The next chapter will cover Background Scripts development.