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