From 8f08aeab1e7353420925911336d64120e20df670 Mon Sep 17 00:00:00 2001 From: Mustapha Jaber Date: Fri, 9 Jun 2017 15:26:21 -0700 Subject: [PATCH 01/22] Added RequestPayment function check recipe --- Recipe.min.js | 34 ++++++++++++++++++++++++++++++++++ cssUsage.src.js | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/Recipe.min.js b/Recipe.min.js index d4d2b57..5341278 100644 --- a/Recipe.min.js +++ b/Recipe.min.js @@ -1540,6 +1540,40 @@ void function() { try { } catch (ex) { /* do something maybe */ throw ex; } }(); +/* + RECIPE: Request Payment + ------------------------------------------------------------- + Author: Mustapha Jaber + Description: Find use of RequestPayment in script. +*/ + +void function() { + 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; + }); +}(); /* RECIPE: z-index on static flex items ------------------------------------------------------------- diff --git a/cssUsage.src.js b/cssUsage.src.js index 9995397..57a1b28 100644 --- a/cssUsage.src.js +++ b/cssUsage.src.js @@ -1540,6 +1540,40 @@ void function() { try { } catch (ex) { /* do something maybe */ throw ex; } }(); +/* + RECIPE: Request Payment + ------------------------------------------------------------- + Author: Mustapha Jaber + Description: Find use of RequestPayment in script. +*/ + +void function() { + 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; + }); +}(); /* RECIPE: z-index on static flex items ------------------------------------------------------------- From 2ee98422340ef3159835c5540007199a337403f5 Mon Sep 17 00:00:00 2001 From: Mustapha Jaber Date: Fri, 9 Jun 2017 15:38:10 -0700 Subject: [PATCH 02/22] Added PaymentRequest.js recipe --- src/recipes/paymentrequest.js | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/recipes/paymentrequest.js diff --git a/src/recipes/paymentrequest.js b/src/recipes/paymentrequest.js new file mode 100644 index 0000000..c213435 --- /dev/null +++ b/src/recipes/paymentrequest.js @@ -0,0 +1,34 @@ +/* + RECIPE: Request Payment + ------------------------------------------------------------- + Author: Mustapha Jaber + Description: Find use of RequestPayment in script. +*/ + +void function() { + 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 From e795a5b7c7e5fc28c632641c4fceeefa9fb439b3 Mon Sep 17 00:00:00 2001 From: Mustapha Jaber Date: Mon, 30 Oct 2017 11:46:01 -0700 Subject: [PATCH 03/22] Added test script for fiddler proxy --- Recipe.min.js | 195 ++++----- cssUsage.src.js | 195 ++++----- src/cssUsage.js | 13 +- src/proxy.js | 512 +++++++++++++++++++++++ src/recipes/archive/paymentrequest.js | 34 +- src/recipes/{ => archive}/zstaticflex.js | 0 src/recipes/paymentrequest.js | 34 -- src/recipes/testproxy.js | 25 ++ 8 files changed, 725 insertions(+), 283 deletions(-) create mode 100644 src/proxy.js rename src/recipes/{ => archive}/zstaticflex.js (100%) delete mode 100644 src/recipes/paymentrequest.js create mode 100644 src/recipes/testproxy.js diff --git a/Recipe.min.js b/Recipe.min.js index 81922c4..ef415f9 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 { @@ -789,15 +789,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); + } } /** @@ -1541,66 +1540,30 @@ void function() { try { } catch (ex) { /* do something maybe */ throw ex; } }(); /* - RECIPE: Request Payment + RECIPE: Fiddler proxy tester ------------------------------------------------------------- Author: Mustapha Jaber - Description: Find use of RequestPayment in script. + 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 paymentRequest(/*HTML DOM Element*/ element, results) { - var nodeName = element.nodeName; - var script = "RequestPayment" - if (nodeName == "SCRIPT") + window.CSSUsage.StyleWalker.recipesToRun.push( function testFiddler(/*HTML DOM Elements*/ elements, results) { + var recipeName = "alert" + if(window.apiCount > 0) { - 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; + results[recipeName] = results[recipeName] || { count: 0, }; + results[recipeName].count = window.apiCount; + }return results; }); }(); -/* - RECIPE: z-index on static flex items - ------------------------------------------------------------- - Author: Francois Remy - Description: Get count of flex items who should create a stacking context but do not really -*/ - -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; - } - - // 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; - } - - }); -}(); - // // This file is only here to create the TSV // necessary to collect the data from the crawler diff --git a/cssUsage.src.js b/cssUsage.src.js index 364d3e5..0955308 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 { @@ -789,15 +789,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); + } } /** @@ -1541,66 +1540,30 @@ void function() { try { } catch (ex) { /* do something maybe */ throw ex; } }(); /* - RECIPE: Request Payment + RECIPE: Fiddler proxy tester ------------------------------------------------------------- Author: Mustapha Jaber - Description: Find use of RequestPayment in script. + 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 paymentRequest(/*HTML DOM Element*/ element, results) { - var nodeName = element.nodeName; - var script = "RequestPayment" - if (nodeName == "SCRIPT") + window.CSSUsage.StyleWalker.recipesToRun.push( function testFiddler(/*HTML DOM Elements*/ elements, results) { + var recipeName = "alert" + if(window.apiCount > 0) { - 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; + results[recipeName] = results[recipeName] || { count: 0, }; + results[recipeName].count = window.apiCount; + }return results; }); }(); -/* - RECIPE: z-index on static flex items - ------------------------------------------------------------- - Author: Francois Remy - Description: Get count of flex items who should create a stacking context but do not really -*/ - -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; - } - - // 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; - } - - }); -}(); - // // 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 7bc04f7..a285e46 100644 --- a/src/cssUsage.js +++ b/src/cssUsage.js @@ -292,15 +292,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/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/zstaticflex.js b/src/recipes/archive/zstaticflex.js similarity index 100% rename from src/recipes/zstaticflex.js rename to src/recipes/archive/zstaticflex.js diff --git a/src/recipes/paymentrequest.js b/src/recipes/paymentrequest.js deleted file mode 100644 index c213435..0000000 --- a/src/recipes/paymentrequest.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - RECIPE: Request Payment - ------------------------------------------------------------- - Author: Mustapha Jaber - Description: Find use of RequestPayment in script. -*/ - -void function() { - 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/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 From f9cee4b0db24cd202585966de7953ca5ca535c94 Mon Sep 17 00:00:00 2001 From: Stanley Hon Date: Tue, 31 Oct 2017 15:13:17 -0700 Subject: [PATCH 04/22] adding a fiddler script for debugging --- src/fiddler-debug/get-element.js | 48 ++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/fiddler-debug/get-element.js diff --git a/src/fiddler-debug/get-element.js b/src/fiddler-debug/get-element.js new file mode 100644 index 0000000..749b4e1 --- /dev/null +++ b/src/fiddler-debug/get-element.js @@ -0,0 +1,48 @@ +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 = "alert" + if(window.apiCount > 0) + { + results[recipeName] = results[recipeName] || { count: 0 }; + results[recipeName].count = window.apiCount; + } + 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 From a8d16556fdf94e3487277f6f88970c8afbbcaf27 Mon Sep 17 00:00:00 2001 From: John Every Date: Wed, 1 Nov 2017 11:27:45 -0700 Subject: [PATCH 05/22] First pass of pointer events recipe. --- Recipe.min.js | 46 ++ cssUsage.src.js | 582 ++++++++++-------- src/recipes/pointer-events_touch-events.js | 46 ++ .../recipes/pointer-events_touch-events.html | 16 + tests/test-page/pointerevents.js | 4 + 5 files changed, 426 insertions(+), 268 deletions(-) create mode 100644 src/recipes/pointer-events_touch-events.js create mode 100644 tests/recipes/pointer-events_touch-events.html create mode 100644 tests/test-page/pointerevents.js diff --git a/Recipe.min.js b/Recipe.min.js index 5566a06..dc314c1 100644 --- a/Recipe.min.js +++ b/Recipe.min.js @@ -1606,6 +1606,52 @@ void function() { }); }(); +/* + RECIPE: Pointer events and touch events + ------------------------------------------------------------- + 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; + // We want to catch all instances of listening for these events. + var script = "pointerup"; + + // Attribute specified on element. + if (element.getAttribute(script)) + { + results[script] = results[script] || { count: 0, }; + results[script].count++; + } + + if (nodeName === "SCRIPT") + { + // 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[script] = results[script] || { count: 0, }; + results[script].count++; + } + // if external script, then we have to go and get it. + 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[script] = results[script] || { count: 0, }; + results[script].count++; + } + } + } + return results; + }); +}(); /* RECIPE: position-sticky_ancestry-tree ------------------------------------------------------------- diff --git a/cssUsage.src.js b/cssUsage.src.js index b4c776e..f5344e1 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 { @@ -1606,6 +1606,52 @@ void function() { }); }(); +/* + RECIPE: Pointer events and touch events + ------------------------------------------------------------- + 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; + // We want to catch all instances of listening for these events. + var script = "pointerup"; + + // Attribute specified on element. + if (element.getAttribute(script)) + { + results[script] = results[script] || { count: 0, }; + results[script].count++; + } + + if (nodeName === "SCRIPT") + { + // 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[script] = results[script] || { count: 0, }; + results[script].count++; + } + // if external script, then we have to go and get it. + 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[script] = results[script] || { count: 0, }; + results[script].count++; + } + } + } + return results; + }); +}(); /* RECIPE: position-sticky_ancestry-tree ------------------------------------------------------------- @@ -1649,217 +1695,217 @@ void function() { }); }(); -/* - RECIPE: z-index on static flex items - ------------------------------------------------------------- - Author: Francois Remy - Description: Get count of flex items who should create a stacking context but do not really -*/ - -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; - } - - // 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; - } - - }); -}(); - -// -// 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('css'); - convertToTSV(INSTRUMENTATION_RESULTS['css']); - currentRowTemplate.pop(); - - currentRowTemplate.push('dom'); - convertToTSV(INSTRUMENTATION_RESULTS['dom']); - currentRowTemplate.pop(); - - currentRowTemplate.push('html'); - convertToTSV(INSTRUMENTATION_RESULTS['html']); - 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; - } - - } +/* + RECIPE: z-index on static flex items + ------------------------------------------------------------- + Author: Francois Remy + Description: Get count of flex items who should create a stacking context but do not really +*/ + +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; + } + + // 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; + } + + }); +}(); + +// +// 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('css'); + convertToTSV(INSTRUMENTATION_RESULTS['css']); + currentRowTemplate.pop(); + + currentRowTemplate.push('dom'); + convertToTSV(INSTRUMENTATION_RESULTS['dom']); + currentRowTemplate.pop(); + + currentRowTemplate.push('html'); + convertToTSV(INSTRUMENTATION_RESULTS['html']); + 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/src/recipes/pointer-events_touch-events.js b/src/recipes/pointer-events_touch-events.js new file mode 100644 index 0000000..ab6f8b1 --- /dev/null +++ b/src/recipes/pointer-events_touch-events.js @@ -0,0 +1,46 @@ +/* + RECIPE: Pointer events and touch events + ------------------------------------------------------------- + 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; + // We want to catch all instances of listening for these events. + var script = "pointerup"; + + // Attribute specified on element. + if (element.getAttribute(script)) + { + results[script] = results[script] || { count: 0, }; + results[script].count++; + } + + if (nodeName === "SCRIPT") + { + // 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[script] = results[script] || { count: 0, }; + results[script].count++; + } + // if external script, then we have to go and get it. + 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[script] = results[script] || { count: 0, }; + results[script].count++; + } + } + } + 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..e92308f --- /dev/null +++ b/tests/recipes/pointer-events_touch-events.html @@ -0,0 +1,16 @@ + + + + Pointer events 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..91d56dd --- /dev/null +++ b/tests/test-page/pointerevents.js @@ -0,0 +1,4 @@ +function func() +{ + addEventListener("pointerup"); +} \ No newline at end of file From b1d070f8fdf9f4c05243e239400c24871a4def4e Mon Sep 17 00:00:00 2001 From: John Every Date: Wed, 1 Nov 2017 14:49:02 -0700 Subject: [PATCH 06/22] Second pass with pointer events and touch events listening. --- Recipe.min.js | 4 +- cssUsage.src.js | 4 +- src/recipes/pointer-events_touch-events.js | 14 ++++--- .../recipes/pointer-events_touch-events.html | 42 +++++++++++++++---- tests/test-page/pointerevents.js | 9 +++- 5 files changed, 54 insertions(+), 19 deletions(-) diff --git a/Recipe.min.js b/Recipe.min.js index dc314c1..cbaaae9 100644 --- a/Recipe.min.js +++ b/Recipe.min.js @@ -1621,7 +1621,7 @@ void function() { var script = "pointerup"; // Attribute specified on element. - if (element.getAttribute(script)) + if (element.getAttribute("on" + script)) { results[script] = results[script] || { count: 0, }; results[script].count++; @@ -1636,7 +1636,7 @@ void function() { results[script].count++; } // if external script, then we have to go and get it. - else if (element.src !== undefined && element.src !== "") + else if (element.src !== undefined && element.src !== "" && !element.src.includes("Recipe.min.js")) { var xhr = new XMLHttpRequest(); xhr.open("GET", element.src, false); diff --git a/cssUsage.src.js b/cssUsage.src.js index f5344e1..a03187a 100644 --- a/cssUsage.src.js +++ b/cssUsage.src.js @@ -1621,7 +1621,7 @@ void function() { var script = "pointerup"; // Attribute specified on element. - if (element.getAttribute(script)) + if (element.getAttribute("on" + script)) { results[script] = results[script] || { count: 0, }; results[script].count++; @@ -1636,7 +1636,7 @@ void function() { results[script].count++; } // if external script, then we have to go and get it. - else if (element.src !== undefined && element.src !== "") + else if (element.src !== undefined && element.src !== "" && !element.src.includes("Recipe.min.js")) { var xhr = new XMLHttpRequest(); xhr.open("GET", element.src, false); diff --git a/src/recipes/pointer-events_touch-events.js b/src/recipes/pointer-events_touch-events.js index ab6f8b1..cdb0167 100644 --- a/src/recipes/pointer-events_touch-events.js +++ b/src/recipes/pointer-events_touch-events.js @@ -13,7 +13,7 @@ void function() { var script = "pointerup"; // Attribute specified on element. - if (element.getAttribute(script)) + if (element.getAttribute("on" + script)) { results[script] = results[script] || { count: 0, }; results[script].count++; @@ -22,13 +22,15 @@ void function() { if (nodeName === "SCRIPT") { // 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) + if (element.text !== undefined) { - results[script] = results[script] || { count: 0, }; - results[script].count++; + if (element.text.indexOf(".on" + script) !== -1) { + results[script] = results[script] || { count: 0, }; + results[script].count++; + } } - // if external script, then we have to go and get it. - else if (element.src !== undefined && element.src !== "") + // if external script, then we have to go and get it and ensure it is not our recipe script. + else if (element.src !== undefined && element.src !== "" && !element.src.includes("Recipe.min.js")) { var xhr = new XMLHttpRequest(); xhr.open("GET", element.src, false); diff --git a/tests/recipes/pointer-events_touch-events.html b/tests/recipes/pointer-events_touch-events.html index e92308f..a5aad0a 100644 --- a/tests/recipes/pointer-events_touch-events.html +++ b/tests/recipes/pointer-events_touch-events.html @@ -1,16 +1,42 @@  - Pointer events 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 index 91d56dd..b972c6e 100644 --- a/tests/test-page/pointerevents.js +++ b/tests/test-page/pointerevents.js @@ -1,4 +1,11 @@ function func() { - addEventListener("pointerup"); + var square2 = document.getElementById("square2"); + + square2.addEventListener("pointerup", func2); +} + +function func2() +{ + alert("Point!!") } \ No newline at end of file From 8dd32ed93eaf4b1391d65cd998158d7580705bbc Mon Sep 17 00:00:00 2001 From: John Every Date: Wed, 1 Nov 2017 17:12:25 -0700 Subject: [PATCH 07/22] Getting pointer events and touch events listening checks test ready for preliminary testing. --- Recipe.min.js | 56 +- cssUsage.src.js | 592 +++++++++--------- src/recipes/pointer-events_touch-events.js | 58 +- .../recipes/pointer-events_touch-events.html | 3 +- 4 files changed, 372 insertions(+), 337 deletions(-) diff --git a/Recipe.min.js b/Recipe.min.js index cbaaae9..d52e888 100644 --- a/Recipe.min.js +++ b/Recipe.min.js @@ -1618,34 +1618,46 @@ void function() { function pointer_events_touch_events(/*HTML DOM Element*/ element, results) { var nodeName = element.nodeName; // We want to catch all instances of listening for these events. - var script = "pointerup"; - // Attribute specified on element. - if (element.getAttribute("on" + script)) + var eventsToCheckFor = ["pointerup", "pointerdown", "pointermove", "pointercancel", "pointerout", "pointerleave", "pointerenter", "pointerover", + "touchstart", "touchend", "touchmove", "touchcancel"]; + + var isExternalJSAndNotRecipeJS = (element.src !== undefined && element.src !== "" && !element.src.includes("Recipe.min.js")); + var xhr; + if (isExternalJSAndNotRecipeJS) { - results[script] = results[script] || { count: 0, }; - results[script].count++; + xhr = new XMLHttpRequest(); + xhr.open("GET", element.src, false); + xhr.send(); } - if (nodeName === "SCRIPT") - { - // 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[script] = results[script] || { count: 0, }; - results[script].count++; + for (const event of eventsToCheckFor) { + + // Attribute specified on element, although this does not work at present. + if (element.getAttribute(".on" + event)) { + results[event] = results[event] || { count: 0, }; + results[event].count++; } - // if external script, then we have to go and get it. - else if (element.src !== undefined && element.src !== "" && !element.src.includes("Recipe.min.js")) - { - 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) + + if (nodeName === "SCRIPT") { + // if inline script. ensure that it's not our recipe script and look for string of interest + if ((element.text !== undefined) && (element.text.indexOf(event) !== -1)) { + var regex = new RegExp(event, 'g'); + var instances = element.text.match(regex); + + results[event] = results[event] || { count: 0, }; + results[event].count += instances.length; + } + // if external script, then we have to go and get it and ensure it is not our recipe script. + else if (isExternalJSAndNotRecipeJS) { - results[script] = results[script] || { count: 0, }; - results[script].count++; + if (xhr.status === 200 && xhr.responseText.indexOf(event) !== -1) { + var regex = new RegExp(event, 'g'); + var instances = xhr.responseText.match(regex); + + results[event] = results[event] || { count: 0, }; + results[event].count += instances.length; + } } } } diff --git a/cssUsage.src.js b/cssUsage.src.js index a03187a..904d012 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 { @@ -1618,34 +1618,46 @@ void function() { function pointer_events_touch_events(/*HTML DOM Element*/ element, results) { var nodeName = element.nodeName; // We want to catch all instances of listening for these events. - var script = "pointerup"; - // Attribute specified on element. - if (element.getAttribute("on" + script)) + var eventsToCheckFor = ["pointerup", "pointerdown", "pointermove", "pointercancel", "pointerout", "pointerleave", "pointerenter", "pointerover", + "touchstart", "touchend", "touchmove", "touchcancel"]; + + var isExternalJSAndNotRecipeJS = (element.src !== undefined && element.src !== "" && !element.src.includes("Recipe.min.js")); + var xhr; + if (isExternalJSAndNotRecipeJS) { - results[script] = results[script] || { count: 0, }; - results[script].count++; + xhr = new XMLHttpRequest(); + xhr.open("GET", element.src, false); + xhr.send(); } - if (nodeName === "SCRIPT") - { - // 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[script] = results[script] || { count: 0, }; - results[script].count++; + for (const event of eventsToCheckFor) { + + // Attribute specified on element, although this does not work at present. + if (element.getAttribute(".on" + event)) { + results[event] = results[event] || { count: 0, }; + results[event].count++; } - // if external script, then we have to go and get it. - else if (element.src !== undefined && element.src !== "" && !element.src.includes("Recipe.min.js")) - { - 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) + + if (nodeName === "SCRIPT") { + // if inline script. ensure that it's not our recipe script and look for string of interest + if ((element.text !== undefined) && (element.text.indexOf(event) !== -1)) { + var regex = new RegExp(event, 'g'); + var instances = element.text.match(regex); + + results[event] = results[event] || { count: 0, }; + results[event].count += instances.length; + } + // if external script, then we have to go and get it and ensure it is not our recipe script. + else if (isExternalJSAndNotRecipeJS) { - results[script] = results[script] || { count: 0, }; - results[script].count++; + if (xhr.status === 200 && xhr.responseText.indexOf(event) !== -1) { + var regex = new RegExp(event, 'g'); + var instances = xhr.responseText.match(regex); + + results[event] = results[event] || { count: 0, }; + results[event].count += instances.length; + } } } } @@ -1695,217 +1707,217 @@ void function() { }); }(); -/* - RECIPE: z-index on static flex items - ------------------------------------------------------------- - Author: Francois Remy - Description: Get count of flex items who should create a stacking context but do not really -*/ - -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; - } - - // 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; - } - - }); -}(); - -// -// 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('css'); - convertToTSV(INSTRUMENTATION_RESULTS['css']); - currentRowTemplate.pop(); - - currentRowTemplate.push('dom'); - convertToTSV(INSTRUMENTATION_RESULTS['dom']); - currentRowTemplate.pop(); - - currentRowTemplate.push('html'); - convertToTSV(INSTRUMENTATION_RESULTS['html']); - 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; - } - - } +/* + RECIPE: z-index on static flex items + ------------------------------------------------------------- + Author: Francois Remy + Description: Get count of flex items who should create a stacking context but do not really +*/ + +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; + } + + // 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; + } + + }); +}(); + +// +// 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('css'); + convertToTSV(INSTRUMENTATION_RESULTS['css']); + currentRowTemplate.pop(); + + currentRowTemplate.push('dom'); + convertToTSV(INSTRUMENTATION_RESULTS['dom']); + currentRowTemplate.pop(); + + currentRowTemplate.push('html'); + convertToTSV(INSTRUMENTATION_RESULTS['html']); + 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/src/recipes/pointer-events_touch-events.js b/src/recipes/pointer-events_touch-events.js index cdb0167..092a2b1 100644 --- a/src/recipes/pointer-events_touch-events.js +++ b/src/recipes/pointer-events_touch-events.js @@ -10,36 +10,46 @@ void function() { function pointer_events_touch_events(/*HTML DOM Element*/ element, results) { var nodeName = element.nodeName; // We want to catch all instances of listening for these events. - var script = "pointerup"; - // Attribute specified on element. - if (element.getAttribute("on" + script)) + var eventsToCheckFor = ["pointerup", "pointerdown", "pointermove", "pointercancel", "pointerout", "pointerleave", "pointerenter", "pointerover", + "touchstart", "touchend", "touchmove", "touchcancel"]; + + var isExternalJSAndNotRecipeJS = (element.src !== undefined && element.src !== "" && !element.src.includes("Recipe.min.js")); + var xhr; + if (isExternalJSAndNotRecipeJS) { - results[script] = results[script] || { count: 0, }; - results[script].count++; + xhr = new XMLHttpRequest(); + xhr.open("GET", element.src, false); + xhr.send(); } - if (nodeName === "SCRIPT") - { - // if inline script. ensure that it's not our recipe script and look for string of interest - if (element.text !== undefined) - { - if (element.text.indexOf(".on" + script) !== -1) { - results[script] = results[script] || { count: 0, }; - results[script].count++; - } + for (const event of eventsToCheckFor) { + + // Attribute specified on element, although this does not work at present. + if (element.getAttribute(".on" + event)) { + results[event] = results[event] || { count: 0, }; + results[event].count++; } - // if external script, then we have to go and get it and ensure it is not our recipe script. - else if (element.src !== undefined && element.src !== "" && !element.src.includes("Recipe.min.js")) - { - 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) + + if (nodeName === "SCRIPT") { + // if inline script. ensure that it's not our recipe script and look for string of interest + if ((element.text !== undefined) && (element.text.indexOf(event) !== -1)) { + var regex = new RegExp(event, 'g'); + var instances = element.text.match(regex); + + results[event] = results[event] || { count: 0, }; + results[event].count += instances.length; + } + // if external script, then we have to go and get it and ensure it is not our recipe script. + else if (isExternalJSAndNotRecipeJS) { - results[script] = results[script] || { count: 0, }; - results[script].count++; + if (xhr.status === 200 && xhr.responseText.indexOf(event) !== -1) { + var regex = new RegExp(event, 'g'); + var instances = xhr.responseText.match(regex); + + results[event] = results[event] || { count: 0, }; + results[event].count += instances.length; + } } } } diff --git a/tests/recipes/pointer-events_touch-events.html b/tests/recipes/pointer-events_touch-events.html index a5aad0a..90f9d12 100644 --- a/tests/recipes/pointer-events_touch-events.html +++ b/tests/recipes/pointer-events_touch-events.html @@ -18,6 +18,7 @@ } #square1 { background-color: yellow; + left: 500px; } #square2 { background-color: blue; @@ -36,7 +37,7 @@

