UNPKG

phantomas

Version:

Headless Chromium-based web performance metrics collector and monitoring tool

335 lines (273 loc) 8.48 kB
/** * phantomas browser "scope" with helper code * * Code below is executed in page's "scope" (injected by the scope.injectJs() helper) */ /* istanbul ignore next */ (function coreScope(scope) { "use strict"; // create a scope var phantomas = (scope.__phantomas = scope.__phantomas || {}); // keep the original JSON functions (#482) phantomas.JSON = { parse: JSON.parse, stringify: JSON.stringify, }; // NodeRunner var nodeRunner = function () { // "Beep, Beep" }; nodeRunner.prototype = { // call callback for each child of node walk: function (node, callback, depth) { if (this.isSkipped(node)) { return; } var childNode, childNodes = (node && node.childNodes) || []; depth = depth || 1; for (var n = 0, len = childNodes.length; n < len; n++) { childNode = childNodes[n]; // callback can return false to stop recursive if (callback(childNode, depth) !== false) { this.walk(childNode, callback, depth + 1); } } }, // override this function when you create an object of class phantomas.nodeRunner // by default only iterate over HTML elements isSkipped: function (node) { return !node || node.nodeType !== Node.ELEMENT_NODE; }, }; // for backtraces (function () { // @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/Stack function getStackFromError(e) { var stack = e.stack .trim() .split("\n") .map(function (item) { return item.replace(/^(\s+at\s|@)/, "").trim(); }) .filter(function (item) { return /:\d+\)?$/.test(item); }); //console.log(stack); return stack; } function getBacktrace() { var stack = []; try { throw new Error("backtrace"); } catch (e) { stack = getStackFromError(e).slice(3); } return stack.join(" / "); } function getCaller(stepBack) { var caller = false; stepBack = stepBack || 0; try { throw new Error("backtrace"); } catch (e) { caller = getStackFromError(e)[3 + stepBack]; } return caller; } phantomas.getBacktrace = getBacktrace; phantomas.getCaller = getCaller; })(); // communication with phantomas core (function () { var stringify = JSON.stringify, origConsoleLog = console.log; // overrride console.log (issue #69) console.log = function () { // pass all arguments as an array, let phantomas format them // @see https://developer.mozilla.org/en-US/docs/Web/API/console // avoid 'TypeError: JSON.stringify cannot serialize cyclic structures.' try { origConsoleLog.call( console, "log:" + stringify(Array.prototype.slice.call(arguments)) ); // eslint-disable-next-line no-empty } catch (e) {} }; function sendMsg(type, data) { scope.__phantomas_emit("scopeMessage", type, data); } function log() { sendMsg("log", Array.prototype.slice.apply(arguments)); } function setMetric(name, value, isFinal) { sendMsg("setMetric", [ name, typeof value !== "undefined" ? value : 0, isFinal === true, ]); } function incrMetric(name, incr /* =1 */) { sendMsg("incrMetric", [name, incr || 1]); } function addToAvgMetric(name, value) { sendMsg("addToAvgMetric", { name: name, value: value, }); } function setMarkerMetric(name) { sendMsg("setMarkerMetric", { name: name, }); } function addOffender(/*metricName, msg, ...*/) { sendMsg("addOffender", Array.prototype.slice.apply(arguments)); } // see lib/index.js code that injects __phantomas_options into page scope function getParam(param, _default) { return scope.__phantomas_options[param] || _default; } // exports phantomas.log = log; phantomas.setMetric = setMetric; phantomas.incrMetric = incrMetric; phantomas.addToAvgMetric = addToAvgMetric; phantomas.setMarkerMetric = setMarkerMetric; phantomas.addOffender = addOffender; phantomas.emit = scope.__phantomas_emit.bind(scope); phantomas.getParam = getParam; })(); /** * Proxy function to be used to track calls to native DOM functions * * Callback is provided with arguments original function was called with * * Example: * * window.__phantomas.proxy(window.document, 'getElementById', function() { * // ... * }); */ (function () { var enabled = true; // turn off spying to not include internal phantomas actions function spyEnabled(state, reason) { enabled = state === true; phantomas.log( "Spying " + (enabled ? "enabled" : "disabled") + (reason ? " - " + reason : "") ); } // pass reportResults = true to prepend arguments passed to callback // with the result of call to the original function - issue #420 function spy(obj, fn, callback, reportResults) { var origFn = obj && obj[fn]; if (typeof origFn !== "function") { return false; } phantomas.log( 'spy: attaching to "%s" function%s', fn, reportResults ? " with results reporting" : "" ); obj[fn] = function () { var args = Array.prototype.slice.call(arguments), results = origFn.apply(this, args); if (enabled && typeof callback === "function") { callback.apply( this, reportResults === true ? [results].concat(args) : args ); } return results; }; // copy custom properties of original function to the mocked one Object.keys(origFn).forEach(function (key) { obj[fn][key] = origFn[key]; }); obj[fn].prototype = origFn.prototype; return true; } var spiedGlobals = {}; // call given callback when "varName" global variable is being defined // used by jQuery module function spyGlobalVar(varName, callback) { phantomas.log("spy: attaching to %s global variable", varName); window.__defineSetter__(varName, function (val) { phantomas.log("spy: %s global variable has been defined", varName); spiedGlobals[varName] = val; callback(val); }); window.__defineGetter__(varName, function () { return spiedGlobals[varName] || undefined; }); } // exports phantomas.spy = spy; phantomas.spyGlobalVar = spyGlobalVar; phantomas.spyEnabled = spyEnabled; })(); /** * Returns "DOM path" to a given node (starting from <body> down to the node) * * Example: body.logged_out.vis-public.env-production > div > div */ function getDOMPath(node, dontGoUpTheDom /* = false */) { var path = [], entry = ""; if (node === window) { return "window"; } while (node instanceof Node) { // div entry = node.nodeName.toLowerCase(); // shorten the path a bit if (["body", "head", "html"].indexOf(entry) > -1) { path.push(entry); break; } if (node instanceof DocumentFragment) { entry = "DocumentFragment"; } // div#foo if (node.id && node.id !== "") { entry += "#" + node.id; } // div#foo.bar.test else if (typeof node.className === "string" && node.className !== "") { entry += "." + node.className.trim().replace(/\s+/g, "."); } // div[0] <- index of child node else if (node.parentNode instanceof Node) { entry += "[" + Math.max( 0, Array.prototype.indexOf.call( node.parentNode.children || node.parentNode.childNodes, node ) ) + "]"; } path.push(entry); if (dontGoUpTheDom === true) { break; } // go up the DOM node = node && node.parentNode; } return path.length > 0 ? path.reverse().join(" > ") : false; } // exports phantomas.getDOMPath = getDOMPath; phantomas.nodeRunner = nodeRunner; phantomas.log( "phantomas page scope initialized for <%s> (is an iframe: %s)", window.location.toString(), window.parent !== window ); })(window);