UNPKG

current-executing-script

Version:

Get the currently executing script, regardless of its source/trigger/synchronicity. Similar to HTML5's `document.currentScript` but arguably much more useful!

195 lines (168 loc) 4.89 kB
/*! * document.currentScript * Polyfill for `document.currentScript`. * Copyright (c) 2015 James M. Greene * Licensed MIT * http://jsfiddle.net/JamesMGreene/9DFc9/ * v0.1.7 */ (function() { var hasStackBeforeThrowing = false, hasStackAfterThrowing = false; (function() { try { var err = new Error(); hasStackBeforeThrowing = typeof err.stack === "string" && !!err.stack; throw err; } catch (thrownErr) { hasStackAfterThrowing = typeof thrownErr.stack === "string" && !!thrownErr.stack; } })(); // This page's URL var pageUrl = window.location.href; // Live NodeList collection var scripts = document.getElementsByTagName("script"); // Get script object based on the `src` URL function getScriptFromUrl(url) { if (typeof url === "string" && url) { for (var i = 0, len = scripts.length; i < len; i++) { if (scripts[i].src === url) { return scripts[i]; } } } return null; } // If there is only a single inline script on the page, return it; otherwise `null` function getSoleInlineScript() { var script = null; for (var i = 0, len = scripts.length; i < len; i++) { if (!scripts[i].src) { if (script) { return null; } script = scripts[i]; } } return script; } // Get the configured default value for how many layers of stack depth to ignore function getStackDepthToSkip() { var depth = 0; if ( typeof _currentScript !== "undefined" && _currentScript && typeof _currentScript.skipStackDepth === "number" ) { depth = _currentScript.skipStackDepth; } return depth; } // Get the currently executing script URL from an Error stack trace function getScriptUrlFromStack(stack, skipStackDepth) { var url, matches, remainingStack, ignoreMessage = typeof skipStackDepth === "number"; skipStackDepth = ignoreMessage ? skipStackDepth : getStackDepthToSkip(); if (typeof stack === "string" && stack) { if (ignoreMessage) { matches = stack.match(/(data:text\/javascript(?:;[^,]+)?,.+?|(?:|blob:)(?:http[s]?|file):\/\/[\/]?.+?\/[^:\)]*?)(?::\d+)(?::\d+)?/); } else { matches = stack.match(/^(?:|[^:@]*@|.+\)@(?=data:text\/javascript|blob|http[s]?|file)|.+?\s+(?: at |@)(?:[^:\(]+ )*[\(]?)(data:text\/javascript(?:;[^,]+)?,.+?|(?:|blob:)(?:http[s]?|file):\/\/[\/]?.+?\/[^:\)]*?)(?::\d+)(?::\d+)?/); if (!(matches && matches[1])) { matches = stack.match(/\)@(data:text\/javascript(?:;[^,]+)?,.+?|(?:|blob:)(?:http[s]?|file):\/\/[\/]?.+?\/[^:\)]*?)(?::\d+)(?::\d+)?/); } } if (matches && matches[1]) { if (skipStackDepth > 0) { remainingStack = stack.slice(stack.indexOf(matches[0]) + matches[0].length); url = getScriptUrlFromStack(remainingStack, (skipStackDepth - 1)); } else { url = matches[1]; } } } return url; } // Get the currently executing `script` DOM element function _currentScript() { // Yes, this IS actually possible if (scripts.length === 0) { return null; } if (scripts.length === 1) { return scripts[0]; } if ("readyState" in scripts[0]) { for (var i = scripts.length; i--; ) { if (scripts[i].readyState === "interactive") { return scripts[i]; } } } if (document.readyState === "loading") { return scripts[scripts.length - 1]; } var stack, e = new Error(); if (hasStackBeforeThrowing) { stack = e.stack; } if (!stack && hasStackAfterThrowing) { try { throw e; } catch (err) { // NOTE: Cannot use `err.sourceURL` or `err.fileName` as they will always be THIS script stack = err.stack; } } if (stack) { var url = getScriptUrlFromStack(stack); var script = getScriptFromUrl(url); if (!script && url === pageUrl) { script = getSoleInlineScript(); } return script; } return null; } // Configuration _currentScript.skipStackDepth = 1; // Inspect the polyfill-ability of this browser var needsPolyfill = !("currentScript" in document); var canDefineGetter = document.__defineGetter__; var canDefineProp = typeof Object.defineProperty === "function" && (function() { var result; try { Object.defineProperty(document, "_xyz", { get: function() { return "blah"; }, configurable: true }); result = document._xyz === "blah"; delete document._xyz; } catch (e) { result = false; } return result; })(); // Add the "private" property for testing, even if the real property can be polyfilled document._currentScript = _currentScript; // Polyfill it! if (needsPolyfill) { if (canDefineProp) { Object.defineProperty(document, "currentScript", { get: _currentScript }); } else if (canDefineGetter) { document.__defineGetter__("currentScript", _currentScript); } } })();