diff --git a/Recipe.min.js b/Recipe.min.js
index 8e87b82..632b362 100644
--- a/Recipe.min.js
+++ b/Recipe.min.js
@@ -437,63 +437,63 @@ function getPatternUsage(results, domClasses, cssClasses) {
return results;
}
-void function() {
-
- window.HtmlUsage = {};
-
- // This function has been added to the elementAnalyzers in
- // CSSUsage.js under onready()
- // is an HTMLElement passed in by elementAnalyzers
- window.HtmlUsage.GetNodeName = function (element) {
-
- // If the browser doesn't recognize the element - throw it away
- if(element instanceof HTMLUnknownElement) {
- return;
- }
-
- var node = element.nodeName;
-
- var tags = HtmlUsageResults.tags || (HtmlUsageResults.tags = {});
- var tag = tags[node] || (tags[node] = 0);
- tags[node]++;
-
- GetAttributes(element, node);
- }
-
- function GetAttributes(element, node) {
- for(var i = 0; i < element.attributes.length; i++) {
- var att = element.attributes[i];
-
- if(IsValidAttribute(element, att.nodeName)) {
- var attributes = HtmlUsageResults.attributes || (HtmlUsageResults.attributes = {});
- var attribute = attributes[att.nodeName] || (attributes[att.nodeName] = {});
- var attributeTag = attribute[node] || (attribute[node] = {count: 0});
- attributeTag.count++;
- }
- }
- }
-
- function IsValidAttribute(element, attname) {
- // We need to convert className
- if(attname == "class") {
- attname = "className";
- }
-
- if(attname == "classname") {
- return false;
- }
-
- // Only keep attributes that are not data
- if(attname.indexOf('data-') != -1) {
- return false;
- }
-
- if(typeof(element[attname]) == "undefined") {
- return false;
- }
-
- return true;
- }
+void function() {
+
+ window.HtmlUsage = {};
+
+ // This function has been added to the elementAnalyzers in
+ // CSSUsage.js under onready()
+ // is an HTMLElement passed in by elementAnalyzers
+ window.HtmlUsage.GetNodeName = function (element) {
+
+ // If the browser doesn't recognize the element - throw it away
+ if(element instanceof HTMLUnknownElement) {
+ return;
+ }
+
+ var node = element.nodeName;
+
+ var tags = HtmlUsageResults.tags || (HtmlUsageResults.tags = {});
+ var tag = tags[node] || (tags[node] = 0);
+ tags[node]++;
+
+ GetAttributes(element, node);
+ }
+
+ function GetAttributes(element, node) {
+ for(var i = 0; i < element.attributes.length; i++) {
+ var att = element.attributes[i];
+
+ if(IsValidAttribute(element, att.nodeName)) {
+ var attributes = HtmlUsageResults.attributes || (HtmlUsageResults.attributes = {});
+ var attribute = attributes[att.nodeName] || (attributes[att.nodeName] = {});
+ var attributeTag = attribute[node] || (attribute[node] = {count: 0});
+ attributeTag.count++;
+ }
+ }
+ }
+
+ function IsValidAttribute(element, attname) {
+ // We need to convert className
+ if(attname == "class") {
+ attname = "className";
+ }
+
+ if(attname == "classname") {
+ return false;
+ }
+
+ // Only keep attributes that are not data
+ if(attname.indexOf('data-') != -1) {
+ return false;
+ }
+
+ if(typeof(element[attname]) == "undefined") {
+ return false;
+ }
+
+ return true;
+ }
}();
void function() { try {
@@ -955,15 +955,14 @@ void function() { try {
var matchedElements = [element];
runRuleAnalyzers(element.style, selectorText, matchedElements, ruleType, isInline);
}
- } else { // We've already walked the DOM crawler and need to run the recipes
- for(var r = 0; r < recipesToRun.length ; r++) {
- var recipeToRun = recipesToRun[r];
- var results = RecipeResults[recipeToRun.name] || (RecipeResults[recipeToRun.name]={});
- recipeToRun(element, results, true);
- }
}
}
-
+ // We've already walked the DOM crawler and need to run the recipes
+ for(var r = 0; r < recipesToRun.length ; r++) {
+ var recipeToRun = recipesToRun[r];
+ var results = RecipeResults[recipeToRun.name] || (RecipeResults[recipeToRun.name]={});
+ recipeToRun(elements, results, true);
+ }
}
/**
@@ -1782,213 +1781,309 @@ void function() { try {
} catch (ex) { /* do something maybe */ throw ex; } }();
/*
- RECIPE: z-index on static flex items
+ RECIPE: Pointer events and touch events listening counter
-------------------------------------------------------------
- Author: Francois Remy
- Description: Get count of flex items who should create a stacking context but do not really
+ Author: joevery
+ Description: Find instances of listening for pointer and touch events.
*/
-void function() {
-
- window.CSSUsage.StyleWalker.recipesToRun.push( function zstaticflex(/*HTML DOM Element*/ element, results) {
- if(!element.parentElement) return;
-
- // the problem happens if the element is a flex item with static position and non-auto z-index
- if(getComputedStyle(element.parentElement).display != 'flex') return results;
- if(getComputedStyle(element).position != 'static') return results;
- if(getComputedStyle(element).zIndex != 'auto') {
- results.likely = 1;
- }
+void function ()
+{
+ window.CSSUsage.StyleWalker.recipesToRun.push(
+ function pointer_events_touch_events(/*HTML DOM Element*/ element, results)
+ {
+ var nodeName = element.nodeName;
+
+ if (nodeName !== undefined) {
+ // We want to catch all instances of listening for these events.
+ var eventsToCheckFor = ["pointerup", "pointerdown", "pointermove", "pointercancel", "pointerout", "pointerleave", "pointerenter", "pointerover",
+ "touchstart", "touchend", "touchmove", "touchcancel"];
+
+ var JsTypes =
+ {
+ ATTRIBUTE: 1,
+ INTERNAL: 2,
+ EXTERNAL: 3,
+ }
+
+ var jsType;
+
+ // Is element a script tag?
+ if (nodeName === "SCRIPT") {
+ // If no text, then it cannot be an internal script.
+ if (element.text !== undefined && element.text !== "") {
+ jsType = JsTypes.INTERNAL;
+ }
+ // if no source, then it cannot be an external script.
+ else if (element.src !== undefined) {
+ // if external script, then we have to go and get it, if it is not our recipe script or the src is not blank.
+ if (element.src.includes("Recipe.min.js") || element.src === "") {
+ return results;
+ }
+ else {
+ jsType = JsTypes.EXTERNAL;
+
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", element.src, false);
+ xhr.send();
+ if (xhr.status !== 200) {
+ // We no longer want to check this element if there was a problem in making request.
+ return results;
+ }
+ }
+ }
+ }
+ // If element is not a script tag, then we will assume that if listening for pointerevents is present that it will be in the form of an attribute.
+ else {
+ jsType = JsTypes.ATTRIBUTE;
+ }
+
+ for (const event of eventsToCheckFor) {
+ switch (jsType) {
+ case JsTypes.ATTRIBUTE:
+ // Attribute specified on element does not seem to work at present, but checking anyway.
+ if (element.attributes["on" + event] !== undefined) {
+ results[event] = results[event] || { count: 0, };
+ results[event].count++;
+ }
+ break;
+
+ case JsTypes.INTERNAL:
+ // Check for one instance if none present then abandon.
+ if (element.text.indexOf(event) !== -1) {
+ results[event] = results[event] || { count: 0, };
+ results[event].count += findNumOfStringInstancesInText_CaseSensitive(event, element.text);
+ }
+ break;
+
+ case JsTypes.EXTERNAL:
+ // Check for one instance if none present then abandon.
+ if (xhr.responseText.indexOf(event) !== -1) {
+ results[event] = results[event] || { count: 0, };
+ results[event].count += findNumOfStringInstancesInText_CaseSensitive(event, xhr.responseText);
+ }
+ break;
+ }
+ }
+
+ return results;
+ }
+ });
- // the problem might happen if z-index could ever be non-auto
- if(element.CSSUsage["z-index"] && element.CSSUsage["z-index"].valuesArray.length > 0) {
- results.possible = 1;
- }
+ function findNumOfStringInstancesInText_CaseSensitive(string, text)
+ {
+ var regex = new RegExp(string, 'g');
+ var instances = text.match(regex);
- });
+ return instances.length;
+ }
}();
-
-//
-// This file is only here to create the TSV
-// necessary to collect the data from the crawler
-//
-void function() {
-
- /* String hash function
- /* credits goes to http://erlycoder.com/49/javascript-hash-functions-to-convert-string-into-integer-hash- */
- const hashCodeOf = (str) => {
- var hash = 5381; var char = 0;
- for (var i = 0; i < str.length; i++) {
- char = str.charCodeAt(i);
- hash = ((hash << 5) + hash) + char;
- }
- return hash;
- }
-
- var ua = navigator.userAgent;
- var uaName = ua.indexOf('Edge')>=0 ? 'EDGE' :ua.indexOf('Chrome')>=0 ? 'CHROME' : 'FIREFOX';
- window.INSTRUMENTATION_RESULTS = {
- UA: uaName,
- UASTRING: ua,
- UASTRING_HASH: hashCodeOf(ua),
- URL: location.href,
- TIMESTAMP: Date.now(),
- css: {/* see CSSUsageResults */},
- html: {/* see HtmlUsageResults */},
- dom: {},
- scripts: {/* "bootstrap.js": 1 */},
- };
- window.INSTRUMENTATION_RESULTS_TSV = [];
-
- /* make the script work in the context of a webview */
- try {
- var console = window.console || (window.console={log:function(){},warn:function(){},error:function(){}});
- console.unsafeLog = console.log;
- console.log = function() {
- try {
- this.unsafeLog.apply(this,arguments);
- } catch(ex) {
- // ignore
- }
- };
- } catch (ex) {
- // we tried...
- }
+/*
+ RECIPE: Fiddler proxy tester
+ -------------------------------------------------------------
+ Author: Mustapha Jaber
+ Description: Use Fiddler to check usage of getElementById on the web.
+*/
+
+window.apiCount = 0;
+window.alert = function (alert) {
+ return function (string) {
+ window.apiCount++;
+ return alert(string);
+ };
+}(window.alert);
+
+void function() {
+ window.CSSUsage.StyleWalker.recipesToRun.push( function testFiddler(/*HTML DOM Elements*/ elements, results) {
+ var recipeName = "alert"
+ if(window.apiCount > 0)
+ {
+ results[recipeName] = results[recipeName] || { count: 0, };
+ results[recipeName].count = window.apiCount;
+ }return results;
+ });
}();
-
-window.onCSSUsageResults = function onCSSUsageResults(CSSUsageResults) {
- // Collect the results (css)
- INSTRUMENTATION_RESULTS.css = CSSUsageResults;
- INSTRUMENTATION_RESULTS.html = HtmlUsageResults;
- INSTRUMENTATION_RESULTS.recipe = RecipeResults;
-
- // Convert it to a more efficient format
- INSTRUMENTATION_RESULTS_TSV = convertToTSV(INSTRUMENTATION_RESULTS);
-
- // Remove tabs and new lines from the data
- for(var i = INSTRUMENTATION_RESULTS_TSV.length; i--;) {
- var row = INSTRUMENTATION_RESULTS_TSV[i];
- for(var j = row.length; j--;) {
- row[j] = (''+row[j]).replace(/(\s|\r|\n)+/g, ' ');
- }
- }
-
- // Convert into one signle tsv file
- var tsvString = INSTRUMENTATION_RESULTS_TSV.map((row) => (row.join('\t'))).join('\n');
- appendTSV(tsvString);
-
- // Add it to the document dom
- function appendTSV(content) {
- if(window.debugCSSUsage) console.log("Trying to append");
- var output = document.createElement('script');
- output.id = "css-usage-tsv-results";
- output.textContent = tsvString;
- output.type = 'text/plain';
- document.querySelector('head').appendChild(output);
- var successfulAppend = checkAppend();
- }
-
- function checkAppend() {
- if(window.debugCSSUsage) if(window.debugCSSUsage) console.log("Checking append");
- var elem = document.getElementById('css-usage-tsv-results');
- if(elem === null) {
- if(window.debugCSSUsage) console.log("Element not appended");
- if(window.debugCSSUsage) console.log("Trying to append again");
- appendTSV();
- }
- else {
- if(window.debugCSSUsage) console.log("Element successfully found");
- }
- }
-
- /** convert the instrumentation results to a spreadsheet for analysis */
- function convertToTSV(INSTRUMENTATION_RESULTS) {
- if(window.debugCSSUsage) console.log("Converting to TSV");
-
- var VALUE_COLUMN = 4;
- var finishedRows = [];
- var currentRowTemplate = [
- INSTRUMENTATION_RESULTS.UA,
- INSTRUMENTATION_RESULTS.UASTRING_HASH,
- INSTRUMENTATION_RESULTS.URL,
- INSTRUMENTATION_RESULTS.TIMESTAMP,
- 0
- ];
-
- currentRowTemplate.push('ua');
- convertToTSV({identifier: INSTRUMENTATION_RESULTS.UASTRING});
- currentRowTemplate.pop();
-
-
-
-
-
-
-
-
-
-
- currentRowTemplate.push('recipe');
- convertToTSV(INSTRUMENTATION_RESULTS['recipe']);
- currentRowTemplate.pop();
-
- var l = finishedRows[0].length;
- finishedRows.sort((a,b) => {
- for(var i = VALUE_COLUMN+1; ib[i]) return +1;
- }
- return 0;
- });
-
- return finishedRows;
-
- /** helper function doing the actual conversion */
- function convertToTSV(object) {
- if(object==null || object==undefined || typeof object == 'number' || typeof object == 'string') {
- finishedRows.push(new Row(currentRowTemplate, ''+object));
- } else {
- for(var key in object) {
- if({}.hasOwnProperty.call(object,key)) {
- currentRowTemplate.push(key);
- convertToTSV(object[key]);
- currentRowTemplate.pop();
- }
- }
- }
- }
-
- /** constructor for a row of our table */
- function Row(currentRowTemplate, value) {
-
- // Initialize an empty row with enough columns
- var row = [
- /*UANAME: edge */'',
- /*UASTRING: mozilla/5.0 (...) */'',
- /*URL: http://.../... */'',
- /*TIMESTAMP: 1445622257303 */'',
- /*VALUE: 0|1|... */'',
- /*DATATYPE: css|dom|html... */'',
- /*SUBTYPE: props|types|api|... */'',
- /*NAME: font-size|querySelector|... */'',
- /*CONTEXT: count|values|... */'',
- /*SUBCONTEXT: px|em|... */'',
- /*... */'',
- /*... */'',
- ];
-
- // Copy the column values from the template
- for(var i = currentRowTemplate.length; i--;) {
- row[i] = currentRowTemplate[i];
- }
-
- // Add the value to the row
- row[VALUE_COLUMN] = value;
-
- return row;
- }
-
- }
+//
+// This file is only here to create the TSV
+// necessary to collect the data from the crawler
+//
+void function() {
+
+ /* String hash function
+ /* credits goes to http://erlycoder.com/49/javascript-hash-functions-to-convert-string-into-integer-hash- */
+ const hashCodeOf = (str) => {
+ var hash = 5381; var char = 0;
+ for (var i = 0; i < str.length; i++) {
+ char = str.charCodeAt(i);
+ hash = ((hash << 5) + hash) + char;
+ }
+ return hash;
+ }
+
+ var ua = navigator.userAgent;
+ var uaName = ua.indexOf('Edge')>=0 ? 'EDGE' :ua.indexOf('Chrome')>=0 ? 'CHROME' : 'FIREFOX';
+ window.INSTRUMENTATION_RESULTS = {
+ UA: uaName,
+ UASTRING: ua,
+ UASTRING_HASH: hashCodeOf(ua),
+ URL: location.href,
+ TIMESTAMP: Date.now(),
+ css: {/* see CSSUsageResults */},
+ html: {/* see HtmlUsageResults */},
+ dom: {},
+ scripts: {/* "bootstrap.js": 1 */},
+ };
+ window.INSTRUMENTATION_RESULTS_TSV = [];
+
+ /* make the script work in the context of a webview */
+ try {
+ var console = window.console || (window.console={log:function(){},warn:function(){},error:function(){}});
+ console.unsafeLog = console.log;
+ console.log = function() {
+ try {
+ this.unsafeLog.apply(this,arguments);
+ } catch(ex) {
+ // ignore
+ }
+ };
+ } catch (ex) {
+ // we tried...
+ }
+}();
+
+window.onCSSUsageResults = function onCSSUsageResults(CSSUsageResults) {
+ // Collect the results (css)
+ INSTRUMENTATION_RESULTS.css = CSSUsageResults;
+ INSTRUMENTATION_RESULTS.html = HtmlUsageResults;
+ INSTRUMENTATION_RESULTS.recipe = RecipeResults;
+
+ // Convert it to a more efficient format
+ INSTRUMENTATION_RESULTS_TSV = convertToTSV(INSTRUMENTATION_RESULTS);
+
+ // Remove tabs and new lines from the data
+ for(var i = INSTRUMENTATION_RESULTS_TSV.length; i--;) {
+ var row = INSTRUMENTATION_RESULTS_TSV[i];
+ for(var j = row.length; j--;) {
+ row[j] = (''+row[j]).replace(/(\s|\r|\n)+/g, ' ');
+ }
+ }
+
+ // Convert into one signle tsv file
+ var tsvString = INSTRUMENTATION_RESULTS_TSV.map((row) => (row.join('\t'))).join('\n');
+ appendTSV(tsvString);
+
+ // Add it to the document dom
+ function appendTSV(content) {
+ if(window.debugCSSUsage) console.log("Trying to append");
+ var output = document.createElement('script');
+ output.id = "css-usage-tsv-results";
+ output.textContent = tsvString;
+ output.type = 'text/plain';
+ document.querySelector('head').appendChild(output);
+ var successfulAppend = checkAppend();
+ }
+
+ function checkAppend() {
+ if(window.debugCSSUsage) if(window.debugCSSUsage) console.log("Checking append");
+ var elem = document.getElementById('css-usage-tsv-results');
+ if(elem === null) {
+ if(window.debugCSSUsage) console.log("Element not appended");
+ if(window.debugCSSUsage) console.log("Trying to append again");
+ appendTSV();
+ }
+ else {
+ if(window.debugCSSUsage) console.log("Element successfully found");
+ }
+ }
+
+ /** convert the instrumentation results to a spreadsheet for analysis */
+ function convertToTSV(INSTRUMENTATION_RESULTS) {
+ if(window.debugCSSUsage) console.log("Converting to TSV");
+
+ var VALUE_COLUMN = 4;
+ var finishedRows = [];
+ var currentRowTemplate = [
+ INSTRUMENTATION_RESULTS.UA,
+ INSTRUMENTATION_RESULTS.UASTRING_HASH,
+ INSTRUMENTATION_RESULTS.URL,
+ INSTRUMENTATION_RESULTS.TIMESTAMP,
+ 0
+ ];
+
+ currentRowTemplate.push('ua');
+ convertToTSV({identifier: INSTRUMENTATION_RESULTS.UASTRING});
+ currentRowTemplate.pop();
+
+
+
+
+
+
+
+
+
+
+ currentRowTemplate.push('recipe');
+ convertToTSV(INSTRUMENTATION_RESULTS['recipe']);
+ currentRowTemplate.pop();
+
+ var l = finishedRows[0].length;
+ finishedRows.sort((a,b) => {
+ for(var i = VALUE_COLUMN+1; ib[i]) return +1;
+ }
+ return 0;
+ });
+
+ return finishedRows;
+
+ /** helper function doing the actual conversion */
+ function convertToTSV(object) {
+ if(object==null || object==undefined || typeof object == 'number' || typeof object == 'string') {
+ finishedRows.push(new Row(currentRowTemplate, ''+object));
+ } else {
+ for(var key in object) {
+ if({}.hasOwnProperty.call(object,key)) {
+ currentRowTemplate.push(key);
+ convertToTSV(object[key]);
+ currentRowTemplate.pop();
+ }
+ }
+ }
+ }
+
+ /** constructor for a row of our table */
+ function Row(currentRowTemplate, value) {
+
+ // Initialize an empty row with enough columns
+ var row = [
+ /*UANAME: edge */'',
+ /*UASTRING: mozilla/5.0 (...) */'',
+ /*URL: http://.../... */'',
+ /*TIMESTAMP: 1445622257303 */'',
+ /*VALUE: 0|1|... */'',
+ /*DATATYPE: css|dom|html... */'',
+ /*SUBTYPE: props|types|api|... */'',
+ /*NAME: font-size|querySelector|... */'',
+ /*CONTEXT: count|values|... */'',
+ /*SUBCONTEXT: px|em|... */'',
+ /*... */'',
+ /*... */'',
+ ];
+
+ // Copy the column values from the template
+ for(var i = currentRowTemplate.length; i--;) {
+ row[i] = currentRowTemplate[i];
+ }
+
+ // Add the value to the row
+ row[VALUE_COLUMN] = value;
+
+ return row;
+ }
+
+ }
};
//
// Execution scheduler:
diff --git a/cssUsage.src.js b/cssUsage.src.js
index b1ae974..9539079 100644
--- a/cssUsage.src.js
+++ b/cssUsage.src.js
@@ -437,63 +437,63 @@ function getPatternUsage(results, domClasses, cssClasses) {
return results;
}
-void function() {
-
- window.HtmlUsage = {};
-
- // This function has been added to the elementAnalyzers in
- // CSSUsage.js under onready()
- // is an HTMLElement passed in by elementAnalyzers
- window.HtmlUsage.GetNodeName = function (element) {
-
- // If the browser doesn't recognize the element - throw it away
- if(element instanceof HTMLUnknownElement) {
- return;
- }
-
- var node = element.nodeName;
-
- var tags = HtmlUsageResults.tags || (HtmlUsageResults.tags = {});
- var tag = tags[node] || (tags[node] = 0);
- tags[node]++;
-
- GetAttributes(element, node);
- }
-
- function GetAttributes(element, node) {
- for(var i = 0; i < element.attributes.length; i++) {
- var att = element.attributes[i];
-
- if(IsValidAttribute(element, att.nodeName)) {
- var attributes = HtmlUsageResults.attributes || (HtmlUsageResults.attributes = {});
- var attribute = attributes[att.nodeName] || (attributes[att.nodeName] = {});
- var attributeTag = attribute[node] || (attribute[node] = {count: 0});
- attributeTag.count++;
- }
- }
- }
-
- function IsValidAttribute(element, attname) {
- // We need to convert className
- if(attname == "class") {
- attname = "className";
- }
-
- if(attname == "classname") {
- return false;
- }
-
- // Only keep attributes that are not data
- if(attname.indexOf('data-') != -1) {
- return false;
- }
-
- if(typeof(element[attname]) == "undefined") {
- return false;
- }
-
- return true;
- }
+void function() {
+
+ window.HtmlUsage = {};
+
+ // This function has been added to the elementAnalyzers in
+ // CSSUsage.js under onready()
+ // is an HTMLElement passed in by elementAnalyzers
+ window.HtmlUsage.GetNodeName = function (element) {
+
+ // If the browser doesn't recognize the element - throw it away
+ if(element instanceof HTMLUnknownElement) {
+ return;
+ }
+
+ var node = element.nodeName;
+
+ var tags = HtmlUsageResults.tags || (HtmlUsageResults.tags = {});
+ var tag = tags[node] || (tags[node] = 0);
+ tags[node]++;
+
+ GetAttributes(element, node);
+ }
+
+ function GetAttributes(element, node) {
+ for(var i = 0; i < element.attributes.length; i++) {
+ var att = element.attributes[i];
+
+ if(IsValidAttribute(element, att.nodeName)) {
+ var attributes = HtmlUsageResults.attributes || (HtmlUsageResults.attributes = {});
+ var attribute = attributes[att.nodeName] || (attributes[att.nodeName] = {});
+ var attributeTag = attribute[node] || (attribute[node] = {count: 0});
+ attributeTag.count++;
+ }
+ }
+ }
+
+ function IsValidAttribute(element, attname) {
+ // We need to convert className
+ if(attname == "class") {
+ attname = "className";
+ }
+
+ if(attname == "classname") {
+ return false;
+ }
+
+ // Only keep attributes that are not data
+ if(attname.indexOf('data-') != -1) {
+ return false;
+ }
+
+ if(typeof(element[attname]) == "undefined") {
+ return false;
+ }
+
+ return true;
+ }
}();
void function() { try {
@@ -955,15 +955,14 @@ void function() { try {
var matchedElements = [element];
runRuleAnalyzers(element.style, selectorText, matchedElements, ruleType, isInline);
}
- } else { // We've already walked the DOM crawler and need to run the recipes
- for(var r = 0; r < recipesToRun.length ; r++) {
- var recipeToRun = recipesToRun[r];
- var results = RecipeResults[recipeToRun.name] || (RecipeResults[recipeToRun.name]={});
- recipeToRun(element, results, true);
- }
}
}
-
+ // We've already walked the DOM crawler and need to run the recipes
+ for(var r = 0; r < recipesToRun.length ; r++) {
+ var recipeToRun = recipesToRun[r];
+ var results = RecipeResults[recipeToRun.name] || (RecipeResults[recipeToRun.name]={});
+ recipeToRun(elements, results, true);
+ }
}
/**
@@ -1782,32 +1781,128 @@ void function() { try {
} catch (ex) { /* do something maybe */ throw ex; } }();
/*
- RECIPE: z-index on static flex items
+ RECIPE: Pointer events and touch events listening counter
-------------------------------------------------------------
- Author: Francois Remy
- Description: Get count of flex items who should create a stacking context but do not really
+ Author: joevery
+ Description: Find instances of listening for pointer and touch events.
*/
-void function() {
+void function ()
+{
+ window.CSSUsage.StyleWalker.recipesToRun.push(
+ function pointer_events_touch_events(/*HTML DOM Element*/ element, results)
+ {
+ var nodeName = element.nodeName;
+
+ if (nodeName !== undefined) {
+ // We want to catch all instances of listening for these events.
+ var eventsToCheckFor = ["pointerup", "pointerdown", "pointermove", "pointercancel", "pointerout", "pointerleave", "pointerenter", "pointerover",
+ "touchstart", "touchend", "touchmove", "touchcancel"];
+
+ var JsTypes =
+ {
+ ATTRIBUTE: 1,
+ INTERNAL: 2,
+ EXTERNAL: 3,
+ }
+
+ var jsType;
+
+ // Is element a script tag?
+ if (nodeName === "SCRIPT") {
+ // If no text, then it cannot be an internal script.
+ if (element.text !== undefined && element.text !== "") {
+ jsType = JsTypes.INTERNAL;
+ }
+ // if no source, then it cannot be an external script.
+ else if (element.src !== undefined) {
+ // if external script, then we have to go and get it, if it is not our recipe script or the src is not blank.
+ if (element.src.includes("Recipe.min.js") || element.src === "") {
+ return results;
+ }
+ else {
+ jsType = JsTypes.EXTERNAL;
+
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", element.src, false);
+ xhr.send();
+ if (xhr.status !== 200) {
+ // We no longer want to check this element if there was a problem in making request.
+ return results;
+ }
+ }
+ }
+ }
+ // If element is not a script tag, then we will assume that if listening for pointerevents is present that it will be in the form of an attribute.
+ else {
+ jsType = JsTypes.ATTRIBUTE;
+ }
+
+ for (const event of eventsToCheckFor) {
+ switch (jsType) {
+ case JsTypes.ATTRIBUTE:
+ // Attribute specified on element does not seem to work at present, but checking anyway.
+ if (element.attributes["on" + event] !== undefined) {
+ results[event] = results[event] || { count: 0, };
+ results[event].count++;
+ }
+ break;
+
+ case JsTypes.INTERNAL:
+ // Check for one instance if none present then abandon.
+ if (element.text.indexOf(event) !== -1) {
+ results[event] = results[event] || { count: 0, };
+ results[event].count += findNumOfStringInstancesInText_CaseSensitive(event, element.text);
+ }
+ break;
+
+ case JsTypes.EXTERNAL:
+ // Check for one instance if none present then abandon.
+ if (xhr.responseText.indexOf(event) !== -1) {
+ results[event] = results[event] || { count: 0, };
+ results[event].count += findNumOfStringInstancesInText_CaseSensitive(event, xhr.responseText);
+ }
+ break;
+ }
+ }
+
+ return results;
+ }
+ });
- window.CSSUsage.StyleWalker.recipesToRun.push( function zstaticflex(/*HTML DOM Element*/ element, results) {
- if(!element.parentElement) return;
+ function findNumOfStringInstancesInText_CaseSensitive(string, text)
+ {
+ var regex = new RegExp(string, 'g');
+ var instances = text.match(regex);
- // the problem happens if the element is a flex item with static position and non-auto z-index
- if(getComputedStyle(element.parentElement).display != 'flex') return results;
- if(getComputedStyle(element).position != 'static') return results;
- if(getComputedStyle(element).zIndex != 'auto') {
- results.likely = 1;
- }
+ return instances.length;
+ }
+}();
+/*
+ RECIPE: Fiddler proxy tester
+ -------------------------------------------------------------
+ Author: Mustapha Jaber
+ Description: Use Fiddler to check usage of getElementById on the web.
+*/
- // the problem might happen if z-index could ever be non-auto
- if(element.CSSUsage["z-index"] && element.CSSUsage["z-index"].valuesArray.length > 0) {
- results.possible = 1;
- }
+window.apiCount = 0;
+window.alert = function (alert) {
+ return function (string) {
+ window.apiCount++;
+ return alert(string);
+ };
+}(window.alert);
+void function() {
+ window.CSSUsage.StyleWalker.recipesToRun.push( function testFiddler(/*HTML DOM Elements*/ elements, results) {
+ var recipeName = "alert"
+ if(window.apiCount > 0)
+ {
+ results[recipeName] = results[recipeName] || { count: 0, };
+ results[recipeName].count = window.apiCount;
+ }return results;
});
}();
-
//
// This file is only here to create the TSV
// necessary to collect the data from the crawler
diff --git a/src/cssUsage.js b/src/cssUsage.js
index 3f29d91..927db26 100644
--- a/src/cssUsage.js
+++ b/src/cssUsage.js
@@ -458,15 +458,14 @@ void function() { try {
var matchedElements = [element];
runRuleAnalyzers(element.style, selectorText, matchedElements, ruleType, isInline);
}
- } else { // We've already walked the DOM crawler and need to run the recipes
- for(var r = 0; r < recipesToRun.length ; r++) {
- var recipeToRun = recipesToRun[r];
- var results = RecipeResults[recipeToRun.name] || (RecipeResults[recipeToRun.name]={});
- recipeToRun(element, results, true);
- }
}
}
-
+ // We've already walked the DOM crawler and need to run the recipes
+ for(var r = 0; r < recipesToRun.length ; r++) {
+ var recipeToRun = recipesToRun[r];
+ var results = RecipeResults[recipeToRun.name] || (RecipeResults[recipeToRun.name]={});
+ recipeToRun(elements, results, true);
+ }
}
/**
diff --git a/src/fiddler-debug/get-element.js b/src/fiddler-debug/get-element.js
new file mode 100644
index 0000000..6f9548a
--- /dev/null
+++ b/src/fiddler-debug/get-element.js
@@ -0,0 +1,53 @@
+window.debugCSSUsage = true
+
+window.apiCount = 0;
+
+document._oldGetElementById = document.getElementById;
+document.getElementById = function(elemIdOrName) {
+ window.apiCount++;
+ return document._oldGetElementById(elemIdOrName);
+};
+
+void function() {
+ console.log("PIN1")
+ document.addEventListener('DOMContentLoaded', function () {
+ console.log("PIN2")
+ var results = {};
+ var recipeName = "getelem"
+
+ if(window.apiCount > 0)
+ {
+ results[recipeName] = results[recipeName] || { count: 0, href: location.href };
+ results[recipeName].count = window.apiCount;
+ }
+ else
+ {
+ results[recipeName] = results[recipeName] || { href: location.href };
+ }
+ console.log("PIN3")
+ appendResults(results);
+
+ // Add it to the document dom
+ function appendResults(results) {
+ if(window.debugCSSUsage) console.log("Trying to append");
+ var output = document.createElement('script');
+ output.id = "css-usage-tsv-results";
+ output.textContent = JSON.stringify(results);
+ output.type = 'text/plain';
+ document.querySelector('head').appendChild(output);
+ var successfulAppend = checkAppend();
+ }
+
+ function checkAppend() {
+ if(window.debugCSSUsage) console.log("Checking append");
+ var elem = document.getElementById('css-usage-tsv-results');
+ if(elem === null) {
+ if(window.debugCSSUsage) console.log("Element not appended");
+ }
+ else {
+ if(window.debugCSSUsage) console.log("Element successfully found");
+ }
+ }
+
+ });
+}();
\ No newline at end of file
diff --git a/src/fiddler-debug/sharedArrayBuffer.js b/src/fiddler-debug/sharedArrayBuffer.js
new file mode 100644
index 0000000..1605546
--- /dev/null
+++ b/src/fiddler-debug/sharedArrayBuffer.js
@@ -0,0 +1,40 @@
+
+window.apiCount = 0;
+
+void(function() {
+ var TrueSharedArrayBuffer = window.SharedArrayBuffer;
+ var SharedArrayBuffer = new Proxy(TrueSharedArrayBuffer, {
+ construct: function(target, argumentsList, newTarget) {
+ window.apiCount++;
+ return new TrueSharedArrayBuffer();
+ }
+ });
+ window.SharedArrayBuffer = SharedArrayBuffer;
+}());
+
+void function() {
+ document.addEventListener('DOMContentLoaded', function () {
+ var results = {};
+ var recipeName = "sab";
+ if(window.apiCount > 0)
+ {
+ results[recipeName] = results[recipeName] || { count: 0, href: location.href };
+ results[recipeName].count = window.apiCount;
+ }
+ else
+ {
+ results[recipeName] = results[recipeName] || { href: location.href };
+ }
+
+ appendResults(results);
+
+ // Add it to the document dom
+ function appendResults(results) {
+ var output = document.createElement('script');
+ output.id = "css-usage-tsv-results";
+ output.textContent = JSON.stringify(results);
+ output.type = 'text/plain';
+ document.querySelector('head').appendChild(output);
+ }
+ });
+}();
\ No newline at end of file
diff --git a/src/fiddler-debug/sharedarraybuffer2.js b/src/fiddler-debug/sharedarraybuffer2.js
new file mode 100644
index 0000000..1605546
--- /dev/null
+++ b/src/fiddler-debug/sharedarraybuffer2.js
@@ -0,0 +1,40 @@
+
+window.apiCount = 0;
+
+void(function() {
+ var TrueSharedArrayBuffer = window.SharedArrayBuffer;
+ var SharedArrayBuffer = new Proxy(TrueSharedArrayBuffer, {
+ construct: function(target, argumentsList, newTarget) {
+ window.apiCount++;
+ return new TrueSharedArrayBuffer();
+ }
+ });
+ window.SharedArrayBuffer = SharedArrayBuffer;
+}());
+
+void function() {
+ document.addEventListener('DOMContentLoaded', function () {
+ var results = {};
+ var recipeName = "sab";
+ if(window.apiCount > 0)
+ {
+ results[recipeName] = results[recipeName] || { count: 0, href: location.href };
+ results[recipeName].count = window.apiCount;
+ }
+ else
+ {
+ results[recipeName] = results[recipeName] || { href: location.href };
+ }
+
+ appendResults(results);
+
+ // Add it to the document dom
+ function appendResults(results) {
+ var output = document.createElement('script');
+ output.id = "css-usage-tsv-results";
+ output.textContent = JSON.stringify(results);
+ output.type = 'text/plain';
+ document.querySelector('head').appendChild(output);
+ }
+ });
+}();
\ No newline at end of file
diff --git a/src/proxy.js b/src/proxy.js
new file mode 100644
index 0000000..ecea845
--- /dev/null
+++ b/src/proxy.js
@@ -0,0 +1,512 @@
+//
+// Documentation:
+// ================
+// This project is a template script you can customize then insert at the begining of your document.
+// It will enable you to log which native functions are being called by your application.
+// You can filter the log to only include particular instances like slow function calls.
+// You can also log more information like the call stack at the time of the log.
+//
+void function () {
+
+ //
+ // Import stuff we want to use (before we wrap or replace them, eventually)
+ //
+
+ var console = window.console;
+ var Object = window.Object;
+ var Window = window.Window;
+ var Document = window.Document;
+ var Function = window.Function;
+ var Set = window.Set;
+ var Proxy = window.Proxy;
+ var WeakMap = window.WeakMap;
+ var Symbol = window.Symbol;
+
+ var objectToString = Object.prototype.toString;
+ var bindFunction = Function.prototype.bind;
+
+ //
+ // Prerequirement: be able to distinguish between native and bound functions
+ // This is done by adding a tag to user-functions returned by "func.bind(obj)"
+ //
+
+ var isBoundUserFunction = window.isBoundUserFunction = Symbol`isBoundUserFunction`;
+ var isKnownNativeFunction = window.isBoundUserFunction = Symbol`isKnownNativeFunction`;
+ var isNativeFunction = function (f) {
+ var isNative = f[isKnownNativeFunction] || (
+ !f[isBoundUserFunction]
+ && /^function[^]*?\([^]*?\)[^]*?\{[^]*?\[native code\][^]*?\}$/m.test(`${f}`)
+ );
+ if (isNative && !f[isKnownNativeFunction]) {
+ f[isKnownNativeFunction] = true;
+ }
+ return isNative;
+ };
+
+ Function.prototype.toString[isKnownNativeFunction] = true;
+
+ Function.prototype.bind = function () {
+ var result = bindFunction.apply(this, arguments);
+ if (!isNativeFunction(this)) result[isBoundUserFunction] = true;
+ return result;
+ };
+
+ //
+ // Helper:
+ // Returns trus if the object is from this window
+ // Returns false if the object is from another iframe/window
+ //
+ var isFromThisRealm = function (obj) {
+ return (obj instanceof Object);
+ }
+
+ //
+ // Helper:
+ // Returns "Object", "Array", "Window", or another native type value
+ //
+
+ var getNativeTypeOf = function (o) {
+ try { o = o ? (o[pts] || o) : o; } catch (ex) { }
+ var s = objectToString.call(o);
+ var i = '[object '.length;
+ return s.substr(i, s.length - i - 1);
+ };
+
+ //
+ // Helper:
+ // Returns a string representation of an object key (o[key] or o.key)
+ //
+
+ var getKeyAsStringFrom = function (o) {
+ try { if (typeof (o) == 'symbol') { return `[${o.toString()}]`; } } catch (ex) { return '[symbol]' }
+ try { if (/^[0-9]+$/.test(o)) { return '[int]'; } } catch (ex) { }
+ try { return `${o}` } catch (ex) { }
+ try { return `[${o.toString()}]`; } catch (ex) { }
+ try { if (o.constructor) return '[' + o.constructor.name + ']'; } catch (ex) { }
+ return '[???]';
+ }
+
+ //
+ // Storage of the proxy-object to/from source-object links
+ //
+
+ var stp = new WeakMap();
+ var pts = window.pts = Symbol`proxyToSource`;
+ var ptsName = window.ptsName = Symbol`proxyToSourceName`;
+ var isAlreadyWrapped = window.isAlreadyWrapped = Symbol`proxyToSourceOverride`;
+
+ //
+ // This is the algorithm we want to run when an API is being used
+ //
+
+ // CUSTOMIZE HERE:
+ // this is where we will store our information, we will export it as window.proxylog on the page
+ var log = new Set();
+
+ // this is how operations on the proxies will work:
+ var proxyCode = {
+
+ // htmlElement.innerHTML (o = htmlElement, k = "innerHTML")
+ get(o, k) {
+
+ // special rule: the proxy-to-source symbol should allow to unwrap the proxy
+ if (k === pts) { return o; }
+
+ // special rule: our internal pointers should not trigger the user-logic
+ if (k === ptsName || k === isAlreadyWrapped || k === isBoundUserFunction) {
+ return o[k];
+ }
+
+ try {
+
+ // if we want to measure how long this operation took:
+ //var operationTime = -performance.now()
+
+ // get the value from the source object
+ var returnValue = o[k];
+
+ } finally {
+
+ // if we want to know how long this operation took:
+ //operationTime += performance.now();
+
+ // CUSTOMIZE HERE:
+ try { var name = `${getNativeTypeOf(o)}.${getKeyAsStringFrom(k)}`; } catch (ex) {/*debugger;*/ };
+ try { if (name) log.add(`${name}`) } catch (ex) {/*debugger;*/ };
+
+ }
+
+ // since we want to continue to receive usage info for the object we are about to return...
+ if (returnValue && (typeof returnValue == 'object' || typeof returnValue == 'function') && isFromThisRealm(returnValue)) {
+
+ // first, we need to know if we can wrap it in a proxy...
+
+ var shouldRefrainFromProxying = returnValue[isAlreadyWrapped];
+ try {
+ var property = Object.getOwnPropertyDescriptor(o, k);
+ let proto = o;
+ while (!property && proto) {
+ proto = Object.getPrototypeOf(proto);
+ property = proto ? Object.getOwnPropertyDescriptor(proto, k) : null;
+ }
+ } catch (ex) {/*debugger;*/ }
+ var doesPropertyAllowProxyWrapping = !property || (property.set || property.writable) || property.configurable;
+
+ if (!shouldRefrainFromProxying && doesPropertyAllowProxyWrapping) {
+
+ // if we can, that is the best option
+ returnValue = wrapInProxy(returnValue);
+
+ } else {
+
+ // if not (rare) we will do our best by special-casing the object
+ try { wrapPropertiesOf(returnValue, name); } catch (ex) {/*debugger;*/ }
+ }
+
+ }
+
+ return returnValue;
+
+ },
+
+ // htmlElement.innerHTML = responseText; (o = htmlElement, k = "innerHTML", v = responseText)
+ set(o, k, v) {
+
+ // special rule: when setting a value in the native world, we need to unwrap the value
+ if (v && v[pts]) { v = v[pts]; }
+
+ // special rule: our internal pointers should not trigger user-logic
+ if (k === ptsName || k === isAlreadyWrapped || k === isBoundUserFunction) {
+ o[k] = v; return true;
+ }
+
+ try {
+
+ // if we want to measure how long this operation took:
+ //var operationTime = -performance.now()
+
+ // set the value on the source object
+ o[k] = v;
+
+ } finally {
+
+ // if we want to know how long this operation took:
+ //operationTime += performance.now();
+
+ // CUSTOMIZE HERE:
+ try { log.add(`${getNativeTypeOf(o)}.${getKeyAsStringFrom(k)}=${getNativeTypeOf(v)}`) } catch (ex) {/*debugger;*/ };
+
+ }
+
+ return true;
+ },
+
+ // htmlElement.focus(); (o = htmlElement.focus, t = htmlElement, a = [])
+ apply(o, t, a) {
+
+ // special rule: if we are calling a native function, none of the arguments can be proxies
+ if (isNativeFunction(o)) {
+ t = t ? (t[pts] || t) : t;
+ a = a.map(x => x ? (x[pts] || x) : x);
+ }
+
+ try {
+
+ // if we want to measure how long this operation took:
+ //var operationTime = -performance.now()
+
+ // call the function and return its result
+ var returnValue = o.apply(t, a);
+
+ } finally {
+
+ // if we want to know how long this operation took:
+ //operationTime += performance.now();
+
+ // CUSTOMIZE HERE:
+ try { var name = `${o[ptsName] || (getNativeTypeOf(t || window) + '.' + getKeyAsStringFrom(o.name || '???'))}`; } catch (ex) {/*debugger;*/ };
+ try { var name = `${name}(${a.map(x => getNativeTypeOf(x)).join(',')})`; } catch (ex) { /*debugger;*/ };
+ try { log.add(`${name}`) } catch (ex) { }
+
+ }
+
+ return wrapInProxy(returnValue, name);
+
+ },
+
+ // new CustomEvent("click"); (o = CustomEvent, a = ["click"])
+ construct(o, a) {
+
+ // special rule: if we are calling a native function, none of the arguments can be proxies
+ if (isNativeFunction(o)) {
+ a = a.map(x => x ? (x[pts] || x) : x);
+ }
+
+ try {
+
+ // if we want to measure how long this operation took:
+ //var operationTime = -performance.now()
+
+ // create a new instance of the object, and return it
+ returnValue = wrapInProxy(Reflect.construct(o, a));
+
+ } finally {
+
+ // if we want to know how long this operation took:
+ //operationTime += performance.now();
+
+ // CUSTOMIZE HERE:
+ try { var name = `new ${o[ptsName] || (getNativeTypeOf(t || window) + '.' + getKeyAsStringFrom(o.name || '???'))}`; } catch (ex) {/*debugger;*/ };
+ try { var name = `${name}(${a.map(x => getNativeTypeOf(x)).join(',')})`; } catch (ex) { /*debugger;*/ };
+ try { log.add(`${name}`) } catch (ex) { }
+
+ }
+
+ return returnValue;
+
+ }
+ };
+
+ //
+ // Helper:
+ // Creates a proxy for the given source object and name, if needed (and return it)
+ //
+ function wrapInProxy(obj, name) {
+
+ // special rule: non-objects do not need a proxy
+ if (obj === null) return obj;
+ if (obj === undefined) return obj;
+ if (!(typeof (obj) == 'function' || typeof (obj) == 'object')) return obj;
+
+ // special rule: do not try to track cross-document objects
+ if (!isFromThisRealm(obj)) { return obj; }
+
+ // special rule: do not proxy an object that has been special-cased
+ if (obj[isAlreadyWrapped]) { try { obj[ptsName] = name } catch (ex) { }; return obj; }
+
+ // special rule: do not touch an object that is already a proxy
+ if (obj[pts] && obj[pts] !== obj) return obj;
+
+ // do not wrap non-native objects (TODO: expand detection?)
+ if (!isNativeFunction(obj) && (!obj.constructor || obj.constructor == Object || !isNativeFunction(obj.constructor))) { /*debugger;*/ return obj; }
+
+ // wrap the object in proxy, and add some metadata
+ try { obj[pts] = obj } catch (ex) { };
+ try { obj[ptsName] = name } catch (ex) { };
+ try {
+ var pxy = stp.get(obj);
+ if (!pxy) {
+ pxy = new Proxy(obj, proxyCode);
+ stp.set(obj, pxy);
+ }
+ return pxy;
+ } catch (ex) {
+ return obj;
+ }
+
+ }
+
+ //
+ // Helper:
+ // Tries to catch get/set on an object without creating a proxy for it (unsafe special case)
+ //
+ function wrapPropertiesOf(obj, name) {
+
+ // special rule: don't rewrap a wrapped object
+ if (obj[isAlreadyWrapped]) return;
+
+ // special rule: don't wrap an object that has a proxy
+ if (stp.has(obj)) return;
+
+ // mark the object as wrapper already
+ obj[isAlreadyWrapped] = true;
+ obj[ptsName] = name;
+
+ // for all the keys of this object
+ let objKeys = new Set(Object.getOwnPropertyNames(obj));
+ for (let key in obj) { objKeys.add(key) };
+ for (let key of objKeys) {
+ try {
+
+ // special rule: avoid problematic global properties
+ if (obj === window && (key == 'window' || key == 'top' || key == 'self' || key == 'document' || key == 'location' || key == 'Object' || key == 'Array' || key == 'Function' || key == 'Date' || key == 'Number' || key == 'String' || key == 'Boolean' || key === isAlreadyWrapped || key === ptsName)) {
+ continue;
+ }
+
+ // TODO?
+ // key=='contentWindow' || key=='contentDocument' || key=='parentWindow' || key=='parentDocument' || key=='ownerDocument'
+
+ // try to find where the property has been defined in the prototype chain
+ let property = Object.getOwnPropertyDescriptor(obj, key);
+ let proto = obj;
+ while (!property && proto) {
+ proto = Object.getPrototypeOf(proto);
+ property = proto ? Object.getOwnPropertyDescriptor(proto, key) : null;
+ }
+ if (!property) continue;
+
+ // try to find if we can override the property
+ if (proto !== obj || property.configurable) {
+
+ if (property.get) {
+
+ // in the case of a getter/setter, we can just duplicate
+ Object.defineProperty(obj, key, {
+ get() {
+
+ try {
+
+ // if we want to measure how long this operation took:
+ //var operationTime = -performance.now()
+
+ // set the value on the source object
+ var returnValue = property.get.call(this);
+
+ } finally {
+
+ // if we want to know how long this operation took:
+ //operationTime += performance.now();
+
+ // CUSTOMIZE HERE:
+ try { log.add(`${getNativeTypeOf(this)}.${getKeyAsStringFrom(key)}`) } catch (ex) {/*debugger;*/ };
+
+ }
+
+ return wrapInProxy(returnValue, name);
+
+ },
+ set(v) {
+
+ // special rule: when setting a value in the native world, we need to unwrap the value
+ if (v && v[pts]) { v = v[pts]; }
+
+ try {
+
+ // if we want to measure how long this operation took:
+ //var operationTime = -performance.now()
+
+ // set the value on the source object
+ var returnValue = property.set.call(this, v);
+
+ } finally {
+
+ // if we want to know how long this operation took:
+ //operationTime += performance.now();
+
+ // CUSTOMIZE HERE:
+ try { log.add(`${getNativeTypeOf(this)}.${getKeyAsStringFrom(key)}=${getNativeTypeOf(v)}`) } catch (ex) {/*debugger;*/ };
+
+ }
+
+ return returnValue;
+
+ }
+ });
+
+ } else if (property.writable) {
+
+ // in the case of a read-write data field, we can only wrap preventively
+ if (property.value && (typeof (property.value) == 'object' || typeof (property.value) == 'function')) {
+ try { obj[key] = wrapInProxy(property.value, name + '.' + key); } catch (ex) {/*debugger;*/ }
+ }
+
+ } else if ("value" in property) {
+
+ // in the case of a readonly data field, we can just duplicate
+ Object.defineProperty(obj, key, {
+ value: wrapInProxy(property.value, name + '.' + key),
+ enumerable: property.enumerable,
+ writable: false,
+ });
+
+ } else {
+
+ // wtf?
+ console.warn("Unable to wrap strange property: ", name, key);
+
+ }
+
+ } else if (property.writable) {
+
+ // in the case of a read-write data field, we can try to wrap preventively
+ if (property.value && (typeof (property.value) == 'object' || typeof (property.value) == 'function')) {
+ try { obj[key] = wrapInProxy(property.value, name + '.' + key); } catch (ex) {/*debugger;*/ }
+ }
+
+ } else if ("value" in property) {
+
+ // in the case of a direct read-write data field, there is nothing we can do
+ if (property.value && (typeof (property.value) == 'object' || typeof (property.value) == 'function')) {
+ console.warn("Unable to wrap readonly property: ", name, key);
+ }
+
+ } else {
+
+ // wtf?
+ console.warn("Unable to wrap strange property: ", name, key);
+
+ }
+
+ } catch (ex) {
+
+ console.warn("Unable to wrap property: ", name, key, ex);
+
+ }
+ }
+
+ // set the metadata again just to be sure
+ obj[isAlreadyWrapped] = true;
+ obj[ptsName] = name;
+
+ }
+
+ //
+ // There are a few objects we don't want to wrap for performance reason
+ //
+
+ let objectsToNeverWrap = [
+ Object, /*Object.prototype*/, String, String.prototype, Number, Number.prototype, Boolean, Boolean.prototype,
+ RegExp, RegExp.prototype, Reflect,
+ Error, /*Error.prototype*/, DOMError, /*DOMError.prototype*/, DOMException, /*DOMException.prototype*/,
+ // TODO: add more here
+ ]
+ objectsToNeverWrap.forEach(o => {
+ o[isAlreadyWrapped] = true; //TODO: unsafe for prototypes because we don't check hasOwnProperty(isAlreadyWrapped) in usage
+ if (typeof (o) == 'function') {
+ o[isKnownNativeFunction] = true;
+ }
+ });
+
+ //
+ // Now it is time to wrap the important objects of this realm
+ //
+
+ if (window.document) {
+ wrapPropertiesOf(window.document, 'document');
+ }
+ if (window.parent !== window) {
+ wrapPropertiesOf(window.parent, 'parent');
+ }
+ if (window.top !== window) {
+ wrapPropertiesOf(window.top, 'top');
+ }
+ wrapPropertiesOf(window, 'window');
+
+ //
+ // Disabled alternatives:
+ //
+
+ //wrapPropertiesOf(location, 'location');
+ //__window = wrapInProxy(window, 'window');
+ //__document = wrapInProxy(document, 'document');
+ //__location = wrapInProxy(location, 'location');
+ //__top = wrapInProxy(location, 'top');
+
+ //
+ // CUSTOMIZE HERE:
+ //
+
+ window.proxylog = log;
+ log.clear();
+
+}();
\ No newline at end of file
diff --git a/src/recipes/archive/paymentrequest.js b/src/recipes/archive/paymentrequest.js
index 6a6c362..c213435 100644
--- a/src/recipes/archive/paymentrequest.js
+++ b/src/recipes/archive/paymentrequest.js
@@ -1,20 +1,34 @@
/*
- RECIPE: Payment Request
+ RECIPE: Request Payment
-------------------------------------------------------------
- Author: Stanley Hon
- Description: This counts any page that includes any script references to PaymentRequest
+ Author: Mustapha Jaber
+ Description: Find use of RequestPayment in script.
*/
void function() {
- window.CSSUsage.StyleWalker.recipesToRun.push( function paymentrequest(/*HTML DOM Element*/ element, results) {
-
- if(element.nodeName == "SCRIPT") {
- if (element.innerText.indexOf("PaymentRequest") != -1) {
- results["use"] = results["use"] || { count: 0 };
- results["use"].count++;
+ window.CSSUsage.StyleWalker.recipesToRun.push( function paymentRequest(/*HTML DOM Element*/ element, results) {
+ var nodeName = element.nodeName;
+ var script = "RequestPayment"
+ if (nodeName == "SCRIPT")
+ {
+ results[nodeName] = results[nodeName] || { count: 0, };
+ // if inline script. ensure that it's not our recipe script and look for string of interest
+ if (element.text !== undefined && element.text.indexOf(script) != -1)
+ {
+ results[nodeName].count++;
+ }
+ else if (element.src !== undefined && element.src != "")
+ {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", element.src, false);
+ //xhr.setRequestHeader("Content-type", "text/javascript");
+ xhr.send();
+ if (xhr.status === 200 && xhr.responseText.indexOf(script) != -1)
+ {
+ results[nodeName].count++;
+ }
}
}
-
return results;
});
}();
\ No newline at end of file
diff --git a/src/recipes/pointer-events_touch-events.js b/src/recipes/pointer-events_touch-events.js
new file mode 100644
index 0000000..29f8c06
--- /dev/null
+++ b/src/recipes/pointer-events_touch-events.js
@@ -0,0 +1,98 @@
+/*
+ RECIPE: Pointer events and touch events listening counter
+ -------------------------------------------------------------
+ Author: joevery
+ Description: Find instances of listening for pointer and touch events.
+*/
+
+void function ()
+{
+ window.CSSUsage.StyleWalker.recipesToRun.push(
+ function pointer_events_touch_events(/*HTML DOM Element*/ element, results)
+ {
+ var nodeName = element.nodeName;
+
+ if (nodeName !== undefined) {
+ // We want to catch all instances of listening for these events.
+ var eventsToCheckFor = ["pointerup", "pointerdown", "pointermove", "pointercancel", "pointerout", "pointerleave", "pointerenter", "pointerover",
+ "touchstart", "touchend", "touchmove", "touchcancel"];
+
+ var JsTypes =
+ {
+ ATTRIBUTE: 1,
+ INTERNAL: 2,
+ EXTERNAL: 3,
+ }
+
+ var jsType;
+
+ // Is element a script tag?
+ if (nodeName === "SCRIPT") {
+ // If no text, then it cannot be an internal script.
+ if (element.text !== undefined && element.text !== "") {
+ jsType = JsTypes.INTERNAL;
+ }
+ // if no source, then it cannot be an external script.
+ else if (element.src !== undefined) {
+ // if external script, then we have to go and get it, if it is not our recipe script or the src is not blank.
+ if (element.src.includes("Recipe.min.js") || element.src === "") {
+ return results;
+ }
+ else {
+ jsType = JsTypes.EXTERNAL;
+
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", element.src, false);
+ xhr.send();
+ if (xhr.status !== 200) {
+ // We no longer want to check this element if there was a problem in making request.
+ return results;
+ }
+ }
+ }
+ }
+ // If element is not a script tag, then we will assume that if listening for pointerevents is present that it will be in the form of an attribute.
+ else {
+ jsType = JsTypes.ATTRIBUTE;
+ }
+
+ for (const event of eventsToCheckFor) {
+ switch (jsType) {
+ case JsTypes.ATTRIBUTE:
+ // Attribute specified on element does not seem to work at present, but checking anyway.
+ if (element.attributes["on" + event] !== undefined) {
+ results[event] = results[event] || { count: 0, };
+ results[event].count++;
+ }
+ break;
+
+ case JsTypes.INTERNAL:
+ // Check for one instance if none present then abandon.
+ if (element.text.indexOf(event) !== -1) {
+ results[event] = results[event] || { count: 0, };
+ results[event].count += findNumOfStringInstancesInText_CaseSensitive(event, element.text);
+ }
+ break;
+
+ case JsTypes.EXTERNAL:
+ // Check for one instance if none present then abandon.
+ if (xhr.responseText.indexOf(event) !== -1) {
+ results[event] = results[event] || { count: 0, };
+ results[event].count += findNumOfStringInstancesInText_CaseSensitive(event, xhr.responseText);
+ }
+ break;
+ }
+ }
+
+ return results;
+ }
+ });
+
+ function findNumOfStringInstancesInText_CaseSensitive(string, text)
+ {
+ var regex = new RegExp(string, 'g');
+ var instances = text.match(regex);
+
+ return instances.length;
+ }
+}();
\ No newline at end of file
diff --git a/src/recipes/testproxy.js b/src/recipes/testproxy.js
new file mode 100644
index 0000000..06f13a4
--- /dev/null
+++ b/src/recipes/testproxy.js
@@ -0,0 +1,25 @@
+/*
+ RECIPE: Fiddler proxy tester
+ -------------------------------------------------------------
+ Author: Mustapha Jaber
+ Description: Use Fiddler to check usage of getElementById on the web.
+*/
+
+window.apiCount = 0;
+window.alert = function (alert) {
+ return function (string) {
+ window.apiCount++;
+ return alert(string);
+ };
+}(window.alert);
+
+void function() {
+ window.CSSUsage.StyleWalker.recipesToRun.push( function testFiddler(/*HTML DOM Elements*/ elements, results) {
+ var recipeName = "alert"
+ if(window.apiCount > 0)
+ {
+ results[recipeName] = results[recipeName] || { count: 0, };
+ results[recipeName].count = window.apiCount;
+ }return results;
+ });
+}();
\ No newline at end of file
diff --git a/tests/recipes/pointer-events_touch-events.html b/tests/recipes/pointer-events_touch-events.html
new file mode 100644
index 0000000..90f9d12
--- /dev/null
+++ b/tests/recipes/pointer-events_touch-events.html
@@ -0,0 +1,43 @@
+
+
+
+ Pointer and touch events listening test
+
+
+
+
+
+
+
+
Pointer and touch events listening test
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/test-page/pointerevents.js b/tests/test-page/pointerevents.js
new file mode 100644
index 0000000..b972c6e
--- /dev/null
+++ b/tests/test-page/pointerevents.js
@@ -0,0 +1,11 @@
+function func()
+{
+ var square2 = document.getElementById("square2");
+
+ square2.addEventListener("pointerup", func2);
+}
+
+function func2()
+{
+ alert("Point!!")
+}
\ No newline at end of file