Debugging and Troubleshooting Polyfills
1. Browser DevTools for Polyfill Debugging
| Tool/Feature | Browser | Use Case | Key Features |
|---|---|---|---|
| Sources Panel | All modern browsers | Inspect polyfill code | Breakpoints, step through |
| Console | All browsers | Test features, log errors | Command line, logging |
| Network Tab | All browsers | Monitor polyfill loading | Timing, size, caching |
| Performance Profiler | Chrome, Edge, Firefox | Analyze polyfill overhead | Flame charts, call trees |
| Memory Profiler | Chrome, Edge | Detect memory leaks | Heap snapshots, allocation |
| Coverage Tool | Chrome, Edge | Find unused polyfills | Code coverage, optimization |
Example: DevTools debugging utilities
// devtools-helpers.js
var PolyfillDebugger = {
// Check if polyfill is loaded
isPolyfillLoaded: function(featureName) {
var polyfillMarkers = {
'Promise': function() {
return window.Promise && Promise.toString().indexOf('[native code]') === -1;
},
'fetch': function() {
return window.fetch && fetch.polyfill === true;
},
'IntersectionObserver': function() {
return window.IntersectionObserver &&
IntersectionObserver.toString().indexOf('[native code]') === -1;
}
};
var checker = polyfillMarkers[featureName];
return checker ? checker() : false;
},
// List all loaded polyfills
listLoadedPolyfills: function() {
var polyfills = [];
var features = [
'Promise', 'fetch', 'Map', 'Set', 'WeakMap', 'WeakSet',
'Symbol', 'Array.from', 'Object.assign', 'IntersectionObserver',
'ResizeObserver', 'requestAnimationFrame', 'CustomEvent'
];
features.forEach(function(feature) {
if (this.isPolyfillLoaded(feature)) {
polyfills.push(feature);
}
}.bind(this));
console.table(polyfills.map(function(name) {
return { Feature: name, Polyfilled: '✓' };
}));
return polyfills;
},
// Set breakpoint on polyfill
breakOnPolyfill: function(methodName) {
var original = window[methodName];
if (!original) {
console.error('Method not found:', methodName);
return;
}
window[methodName] = function() {
debugger; // Breakpoint here
return original.apply(this, arguments);
};
console.log('Breakpoint set on:', methodName);
},
// Trace polyfill calls
tracePolyfill: function(obj, methodName) {
var original = obj[methodName];
if (!original) {
console.error('Method not found:', methodName);
return;
}
obj[methodName] = function() {
console.group('Polyfill Call: ' + methodName);
console.log('Arguments:', arguments);
console.trace('Call Stack');
var result = original.apply(this, arguments);
console.log('Return Value:', result);
console.groupEnd();
return result;
};
console.log('Tracing enabled for:', methodName);
},
// Inspect polyfill source
inspectPolyfill: function(featureName) {
var feature = window[featureName];
if (!feature) {
console.error('Feature not found:', featureName);
return;
}
console.group('Polyfill Inspection: ' + featureName);
console.log('Type:', typeof feature);
console.log('Source:', feature.toString());
console.log('Properties:', Object.getOwnPropertyNames(feature));
// Check if native or polyfilled
var isNative = feature.toString().indexOf('[native code]') !== -1;
console.log('Native:', isNative);
console.log('Polyfilled:', !isNative);
console.groupEnd();
},
// Monitor polyfill performance
monitorPerformance: function(obj, methodName, iterations) {
iterations = iterations || 1000;
var original = obj[methodName];
if (!original) {
console.error('Method not found:', methodName);
return;
}
console.log('Benchmarking ' + methodName + ' (' + iterations + ' iterations)...');
var start = performance.now();
for (var i = 0; i < iterations; i++) {
original.call(obj);
}
var end = performance.now();
var duration = end - start;
var avgTime = duration / iterations;
console.log('Total Time: ' + duration.toFixed(2) + 'ms');
console.log('Average Time: ' + avgTime.toFixed(4) + 'ms');
console.log('Ops/Second: ' + (1000 / avgTime).toFixed(0));
},
// Check code coverage for polyfills
checkCoverage: function() {
console.log('Run this in Chrome DevTools:');
console.log('1. Open Coverage tab (Cmd+Shift+P > "Show Coverage")');
console.log('2. Click record button');
console.log('3. Interact with your app');
console.log('4. Click stop to see unused polyfills');
console.log('5. Filter by "polyfill" to see polyfill coverage');
},
// Debug feature detection
debugFeatureDetection: function() {
var features = {
'Promise': typeof Promise !== 'undefined',
'fetch': typeof fetch !== 'undefined',
'Map': typeof Map !== 'undefined',
'Set': typeof Set !== 'undefined',
'Symbol': typeof Symbol !== 'undefined',
'Proxy': typeof Proxy !== 'undefined',
'WeakMap': typeof WeakMap !== 'undefined',
'IntersectionObserver': typeof IntersectionObserver !== 'undefined',
'ResizeObserver': typeof ResizeObserver !== 'undefined',
'CustomElements': typeof customElements !== 'undefined',
'ShadowDOM': 'attachShadow' in Element.prototype,
'ES6 Classes': (function() {
try {
eval('class Test {}');
return true;
} catch (e) {
return false;
}
})()
};
console.table(features);
return features;
}
};
// Console helpers - add to global scope for easy access
window.polyfillDebug = PolyfillDebugger;
// Usage examples:
// polyfillDebug.listLoadedPolyfills();
// polyfillDebug.breakOnPolyfill('fetch');
// polyfillDebug.tracePolyfill(Array.prototype, 'find');
// polyfillDebug.inspectPolyfill('Promise');
// polyfillDebug.monitorPerformance(Array.prototype, 'map', 10000);
// polyfillDebug.debugFeatureDetection();
Note: Use Chrome DevTools Coverage to identify unused
polyfills. Filter by "polyfill" and remove unused code to reduce bundle size.
2. Common Polyfill Issues and Solutions
| Issue | Symptoms | Root Cause | Solution |
|---|---|---|---|
| Polyfill Not Loading | Feature undefined, errors | Missing import, CSP block | Check network, CSP headers |
| Order of Execution | Race conditions, undefined refs | Async loading conflicts | Load polyfills synchronously |
| Polyfill Conflicts | Incorrect behavior, overrides | Multiple polyfills clash | Consolidate, check order |
| Performance Degradation | Slow page load, jank | Heavy polyfills, poor impl | Profile, optimize, lazy load |
| Memory Leaks | Growing heap, crashes | Retained references | Use WeakMap, cleanup |
| CSP Violations | Blocked execution | unsafe-eval in polyfill | Use CSP-safe alternatives |
| Build Tool Issues | Missing polyfills in prod | Incorrect transpilation | Configure Babel properly |
Example: Common issue detection and fixes
// polyfill-troubleshooter.js
var PolyfillTroubleshooter = {
// Diagnose polyfill issues
diagnose: function() {
var issues = [];
// Check 1: Are polyfills loading?
if (!this.checkPolyfillsLoaded()) {
issues.push({
severity: 'error',
issue: 'Polyfills not loading',
solution: 'Check network tab for failed requests'
});
}
// Check 2: Are there conflicts?
var conflicts = this.detectConflicts();
if (conflicts.length > 0) {
issues.push({
severity: 'warning',
issue: 'Polyfill conflicts detected',
conflicts: conflicts,
solution: 'Remove duplicate polyfills or adjust load order'
});
}
// Check 3: Performance issues?
var perfIssues = this.checkPerformance();
if (perfIssues.length > 0) {
issues.push({
severity: 'warning',
issue: 'Performance issues detected',
details: perfIssues,
solution: 'Consider lazy loading or optimizing polyfills'
});
}
// Check 4: Memory leaks?
this.checkMemoryLeaks().then(function(hasLeaks) {
if (hasLeaks) {
issues.push({
severity: 'error',
issue: 'Potential memory leak detected',
solution: 'Use Chrome DevTools Memory profiler'
});
}
});
// Check 5: CSP violations?
if (this.checkCSPViolations()) {
issues.push({
severity: 'error',
issue: 'CSP violations detected',
solution: 'Use CSP-safe polyfills or adjust CSP headers'
});
}
this.reportIssues(issues);
return issues;
},
// Check if polyfills are loaded
checkPolyfillsLoaded: function() {
var scripts = document.querySelectorAll('script[src*="polyfill"]');
if (scripts.length === 0) {
console.warn('No polyfill scripts found');
return false;
}
var allLoaded = true;
scripts.forEach(function(script) {
if (!script.complete || script.readyState === 'loading') {
console.error('Polyfill not loaded:', script.src);
allLoaded = false;
}
});
return allLoaded;
},
// Detect polyfill conflicts
detectConflicts: function() {
var conflicts = [];
// Check for duplicate polyfills
var methods = [
{ obj: Array.prototype, name: 'find' },
{ obj: Array.prototype, name: 'includes' },
{ obj: Object, name: 'assign' },
{ obj: String.prototype, name: 'includes' }
];
methods.forEach(function(method) {
var descriptor = Object.getOwnPropertyDescriptor(method.obj, method.name);
if (descriptor && descriptor.configurable === false) {
// Check if it was polyfilled after being native
var source = method.obj[method.name].toString();
var isNative = source.indexOf('[native code]') !== -1;
if (!isNative && !descriptor.configurable) {
conflicts.push({
method: method.name,
issue: 'Non-configurable polyfill override',
recommendation: 'Check polyfill load order'
});
}
}
});
return conflicts;
},
// Check performance issues
checkPerformance: function() {
var issues = [];
// Check load time
var resources = performance.getEntriesByType('resource');
resources.forEach(function(resource) {
if (resource.name.indexOf('polyfill') !== -1) {
if (resource.duration > 500) {
issues.push({
file: resource.name,
issue: 'Slow loading (>500ms)',
duration: resource.duration.toFixed(2) + 'ms'
});
}
if (resource.transferSize > 100 * 1024) {
issues.push({
file: resource.name,
issue: 'Large file size (>100KB)',
size: (resource.transferSize / 1024).toFixed(2) + 'KB'
});
}
}
});
return issues;
},
// Check for memory leaks
checkMemoryLeaks: function() {
if (!performance.memory) {
console.warn('performance.memory not available');
return Promise.resolve(false);
}
var initialHeap = performance.memory.usedJSHeapSize;
return new Promise(function(resolve) {
setTimeout(function() {
var currentHeap = performance.memory.usedJSHeapSize;
var growth = currentHeap - initialHeap;
var growthMB = growth / 1024 / 1024;
if (growthMB > 10) {
console.warn('Significant memory growth detected: ' +
growthMB.toFixed(2) + 'MB');
resolve(true);
} else {
resolve(false);
}
}, 5000);
});
},
// Check for CSP violations
checkCSPViolations: function() {
var hasViolations = false;
// Listen for CSP violations
document.addEventListener('securitypolicyviolation', function(e) {
console.error('CSP Violation:', e.violatedDirective);
hasViolations = true;
});
return hasViolations;
},
// Report all issues
reportIssues: function(issues) {
if (issues.length === 0) {
console.log('✓ No polyfill issues detected');
return;
}
console.group('Polyfill Issues Detected (' + issues.length + ')');
issues.forEach(function(issue, index) {
var icon = issue.severity === 'error' ? '❌' : '⚠️';
console.group(icon + ' Issue ' + (index + 1) + ': ' + issue.issue);
console.log('Severity:', issue.severity);
console.log('Solution:', issue.solution);
if (issue.conflicts) {
console.log('Conflicts:', issue.conflicts);
}
if (issue.details) {
console.table(issue.details);
}
console.groupEnd();
});
console.groupEnd();
},
// Fix common issues automatically
autoFix: function() {
console.log('Attempting automatic fixes...');
// Fix 1: Remove duplicate polyfills
this.removeDuplicatePolyfills();
// Fix 2: Optimize polyfill loading
this.optimizeLoading();
console.log('Auto-fix complete. Run diagnose() to verify.');
},
// Remove duplicate polyfills
removeDuplicatePolyfills: function() {
var scripts = document.querySelectorAll('script[src*="polyfill"]');
var seen = {};
scripts.forEach(function(script) {
var src = script.src;
if (seen[src]) {
console.log('Removing duplicate polyfill:', src);
script.remove();
} else {
seen[src] = true;
}
});
},
// Optimize polyfill loading
optimizeLoading: function() {
// Add async/defer to non-critical polyfills
var scripts = document.querySelectorAll('script[src*="polyfill"]');
scripts.forEach(function(script) {
if (!script.async && !script.defer) {
// Check if polyfill is critical
var isCritical = script.src.includes('critical') ||
script.src.includes('core');
if (!isCritical) {
script.defer = true;
console.log('Added defer to:', script.src);
}
}
});
}
};
// Quick diagnostic command
window.polyfillCheck = function() {
return PolyfillTroubleshooter.diagnose();
};
// Usage:
// polyfillCheck(); // Run diagnostics
// PolyfillTroubleshooter.autoFix(); // Attempt automatic fixes
Warning: Load polyfills before app code. Async loading can
cause race conditions where app code tries to use polyfilled features before they're available.
3. Performance Profiling with Polyfills
| Metric | Tool | Target | Action |
|---|---|---|---|
| Parse Time | DevTools Performance | <50 ms | Minify, compress |
| Execution Time | Performance API | <100 ms | Lazy load |
| Method Overhead | Benchmarks | <2x native | Optimize implementation |
| Bundle Size | webpack-bundle-analyzer | <100 KB | Tree shake, split |
| FCP Impact | Lighthouse | <100 ms delay | Defer non-critical |
| TTI Impact | WebPageTest | <200 ms delay | Code splitting |
Example: Performance profiling suite
// performance-profiler.js
var PolyfillProfiler = {
profiles: [],
// Profile polyfill loading
profileLoading: function(polyfillName, loadFn) {
var startMark = 'polyfill-load-start-' + polyfillName;
var endMark = 'polyfill-load-end-' + polyfillName;
var measureName = 'polyfill-load-' + polyfillName;
performance.mark(startMark);
return Promise.resolve(loadFn()).then(function() {
performance.mark(endMark);
performance.measure(measureName, startMark, endMark);
var measure = performance.getEntriesByName(measureName)[0];
var profile = {
name: polyfillName,
type: 'loading',
duration: measure.duration,
timestamp: Date.now()
};
this.profiles.push(profile);
console.log('Polyfill ' + polyfillName + ' loaded in ' +
measure.duration.toFixed(2) + 'ms');
return profile;
}.bind(this));
},
// Profile method execution
profileMethod: function(obj, methodName, iterations) {
iterations = iterations || 10000;
var method = obj[methodName];
if (!method) {
console.error('Method not found:', methodName);
return;
}
// Warm up
for (var i = 0; i < 100; i++) {
method.call(obj, i);
}
// Profile
var start = performance.now();
for (var i = 0; i < iterations; i++) {
method.call(obj, i);
}
var end = performance.now();
var duration = end - start;
var avgTime = duration / iterations;
var profile = {
name: methodName,
type: 'execution',
totalTime: duration,
avgTime: avgTime,
iterations: iterations,
opsPerSecond: Math.round(1000 / avgTime)
};
this.profiles.push(profile);
console.log('Method: ' + methodName);
console.log(' Total: ' + duration.toFixed(2) + 'ms');
console.log(' Average: ' + avgTime.toFixed(4) + 'ms');
console.log(' Ops/sec: ' + profile.opsPerSecond.toLocaleString());
return profile;
},
// Compare native vs polyfill performance
comparePerformance: function(nativeFn, polyfillFn, testName, iterations) {
iterations = iterations || 10000;
console.group('Performance Comparison: ' + testName);
// Profile native
var nativeStart = performance.now();
for (var i = 0; i < iterations; i++) {
nativeFn();
}
var nativeDuration = performance.now() - nativeStart;
// Profile polyfill
var polyfillStart = performance.now();
for (var i = 0; i < iterations; i++) {
polyfillFn();
}
var polyfillDuration = performance.now() - polyfillStart;
var slowdown = polyfillDuration / nativeDuration;
var acceptable = slowdown < 2;
console.log('Native: ' + nativeDuration.toFixed(2) + 'ms');
console.log('Polyfill: ' + polyfillDuration.toFixed(2) + 'ms');
console.log('Slowdown: ' + slowdown.toFixed(2) + 'x');
console.log('Acceptable: ' + (acceptable ? '✓' : '✗'));
console.groupEnd();
return {
test: testName,
native: nativeDuration,
polyfill: polyfillDuration,
slowdown: slowdown,
acceptable: acceptable
};
},
// Profile FCP impact
profileFCPImpact: function() {
if (!PerformanceObserver) {
console.warn('PerformanceObserver not available');
return;
}
var observer = new PerformanceObserver(function(list) {
list.getEntries().forEach(function(entry) {
if (entry.name === 'first-contentful-paint') {
var fcp = entry.startTime;
// Calculate polyfill impact
var polyfillMarks = performance.getEntriesByType('mark')
.filter(function(m) { return m.name.includes('polyfill'); });
var polyfillTime = 0;
polyfillMarks.forEach(function(mark) {
var measure = performance.getEntriesByName(mark.name)[0];
if (measure && measure.startTime < fcp) {
polyfillTime += measure.duration || 0;
}
});
var impact = (polyfillTime / fcp) * 100;
console.log('FCP: ' + fcp.toFixed(2) + 'ms');
console.log('Polyfill Time: ' + polyfillTime.toFixed(2) + 'ms');
console.log('Impact: ' + impact.toFixed(1) + '%');
this.profiles.push({
type: 'fcp-impact',
fcp: fcp,
polyfillTime: polyfillTime,
impact: impact
});
}
}.bind(this));
}.bind(this));
observer.observe({ entryTypes: ['paint'] });
},
// Generate comprehensive report
generateReport: function() {
console.group('📊 Polyfill Performance Report');
// Group by type
var byType = {};
this.profiles.forEach(function(profile) {
if (!byType[profile.type]) {
byType[profile.type] = [];
}
byType[profile.type].push(profile);
});
// Loading performance
if (byType.loading) {
console.group('Loading Performance');
var totalLoadTime = byType.loading.reduce(function(sum, p) {
return sum + p.duration;
}, 0);
console.log('Total Load Time: ' + totalLoadTime.toFixed(2) + 'ms');
console.table(byType.loading);
console.groupEnd();
}
// Execution performance
if (byType.execution) {
console.group('Execution Performance');
console.table(byType.execution.map(function(p) {
return {
Method: p.name,
'Avg Time': p.avgTime.toFixed(4) + 'ms',
'Ops/sec': p.opsPerSecond.toLocaleString()
};
}));
console.groupEnd();
}
// FCP impact
if (byType['fcp-impact']) {
console.group('FCP Impact');
byType['fcp-impact'].forEach(function(p) {
console.log('FCP: ' + p.fcp.toFixed(2) + 'ms');
console.log('Polyfill Impact: ' + p.impact.toFixed(1) + '%');
});
console.groupEnd();
}
// Recommendations
console.group('Recommendations');
var recommendations = this.generateRecommendations(byType);
recommendations.forEach(function(rec) {
console.log(rec.icon + ' ' + rec.message);
});
console.groupEnd();
console.groupEnd();
return {
profiles: this.profiles,
summary: byType,
recommendations: recommendations
};
},
// Generate recommendations
generateRecommendations: function(byType) {
var recommendations = [];
// Check loading time
if (byType.loading) {
var totalLoadTime = byType.loading.reduce(function(sum, p) {
return sum + p.duration;
}, 0);
if (totalLoadTime > 100) {
recommendations.push({
icon: '⚠️',
message: 'Total polyfill load time exceeds 100ms (' +
totalLoadTime.toFixed(2) + 'ms). Consider lazy loading.'
});
}
}
// Check execution performance
if (byType.execution) {
byType.execution.forEach(function(profile) {
if (profile.avgTime > 1) {
recommendations.push({
icon: '⚠️',
message: profile.name + ' is slow (>1ms). Optimize implementation.'
});
}
});
}
// Check FCP impact
if (byType['fcp-impact']) {
byType['fcp-impact'].forEach(function(profile) {
if (profile.impact > 20) {
recommendations.push({
icon: '❌',
message: 'Polyfills delay FCP by ' + profile.impact.toFixed(1) +
'%. Use differential serving.'
});
}
});
}
if (recommendations.length === 0) {
recommendations.push({
icon: '✓',
message: 'All metrics within acceptable ranges'
});
}
return recommendations;
}
};
// Usage examples:
// Profile polyfill loading
PolyfillProfiler.profileLoading('Promise', function() {
return import('es6-promise/auto');
});
// Profile method execution
PolyfillProfiler.profileMethod(Array.prototype, 'find', 10000);
// Compare native vs polyfill
if (Array.prototype.map) {
var nativeMap = Array.prototype.map;
var polyfillMap = function() { /* polyfill implementation */ };
PolyfillProfiler.comparePerformance(
function() { [1,2,3].map(function(x) { return x * 2; }); },
function() { [1,2,3].map(function(x) { return x * 2; }); },
'Array.map',
10000
);
}
// Monitor FCP impact
PolyfillProfiler.profileFCPImpact();
// Generate report after page load
window.addEventListener('load', function() {
setTimeout(function() {
PolyfillProfiler.generateReport();
}, 2000);
});
Note: Profile polyfills in target browsers (especially legacy
ones). Modern browser performance doesn't reflect real-world usage.
4. Feature Detection Debugging Techniques
| Technique | Use Case | Example | Gotcha |
|---|---|---|---|
| typeof Check | Check if feature exists | typeof Promise !== 'undefined' | May exist but be broken |
| in Operator | Check method existence | 'includes' in Array.prototype | Doesn't verify correctness |
| try-catch Test | Test feature behavior | Try executing, catch errors | Can hide other errors |
| Native Code Check | Verify if native | fn.toString() includes '[native code]' | Can be spoofed |
| Behavior Test | Test actual functionality | Execute and verify result | Can be slow |
| CSS.supports() | CSS feature detection | CSS.supports('display', 'grid') | Limited browser support |
Example: Advanced feature detection utilities
// feature-detection-debugger.js
var FeatureDetector = {
// Comprehensive feature check
detectFeature: function(featureName) {
var detectors = {
'Promise': this.detectPromise.bind(this),
'fetch': this.detectFetch.bind(this),
'Map': this.detectMap.bind(this),
'Set': this.detectSet.bind(this),
'Symbol': this.detectSymbol.bind(this),
'Proxy': this.detectProxy.bind(this),
'IntersectionObserver': this.detectIntersectionObserver.bind(this),
'CustomElements': this.detectCustomElements.bind(this)
};
var detector = detectors[featureName];
if (!detector) {
console.error('No detector for feature:', featureName);
return null;
}
var result = detector();
console.group('Feature Detection: ' + featureName);
console.log('Exists:', result.exists);
console.log('Native:', result.isNative);
console.log('Working:', result.isWorking);
console.log('Details:', result.details);
console.groupEnd();
return result;
},
// Detect Promise support
detectPromise: function() {
var exists = typeof Promise !== 'undefined';
var isNative = exists && Promise.toString().indexOf('[native code]') !== -1;
var isWorking = false;
var details = {};
if (exists) {
try {
var promise = new Promise(function(resolve) {
resolve('test');
});
isWorking = promise instanceof Promise;
// Test Promise.all
details.hasAll = typeof Promise.all === 'function';
details.hasRace = typeof Promise.race === 'function';
details.hasAllSettled = typeof Promise.allSettled === 'function';
details.hasAny = typeof Promise.any === 'function';
} catch (e) {
details.error = e.message;
}
}
return { exists: exists, isNative: isNative, isWorking: isWorking, details: details };
},
// Detect fetch support
detectFetch: function() {
var exists = typeof fetch !== 'undefined';
var isNative = exists && fetch.toString().indexOf('[native code]') !== -1;
var isWorking = false;
var details = {};
if (exists) {
try {
// Check if fetch returns a Promise
var result = fetch('data:text/plain,test');
isWorking = result instanceof Promise;
result.catch(function() {}); // Prevent unhandled rejection
details.hasAbortController = typeof AbortController !== 'undefined';
details.hasHeaders = typeof Headers !== 'undefined';
details.hasRequest = typeof Request !== 'undefined';
details.hasResponse = typeof Response !== 'undefined';
} catch (e) {
details.error = e.message;
}
}
return { exists: exists, isNative: isNative, isWorking: isWorking, details: details };
},
// Detect Map support
detectMap: function() {
var exists = typeof Map !== 'undefined';
var isNative = exists && Map.toString().indexOf('[native code]') !== -1;
var isWorking = false;
var details = {};
if (exists) {
try {
var map = new Map();
map.set('key', 'value');
isWorking = map.get('key') === 'value' && map.size === 1;
// Test iterator
details.hasIterator = typeof map[Symbol.iterator] === 'function';
details.hasForEach = typeof map.forEach === 'function';
} catch (e) {
details.error = e.message;
}
}
return { exists: exists, isNative: isNative, isWorking: isWorking, details: details };
},
// Detect Set support
detectSet: function() {
var exists = typeof Set !== 'undefined';
var isNative = exists && Set.toString().indexOf('[native code]') !== -1;
var isWorking = false;
var details = {};
if (exists) {
try {
var set = new Set();
set.add('value');
isWorking = set.has('value') && set.size === 1;
details.hasIterator = typeof set[Symbol.iterator] === 'function';
details.hasForEach = typeof set.forEach === 'function';
} catch (e) {
details.error = e.message;
}
}
return { exists: exists, isNative: isNative, isWorking: isWorking, details: details };
},
// Detect Symbol support
detectSymbol: function() {
var exists = typeof Symbol !== 'undefined';
var isNative = exists && Symbol.toString().indexOf('[native code]') !== -1;
var isWorking = false;
var details = {};
if (exists) {
try {
var sym = Symbol('test');
isWorking = typeof sym === 'symbol';
details.hasFor = typeof Symbol.for === 'function';
details.hasKeyFor = typeof Symbol.keyFor === 'function';
details.hasIterator = typeof Symbol.iterator !== 'undefined';
} catch (e) {
details.error = e.message;
}
}
return { exists: exists, isNative: isNative, isWorking: isWorking, details: details };
},
// Detect Proxy support
detectProxy: function() {
var exists = typeof Proxy !== 'undefined';
var isNative = exists && Proxy.toString().indexOf('[native code]') !== -1;
var isWorking = false;
var details = {};
if (exists) {
try {
var target = {};
var handler = {
get: function(obj, prop) {
return prop in obj ? obj[prop] : 'proxy';
}
};
var proxy = new Proxy(target, handler);
isWorking = proxy.test === 'proxy';
details.hasRevocable = typeof Proxy.revocable === 'function';
} catch (e) {
details.error = e.message;
}
}
return { exists: exists, isNative: isNative, isWorking: isWorking, details: details };
},
// Detect IntersectionObserver support
detectIntersectionObserver: function() {
var exists = typeof IntersectionObserver !== 'undefined';
var isNative = exists && IntersectionObserver.toString().indexOf('[native code]') !== -1;
var isWorking = false;
var details = {};
if (exists) {
try {
var observer = new IntersectionObserver(function() {});
isWorking = typeof observer.observe === 'function';
observer.disconnect();
} catch (e) {
details.error = e.message;
}
}
return { exists: exists, isNative: isNative, isWorking: isWorking, details: details };
},
// Detect Custom Elements support
detectCustomElements: function() {
var exists = typeof customElements !== 'undefined';
var isNative = exists && customElements.define.toString().indexOf('[native code]') !== -1;
var isWorking = false;
var details = {};
if (exists) {
try {
isWorking = typeof customElements.define === 'function';
details.hasGet = typeof customElements.get === 'function';
details.hasWhenDefined = typeof customElements.whenDefined === 'function';
} catch (e) {
details.error = e.message;
}
}
return { exists: exists, isNative: isNative, isWorking: isWorking, details: details };
},
// Batch detection
detectAll: function() {
var features = [
'Promise', 'fetch', 'Map', 'Set', 'Symbol', 'Proxy',
'IntersectionObserver', 'CustomElements'
];
var results = {};
features.forEach(function(feature) {
results[feature] = this.detectFeature(feature);
}.bind(this));
console.table(Object.keys(results).map(function(name) {
var result = results[name];
return {
Feature: name,
Exists: result.exists ? '✓' : '✗',
Native: result.isNative ? '✓' : '✗',
Working: result.isWorking ? '✓' : '✗'
};
}));
return results;
},
// Debug why feature detection fails
debugDetection: function(featureName, detectionCode) {
console.group('Debugging Feature Detection: ' + featureName);
try {
var result = detectionCode();
console.log('Detection Result:', result);
console.log('Type:', typeof result);
console.log('Truthy:', !!result);
} catch (e) {
console.error('Detection Error:', e);
console.log('Stack:', e.stack);
}
console.groupEnd();
}
};
// Global helper
window.detectFeature = FeatureDetector.detectFeature.bind(FeatureDetector);
window.detectAllFeatures = FeatureDetector.detectAll.bind(FeatureDetector);
// Usage:
// detectFeature('Promise');
// detectAllFeatures();
// FeatureDetector.debugDetection('CustomElements', function() {
// return typeof customElements !== 'undefined';
// });
Warning: Feature detection can give false positives. Always
test behavior, not just existence. Some browsers partially implement features.
5. Polyfill Conflict Resolution Strategies
| Conflict Type | Cause | Detection | Resolution |
|---|---|---|---|
| Duplicate Polyfills | Multiple libraries include same polyfill | Check for multiple implementations | Deduplicate, use single source |
| Version Conflicts | Different polyfill versions | Compare implementations | Use latest compatible version |
| Override Conflicts | Polyfill overrides native incorrectly | Check for native code | Guard with feature detection |
| Load Order Issues | Polyfills loaded in wrong order | Check dependencies | Correct load order |
| Namespace Collisions | Global variable conflicts | Check window object | Use modules, avoid globals |
| Prototype Pollution | Unsafe prototype extensions | Check prototype chain | Use Object.defineProperty |
Example: Conflict detection and resolution
// conflict-resolver.js
var PolyfillConflictResolver = {
conflicts: [],
// Detect all conflicts
detectConflicts: function() {
this.conflicts = [];
// Check for duplicate polyfills
this.checkDuplicates();
// Check for override conflicts
this.checkOverrides();
// Check for namespace collisions
this.checkNamespaceCollisions();
// Report conflicts
this.reportConflicts();
return this.conflicts;
},
// Check for duplicate polyfills
checkDuplicates: function() {
var methods = [
{ obj: Array.prototype, name: 'find', feature: 'Array.find' },
{ obj: Array.prototype, name: 'includes', feature: 'Array.includes' },
{ obj: Object, name: 'assign', feature: 'Object.assign' },
{ obj: String.prototype, name: 'includes', feature: 'String.includes' },
{ obj: window, name: 'Promise', feature: 'Promise' },
{ obj: window, name: 'fetch', feature: 'fetch' }
];
methods.forEach(function(method) {
var fn = method.obj[method.name];
if (!fn) return;
// Check if method has been polyfilled multiple times
var descriptor = Object.getOwnPropertyDescriptor(method.obj, method.name);
if (descriptor && descriptor.writable === false && descriptor.configurable === false) {
// Check if there are multiple definitions
var source = fn.toString();
if (source.includes('polyfill') && source.length > 1000) {
this.conflicts.push({
type: 'duplicate',
feature: method.feature,
severity: 'warning',
message: 'Possible duplicate polyfill detected'
});
}
}
}.bind(this));
},
// Check for override conflicts
checkOverrides: function() {
var methods = [
{ obj: Array.prototype, name: 'map' },
{ obj: Array.prototype, name: 'filter' },
{ obj: Array.prototype, name: 'reduce' },
{ obj: Object, name: 'keys' },
{ obj: Object, name: 'values' }
];
methods.forEach(function(method) {
var fn = method.obj[method.name];
if (!fn) return;
var source = fn.toString();
var isNative = source.indexOf('[native code]') !== -1;
// Check if a polyfill overrode a native method
var descriptor = Object.getOwnPropertyDescriptor(method.obj, method.name);
if (!isNative && descriptor && !descriptor.configurable) {
this.conflicts.push({
type: 'override',
feature: method.obj.constructor.name + '.' + method.name,
severity: 'error',
message: 'Polyfill incorrectly overrides native method'
});
}
}.bind(this));
},
// Check for namespace collisions
checkNamespaceCollisions: function() {
var globalVars = ['Promise', 'Map', 'Set', 'Symbol', 'Proxy', 'Reflect'];
globalVars.forEach(function(varName) {
var value = window[varName];
if (!value) return;
// Check if variable is defined multiple times
var descriptor = Object.getOwnPropertyDescriptor(window, varName);
if (descriptor && descriptor.configurable === false) {
var source = value.toString();
if (source.includes('polyfill')) {
this.conflicts.push({
type: 'namespace',
feature: varName,
severity: 'warning',
message: 'Global namespace collision detected'
});
}
}
}.bind(this));
},
// Report all conflicts
reportConflicts: function() {
if (this.conflicts.length === 0) {
console.log('✓ No polyfill conflicts detected');
return;
}
console.group('⚠️ Polyfill Conflicts (' + this.conflicts.length + ')');
this.conflicts.forEach(function(conflict, index) {
var icon = conflict.severity === 'error' ? '❌' : '⚠️';
console.group(icon + ' Conflict ' + (index + 1) + ': ' + conflict.type);
console.log('Feature:', conflict.feature);
console.log('Severity:', conflict.severity);
console.log('Message:', conflict.message);
console.groupEnd();
});
console.groupEnd();
},
// Resolve conflicts automatically
resolveConflicts: function() {
console.log('Attempting to resolve conflicts...');
this.conflicts.forEach(function(conflict) {
switch (conflict.type) {
case 'duplicate':
this.resolveDuplicate(conflict);
break;
case 'override':
this.resolveOverride(conflict);
break;
case 'namespace':
this.resolveNamespace(conflict);
break;
}
}.bind(this));
console.log('Conflict resolution complete');
},
// Resolve duplicate polyfills
resolveDuplicate: function(conflict) {
console.log('Resolving duplicate:', conflict.feature);
// Remove duplicate script tags
var scripts = document.querySelectorAll('script[src*="' + conflict.feature.toLowerCase() + '"]');
if (scripts.length > 1) {
for (var i = 1; i < scripts.length; i++) {
console.log('Removing duplicate script:', scripts[i].src);
scripts[i].remove();
}
}
},
// Resolve override conflicts
resolveOverride: function(conflict) {
console.error('Cannot auto-resolve override conflict:', conflict.feature);
console.log('Manual intervention required:');
console.log('1. Check if native implementation exists');
console.log('2. Remove polyfill if native is available');
console.log('3. Or guard polyfill with feature detection');
},
// Resolve namespace collisions
resolveNamespace: function(conflict) {
console.log('Resolving namespace collision:', conflict.feature);
console.log('Consider using ES modules instead of global variables');
},
// Prevent future conflicts
preventConflicts: function() {
// Freeze native methods to prevent override
var methods = [
{ obj: Array.prototype, names: ['map', 'filter', 'reduce', 'forEach'] },
{ obj: Object, names: ['keys', 'values', 'entries', 'assign'] }
];
methods.forEach(function(group) {
group.names.forEach(function(name) {
if (group.obj[name]) {
var descriptor = Object.getOwnPropertyDescriptor(group.obj, name);
if (descriptor && descriptor.configurable) {
Object.defineProperty(group.obj, name, {
value: group.obj[name],
writable: false,
configurable: false,
enumerable: false
});
console.log('Protected:', group.obj.constructor.name + '.' + name);
}
}
});
});
}
};
// Global helper
window.checkPolyfillConflicts = function() {
return PolyfillConflictResolver.detectConflicts();
};
window.resolvePolyfillConflicts = function() {
PolyfillConflictResolver.resolveConflicts();
};
// Usage:
// checkPolyfillConflicts();
// resolvePolyfillConflicts();
// PolyfillConflictResolver.preventConflicts();
Key Takeaways - Debugging & Troubleshooting
- DevTools: Use Sources, Console, Network, Performance, and Coverage tabs
- Common Issues: Load order, conflicts, CSP violations, performance
- Profiling: Measure parse, execution, FCP impact, target <2x native
- Detection: Test behavior not just existence, check for native code
- Conflicts: Deduplicate, correct load order, use feature detection guards