Skip to content
Permalink
038c04fa90
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
executable file 443 lines (375 sloc) 13.1 KB
// Wrap in a function closure to hide variables
(function () {
// Bypass the Jetpack DOM wrapper
//let(window = unsafeWindow) {
// Header guard workaround for Jetpack multiple script loading bug
if(typeof window.navigator.instrumented == "undefined") {
window.navigator.__defineGetter__("instrumented", function() { return true; });
// Debugging
// Default is off, to enable include in your script
// window.navigator.__defineGetter__("intrumentation_debugging", function() { return true; });
window.navigator.__defineGetter__("intrumentation_debugging", function() { return false; });
function debugging() { return window.navigator.instrumentation_debugging; }
// Debugging tool - last accessed variable
var last_accessed = "";
window.navigator.__defineGetter__("last_accessed", function() { return last_accessed; });
// Instrumentation helpers
// Recursively generates a path for an element
function getPathToDomElement(element) {
if(element == document.body)
return element.tagName;
if(element.parentNode == null)
return 'NULL/' + element.tagName;
var siblingIndex = 1;
var siblings = element.parentNode.childNodes;
for (var i = 0; i < siblings.length; i++) {
var sibling = siblings[i];
if (sibling == element) {
var path = getPathToDomElement(element.parentNode);
path += '/' + element.tagName + '[' + siblingIndex;
path += ',' + element.id;
path += ',' + element.className;
if(element.tagName == 'A')
path += ',' + element.href;
path += ']';
return path;
}
if (sibling.nodeType == 1 && sibling.tagName == element.tagName)
siblingIndex++;
}
}
// Helper for JSONifying objects
function serializeObject(object) {
// Handle permissions errors
try {
if(object == null)
return "null";
if(typeof object == "function")
return "FUNCTION";
if(typeof object != "object")
return object;
var seenObjects = [];
return JSON.stringify(object, function(key, value) {
if(value == null)
return "null";
if(typeof value == "function")
return "FUNCTION";
if(typeof value == "object") {
// Remove wrapping on content objects
if("wrappedJSObject" in value) {
value = value.wrappedJSObject;
}
// Serialize DOM elements
if(value instanceof HTMLElement)
return getPathToDomElement(value);
// Prevent serialization cycles
if(key == "" || seenObjects.indexOf(value) < 0) {
seenObjects.push(value);
return value;
}
else
return typeof value;
}
return value;
});
}
catch(error) {
console.log("SERIALIZATION ERROR: " + error);
return "SERIALIZATION ERROR: " + error;
}
}
function logErrorToConsole(error) {
console.log("Error name: " + error.name);
console.log("Error message: " + error.message);
console.log("Error filename: " + error.fileName);
console.log("Error line number: " + error.lineNumber);
console.log("Error stack: " + error.stack);
}
// Prevent logging of gets arising from logging
var inLog = false;
// For gets, sets, etc. on a single value
function logValue(instrumentedVariableName, value, operation) {
if(inLog)
return;
inLog = true;
try {
self.port.emit("instrumentation", {
operation: operation,
symbol: instrumentedVariableName,
value: serializeObject(value)
});
}
catch(error) {
/*
console.log("Unsuccessful value log!");
console.log("Operation: " + operation);
console.log("Symbol: " + instrumentedVariableName);
console.log("String Value: " + value);
console.log("Serialized Value: " + serializeObject(value));
logErrorToConsole(error);
*/
}
inLog = false;
}
// For functions
function logCall(instrumentedFunctionName, args) {
if(inLog)
return;
inLog = true;
try {
/*
console.log("logCall");
console.log("Function Name: " + instrumentedFunctionName);
console.log("Args: " + args.length);
for(var i = 0; i < args.length; i++) {
var logLine = "Arg " + i + ": ";
console.log(logLine + typeof args[i]);
if(typeof args[i] == "string")
console.log(logLine + args[i]);
if(typeof args[i] == "object") {
console.log("" + args[i]);
console.log("" + args[i].wrappedJSObject);
console.log(logLine + Object.keys(args[i]));
}
}*/
// Convert special arguments array to a standard array for JSONifying
var serialArgs = [ ];
for(var i = 0; i < args.length; i++)
serialArgs.push(serializeObject(args[i]));
self.port.emit("instrumentation", {
operation: "call",
symbol: instrumentedFunctionName,
args: serialArgs,
value: ""
});
}
catch(error) {
console.log("Unsuccessful call log: " + instrumentedFunctionName);
logErrorToConsole(error);
}
inLog = false;
}
/*
function log(instrumentedVariableName) {
try {
self.postMessage(instrumentedVariableName);
if(debugging())
last_accessed = instrumentedVariableName;
}
catch(error) {
console.log("Attempted to log: " + instrumentedVariableName);
}
}
*/
// Disable setting the document location directly
// Jetpack scripts currently detach when the document is changed in JavaScript
// Make an anonymous handler function that returns an object
function makeHandler(object) {
return function() { return object; };
}
// Make an instrumented object proxy
function makeObjectProxy(objectName, object) {
return Proxy.create({
getOwnPropertyDescriptor: function(name) {
return Object.getOwnPropertyDescriptor(object, name);
},
getPropertyDescriptor: function(name) {
return Object.getPropertyDescriptor(object, name);
},
getOwnPropertyNames: function() {
return Object.getOwnPropertyNames(object);
},
getPropertyNames: function() {
return Object.getPropertyNames(object);
},
defineProperty: function(name, description) {
Object.defineProperty(object, name, description);
},
delete: function(name) {
return delete object[name];
},
fix: function() {
return undefined;
},
has: function(name) {
return name in object;
},
hasOwn: function(name) {
return ({}).hasOwnProperty.call(object, name);
},
get: function(receiver, name) {
if(typeof object[name] == "function")
return makeFunctionProxy(object, objectName + "." + name, object[name]);
else {
logValue(objectName + "." + name, object[name], "get");
return object[name];
}
},
set: function(receiver, name, val) {
logValue(objectName + "." + name, val, "set");
object[name] = val;
return true;
},
enumerate: function() {
logValue(objectName, null, "enumerate");
var result = [];
for(name in object)
result.push(name);
return result;
},
keys: function() {
logValue(objectName, null, "keys");
return Object.keys(object);
}
});
}
function prettyPrintParameter(parameter)
{
if(typeof parameter == "string")
return '"' + parameter + '"';
else
return parameter;
}
// Make an instrumented function proxy
function makeFunctionProxy(object, functionName, func) {
return Proxy.createFunction({
getOwnPropertyDescriptor: function(name) {
return Object.getOwnPropertyDescriptor(func, name);
},
getPropertyDescriptor: function(name) {
return Object.getPropertyDescriptor(func, name);
},
getOwnPropertyNames: function() {
return Object.getOwnPropertyNames(func);
},
getPropertyNames: function() {
return Object.getPropertyNames(func);
},
defineProperty: function(name, description) {
Object.defineProperty(func, name, description);
},
delete: function(name) {
return delete func[name];
},
fix: function() {
return undefined;
},
has: function(name) {
return name in func;
},
hasOwn: function(name) {
return ({}).hasOwnProperty.call(func, name);
},
get: function(receiver, name) {
return func[name];
},
set: function(receiver, name, val) {
func[name] = val;
return true;
},
enumerate: function() {
for(name in func)
result.push(name);
return result;
},
keys: function() {
return Object.keys(func);
}
},
function() {
logCall(functionName, arguments);
return func.apply(object, arguments);
},
function() {
return null;
});
}
// Make an instrumented object handler
function makeObjectProxyHandler(objectName, object) {
return makeHandler(makeObjectProxy(objectName, object));
}
// Make an instrumented function handler
function makeFunctionProxyHandler(object, functionName, func) {
return makeHandler(makeFunctionProxy(object, functionName, func));
}
// Make an instrumented variable handler
// TODO: Include support for setting variables
function makeVariableProxyHandler(name, value) {
return function() {
logValue(name, value, "get");
return value;
};
}
// Instrument an object's property, treating the property as an object
function instrumentObjectPropertyAsObject(object, objectName, property, propertyName) {
object.__defineGetter__(propertyName, makeObjectProxyHandler(objectName + "." + propertyName, property));
}
// Instrument an object's property, treating the property as a variable
function instrumentObjectPropertyAsVariable(object, objectName, property, propertyName) {
object.__defineGetter__(propertyName, makeVariableProxyHandler(objectName + "." + propertyName, property));
}
// Instrumentation
// Instrument window.screen.*
// WORKS
instrumentObjectPropertyAsObject(window, "window", window.screen, "screen");
// Instrument window.navigator.*
// window.navigator is defined as const, so must instrument variable by variable
// WORKS
var navigatorProperties = [ "appCodeName", "appMinorVersion", "appName", "appVersion", "cookieEnabled", "cpuClass", "onLine", "opsProfile", "platform", "product", "systemLanguage", "userAgent", "userLanguage", "userProfile" ];
for each (var property in navigatorProperties)
instrumentObjectPropertyAsVariable(window.navigator, "window.navigator", window.navigator[property], property);
// Instrument each plugin in window.navigator.plugins
// WORKS
// TODO: Instrument plugins returned by the item and namedItem methods
// TODO: Instrument the mime type within each plugin
// TODO: Separately instrument the mimetypes for lookup by type and index
for(var i = 0; i < window.navigator.plugins.length; i++) {
// Instrument name lookup
if(typeof window.navigator.plugins[i].name == "string" && window.navigator.plugins[i].name != "")
window.navigator.plugins.__defineGetter__(window.navigator.plugins[i].name, makeObjectProxyHandler('window.navigator.plugins["' + window.navigator.plugins[i].name + '"]', window.navigator.plugins[i]));
// Instrument index lookup
window.navigator.plugins.__defineGetter__(i, makeObjectProxyHandler("window.navigator.plugins[" + i + "]", window.navigator.plugins[i]));
}
// Instrument window.navigator.plugins.*
// WORKS
instrumentObjectPropertyAsObject(window.navigator, "window.navigator", window.navigator.plugins, "plugins");
// Instrument each mime type in window.navigator.mimeTypes
// Uses deep copies of each mime type to preserve the path through the enabledPlugin property
// WORKS
// TODO: Instrument mime types returned by the item and namedItem methods
// TODO: Separately instrument enabledPlugin for lookup by type and index
for(var i = 0; i < window.navigator.mimeTypes.length; i++) {
// Instrument type lookup
if(typeof window.navigator.mimeTypes[i].type == "string" && window.navigator.mimeTypes[i].type != "") {
window.navigator.mimeTypes[i].__defineGetter__("enabledPlugin", makeObjectProxyHandler('window.navigator.mimeTypes["' + window.navigator.mimeTypes[i].type + '"].enabledPlugin', window.navigator.mimeTypes[i].enabledPlugin));
window.navigator.mimeTypes.__defineGetter__(window.navigator.mimeTypes[i].type, makeObjectProxyHandler('window.navigator.mimeTypes["' + window.navigator.mimeTypes[i].type + '"]', window.navigator.mimeTypes[i]));
}
// Instrument index lookup
window.navigator.mimeTypes.__defineGetter__(i, makeObjectProxyHandler("window.navigator.mimeTypes[" + i + "]", window.navigator.mimeTypes[i]));
}
// Instrument window.navigator.mimeTypes.*
// WORKS
instrumentObjectPropertyAsObject(window.navigator, "window.navigator", window.navigator.mimeTypes, "mimeTypes");
// Instrument window.navigator.geolocation.* (HTML5 geolocation API)
// WORKS
instrumentObjectPropertyAsObject(window.navigator, "window.navigator", window.navigator.geolocation, "geolocation");
// Instrument window.localStorage (HTML5 local storage API)
// WORKS
instrumentObjectPropertyAsObject(window, "window", window.localStorage, "localStorage");
// Instrument window.sessionStorage (HTML5 session storage API)
// WORKS
instrumentObjectPropertyAsObject(window, "window", window.sessionStorage, "sessionStorage");
// Instrument the HTML5 storage event
//window.addEventListener("storage", function(event) { log("EVENT: " + event.type); }, false);
// Instrument window.getComputedStyle
// WORKS
// TODO: Better represent the element called on
window.__defineGetter__("getComputedStyle", makeFunctionProxyHandler(window, "window.getComputedStyle", window.getComputedStyle));
// Instrument window.name
// WORKS
var window_name = window.name;
window.__defineGetter__("name", function() { logValue("window.name", window_name, "get"); return window_name; });
window.__defineSetter__("name", function(value) { logValue("window.name", value, "set"); window_name = value; });
}
//}
})();