Pointer and touch events listening test

\ No newline at end of file From 367bc7f40bd2e663996a01bdf3565993f6583db6 Mon Sep 17 00:00:00 2001 From: John Every Date: Wed, 1 Nov 2017 17:18:11 -0700 Subject: [PATCH 08/22] Remove unnecessary recipes and tests to make crawl faster. --- Recipe.min.js | 136 ------------------ cssUsage.src.js | 136 ------------------ src/recipes/object-fit-onvideo.js | 24 ---- src/recipes/object-fit_object-position.js | 40 ------ src/recipes/position-sticky_ancestry-tree.js | 42 ------ src/recipes/zstaticflex.js | 26 ---- tests/recipes/object-fit-onvideo.html | 24 ---- tests/recipes/object-fit_object-position.html | 35 ----- .../position-sticky_ancestry-tree.html | 106 -------------- 9 files changed, 569 deletions(-) delete mode 100644 src/recipes/object-fit-onvideo.js delete mode 100644 src/recipes/object-fit_object-position.js delete mode 100644 src/recipes/position-sticky_ancestry-tree.js delete mode 100644 src/recipes/zstaticflex.js delete mode 100644 tests/recipes/object-fit-onvideo.html delete mode 100644 tests/recipes/object-fit_object-position.html delete mode 100644 tests/recipes/position-sticky_ancestry-tree.html diff --git a/Recipe.min.js b/Recipe.min.js index d52e888..3611560 100644 --- a/Recipe.min.js +++ b/Recipe.min.js @@ -1540,72 +1540,6 @@ void function() { try { } catch (ex) { /* do something maybe */ throw ex; } }(); -/* - RECIPE: object_fit_object_position - ------------------------------------------------------------- - Author: joevery - Description: Get count of how many times object-fit and object-position CSS properties are used on websites and classify by HTML tag. -*/ -void function() { - window.CSSUsage.StyleWalker.recipesToRun.push( - function object_fit_object_position(element, results) - { - // Need this to ensure no errors on html and head tags when running the code below. - if (!element.CSSUsage) return; - - if (element.CSSUsage["object-fit"]) - { - // Allows counting of number of HTML tags using object-fit CSS property on page. - results["object-fit"] = results["object-fit"] || { count: 0, }; - results["object-fit"].count++; - - // Classify count by HTML tag. - var nodeName = element.nodeName; - results["object-fit"][nodeName] = results["object-fit"][nodeName] || { count: 0, }; - results["object-fit"][nodeName].count++; - } - - if (element.CSSUsage["object-position"]) - { - // Allows counting of number of HTML tags using object-position CSS property on page. - results["object-position"] = results["object-position"] || { count: 0, }; - results["object-position"].count++; - - // Classify count by HTML tag. - var nodeName = element.nodeName; - results["object-position"][nodeName] = results["object-position"][nodeName] || { count: 0, }; - results["object-position"][nodeName].count++; - } - - return results; - }); -}(); - -/* - RECIPE: object_fit_onvideo - ------------------------------------------------------------- - Author: joevery - Description: Get count of how many times object-fit CSS property is used on a video element on websites. -*/ -void function() { - window.CSSUsage.StyleWalker.recipesToRun.push( - function object_fit_onvideo(element, results) - { - var nodeName = element.nodeName; - - if (nodeName == "VIDEO") - { - if (element.CSSUsage["object-fit"]) - { - var key = "VIDEO" + "_" + "object-fit"; - results[key] = results[key] || { count: 0, }; - results[key].count++; - } - } - return results; - }); -}(); - /* RECIPE: Pointer events and touch events ------------------------------------------------------------- @@ -1664,76 +1598,6 @@ void function() { return results; }); }(); -/* - RECIPE: position-sticky_ancestry-tree - ------------------------------------------------------------- - Author: joevery - Description: Get count of how many times position CSS property with value sticky are used on websites and classify by HTML tag. -*/ -void function() { - window.CSSUsage.StyleWalker.recipesToRun.push( - function position_sticky_ancestry_tree(element, results) - { - // Need this to ensure no errors on html and head tags when running the code below. - if (!element.CSSUsage) return; - - if (element.CSSUsage["position"] == "sticky") - { - var nodeName = element.nodeName; - - var ancestryTreeArray = [nodeName]; - - var currentParent = element.parentElement; - - while (currentParent) - { - ancestryTreeArray.push(currentParent.nodeName); - currentParent = currentParent.parentElement; - } - - var ancestryTree = ancestryTreeArray.join("-"); - - results["sticky"] = results["sticky"] || { count: 0, }; - results["sticky"].count++; - - results["sticky"][nodeName] = results["sticky"][nodeName] || { count: 0, }; - results["sticky"][nodeName].count++; - - results["sticky"][nodeName][ancestryTree] = results["sticky"][nodeName][ancestryTree] || { count: 0, }; - results["sticky"][nodeName][ancestryTree].count++; - } - - return results; - }); -}(); - -/* - RECIPE: z-index on static flex items - ------------------------------------------------------------- - Author: Francois Remy - Description: Get count of flex items who should create a stacking context but do not really -*/ - -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; - } - - // 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; - } - - }); -}(); - // // This file is only here to create the TSV // necessary to collect the data from the crawler diff --git a/cssUsage.src.js b/cssUsage.src.js index 904d012..27a35f4 100644 --- a/cssUsage.src.js +++ b/cssUsage.src.js @@ -1540,72 +1540,6 @@ void function() { try { } catch (ex) { /* do something maybe */ throw ex; } }(); -/* - RECIPE: object_fit_object_position - ------------------------------------------------------------- - Author: joevery - Description: Get count of how many times object-fit and object-position CSS properties are used on websites and classify by HTML tag. -*/ -void function() { - window.CSSUsage.StyleWalker.recipesToRun.push( - function object_fit_object_position(element, results) - { - // Need this to ensure no errors on html and head tags when running the code below. - if (!element.CSSUsage) return; - - if (element.CSSUsage["object-fit"]) - { - // Allows counting of number of HTML tags using object-fit CSS property on page. - results["object-fit"] = results["object-fit"] || { count: 0, }; - results["object-fit"].count++; - - // Classify count by HTML tag. - var nodeName = element.nodeName; - results["object-fit"][nodeName] = results["object-fit"][nodeName] || { count: 0, }; - results["object-fit"][nodeName].count++; - } - - if (element.CSSUsage["object-position"]) - { - // Allows counting of number of HTML tags using object-position CSS property on page. - results["object-position"] = results["object-position"] || { count: 0, }; - results["object-position"].count++; - - // Classify count by HTML tag. - var nodeName = element.nodeName; - results["object-position"][nodeName] = results["object-position"][nodeName] || { count: 0, }; - results["object-position"][nodeName].count++; - } - - return results; - }); -}(); - -/* - RECIPE: object_fit_onvideo - ------------------------------------------------------------- - Author: joevery - Description: Get count of how many times object-fit CSS property is used on a video element on websites. -*/ -void function() { - window.CSSUsage.StyleWalker.recipesToRun.push( - function object_fit_onvideo(element, results) - { - var nodeName = element.nodeName; - - if (nodeName == "VIDEO") - { - if (element.CSSUsage["object-fit"]) - { - var key = "VIDEO" + "_" + "object-fit"; - results[key] = results[key] || { count: 0, }; - results[key].count++; - } - } - return results; - }); -}(); - /* RECIPE: Pointer events and touch events ------------------------------------------------------------- @@ -1664,76 +1598,6 @@ void function() { return results; }); }(); -/* - RECIPE: position-sticky_ancestry-tree - ------------------------------------------------------------- - Author: joevery - Description: Get count of how many times position CSS property with value sticky are used on websites and classify by HTML tag. -*/ -void function() { - window.CSSUsage.StyleWalker.recipesToRun.push( - function position_sticky_ancestry_tree(element, results) - { - // Need this to ensure no errors on html and head tags when running the code below. - if (!element.CSSUsage) return; - - if (element.CSSUsage["position"] == "sticky") - { - var nodeName = element.nodeName; - - var ancestryTreeArray = [nodeName]; - - var currentParent = element.parentElement; - - while (currentParent) - { - ancestryTreeArray.push(currentParent.nodeName); - currentParent = currentParent.parentElement; - } - - var ancestryTree = ancestryTreeArray.join("-"); - - results["sticky"] = results["sticky"] || { count: 0, }; - results["sticky"].count++; - - results["sticky"][nodeName] = results["sticky"][nodeName] || { count: 0, }; - results["sticky"][nodeName].count++; - - results["sticky"][nodeName][ancestryTree] = results["sticky"][nodeName][ancestryTree] || { count: 0, }; - results["sticky"][nodeName][ancestryTree].count++; - } - - return results; - }); -}(); - -/* - RECIPE: z-index on static flex items - ------------------------------------------------------------- - Author: Francois Remy - Description: Get count of flex items who should create a stacking context but do not really -*/ - -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; - } - - // 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; - } - - }); -}(); - // // This file is only here to create the TSV // necessary to collect the data from the crawler diff --git a/src/recipes/object-fit-onvideo.js b/src/recipes/object-fit-onvideo.js deleted file mode 100644 index b23cae6..0000000 --- a/src/recipes/object-fit-onvideo.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - RECIPE: object_fit_onvideo - ------------------------------------------------------------- - Author: joevery - Description: Get count of how many times object-fit CSS property is used on a video element on websites. -*/ -void function() { - window.CSSUsage.StyleWalker.recipesToRun.push( - function object_fit_onvideo(element, results) - { - var nodeName = element.nodeName; - - if (nodeName == "VIDEO") - { - if (element.CSSUsage["object-fit"]) - { - var key = "VIDEO" + "_" + "object-fit"; - results[key] = results[key] || { count: 0, }; - results[key].count++; - } - } - return results; - }); -}(); diff --git a/src/recipes/object-fit_object-position.js b/src/recipes/object-fit_object-position.js deleted file mode 100644 index 15b6e9e..0000000 --- a/src/recipes/object-fit_object-position.js +++ /dev/null @@ -1,40 +0,0 @@ -/* - RECIPE: object_fit_object_position - ------------------------------------------------------------- - Author: joevery - Description: Get count of how many times object-fit and object-position CSS properties are used on websites and classify by HTML tag. -*/ -void function() { - window.CSSUsage.StyleWalker.recipesToRun.push( - function object_fit_object_position(element, results) - { - // Need this to ensure no errors on html and head tags when running the code below. - if (!element.CSSUsage) return; - - if (element.CSSUsage["object-fit"]) - { - // Allows counting of number of HTML tags using object-fit CSS property on page. - results["object-fit"] = results["object-fit"] || { count: 0, }; - results["object-fit"].count++; - - // Classify count by HTML tag. - var nodeName = element.nodeName; - results["object-fit"][nodeName] = results["object-fit"][nodeName] || { count: 0, }; - results["object-fit"][nodeName].count++; - } - - if (element.CSSUsage["object-position"]) - { - // Allows counting of number of HTML tags using object-position CSS property on page. - results["object-position"] = results["object-position"] || { count: 0, }; - results["object-position"].count++; - - // Classify count by HTML tag. - var nodeName = element.nodeName; - results["object-position"][nodeName] = results["object-position"][nodeName] || { count: 0, }; - results["object-position"][nodeName].count++; - } - - return results; - }); -}(); diff --git a/src/recipes/position-sticky_ancestry-tree.js b/src/recipes/position-sticky_ancestry-tree.js deleted file mode 100644 index da38465..0000000 --- a/src/recipes/position-sticky_ancestry-tree.js +++ /dev/null @@ -1,42 +0,0 @@ -/* - RECIPE: position-sticky_ancestry-tree - ------------------------------------------------------------- - Author: joevery - Description: Get count of how many times position CSS property with value sticky are used on websites and classify by HTML tag. -*/ -void function() { - window.CSSUsage.StyleWalker.recipesToRun.push( - function position_sticky_ancestry_tree(element, results) - { - // Need this to ensure no errors on html and head tags when running the code below. - if (!element.CSSUsage) return; - - if (element.CSSUsage["position"] == "sticky") - { - var nodeName = element.nodeName; - - var ancestryTreeArray = [nodeName]; - - var currentParent = element.parentElement; - - while (currentParent) - { - ancestryTreeArray.push(currentParent.nodeName); - currentParent = currentParent.parentElement; - } - - var ancestryTree = ancestryTreeArray.join("-"); - - results["sticky"] = results["sticky"] || { count: 0, }; - results["sticky"].count++; - - results["sticky"][nodeName] = results["sticky"][nodeName] || { count: 0, }; - results["sticky"][nodeName].count++; - - results["sticky"][nodeName][ancestryTree] = results["sticky"][nodeName][ancestryTree] || { count: 0, }; - results["sticky"][nodeName][ancestryTree].count++; - } - - return results; - }); -}(); diff --git a/src/recipes/zstaticflex.js b/src/recipes/zstaticflex.js deleted file mode 100644 index 31ebec7..0000000 --- a/src/recipes/zstaticflex.js +++ /dev/null @@ -1,26 +0,0 @@ -/* - RECIPE: z-index on static flex items - ------------------------------------------------------------- - Author: Francois Remy - Description: Get count of flex items who should create a stacking context but do not really -*/ - -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; - } - - // 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; - } - - }); -}(); diff --git a/tests/recipes/object-fit-onvideo.html b/tests/recipes/object-fit-onvideo.html deleted file mode 100644 index 52e968e..0000000 --- a/tests/recipes/object-fit-onvideo.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - Object fit on video element test - - - - - -
-

Object fit on video element test

-
- -
-
- - \ No newline at end of file diff --git a/tests/recipes/object-fit_object-position.html b/tests/recipes/object-fit_object-position.html deleted file mode 100644 index 7a95633..0000000 --- a/tests/recipes/object-fit_object-position.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - Object fit and object position test - - - - - -
-

Object fit and object position test

-
- - -
-
- - -
-
- - - -
-
- - \ No newline at end of file diff --git a/tests/recipes/position-sticky_ancestry-tree.html b/tests/recipes/position-sticky_ancestry-tree.html deleted file mode 100644 index a47867e..0000000 --- a/tests/recipes/position-sticky_ancestry-tree.html +++ /dev/null @@ -1,106 +0,0 @@ - - - - Position-sticky ancestry tree test - - - - -
-
-
A-sticky-label
-
Andrew W.K.
-
Apparat
-
Arcade Fire
-
At The Drive-In
-
Aziz Ansari
-
Andrew W.K.
-
Apparat
-
Arcade Fire
-
At The Drive-In
-
Aziz Ansari
-
-
-
C
-
Chromeo
-
Common
-
Converge
-
Crystal Castles
-
Cursive
-
Chromeo
-
Common
-
Converge
-
Crystal Castles
-
Cursive
-
-
-
E
-
Explosions In The Sky
-
Explosions In The Sky
-
Explosions In The Sky
-
Explosions In The Sky
-
Explosions In The Sky
-
Explosions In The Sky
-
Explosions In The Sky
-
Explosions In The Sky
-
Explosions In The Sky
-
Explosions In The Sky
-
Explosions In The Sky
-
-
-
T
-
Ted Leo & The Pharmacists
-
T-Pain
-
Thrice
-
TV On The Radio
-
Two Gallants
-
Ted Leo & The Pharmacists
-
T-Pain
-
Thrice
-
TV On The Radio
-
Two Gallants
-
Ted Leo & The Pharmacists
-
T-Pain
-
Thrice
-
TV On The Radio
-
Two Gallants
-
-
- - \ No newline at end of file From f5bcd8db8bf556097e0a8dcf93388a3dae13ec28 Mon Sep 17 00:00:00 2001 From: John Every Date: Thu, 2 Nov 2017 10:07:22 -0700 Subject: [PATCH 09/22] Delete VS files that should not have been committed. --- .vs/ProjectSettings.json | 3 - .vs/VSWorkspaceState.json | 12 - .vs/config/applicationhost.config | 1020 -------------------------- .vs/css-usage/v15/.suo | Bin 63488 -> 0 bytes .vs/css-usage/v15/.suo~RF40aa3db.TMP | 0 .vs/css-usage/v15/suo6E84.tmp | Bin 16384 -> 0 bytes .vs/css-usage/v15/suoA3C3.tmp | Bin 27136 -> 0 bytes .vs/slnx.sqlite | Bin 163840 -> 0 bytes 8 files changed, 1035 deletions(-) delete mode 100644 .vs/ProjectSettings.json delete mode 100644 .vs/VSWorkspaceState.json delete mode 100644 .vs/config/applicationhost.config delete mode 100644 .vs/css-usage/v15/.suo delete mode 100644 .vs/css-usage/v15/.suo~RF40aa3db.TMP delete mode 100644 .vs/css-usage/v15/suo6E84.tmp delete mode 100644 .vs/css-usage/v15/suoA3C3.tmp delete mode 100644 .vs/slnx.sqlite diff --git a/.vs/ProjectSettings.json b/.vs/ProjectSettings.json deleted file mode 100644 index 866f1e1..0000000 --- a/.vs/ProjectSettings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "CurrentProjectSetting": null -} \ No newline at end of file diff --git a/.vs/VSWorkspaceState.json b/.vs/VSWorkspaceState.json deleted file mode 100644 index 31df1ec..0000000 --- a/.vs/VSWorkspaceState.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "ExpandedNodes": [ - "", - "\\src", - "\\src\\recipes", - "\\src\\recipes\\archive", - "\\tests\\recipes", - "\\tests\\test-page" - ], - "SelectedNode": "\\tests", - "PreviewInSolutionExplorer": false -} \ No newline at end of file diff --git a/.vs/config/applicationhost.config b/.vs/config/applicationhost.config deleted file mode 100644 index 15800df..0000000 --- a/.vs/config/applicationhost.config +++ /dev/null @@ -1,1020 +0,0 @@ - - - - - - - -
-
-
-
-
-
-
-
- - - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- -
-
-
-
-
-
- -
-
-
-
-
- -
-
-
- -
-
- -
-
- -
-
-
- - -
-
-
-
-
-
- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.vs/css-usage/v15/.suo b/.vs/css-usage/v15/.suo deleted file mode 100644 index 5a7e502b27a038f24f73e8ff8a5a7529a816af73..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63488 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 diff --git a/.vs/css-usage/v15/.suo~RF40aa3db.TMP b/.vs/css-usage/v15/.suo~RF40aa3db.TMP deleted file mode 100644 index e69de29..0000000 diff --git a/.vs/css-usage/v15/suo6E84.tmp b/.vs/css-usage/v15/suo6E84.tmp deleted file mode 100644 index 1d58a19ae53bcf18855956be74d4fbab1467910e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16384 zcmeIyu?Ye(6adgKqF|?ug_V=oxqyQxh>hbof|KY7I*7(Z5Kfy*bA{ad;m9ZXcR5~2 zu9w;EbY9%!Csr|xXPU&gF+Rw7dEPONJfBj@KYhvbkoY5kNB-Bcf1;lPo7m-ch-Iu} zoBMqn%Q3Ewd5pf!e9eo^`=X}4t){tiZM9v@_kH~?Ons980RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly&{u&Mwv6*> diff --git a/.vs/css-usage/v15/suoA3C3.tmp b/.vs/css-usage/v15/suoA3C3.tmp deleted file mode 100644 index e95b02b18646a661bf5e4a71ae558e64e3c25621..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27136 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 diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite deleted file mode 100644 index 8c396fbf64f01b2ed07f5d678167f1390e89c772..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 163840 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 PJw)b{#O&N#6!-rh+E`@{ From 88430adf8b18e2bd608ba3cff7e2e1fbe0649cef Mon Sep 17 00:00:00 2001 From: Stanley Hon Date: Thu, 2 Nov 2017 10:56:15 -0700 Subject: [PATCH 10/22] Adding some more stuff to get-element fiddler script --- src/fiddler-debug/get-element.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/fiddler-debug/get-element.js b/src/fiddler-debug/get-element.js index 749b4e1..2f8bad7 100644 --- a/src/fiddler-debug/get-element.js +++ b/src/fiddler-debug/get-element.js @@ -13,12 +13,18 @@ void function() { document.addEventListener('DOMContentLoaded', function () { console.log("PIN2") var results = {}; - var recipeName = "alert" + var recipeName = "getelem" + + if(window.apiCount > 0) { - results[recipeName] = results[recipeName] || { count: 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); From 9c5ebcd0d51af83b77ce04cd16b92995716d8d88 Mon Sep 17 00:00:00 2001 From: John Every Date: Thu, 2 Nov 2017 12:01:15 -0700 Subject: [PATCH 11/22] Updates to recipe in response to PR. --- Recipe.min.js | 542 +++++++++++---------- cssUsage.src.js | 68 +-- src/recipes/pointer-events_touch-events.js | 66 ++- 3 files changed, 356 insertions(+), 320 deletions(-) diff --git a/Recipe.min.js b/Recipe.min.js index 3611560..ccb3777 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 { @@ -1541,7 +1541,7 @@ void function() { try { } catch (ex) { /* do something maybe */ throw ex; } }(); /* - RECIPE: Pointer events and touch events + RECIPE: Pointer events and touch events listening counter ------------------------------------------------------------- Author: joevery Description: Find instances of listening for pointer and touch events. @@ -1551,234 +1551,246 @@ void function() { window.CSSUsage.StyleWalker.recipesToRun.push( function pointer_events_touch_events(/*HTML DOM Element*/ element, results) { var nodeName = element.nodeName; - // We want to catch all instances of listening for these events. + // 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 isExternalJSAndNotRecipeJS = (element.src !== undefined && element.src !== "" && !element.src.includes("Recipe.min.js")); - var xhr; - if (isExternalJSAndNotRecipeJS) - { - xhr = new XMLHttpRequest(); - xhr.open("GET", element.src, false); - xhr.send(); - } - - for (const event of eventsToCheckFor) { + var doneXhr = false; - // Attribute specified on element, although this does not work at present. - if (element.getAttribute(".on" + event)) { + for (const event of eventsToCheckFor) + { + // Attribute specified on element, although this does not seem to work at present. + if (element.getAttribute(".on" + event)) + { results[event] = results[event] || { count: 0, }; results[event].count++; } + // Is element a script tag? if (nodeName === "SCRIPT") { - // if inline script. ensure that it's not our recipe script and look for string of interest - if ((element.text !== undefined) && (element.text.indexOf(event) !== -1)) { - var regex = new RegExp(event, 'g'); - var instances = element.text.match(regex); - + // if in-line script. + if ((element.text !== undefined) && (element.text.indexOf(event) !== -1)) + { results[event] = results[event] || { count: 0, }; - results[event].count += instances.length; + results[event].count += findNumOfStringInstancesInText_CaseSensitive(event, element.text); } - // if external script, then we have to go and get it and ensure it is not our recipe script. - else if (isExternalJSAndNotRecipeJS) + // if external script, then we have to go and get it if it is not our recipe script. + else if ((element.src !== undefined && element.src !== "" && !element.src.includes("Recipe.min.js"))) { - if (xhr.status === 200 && xhr.responseText.indexOf(event) !== -1) { - var regex = new RegExp(event, 'g'); - var instances = xhr.responseText.match(regex); - + if (!doneXhr) + { + 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. + break; + } + + doneXhr = true; + } + if (xhr.responseText.indexOf(event) !== -1) { results[event] = results[event] || { count: 0, }; - results[event].count += instances.length; + results[event].count += findNumOfStringInstancesInText_CaseSensitive(event, xhr.responseText); } } } } return results; - }); -}(); -// -// 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; - } + 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... + } +}(); + +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 27a35f4..93c91e3 100644 --- a/cssUsage.src.js +++ b/cssUsage.src.js @@ -1541,7 +1541,7 @@ void function() { try { } catch (ex) { /* do something maybe */ throw ex; } }(); /* - RECIPE: Pointer events and touch events + RECIPE: Pointer events and touch events listening counter ------------------------------------------------------------- Author: joevery Description: Find instances of listening for pointer and touch events. @@ -1551,52 +1551,64 @@ void function() { window.CSSUsage.StyleWalker.recipesToRun.push( function pointer_events_touch_events(/*HTML DOM Element*/ element, results) { var nodeName = element.nodeName; - // We want to catch all instances of listening for these events. + // 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 isExternalJSAndNotRecipeJS = (element.src !== undefined && element.src !== "" && !element.src.includes("Recipe.min.js")); - var xhr; - if (isExternalJSAndNotRecipeJS) - { - xhr = new XMLHttpRequest(); - xhr.open("GET", element.src, false); - xhr.send(); - } + var doneXhr = false; - for (const event of eventsToCheckFor) { - - // Attribute specified on element, although this does not work at present. - if (element.getAttribute(".on" + event)) { + for (const event of eventsToCheckFor) + { + // Attribute specified on element, although this does not seem to work at present. + if (element.getAttribute(".on" + event)) + { results[event] = results[event] || { count: 0, }; results[event].count++; } + // Is element a script tag? if (nodeName === "SCRIPT") { - // if inline script. ensure that it's not our recipe script and look for string of interest - if ((element.text !== undefined) && (element.text.indexOf(event) !== -1)) { - var regex = new RegExp(event, 'g'); - var instances = element.text.match(regex); - + // if in-line script. + if ((element.text !== undefined) && (element.text.indexOf(event) !== -1)) + { results[event] = results[event] || { count: 0, }; - results[event].count += instances.length; + results[event].count += findNumOfStringInstancesInText_CaseSensitive(event, element.text); } - // if external script, then we have to go and get it and ensure it is not our recipe script. - else if (isExternalJSAndNotRecipeJS) + // if external script, then we have to go and get it if it is not our recipe script. + else if ((element.src !== undefined && element.src !== "" && !element.src.includes("Recipe.min.js"))) { - if (xhr.status === 200 && xhr.responseText.indexOf(event) !== -1) { - var regex = new RegExp(event, 'g'); - var instances = xhr.responseText.match(regex); - + if (!doneXhr) + { + 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. + break; + } + + doneXhr = true; + } + if (xhr.responseText.indexOf(event) !== -1) { results[event] = results[event] || { count: 0, }; - results[event].count += instances.length; + results[event].count += findNumOfStringInstancesInText_CaseSensitive(event, xhr.responseText); } } } } return results; - }); + }); + + 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 diff --git a/src/recipes/pointer-events_touch-events.js b/src/recipes/pointer-events_touch-events.js index 092a2b1..a7bb19b 100644 --- a/src/recipes/pointer-events_touch-events.js +++ b/src/recipes/pointer-events_touch-events.js @@ -1,5 +1,5 @@ /* - RECIPE: Pointer events and touch events + RECIPE: Pointer events and touch events listening counter ------------------------------------------------------------- Author: joevery Description: Find instances of listening for pointer and touch events. @@ -9,50 +9,62 @@ void function() { window.CSSUsage.StyleWalker.recipesToRun.push( function pointer_events_touch_events(/*HTML DOM Element*/ element, results) { var nodeName = element.nodeName; - // We want to catch all instances of listening for these events. + // 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 isExternalJSAndNotRecipeJS = (element.src !== undefined && element.src !== "" && !element.src.includes("Recipe.min.js")); - var xhr; - if (isExternalJSAndNotRecipeJS) - { - xhr = new XMLHttpRequest(); - xhr.open("GET", element.src, false); - xhr.send(); - } - - for (const event of eventsToCheckFor) { + var doneXhr = false; - // Attribute specified on element, although this does not work at present. - if (element.getAttribute(".on" + event)) { + for (const event of eventsToCheckFor) + { + // Attribute specified on element, although this does not seem to work at present. + if (element.getAttribute(".on" + event)) + { results[event] = results[event] || { count: 0, }; results[event].count++; } + // Is element a script tag? if (nodeName === "SCRIPT") { - // if inline script. ensure that it's not our recipe script and look for string of interest - if ((element.text !== undefined) && (element.text.indexOf(event) !== -1)) { - var regex = new RegExp(event, 'g'); - var instances = element.text.match(regex); - + // if in-line script. + if ((element.text !== undefined) && (element.text.indexOf(event) !== -1)) + { results[event] = results[event] || { count: 0, }; - results[event].count += instances.length; + results[event].count += findNumOfStringInstancesInText_CaseSensitive(event, element.text); } - // if external script, then we have to go and get it and ensure it is not our recipe script. - else if (isExternalJSAndNotRecipeJS) + // if external script, then we have to go and get it if it is not our recipe script. + else if ((element.src !== undefined && element.src !== "" && !element.src.includes("Recipe.min.js"))) { - if (xhr.status === 200 && xhr.responseText.indexOf(event) !== -1) { - var regex = new RegExp(event, 'g'); - var instances = xhr.responseText.match(regex); + if (!doneXhr) + { + 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. + break; + } + doneXhr = true; + } + if (xhr.responseText.indexOf(event) !== -1) { results[event] = results[event] || { count: 0, }; - results[event].count += instances.length; + results[event].count += findNumOfStringInstancesInText_CaseSensitive(event, xhr.responseText); } } } } 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 From 105bcc73d0a31d67cd01ab5a4c0ad5c4b6925772 Mon Sep 17 00:00:00 2001 From: John Every Date: Thu, 2 Nov 2017 15:05:07 -0700 Subject: [PATCH 12/22] Update to make recipe script more efficient. --- .vs/ProjectSettings.json | 3 + .vs/config/applicationhost.config | 1020 ++++++++++++++++++++ .vs/css-usage/v15/.suo | Bin 0 -> 44032 bytes .vs/slnx.sqlite | Bin 0 -> 159744 bytes src/recipes/pointer-events_touch-events.js | 91 +- 5 files changed, 1086 insertions(+), 28 deletions(-) create mode 100644 .vs/ProjectSettings.json create mode 100644 .vs/config/applicationhost.config create mode 100644 .vs/css-usage/v15/.suo create mode 100644 .vs/slnx.sqlite diff --git a/.vs/ProjectSettings.json b/.vs/ProjectSettings.json new file mode 100644 index 0000000..866f1e1 --- /dev/null +++ b/.vs/ProjectSettings.json @@ -0,0 +1,3 @@ +{ + "CurrentProjectSetting": null +} \ No newline at end of file diff --git a/.vs/config/applicationhost.config b/.vs/config/applicationhost.config new file mode 100644 index 0000000..15800df --- /dev/null +++ b/.vs/config/applicationhost.config @@ -0,0 +1,1020 @@ + + + + + + + +
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+ + +
+
+
+
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.vs/css-usage/v15/.suo b/.vs/css-usage/v15/.suo new file mode 100644 index 0000000000000000000000000000000000000000..a65ecce5d0385c2df47a302f65344ef4dac4d800 GIT binary patch literal 44032 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 literal 0 HcmV?d00001 diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..e7d5cfedad796c8bd5038c282edf336d9cccf388 GIT binary patch literal 159744 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 z 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±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 literal 0 HcmV?d00001 diff --git a/src/recipes/pointer-events_touch-events.js b/src/recipes/pointer-events_touch-events.js index a7bb19b..b0dbd41 100644 --- a/src/recipes/pointer-events_touch-events.js +++ b/src/recipes/pointer-events_touch-events.js @@ -5,58 +5,93 @@ 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) { + function pointer_events_touch_events(/*HTML DOM Element*/ element, results) + { var nodeName = element.nodeName; // 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 doneXhr = false; + var JsTypes = + { + ATTRIBUTE: 1, + INTERNAL: 2, + EXTERNAL: 3, + } - for (const event of eventsToCheckFor) + var jsType; + + // Is element a script tag? + if (nodeName === "SCRIPT") { - // Attribute specified on element, although this does not seem to work at present. - if (element.getAttribute(".on" + event)) + // If no text, then it cannot be an internal script. + if (element.text !== undefined) { - results[event] = results[event] || { count: 0, }; - results[event].count++; + jsType = JsTypes.INTERNAL; } - - // Is element a script tag? - if (nodeName === "SCRIPT") { - // if in-line script. - if ((element.text !== undefined) && (element.text.indexOf(event) !== -1)) + // 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 !== "") { - results[event] = results[event] || { count: 0, }; - results[event].count += findNumOfStringInstancesInText_CaseSensitive(event, element.text); + return results; } - // if external script, then we have to go and get it if it is not our recipe script. - else if ((element.src !== undefined && element.src !== "" && !element.src.includes("Recipe.min.js"))) + else { - if (!doneXhr) + jsType = JsTypes.EXTERNAL; + + var xhr = new XMLHttpRequest(); + xhr.open("GET", element.src, false); + xhr.send(); + if (xhr.status !== 200) { - var xhr = new XMLHttpRequest(); - xhr.open("GET", element.src, false); - xhr.send(); + // 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; + } - if (xhr.status !== 200) - { - // We no longer want to check this element if there was a problem in making request. - break; - } + 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.getAttribute(".on" + event)) { + results[event] = results[event] || { count: 0, }; + results[event].count++; + } + break; - doneXhr = true; + 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; }); From 86923522f285e5909e4f64af69097ff041f1e3c6 Mon Sep 17 00:00:00 2001 From: John Every Date: Thu, 2 Nov 2017 15:16:33 -0700 Subject: [PATCH 13/22] Forgot to test, so here is updated version of recipe after fixes. --- Recipe.min.js | 91 +++++++++++++++------- cssUsage.src.js | 91 +++++++++++++++------- src/recipes/pointer-events_touch-events.js | 4 +- 3 files changed, 128 insertions(+), 58 deletions(-) diff --git a/Recipe.min.js b/Recipe.min.js index ccb3777..4418d8c 100644 --- a/Recipe.min.js +++ b/Recipe.min.js @@ -1547,58 +1547,93 @@ void function() { try { 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) { + function pointer_events_touch_events(/*HTML DOM Element*/ element, results) + { var nodeName = element.nodeName; // 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 doneXhr = false; + var JsTypes = + { + ATTRIBUTE: 1, + INTERNAL: 2, + EXTERNAL: 3, + } + + var jsType; - for (const event of eventsToCheckFor) + // Is element a script tag? + if (nodeName === "SCRIPT") { - // Attribute specified on element, although this does not seem to work at present. - if (element.getAttribute(".on" + event)) + // If no text, then it cannot be an internal script. + if (element.text !== undefined && element.text !== "") { - results[event] = results[event] || { count: 0, }; - results[event].count++; + jsType = JsTypes.INTERNAL; } - - // Is element a script tag? - if (nodeName === "SCRIPT") { - // if in-line script. - if ((element.text !== undefined) && (element.text.indexOf(event) !== -1)) + // 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 === "") { - results[event] = results[event] || { count: 0, }; - results[event].count += findNumOfStringInstancesInText_CaseSensitive(event, element.text); + return results; } - // if external script, then we have to go and get it if it is not our recipe script. - else if ((element.src !== undefined && element.src !== "" && !element.src.includes("Recipe.min.js"))) + else { - if (!doneXhr) + jsType = JsTypes.EXTERNAL; + + var xhr = new XMLHttpRequest(); + xhr.open("GET", element.src, false); + xhr.send(); + if (xhr.status !== 200) { - var xhr = new XMLHttpRequest(); - xhr.open("GET", element.src, false); - xhr.send(); + // 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; + } - if (xhr.status !== 200) - { - // We no longer want to check this element if there was a problem in making request. - break; - } + 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.getAttribute(".on" + event)) { + results[event] = results[event] || { count: 0, }; + results[event].count++; + } + break; - doneXhr = true; + 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; }); diff --git a/cssUsage.src.js b/cssUsage.src.js index 93c91e3..8a22a02 100644 --- a/cssUsage.src.js +++ b/cssUsage.src.js @@ -1547,58 +1547,93 @@ void function() { try { 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) { + function pointer_events_touch_events(/*HTML DOM Element*/ element, results) + { var nodeName = element.nodeName; // 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 doneXhr = false; + var JsTypes = + { + ATTRIBUTE: 1, + INTERNAL: 2, + EXTERNAL: 3, + } + + var jsType; - for (const event of eventsToCheckFor) + // Is element a script tag? + if (nodeName === "SCRIPT") { - // Attribute specified on element, although this does not seem to work at present. - if (element.getAttribute(".on" + event)) + // If no text, then it cannot be an internal script. + if (element.text !== undefined && element.text !== "") { - results[event] = results[event] || { count: 0, }; - results[event].count++; + jsType = JsTypes.INTERNAL; } - - // Is element a script tag? - if (nodeName === "SCRIPT") { - // if in-line script. - if ((element.text !== undefined) && (element.text.indexOf(event) !== -1)) + // 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 === "") { - results[event] = results[event] || { count: 0, }; - results[event].count += findNumOfStringInstancesInText_CaseSensitive(event, element.text); + return results; } - // if external script, then we have to go and get it if it is not our recipe script. - else if ((element.src !== undefined && element.src !== "" && !element.src.includes("Recipe.min.js"))) + else { - if (!doneXhr) + jsType = JsTypes.EXTERNAL; + + var xhr = new XMLHttpRequest(); + xhr.open("GET", element.src, false); + xhr.send(); + if (xhr.status !== 200) { - var xhr = new XMLHttpRequest(); - xhr.open("GET", element.src, false); - xhr.send(); + // 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; + } - if (xhr.status !== 200) - { - // We no longer want to check this element if there was a problem in making request. - break; - } + 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.getAttribute(".on" + event)) { + results[event] = results[event] || { count: 0, }; + results[event].count++; + } + break; - doneXhr = true; + 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; }); diff --git a/src/recipes/pointer-events_touch-events.js b/src/recipes/pointer-events_touch-events.js index b0dbd41..df0323d 100644 --- a/src/recipes/pointer-events_touch-events.js +++ b/src/recipes/pointer-events_touch-events.js @@ -29,7 +29,7 @@ void function () if (nodeName === "SCRIPT") { // If no text, then it cannot be an internal script. - if (element.text !== undefined) + if (element.text !== undefined && element.text !== "") { jsType = JsTypes.INTERNAL; } @@ -37,7 +37,7 @@ void function () 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 !== "") + if (element.src.includes("Recipe.min.js") || element.src === "") { return results; } From 2160e801b077d86fdef52713fe65a5cf21d034eb Mon Sep 17 00:00:00 2001 From: John Every Date: Thu, 2 Nov 2017 15:17:59 -0700 Subject: [PATCH 14/22] Remove .vs folder again. --- .vs/ProjectSettings.json | 3 - .vs/config/applicationhost.config | 1020 ----------------------------- .vs/css-usage/v15/.suo | Bin 44032 -> 0 bytes .vs/slnx.sqlite | Bin 159744 -> 0 bytes 4 files changed, 1023 deletions(-) delete mode 100644 .vs/ProjectSettings.json delete mode 100644 .vs/config/applicationhost.config delete mode 100644 .vs/css-usage/v15/.suo delete mode 100644 .vs/slnx.sqlite diff --git a/.vs/ProjectSettings.json b/.vs/ProjectSettings.json deleted file mode 100644 index 866f1e1..0000000 --- a/.vs/ProjectSettings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "CurrentProjectSetting": null -} \ No newline at end of file diff --git a/.vs/config/applicationhost.config b/.vs/config/applicationhost.config deleted file mode 100644 index 15800df..0000000 --- a/.vs/config/applicationhost.config +++ /dev/null @@ -1,1020 +0,0 @@ - - - - - - - -
-
-
-
-
-
-
-
- - - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- -
-
-
-
-
-
- -
-
-
-
-
- -
-
-
- -
-
- -
-
- -
-
-
- - -
-
-
-
-
-
- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.vs/css-usage/v15/.suo b/.vs/css-usage/v15/.suo deleted file mode 100644 index a65ecce5d0385c2df47a302f65344ef4dac4d800..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44032 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 diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite deleted file mode 100644 index e7d5cfedad796c8bd5038c282edf336d9cccf388..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 159744 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 z za9%77;baSal0c^(aImTuH|*1XNJ!s~fBfs)Esn2Q3;eQn^Q}8P*XrCltn{E^rTdDR zY<0Aj*1|@#P+R+E^jLVt>NK$i3wPN@+tP*zm)qK~)HC4{i~7cI&o=*-tqn_v{F}DA zj%6YHq#v#stD z%M1VE&{@vEH?+)o@G!Cihmmc9+PR&45gxL&V5xKuT9Y)WEIvTzCJ~($4ILN$bE`Ll zH1pXoE*gTusHDm-tST1%!oFWjCD#~0hR@k!h-Sy#niq8!H#0<;N9~t`BX$zrZf6m`eXVvu*2Wb>-99<0l)U&q@AN})f!=E{y6nh_3!E(>Na&BILDu> zI+f3qXOy$ySNiozh2l{-_cQQ&{JHM+?iP2In}gq?pL3lHzkheQs$6OC%lrHCW%3Df zuUsyVmmSjmu?eOb)x$uMV7VJLnhxz|P!7Fg^8~HQ*W&H7c zm|w{I_%Yn)+>6{D-0@s5*ThX{FO2-##C*Gh{~)FGFKgQ}di(-UZN%Lq!7_)#E;n{^ zbpAHyNoCyv*U=`-u z1JQ^a{9#8EZ|BA$AFr`g*y`z+G^7;vJvt!&?Gv5?8$+2-qS6INkSn0fb2{cUB6?ea zWxO(sY}}Et!;J}Ny(I1`<`@WZGqb(3UJ`e49$0L&JL@HJ7s7T?#3b8-eR!SmDk>}}-i z9Ufnito%@@^ z99KJb8##Yx8{}~iWZvK)kA)z~-64N4$fKdSlUqY;dAP<8e$-N*6K^_W0lDj|tV37TF zW4Dp}L2QHUhak!AAPGSp0zv+t_TB?Ljw0zF-ksS=8x<^BVU=u4j@Ytn%SpE7Y#HZ( z6ArQ^+rqYFBuB8pz^*Kq-6b74961LQ4>&SMIF5LL;SL|sWH{0RCLDZKQ`@^UyEChm z{r=zQ{hkMb_t(?aUER|&)6-p5Q-xkZ4ztNU(+l>Zmk`q?_vB>nML!{0lkm!-sq186 z^j>rdqO}OQ=AWdR=PDC+ADzD!J%wluLTzoDjLwz2p*?gEVr*}hP`nqtfzTvw_SBv@ zcrSYWVBQUr?C+8?lnL8w^Y@|y5VLMY)MHbE=! zoko|=Wdn?u!=jZ9&cK&>xxEh^>cK(2=Tf#MTLe+tGn)S6XST z&2J~ysH3Jo*vqa#SLZ?kCh24hv&DS z05!}`btQCSXHq9t2!q;pbg7MFN9DI~w`k*7Xk%Ma8_S@LtzBwkNlkwH7K=8PKpSmI zZ7ha1TD#Q7F(dNZagA&?R>wdao0HmD2yL`EZOno5 z*kIAd9BAXXq&8+l8%eRCh}@eHsHlw}U~^lyNF z|FhgX+^gIZ+`Zf)`1yayb&u<8*Ctn;tG_G5`Kt3?XTaI$oZ{@|bikheUgZz4r=P0~ zR9y0YIV`uz^JS;+1K(31MOft<4}JjN^gig_>YZ}r|ACf0`X6jmEdDAxa2d(mRdnk0 zi096O?X6rEonT_Ct9V}0av<<-o<38wiqhHnbJlJ1u|el0$$@7qKny{P^2(r zkxGd2N?I&lgVs=*r}=YD-P%(;6y2Rx z(fhF|_zF&g>&-4G+F96vs~u1X!VWMAfX;YNmjr+PmjO5XKhh|8aRC(E7kAMXGoXtl zfZMsqP3@%6SsWBv8^4fI86JGwKM)8r(;9?8DHMg7PFqRBRY>x`oRk5*+YR?11eob! zB^fF~OcHH511*8vD<18jWf2fTn#rb4jCO$kps|+8K-v>O%Z)AT;D00x1;V;GflU-* zV>S?G+3cEsUuX&ZC9j{@($F}2Z9E0_*JMF4xc;3KTPS-@o}!}6%hI)KT-D2_wYVHJ zW6~%1U8ip$}2B;`&W3Kz)bacM5jYrV!GxPl_%HY@)Nz!-}skm~o>Q6iL2PJIQ z{Rw9HRTQ(a28?SW(0KS&6t`KB_Y@9NQJfarV5On)C{$6#rZI#qnei5@bd6L|)OL6p zk35y+Y%t{mXM@J0P>p%!1yZ8MqfA9%o92xbs4)daEP)!2G8H9itV~SN5lf)PqfAA? z8Y>g49!sFcBTq#s8w(Pm>tt?rj*1ers8n93@hDVLcxFCgnS%;dl%ZKrr(A=zS+}7BdVm$)F2Re2JYdqu7-s=H4=);2P?@}Fe$AVvqK@ha z6?1JcxBdd1KxLM8`@^{yJ7GHnoj`q-NhiLAPM`+MvJ*Pfm!#L7js~6h8ajdcE{jfl z0i8g-mq{l+%yYtW{|5X13}Lu10KC-Y3NG;Q|DEGg$J^l3?yruA9d|iya9rj%2mI#k zavbki>sZDWav7Xp|4II%I8VAyK3RU({T6u2o6o<-Kg{3c``q^idXiVco%zTcx(CX{04p{_XGDC_cr$m|E}21 zU(TPyw>vQ3l+jMX^MdD5t@%Pt~S>q*Ju}fwdMRs_#Qq6&UT&#U*pf^MsgMM8S>}g3E(luNMV{% z=NzQ;1Rv4|l@0Ps@*~{i(i7rxcMilctmAp^M&Tl9vh*r9LmDbwCUtPj<(tHRc?zXs zNfzF5{!x5Vyp6Bn&T)8Tr&uLs@qVsVxKsL6I7v88m>^8xPU4Q^7I0&^K3tal7yF0y zx9s}|^NqMWOZ&+bj^|&qRduPxX0%9x8rlv}9E?^Y;5Kt2y;vJG;fCl(Lu*ZtAp8_h z{uoaliYI@_;(K$3jkfT0a@JS)8IEq#EPUCG?~e`7sow(gNV8^tEPPtRkH^)j#Un4g zk6&aaEzpGb^7tXhYeq|Re5f^(toky z{CFoleZ;({cOq|EV+l9w+PO-XzF3#O2rh5@P>kr(?YeX@o9_#3VI2^2(##!=J%Fab zH~c1;qw6;=YFxc;BlkE~qaKJC=vd6fPr)hQDNL{_+SS+5_nDrMc)5_=v^cd4zBPei zw+ufZ7UBJ)ZGgCr*sWy$HJJb7?GThgi%LckV#k+7| zF|T#&rqw&O``P}bbf-;%Jd3+hye;)zDQY&2v1fly^w~GK&=vclm8QA6P^?JJpSvM7 z-Tn;@jdo{=?47?S5VQUR`=#ioOjqy+Jw6@kY{=VgFh(nSaFn(h8H?dAiv+ zaW9zW9+fc9iEmn6t4F0LOxl`Zl6SO8SGnD0nTw?lz~e?^e3TC+j`IA3{@K5kPqYc^ zQeQXmMbohrUoh$HER(#M7GuEOY(cQU?^$ni#82acq!)^KK>k5{&QORV)|(85xFF%& zxkU*X_V?l~e3;zAzYH(R6U<(e-1DZnrLg?ay=0}L&L#j!G2% zSJ7#SGJPMU8tVs8%Ib`lck@;%%M50Nx3K0ixi zlOKL*lu6!5le`fodBaWes!j5Sbort$_0WfsIDJSnyINhEU^3xIwfaYeTWL62VK`b~ zI2z`?#kM%QC~boDbJ6sANIyzx$3b5&+uhOhyO6#intlt?mqycXLi(&|`hG|U^sPt@ zhNCrIKIJK0uAzMwL;c}&Z*flhP&S&sFMjuMx9ZZ`Hxkh@$LaE$bm?Ps>4hLH`O3Hg z78)V%Ktf&vOl)k9t=A#Vlb?J(v!c0yi5Lf#I@o0E{Y9r9`t z^0q-6CuQmB8;O3d+%{9rJW0E)9BySel|If4SxB1@noez5ZV|_W^554z$ z_jotJ&U}!!!1J@`U!G??H+n*z?ViP+(e6X;d)%$=L0}*7Jow(53fBIAcD6cuD+iQw zmFe8WV^}Qf|2#&qBi9>|{2zLk_;Q4N#<7dY+j$1*hupPd! zAM5A=9@0MJpW|=kckq+>Qji4vg}a1nh41H`_Sfx~+Z*jwb_F___FrWJlOa;~&k1wT5Dl+evD#K`8!!*`u@XnR0!)STKbh=O_o!DA3?-i@VXn?~i zG6RK9LQToM-CY?*>zwpE$^9Tyh0%0}=}eLg_>wXdnR|CwWf-k@SUaqPmZ7F(Zog0w zMx!1!(7O0ix+L&^0j>zz`E*+F386BK<~?2L#O=Tvt$sS?eVx!FjD|p(tQqbk48f+! zk7Ehw>9u*4VYL0}LI=*1s={az#70{;a8{-#GMJwD!)P(Y#C!%diwveG{xF&cS&-|p z^1^5##H4_+XnZo5p5%woOeop(L}Tk@Y2}1eC$_BrjZVPWk~ztISE?WmSe(0*PIMM7xp1V?_qjlgjXlu2p#@qhEMA zRAC{xrdpex$Y6TXFMMpeg`ZO@!popAyCH$IndylPrYE)GrBF}{zzjDelMJVAdLrBJ z?p+gJ0xX#MGGj5(;3mL47slpQ0@B$#4+>O`g)rRK-=?I4VTEQQlXWjS}(D+>Q#xKsR z3C{u2VP2>Ugh}DqQ2mJ1EV4aZUKO4NY}izlBxgy=fDu2#-!D89DzPU8%2-9Rm+gD= z283ro;k498vOSPLEqpW-KVrp+Y(J&{wD9y)tkU~Js}0V0PyZ?5I$&jKypW##gls># z-<0sQRE*-L4(u6?8Dhjq(4&+3O$$#=b#gi-QW7T}4Os22m=>OridFjKw5wuDcyg-K zOLv@3ET0mdl!{S&Fp|zLPJ)v=0VcGGz$ozdT z7}c^zz@kB^4Ws(@2w1EE7O1vmEUd1rRX&`^GNB-hs$FL4wXJ^R)<*3Ih;w)N!>HV4 zZyeg?(l3(dXN6H=>+{hYY~@PKtSktln%0!zoHhP1nrB%soHIQuj7C=mhI9HChSAiD z7;>DUhtqAR_`_&zWx;X!lB_V=P#HK*A5suT8!6K+)@|~K(K^b4VcndpFj__#7}gD} z4x=qpmv7neLccH?Nu~9I=`RcjC)i8rG%oON(jCEt0Q-b6+Djebixcb8m(s`5yV5_USET3QtNn-nFGL9x-xOaJpBJAJ9{~^I zcZfHMS3@|d^Tdd_7eX@a1YiCe#0GJ>cnm~gnmBtV`M_-VG6gs*ITKol}RoEaj2+M_IgxSJ0VZ1O3 zd>{-E%7tQ~yO1F$0tYgb|A0?~55ZF74adulXB|&C9(3H}xD6s1T;aF~oZ=E&#wQtzHoi$dfWAe>t*mG`Go61_{QWm*Y&O|To<{{ajC9=Yqx8g ztHrh6RSz*8=7R^y$*!@k;jT(oUsn%Tp{twA1JNCRa~^Vj?flgFzVm?dHRlV?r=5>F z?|0tmyxDn;^D^i8&NIRL#3{}boNeHPqS3j+xyU)kS?8SK9PJ$HJjz)CF(LAtnNFvZ zSANODNEjZjgrJz9h`>)!NKinKPmo8@onSCQCBYzqfdoep3?S%F(2t-mK_7zN1Qi5c z0v|ypK?XrCK@LGTf^35SyJu8R9Rx3?IOIJEdJvQmloBi?SU@nJU>?C-f;j}U31$(@ zB$z>PG{JO&I)Z5gQwgRJOeUB_Fp*#a!FYmNf^h_63C0lA5R4`mMKF?J1i^5EYJy<| zLkWfuRLPrc+R4eH7FFhdC?=n@4%y4nT7pJ`27)yNs|o4}RuQZuSV6Fy;8=oX1WO5) z5G*EGL~sm&b}3~^yOgq|T}oNfE~PBd*-F}_lqKy_%93^|Wl6h~vZP%~S<)`0ENPcg zmb6O==9Scfb}3~^yOgq|T}oNfE~P9*FQo@d!bffdE&?ZkLLd`J1R{Yz;2_`$I08F? zO#}OH1iupem*5wIp9y{<_>tfcMyGOTXum$~*Q@<{v|qRO>(YLm+OMMhLRdAZC27A} zA7uzjikDZ^Iv~RfNjuUyAd6ZDWKrvY48dMCn$`hXJVZx75PVPY9l?JHz9smE;A?`f z2)-ovH^CPKpA&pW@F~G31RwkML%cNcBRcw!-~)n#1n(2PNANDeI|OeNyhZRYf&&C^ z68w|k9|Ug@yiTy6;5C9*30@)iJHg8YFA=;*@B+c}1kVxtjo?{=zY;t{@HD|w1WywD zh2RN-#|a)Ic$DA~f`H!Ji23CAf#+Zi2fA?j*Q_U?0Kl2-V!j z2tzq-2nKT65cKA>A*kTAAt>jxAt>jxAt>jxA?U?vL(r4chM*^>4M9&%8-gC3HUvF5 zZ3udB+7R^Mv>__;iC`!<5DW?rV38xJ~38xJ~38xJ~38xJ~38xJ~5vL775vL77 z5vL775vL7-pVNlG&uK&8NAK8n&d+H>;ODd<@T0G6%+!X!&uK&8N6*=qc^kp41h){} zj8MqkgiyfUh>*|SfRM*sPjDT>NDv@64WS!% z3c((NlL<~D*o~0Q?IJjl-~@u51Um?}BV=*g2(}VzA!s9LB{-g7GeHYMGr=Z;jRYGA zjw5IySWmEyU@bu-K?A`WgiLNVK|R4Lf|Ud-2$mBZOR$V!DZvti4EqlR-xGXC@E?M2 z5q$P<2)-uxir`Cve-nH`@HxR}1fLRoLhv!cM+6@dd_ZuJ;C+Jk5Io#sf<*+!Ajr}L zg7E~k1mg(C5{x0JAs9_CieMzc2!i1R)da%`h7t@Rs3I6lP)RU|U?9O!1Oo{A6Z9kK zOVEd)H$eqKIYBRio&-Gz$_PpcN(hPxiU|A!g#-lz`2=|c-3f9DatOK+WD{f&WD;Z$ z_z1iN9s)Oki@-^s5Xb})fk+?_I0$$GjsQZf+WJ*<+TBylX?IUGr`|R}eC2p$no^=T(_SOnxc-gi9hxZH7)W2K`OoRhu>aqn9BA$%T0 ztb3Tdf;*LK;HH2Tz0>|T`z`jcz1cq7UTM#?eFI$|5JE9G(;gU&D2zu6FI?{ShftKw zq%sC2Dle$L{2>%iTaeEv^M_FAY(Wlm_lHoFY(YK^$S5YZAfH<351|Oyf_w^)QM_wG z-cwi;LLn~G7PW!fwfT)38`o@UX`Kj`|6n%{U-6jd!nmw;7l!C-n&c0ac%fb{458H4 z>@y}_SOHWN$eL4^1C>6JG2^@xs3=A?ue$`OBu6!?yI8IXp&0crPkEXF{h7sffyT@^y1EK{c?nN=iZKqa?(L zoi?>>YaHL)w5c9L-OZ}s)Y#VAn4AYy?T!&4l;d{2s^cq9X-NM?EHf%+sPK+2;Azs! ze2q_&l--bN;HmnGtkeBHtHrKbdkq`bhT@t!+O?iF@ z1-#6FK{L_Net7V0SQ$cTE;9|FE~fC`!-H?b2_cl+jvGhAtqIO2?`Ue<0$R*@EiGG^ zHN7PP`gFsZ>eG=yK$T7L-bm^W@(2*0Rh zVs01_%dbgN(V#CgbMr#@`EBy^>X@E2Ayf*aBf6M%l0Q@eOe}8XNiKh=*qZDqofawr z^8dhzI;F~xA6V(`DxBOQ@FjiL1>Km{Nx9>F@?q%8(5Trn>Xdlu|vpYaIYnO7$eD!bV#+a8{<2=*7#g z?uJTif-$aJ(3Dc$WlaWCO7%n_Cr^=RN~xXzB(@TXn?0fzuY7f<1r1Co)g2ZzFr`$t zYczU4$9ZaAyzf9uHNR{(h>pDJ6RG>Zfi_w=kGesx45MJr%&&%#;$nc-5-SP%zO3ByCel@Z#0G zM%@G~*usmMi=<^r$)hQyy79<*0H&1chQsf{9i<-S659yd>o#R0#HaVlZ_E4S-SSd- zwCtC@l^&O_0xkG)QWYo_4u}tkr;5jk)uJ2X)jtfe^G=0t+-C>_g$&0xj(<8Haa;n{ z{PP{d90mMap#P5WbNFi5zwhTB?RVH)?4#{DwlASU<6lk?CqL#6B^Xs%#MIIc)xl)^VsBfJ)8S2gnflX}R0YSr~{-!*H^OWngxZyL%uu3j;Ma^JRC>8yCPy_^?kiJ4YxC;8d->ACg+m zfR=Hx*5+J8%g(&f0i3E2Yk0CHVNd|)a&77bzH2)3gp@TTqcDI|ylFqPz!OpcXZgeM zgp}1s$_wCYAlb=KR(W<|0H=G_sKK}`tCy=RfHOOD&l7W86iX;s;0Y;!6FMss^X?N% zC|TeMDS-1hD-)|8ODI|32`PY6xJf6$6H)-DW;UfnH_KT$uEGG$xvcYsdnyY&Aq8+c zwV=v{f#Y;k0EF8cMRMFUj#yQihT_rCUy*9_q03k52KK;|C85tb%-P zMGyBpTfTL&FwY!YToPCb^)&ec)MG*^hDzqBo+W`5DNBs(J2mh1O(yUTI(fkeU34 zT^ELmhyS#wGB6*Cu$5+FV57uisPd|NKwutJVOx8nc;9#g#s4M_2+U2t_|s5)PWr_k z7X}1or(gUrC_XFw;*Sd8jkYnqLe?anOrUQ#fD^sHPi0^R@Mc5F%CUWV;xSf%p*%YM z?%eA32c|=Db~hMyOdkH?Uj9IxHTj}4e_)z5`P}aQ0P1ion4be=OP!5}KfBN$m~379 zEFe#^CZAbY6F{8}Tf>`cNX&C#7`M6$0~0jv$r=g|zZu>b<1MM1fLd!w-3ZiimeeMo zjN>e5Py^J%Jmp31yufIv&bFK}<%ox$o0S(BWlrjsRTCJgYt&prVx9}* z+}m9h7y;GUHa|&Cl9U0J3f&_D!=X}_l^-5H!!aUIeOOhw-#n#(VNlh2>f?Tar)ek@ zF~8xt15^EhA=czqx)%kiV&nw9iHCat^d^D9Kxb3DPFbQ$Djx2x^87$06lBthxZcFW z-H}@v7z8vnrF1IM^>F)!Ob85wDn}q{_i#6@t_+~khqa4)gw_^takXWqx!%OXT?wzE z0Z_$aG(+2S1O0)ly?WtA7F`N@IJIX@0QEfVIWWKZ%yVJ%+KYw;P{Y$Dy@`k01~Ud} zc}!*uuBm5f0F^xK=3(w<8A7}D z(ydZ|Dbu~y{SWt1?tAzN;z99E=M3Lt-Wz>K^CQJ}ajSB+bCa^$=@t%wmHvs&ljQfE zMZ#oZkdQAvso0bm@+4)bv)cEn_bJae{2X6^`)&$nde*Y>F1T@W_hXOOUIku zbG-pR3w-)5dgx8DXaLHs=MxGxndvt9*sZ z$I6S!z0x1JO#8cXe_xgF26?~lS$9wQeEC%00`Hwlv$C3djJuBC?05nE73Oj;aCh;$ zdGukC`VZYNv4yQ}Q7!I5e@~1MTk(oJt;+A<3;5yY;RD69ynIRA<*gVt9;)cWgZ5cs zhc3Mry_K;0TDZe-w9jyKyPGd$ev*aTki^0*3b*P>HyKDb#@iCEwd}lbjiI-~mBvFY zEIf5yxKzjR5}D6N#}fQQ*d~i^$3KYfrnF#o{GT0rZPL6(uf9*>MY`^a=Nm}p#kq@T z3s5(ck>()YyE-1WNjHM;bby7c9`^e$a` zr!Ku!m)@vLZ_uR+bm@F)s!j03GelkE;*~f|>^>1MGaOxNIJ!hXx)-BEBXASBh4*MF z@R7*7SswQr%j14!DdFdM2ZT@1^HidC3&i_oxn9#;mzW*9DB=V0C)GuFQ(SG3QcrjW4h-S)je&(Z>CSP@GCyea2ARFMz}01f=9eSywH}|=nrg+#E*1+ zdrg=AyIHqH!8DiC*vFNcTy|ncrWk9`{LI8prFfBSv+)-td_p1a1E)Qzn7BxirkZx> zGdb2=_k4aBjCZmv4L979QZ4sWDs5t`7VJ})ulB*i_v^7DtRMZy#`!yCx=nM+m-v{APB&jFNtSkCgxk*A>1k}!m(BaxARGx`zo7^W03F^ zia%S8+)t)wdMNt9iJD`t=nFp=Z=q;9C$RyaRRdnr>5N-$p3Sj=k>Z0Dn;j(WA)y{f zT)a1u-F1`A@b=I=;NfOgEMp8sEfzbw>6tE`)9#w2ToFelDL4Ag0k%@C0jWp)!ICoc zosfLR{5X2kn;D!*^a8^^R>xV0y;4>ZOH-1$fvL&8*ux~vu$lp-;*>0;T&s#=RmzHD zKabt!y~-xRRf(UjRBpviDv4b<%h_5Sq91Rrx90=T2ij7L=0B;AuA$z#bOpLmG#3L& zz2fCW&E2ATIdKF|C2S>=p+E5H54`LwxY4oHs935zmMV^=iejn4SgJcq*?;rK`$uoU zM0ubzG{xEV*VXQ)OZSbZ#bvs*?hPX9-XLO)u8bD5o<>|8Wms`@#~Tk@eBJQ+yP}he zv@H33Ax*+3FY%njY1Ze&O-wY|`C=mmA-C;wrPz;AY@9F$b1GLOUefH1s+uZfQM54Qhpis$8GXp@FH-l)F@4q za>XCPrr>V)PJg~A2_Fbg2v-VQg<%kT;BSsI9m^g4LGth#uktJSNqhzG=Dy+nsaX>2 z;Z}2#xC&0Te`92bbwQf;Otq{`@R}aiD45HpmlNs6V z!ua{~C(m0zzsm5Xj=~^n-^{5?fQouIbLwKCqQ=dfx(KMKWizMF1uAOR%&BvLiaIoN z>U5x@Ce55$2UOITnNueL6?J9i)QLbvJ()Rm0#H#SW=w1X0Zu)wc=Jh+4ysa&!-(R*T&cxIx5!%JBXr-Givmic#P#i>m=; zc)w!{gQ&|gZ>f*eJ&0;7Rxfss4ey=bFG$)dko3VlHm80ky6KOua*anr`{YgyqOMAl z>OtYUw#i1v{!=&3-@17-eAUs|kcy@Z?3UF(7*k_`N^vta+e(XTqd)grDvgcOb~Tlhg&~8t8WmMVVdn5w7;pb zp{X9e)WLhB&2RvMzA+_1)QmATcT-}7pdLj9B|%h7IkXuhJ}!VGwYk1*1|)qODd(#nu1_Vy9UEdZ#?=f8FW-Z zFs58fB$oH>7ew7!I;T*QQ&xJ0*VR{2ViB;*HCtEYLP zg7MVkqS}w*&)iWMM73RXsx}%e_%r8c22nGo`JjTD;CIWPS(zV1Rh${Oj5US9m>v$w z8%!Yl8PhX^sD{&X%jjPaMAaK{Gx*%{J8h>F22tH+-Z|%z%pfY<^z58N@`I>RGwYnP zsW6C&GIMUqoXjBV#Pr;hfkT6+2-9qB$owuH2XTI(ZxGdFY`#k2%(0LY`Ug=brWt6! zh#8%08IP0#PM8=(RhZ^!i%MIB1%jFu6eXV%3WKOJV*^2Zefm}MM!CBOQCk*$J;%)i z^M>V34VD8X`o@h}W5(Pmrt4-aZwU0Dmt_wIK@WPG_Mjj1phv16=-e=}=ONZR9ZkvW z13f6S>_IQ+L8)mEit;80OMt@WrleNVbH|uh2<;bJw%;AvFEVXkhW6pXux>x@sxhf+ zagvfJ3c0~T;KOvhu+M@2m0a5;?moc+AZW`1Q2tuR`^~kTE9C_9H6BsLti~hP7Afo# z%uA0&$lW{GofU-}+PbyfeD7dxS|xWv$(*!GZiSNF(kgj8l*~@6uCibz zlw=l*u~5IUgp#|+Q69{IGVI;03ooooD!FrA1-+fLITiZl>7>jgE+^=Q zGR!(JY9f$3abWMD3-Z~b()xOiC>3dBAh2=Ty&nkOfqEy1Bo1zu|rWJiwjgUIjh@ z`at}>uUz|G4}&kaQ(XSw-kTy-O5McI#r^R0!8zhCag{hi>>-N6 zTf#HKEkZzO7Ul{4gl>*Qj+Y#_gV(#=V7t)I(G7g=J;mPy(F9iWQ}}M&uiSgwKJH?O z#y0~T-W1qBw*TFJuRQ=>1m?l1r~Dh#jyrv}>FLx6X2O)J{C3>^$F9%uTkAJ!5y+GY zQ+(O&xcAS@q-8dIBe+GGFqs?Fj(h&}+n6{uza8ZOW^GJ_Hc$#+Y-0knfxG|o+o&Cr z-;NRivo>m>4U`8M+ZYFJpd29mHpWcIZ%5gHSsP<=vfEKIU~HpC7}Smuf%MxLH6p(q zr37YejDj{$Mqq4XB(#ANg7n)Meo}rrN)F807!GZq?7-MYHMD^;gY?@NIy%1{WeH|& z49ywTj-rF~xeb|^-;N>zGj2nm$0!gm_P9zYZpZCEZF3Y@EVZp^ZCc&PP?<1zP`iG& z-}$35U`~EJ?)J?(F#tM&yLn?L`a>sh=WgAJe&y}BHD~V^ow=JXsZ1!Z$#2IkxLMQX z&@^tjjZOCw%G+_9-5KW|aKGVZ+l+IMZrSa)%{Fo_1J1Z%?u>H@aK^o|8RrtoCBP3+t?ZBZ0Iv?A;p?vNO(}VfpR2voz!E0nWIuG;($WXWUtK#@RVCza4j(W}KbC8TXh* z&I)kGU1n#TrQ!MQxUDqfECFZSMjAPbFl*v=G5vYY0na&Z4b9qcKpVI*G`7LR6oZ>W z>nVoIZ^ykJV+n8fR*1X`KT3^L$ZyA;9;0cI)wCa_Mt0`5<6ci&oWbTBv+*C)Ds0zn z-He)uk@b$kcHFu}H@6AYV}Xj>H*@MTpz1bd33Wlo(TpG z>w&t%k_xj;aJwZHHLBYzsi;xiYDqK02XYE;`Ssk4FFYDtATA$Ytc6`uOwW=kq+ zMO!SXs1NOFpeEc!)kEPf+5q`UcTsf}+(pOfDeNw)9t?L;lVwYT zB&Xvp+tZH5LDE^u+2Be3KG(aRN;mKM$a$&j8}Aj~uyU<(lH)mHxA$kM)bXw7aqmXo z4%nBk0)O}urJ>@p&c(tPO0M{+_z&l^iXdJLp8b}IQ^g_fCp>=VNN2h5yznPwy)sW& zC;a05R#+s=_8szl1=a@d_}=in1Tu$5efRnHfwlXO^4Ibw^1Jdsz*69^@?-M-@*VPx z@|E&M^4ap~@+tC8`FOcWu9ufUjDk9OygX7KEccbmAVPsn`bc^~y2p2cuidx7x7xSF zH^(>CIa|5k`;qr8?`z)YyiW*ciT%Vfsk`$rp^s1^(>a_=|glW0yy8|J(h#=OxFbj-dNGUy-BHeZKp2Pa{N0 zyw{iE-T@x(dwI@v-r{-@WFNP>E(I_4C%Bqiiy=Bx}R^Cy@E0xZ5;-4fzI$3!orAXp;;_oBy|F021OR6OKr?d`D zm*6{aX{{+;JjpzJjj+-tfJIc_1m%+W9LAY8D5u0*O^V$T&6~ZwnSEF$UWhhlu`lbz z5cUiX#nV|Lz!Ik!OP*>xJVoL&xuz}rfGGAA57}h&n1d!+iJo)Bw~{;je6$_m0s~{= zeBEryo3ZpK3gnwK&&4QZnONL-{p;+}>+B)0rjv^`x!BH>Z3 zP~7(=|3LKax-XINrzqi`M8e%s!kvkPJEDZ!6A8CP3AaS?hA3Ve#jB$D$0%MJ#fzeN zeiYA*;+atlMe+0~wny=_DDH{k?kMhz;^UiF{&)-R!9nzBSGLCTRi?-Zi-a-bpU@ zm>YWpY8>h4gexchil)TqL}L2<*|W4yZFCO`2j7qwo*Yg}%n+Oj9Hso^r(2Rt@@&qF zqP-GV8b>fAp%Kwh%1=yX2*W+-`r77$4^Lq}kGg!4rr|JXVq9kGD-CmBjV7cgA+3bCT8?c z$lwyIaJg>dAabWAWJq-h8RD@COvS9Eapz`wqYs3%DmwnsT#q}k;!$YWY-+B@Jx66@ z_J!Pw_~~BM>j}41f3O6eP}7xwG|N+O^Z0GaccR$Mv;udAdG_h%*{e+$7bXm6pp*Mp z8t*yaJwbOm;?_=Thzpa3Tda$YHWy?G&Db2?UEDJ5`%?D4;T|$Q6>+t(+v4&}z8vfh z;}(+K->_AK@get24}L0_(%O&v3qI!X^wl+QY-(=gHX9Gu8V?%{Z@(vfTWsR`di~IB z!#)@maNJEvS@y4dUYo9#v?6*3a%|PcZDKj=SwdTCLO;3w&}ww%ugK#2+MzYwI?(0Y zqUXHQbTFmazUWmDceBKHOu%f+YtIEHY|$L~z-VP5513 z8@!Q>8GN+Uv!i%)6sJaUQWVEWacmSvM{z_Hhefd}ii1%9zs&Zi&G)+Ra^F_iIrsMd z>V4mPxA#2nHt#ZTAFl`W{!e($@=W$rcqI4Z?(5yhx<|STT?bv)x|fbmv)^Yw%|6TC z6J&57K(}o@BB)2zY)-(sCuV9AODM8x7^)vD|)qjn%b7@8YF%_<{A-E+xeV|K3yYE(5BjKD1ZK8~m0EY@W8 zil9QZlVS_y!U*bC&3uN-;?(^05!A6ZHj<-L@DBUv2O zzchl{U`_u7rP<8R5c=MK<@yLJg8vu#-e>TX2IAX_0*cy5gJHzOO|*HRi1PYh@cWXEjMMd@Sr##g1YQ9i{A&uQN5jJ@wNODln9&Gf%TS5Z)z% zYIJtF$qI#jp#u=!C4%~N3o?XviJ%(Yf(+qZBB)8XAVYYU$Uti{gm;OcCf%Yogm;Oc zGTnj<;awuARcF46%)c--&xI3*@GcQlwwq487~Um<>UMJ~gm;Oc+TENA;awuAVmGHk zc$Ww&+0CgC-X(&{c5^C(cZr~;{V-1%!n;IJ8P8sW_$` zr#=kt5{VfBz&IJ7CJgTqiCF<8lOeoIBxVM{CAcY*weT(xGz4H%yv{;PmsDg7?-D_q zfMhpiGKP1FphWs#H zXk);ZU~C17;awtVUT}D~B!+j1p#4FY+?2@}-X%hY3CV8CWDM^TLBj-g^RSQF8NyYA z@GcQA6ggryWip0$iNp*V49_cucZr}y!x3Ff2=5Y!89yXn83^wZLF)(eso4SHT_R`# z@jq~)5Z)z%_7b|gYGTSyCS?ro5+jNx4(XjPFsD`I$;2%1!wkRZHE z1nntUzdC=)FuY3yjVw&KL3o!)%)$bO+wj4TjNx4(F%yeqGK6=Dpp8YECk)|T!ZFhe z!@a{pc$YBRUUW7AVR)A?nqshmIx(Xz38M?)UBYOU!M2)6)s!u<1QdesE@3p#VBEm< z3R?i_l28!ByM)nJql?i_WDM^TMl+61Td0ETZL@u%TxnDOqCBMBtK6>Kpj_$u(f5t- zGv7hqzkK_BFM^K#G2a8eyL`9!t_2yv1-`R_Y&_PykT#< z_hj%6cszV#u*Q3=cOhuxr+RC>BfVAL{@z~RB5$tO=aqktWS?Fnd*wUuz3>s(68uB? zyYjabdo_i}4Os_17HMwCc-)Y6=pPn~4H=IcvW}SD5kt|*%33bN+!dGtRrS)l}v^WSw}PUh22XD%wa>;5wo&Nt_>Tq4m7i} zs0|yk4z#wiAj5{N15K{js?2=rWu6PS7;MNo$Sx~+d8NhM>{ze0ZAOI+S;sm{Ds0F) z)>=|wL)Ot~NreqrM}s95He?-ZfO?px3>&hJ)lfZYMWDso?5H;sSh_~9m}AI`8|mnvW}(J zWY~~(EQyg5He?iUvtu#P*|I^m3)3Ybq#$g_Iu=2}V}Y*SPB&y)yv+{O9$1VfY{)uLZ;-Gd!+4wEn*Z7Ob6|e+ndidj!G^2@ zH45o%$S|UFHK)Y`Pa~r91pWc6FoD0H;68#s5!_2~55e68cM;r4a0kIYg4+phBe<2| z7J{1zZX±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 From 1de5d22a07581fd16fe641c38c4947bd66e2db3c Mon Sep 17 00:00:00 2001 From: John Every Date: Thu, 2 Nov 2017 15:20:01 -0700 Subject: [PATCH 15/22] Ignore changes to .vs folder. --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6a2ef74..ffdf94f 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,6 @@ build/Release # Dependency directory # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git -node_modules \ No newline at end of file +node_modules + +.vs \ No newline at end of file From 65381ff90d20f841ddc0eafecb27857a9d1367d9 Mon Sep 17 00:00:00 2001 From: Stanley Hon Date: Thu, 9 Nov 2017 12:06:31 -0800 Subject: [PATCH 16/22] adding a fiddler debugging script --- src/fiddler-debug/get-element.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/fiddler-debug/get-element.js b/src/fiddler-debug/get-element.js index 2f8bad7..6f9548a 100644 --- a/src/fiddler-debug/get-element.js +++ b/src/fiddler-debug/get-element.js @@ -15,7 +15,6 @@ void function() { var results = {}; var recipeName = "getelem" - if(window.apiCount > 0) { results[recipeName] = results[recipeName] || { count: 0, href: location.href }; From 22d8fff603f74c6b636cace2419d754d5c250e97 Mon Sep 17 00:00:00 2001 From: Stanley Hon Date: Fri, 8 Dec 2017 14:30:57 -0800 Subject: [PATCH 17/22] adding Praab's test script --- src/fiddler-debug/sharedArrayBuffer.js.txt | 58 ++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 src/fiddler-debug/sharedArrayBuffer.js.txt diff --git a/src/fiddler-debug/sharedArrayBuffer.js.txt b/src/fiddler-debug/sharedArrayBuffer.js.txt new file mode 100644 index 0000000..92832c5 --- /dev/null +++ b/src/fiddler-debug/sharedArrayBuffer.js.txt @@ -0,0 +1,58 @@ +window.debugCSSUsage = true + +window.apiCount = 0; + +void(function() { + var TrueSharedArrayBuffer = window.SharedArrayBuffer; + var SharedArrayBuffer = new Proxy(TrueSharedArrayBuffer, { + construct: function(target, argumentsList, newTarget) { + window.apiCount++; // increment counter + return new TrueSharedArrayBuffer(); + } + }); + window.SharedArrayBuffer = SharedArrayBuffer; +}()); + +void function() { + console.log("PIN1") + document.addEventListener('DOMContentLoaded', function () { + console.log("PIN2") + var results = {}; + var recipeName = "sab"; // sab = SharedArrayBuffer + + 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 From 17d7db13cc341f74fc54fc2b76d2086fec4b6001 Mon Sep 17 00:00:00 2001 From: Stanley Hon Date: Fri, 8 Dec 2017 14:31:46 -0800 Subject: [PATCH 18/22] Renaming file --- .../{sharedArrayBuffer.js.txt => sharedArrayBuffer.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/fiddler-debug/{sharedArrayBuffer.js.txt => sharedArrayBuffer.js} (100%) diff --git a/src/fiddler-debug/sharedArrayBuffer.js.txt b/src/fiddler-debug/sharedArrayBuffer.js similarity index 100% rename from src/fiddler-debug/sharedArrayBuffer.js.txt rename to src/fiddler-debug/sharedArrayBuffer.js From 5931c0210e18f925ca3b72e6988de3a717a3511b Mon Sep 17 00:00:00 2001 From: Stanley Hon Date: Mon, 18 Dec 2017 11:29:52 -0800 Subject: [PATCH 19/22] adding SAB script. --- src/fiddler-debug/sharedArrayBuffer.js | 33 ++++++-------------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/src/fiddler-debug/sharedArrayBuffer.js b/src/fiddler-debug/sharedArrayBuffer.js index 92832c5..fb5dd17 100644 --- a/src/fiddler-debug/sharedArrayBuffer.js +++ b/src/fiddler-debug/sharedArrayBuffer.js @@ -1,25 +1,20 @@ -window.debugCSSUsage = true window.apiCount = 0; void(function() { var TrueSharedArrayBuffer = window.SharedArrayBuffer; var SharedArrayBuffer = new Proxy(TrueSharedArrayBuffer, { - construct: function(target, argumentsList, newTarget) { - window.apiCount++; // increment counter - return new TrueSharedArrayBuffer(); - } + construct: function(target, argumentsList, newTarget) { + window.apiCount++; + return new TrueSharedArrayBuffer(); + } }); window.SharedArrayBuffer = SharedArrayBuffer; }()); void function() { - console.log("PIN1") document.addEventListener('DOMContentLoaded', function () { - console.log("PIN2") - var results = {}; - var recipeName = "sab"; // sab = SharedArrayBuffer - + var results = {}; if(window.apiCount > 0) { results[recipeName] = results[recipeName] || { count: 0, href: location.href }; @@ -29,30 +24,16 @@ void function() { { 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 From a12c2225f3ee0bd34b03cd5f4b36b7f16e8a0d6a Mon Sep 17 00:00:00 2001 From: Stanley Hon Date: Mon, 18 Dec 2017 14:42:10 -0800 Subject: [PATCH 20/22] Fixing a typo. --- src/fiddler-debug/sharedArrayBuffer.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/fiddler-debug/sharedArrayBuffer.js b/src/fiddler-debug/sharedArrayBuffer.js index fb5dd17..1605546 100644 --- a/src/fiddler-debug/sharedArrayBuffer.js +++ b/src/fiddler-debug/sharedArrayBuffer.js @@ -14,7 +14,8 @@ void(function() { void function() { document.addEventListener('DOMContentLoaded', function () { - var results = {}; + var results = {}; + var recipeName = "sab"; if(window.apiCount > 0) { results[recipeName] = results[recipeName] || { count: 0, href: location.href }; From 557b57121a1dc4f18265c9368bde7b486f99d84c Mon Sep 17 00:00:00 2001 From: Stanley Hon Date: Mon, 18 Dec 2017 14:50:13 -0800 Subject: [PATCH 21/22] adding another SAB --- src/fiddler-debug/sharedarraybuffer2.js | 40 +++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/fiddler-debug/sharedarraybuffer2.js 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 From edc7f0c137ec9f08638df915de089643d4a09f13 Mon Sep 17 00:00:00 2001 From: John Every Date: Mon, 8 Jan 2018 15:26:57 -0800 Subject: [PATCH 22/22] Initial changes to recipe. --- Recipe.min.js | 656 ++++++++++----------- cssUsage.src.js | 640 ++++++++++---------- src/recipes/pointer-events_touch-events.js | 129 ++-- 3 files changed, 715 insertions(+), 710 deletions(-) diff --git a/Recipe.min.js b/Recipe.min.js index 999d03b..ba43dfe 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 { @@ -1553,87 +1553,80 @@ void function () { var nodeName = element.nodeName; - // 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"]; + 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 JsTypes = + { + ATTRIBUTE: 1, + INTERNAL: 2, + EXTERNAL: 3, + } - var jsType; + 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; + // 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; } - 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. + // 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.getAttribute(".on" + event)) { - 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; + // 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; + } - 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; + 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; } - - return results; }); function findNumOfStringInstancesInText_CaseSensitive(string, text) @@ -1643,212 +1636,213 @@ void function () return instances.length; } -/* - 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; - }); }(); -// -// 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 eab861d..5e43405 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 { @@ -1553,87 +1553,80 @@ void function () { var nodeName = element.nodeName; - // 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"]; + 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 JsTypes = + { + ATTRIBUTE: 1, + INTERNAL: 2, + EXTERNAL: 3, + } - var jsType; + 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; + // 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; } - 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. + // 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.getAttribute(".on" + event)) { - 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; + // 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; + } - 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; + 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; } - - return results; }); function findNumOfStringInstancesInText_CaseSensitive(string, text) @@ -1644,190 +1637,215 @@ void function () 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... - } -}(); - -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('css'); - convertToTSV(INSTRUMENTATION_RESULTS['css']); - currentRowTemplate.pop(); - - currentRowTemplate.push('dom'); - convertToTSV(INSTRUMENTATION_RESULTS['dom']); - currentRowTemplate.pop(); - - currentRowTemplate.push('html'); - convertToTSV(INSTRUMENTATION_RESULTS['html']); - 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; - } - - } +/* + 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; + }); +}(); +// +// 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('css'); + convertToTSV(INSTRUMENTATION_RESULTS['css']); + currentRowTemplate.pop(); + + currentRowTemplate.push('dom'); + convertToTSV(INSTRUMENTATION_RESULTS['dom']); + currentRowTemplate.pop(); + + currentRowTemplate.push('html'); + convertToTSV(INSTRUMENTATION_RESULTS['html']); + 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/src/recipes/pointer-events_touch-events.js b/src/recipes/pointer-events_touch-events.js index df0323d..29f8c06 100644 --- a/src/recipes/pointer-events_touch-events.js +++ b/src/recipes/pointer-events_touch-events.js @@ -12,87 +12,80 @@ void function () { var nodeName = element.nodeName; - // 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"]; + 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 === "") + var JsTypes = { - return results; + ATTRIBUTE: 1, + INTERNAL: 2, + EXTERNAL: 3, } - 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. + 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; - } + // 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.getAttribute(".on" + event)) { - results[event] = results[event] || { count: 0, }; - results[event].count++; - } - break; + 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.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; + 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; } - - return results; }); function findNumOfStringInstancesInText_CaseSensitive(string, text)