Improves performance on Amazon pages by throttling background tasks and pausing off-screen iframe rendering. Reduces CPU usage and improves page responsiveness.
当前为
// ==UserScript==
// @name Amazon Page Smoother
// @namespace http://tampermonkey.net/
// @version 0.0.3
// @description Improves performance on Amazon pages by throttling background tasks and pausing off-screen iframe rendering. Reduces CPU usage and improves page responsiveness.
// @license Unlicense
// @author VeleSila
// @match https://www.amazon.com/*
// @match https://www.amazon.co.jp/*
// @match https://www.amazon.co.uk/*
// @match https://www.amazon.es/*
// @match https://www.amazon.fr/*
// @match https://www.amazon.de/*
// @match https://www.amazon.it/*
// @match https://www.amazon.ca/*
// @match https://www.amazon.com.au/*
// @exclude */cart/*
// @exclude */buy/*
// @grant none
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
// --- Configuration ---
const CONFIG = {
// Foreground tick rate (ms) - lower = more responsive but higher CPU
FOREGROUND_TICK_RATE_MS: 150,
// Background tick rate (ms) - higher = less CPU usage when tab is inactive
BACKGROUND_TICK_RATE_MS: 2000,
// Minimum delay for throttled intervals (prevents too-frequent execution)
MIN_INTERVAL_DELAY_MS: 50,
// Maximum number of tasks to process per tick (prevents long frames)
MAX_TASKS_PER_TICK: 20,
// Enable iframe optimization (can be disabled if causing issues)
ENABLE_IFRAME_OPTIMIZATION: true,
// Enable timer throttling (can be disabled if causing issues)
ENABLE_TIMER_THROTTLING: true
};
/**
* Feature detection for modern browser APIs
*/
const SUPPORTS = {
intersectionObserver: 'IntersectionObserver' in window,
contentVisibility: CSS.supports('content-visibility', 'auto') || CSS.supports('content-visibility', 'hidden')
};
/**
* Safe DOM element checker
*/
function isElementInDOM(element) {
try {
return element && element.isConnected && document.body.contains(element);
} catch (e) {
return false;
}
}
/**
* Consolidates multiple setInterval calls into a single, more efficient loop.
* Uses requestIdleCallback when available for better background performance.
*/
function initializeTimerThrottler() {
if (!CONFIG.ENABLE_TIMER_THROTTLING) return;
if (window.self !== window.top) return; // Only run in the top-level window
const nativeSetInterval = window.setInterval;
const nativeClearInterval = window.clearInterval;
const nativeRequestIdleCallback = window.requestIdleCallback ||
((cb) => setTimeout(cb, 1));
let nextTaskId = 1;
const scheduledTasks = new Map();
let isMasterLoopRunning = false;
const cleanupQueue = new Set();
/**
* Master loop that processes scheduled tasks based on tab visibility
*/
const masterLoop = () => {
if (!isMasterLoopRunning) return;
const now = performance.now();
const isBackground = document.hidden;
const tickRate = isBackground ? CONFIG.BACKGROUND_TICK_RATE_MS : CONFIG.FOREGROUND_TICK_RATE_MS;
const maxTasks = isBackground ? Math.max(1, Math.floor(CONFIG.MAX_TASKS_PER_TICK / 2)) : CONFIG.MAX_TASKS_PER_TICK;
let tasksProcessed = 0;
// Process cleanup queue first
cleanupQueue.forEach(id => {
scheduledTasks.delete(id);
});
cleanupQueue.clear();
// Process tasks
for (const [id, task] of scheduledTasks) {
if (tasksProcessed >= maxTasks) break;
if (now - task.lastExecution >= task.delay) {
try {
if (isBackground && 'requestIdleCallback' in window) {
nativeRequestIdleCallback(() => {
if (scheduledTasks.has(id)) {
task.callback();
task.lastExecution = performance.now();
}
}, { timeout: 500 });
} else {
task.callback();
task.lastExecution = now;
}
tasksProcessed++;
} catch (error) {
console.warn(`[Amazon Page Smoother] Error in throttled task ${id}:`, error);
cleanupQueue.add(id);
}
}
}
// Schedule next tick
setTimeout(masterLoop, tickRate);
};
// Override setInterval with throttling logic
window.setInterval = (callback, delay = 0, ...args) => {
if (typeof callback !== 'function') {
return nativeSetInterval(callback, delay, ...args);
}
const id = nextTaskId++;
const adjustedDelay = Math.max(delay, CONFIG.MIN_INTERVAL_DELAY_MS);
scheduledTasks.set(id, {
callback: () => callback(...args),
delay: adjustedDelay,
lastExecution: performance.now(),
originalDelay: delay
});
if (!isMasterLoopRunning) {
isMasterLoopRunning = true;
setTimeout(masterLoop, 0);
}
return id;
};
// Override clearInterval with proper cleanup
window.clearInterval = (id) => {
if (scheduledTasks.has(id)) {
cleanupQueue.add(id);
return;
}
nativeClearInterval(id);
};
// Handle page unload to clean up
window.addEventListener('beforeunload', () => {
isMasterLoopRunning = false;
scheduledTasks.clear();
cleanupQueue.clear();
}, { passive: true });
// Handle page visibility changes
document.addEventListener('visibilitychange', () => {
if (!document.hidden && !isMasterLoopRunning && scheduledTasks.size > 0) {
isMasterLoopRunning = true;
setTimeout(masterLoop, 0);
}
}, { passive: true });
}
/**
* Pauses rendering for iframes that are not currently visible on screen.
* Uses content-visibility: hidden for off-screen iframes to save rendering resources.
*/
function initializeIframeManager() {
if (!CONFIG.ENABLE_IFRAME_OPTIMIZATION) return;
if (!SUPPORTS.intersectionObserver || !SUPPORTS.contentVisibility) {
console.debug('[Amazon Page Smoother] Skipping iframe optimization - browser lacks required features');
return;
}
if (window.self !== window.top) return;
let visibilityWatcher;
let domWatcher;
const observedElements = new WeakMap();
const iframeSelector = 'iframe:not([src^="javascript:"]):not([src^="data:"])';
/**
* Setup IntersectionObserver for iframe visibility tracking
*/
function setupVisibilityObserver() {
visibilityWatcher = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (!isElementInDOM(entry.target)) {
visibilityWatcher.unobserve(entry.target);
return;
}
const isVisible = entry.isIntersecting || entry.intersectionRatio > 0.1;
const currentState = observedElements.get(entry.target) || 'visible';
if (isVisible && currentState !== 'visible') {
entry.target.style.contentVisibility = 'visible';
observedElements.set(entry.target, 'visible');
} else if (!isVisible && currentState !== 'hidden') {
// Only hide if not actively being interacted with
if (!entry.target.matches(':hover,:focus,:active')) {
entry.target.style.contentVisibility = 'hidden';
observedElements.set(entry.target, 'hidden');
}
}
});
},
{
root: null,
threshold: [0, 0.1, 1.0],
rootMargin: '100px' // Start observing before iframe enters viewport
}
);
}
/**
* Observe a single iframe element
*/
function observeIframe(iframe) {
if (!iframe || !isElementInDOM(iframe) || observedElements.has(iframe)) return;
try {
// Skip iframes that are likely critical or have specific requirements
if (iframe.src && (
iframe.src.includes('captcha') ||
iframe.src.includes('login') ||
iframe.src.includes('checkout') ||
iframe.hasAttribute('data-critical')
)) {
return;
}
observedElements.set(iframe, 'visible');
visibilityWatcher.observe(iframe);
iframe.dataset.amazonSmoother = 'managed';
} catch (e) {
console.warn('[Amazon Page Smoother] Failed to observe iframe:', e);
}
}
/**
* Setup MutationObserver to handle dynamically added iframes
*/
function setupDOMObserver() {
domWatcher = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.matches(iframeSelector)) {
observeIframe(node);
} else {
node.querySelectorAll(iframeSelector).forEach(observeIframe);
}
}
});
}
});
});
}
/**
* Start managing iframes
*/
function startManaging() {
if (!document.body) return;
setupVisibilityObserver();
setupDOMObserver();
// Observe existing iframes
document.querySelectorAll(iframeSelector).forEach(observeIframe);
// Start observing DOM changes
domWatcher.observe(document.body, {
childList: true,
subtree: true,
attributes: false,
characterData: false
});
}
/**
* Cleanup function
*/
function cleanup() {
if (domWatcher) {
domWatcher.disconnect();
}
if (visibilityWatcher) {
visibilityWatcher.disconnect();
}
// Reset contentVisibility on all managed iframes
document.querySelectorAll('[data-amazon-smoother="managed"]').forEach(iframe => {
iframe.style.contentVisibility = 'visible';
iframe.removeAttribute('data-amazon-smoother');
});
observedElements = new WeakMap();
}
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', startManaging, { once: true, passive: true });
} else {
startManaging();
}
// Cleanup on page unload
window.addEventListener('beforeunload', cleanup, { passive: true });
}
// --- Script Initialization ---
try {
console.debug('[Amazon Page Smoother] Initializing performance optimizations...');
const initStart = performance.now();
if (CONFIG.ENABLE_TIMER_THROTTLING) {
initializeTimerThrottler();
}
if (CONFIG.ENABLE_IFRAME_OPTIMIZATION && SUPPORTS.intersectionObserver && SUPPORTS.contentVisibility) {
initializeIframeManager();
}
const initTime = performance.now() - initStart;
console.debug(`[Amazon Page Smoother] Initialized in ${initTime.toFixed(2)}ms`);
} catch (error) {
console.error('[Amazon Page Smoother] Critical initialization error:', error);
}
})();