Chapter 04 Content Scripts

Haiyue
36min

Chapter 4: Content Scripts

Learning Objectives
  1. Understand the role and execution environment of Content Scripts
  2. Master methods for interacting with web page DOM
  3. 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

🔄 正在渲染 Mermaid 图表...

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();
Security Reminders
  1. Never trust user input: Always validate and sanitize all input
  2. Avoid eval(): Don’t use eval() or new Function() to execute string code
  3. Use HTTPS: Only run sensitive operations on secure HTTPS pages
  4. Least Privilege: Only request necessary permissions and access
  5. Regular Updates: Update dependency libraries and security patches promptly

Chapter Summary

This chapter provided in-depth coverage of Chrome Extension Content Scripts development:

  1. Execution Environment: Understood Content Script’s isolated environment and DOM access mechanism
  2. Injection Timing: Mastered characteristics and use cases of different injection timings
  3. Page Interaction: Learned communication methods with page scripts
  4. Performance Optimization: Understood various performance optimization techniques
  5. Security Practices: Mastered security development best practices

The next chapter will cover Background Scripts development.

Categories