From d882aca41df6bebcab2caf61deb52c94c3fa7708 Mon Sep 17 00:00:00 2001 From: Elbert Alias Date: Sat, 27 May 2017 14:17:52 +1000 Subject: [PATCH] Replace innerHTML with jsonToDOM --- .../webextension/_locales/en/messages.json | 2 +- src/drivers/webextension/js/i18n.js | 2 +- src/drivers/webextension/js/jsontodom.js | 63 ++++++++++++++ src/drivers/webextension/js/popup.js | 83 ++++++++++++++----- src/drivers/webextension/popup.html | 1 + 5 files changed, 126 insertions(+), 25 deletions(-) create mode 100644 src/drivers/webextension/js/jsontodom.js diff --git a/src/drivers/webextension/_locales/en/messages.json b/src/drivers/webextension/_locales/en/messages.json index 3edc49047..94a1db7d8 100644 --- a/src/drivers/webextension/_locales/en/messages.json +++ b/src/drivers/webextension/_locales/en/messages.json @@ -18,7 +18,7 @@ "categoryName6": { "message": "Ecommerce" }, "categoryName7": { "message": "Photo Galleries" }, "categoryName8": { "message": "Wikis" }, - "categoryName9": { "message": "Hostin Panels" }, + "categoryName9": { "message": "Hosting Panels" }, "categoryName10": { "message": "Analytics" }, "categoryName11": { "message": "Blog" }, "categoryName12": { "message": "JavaScript Framework" }, diff --git a/src/drivers/webextension/js/i18n.js b/src/drivers/webextension/js/i18n.js index c5ea110e3..6921c0f0f 100644 --- a/src/drivers/webextension/js/i18n.js +++ b/src/drivers/webextension/js/i18n.js @@ -4,6 +4,6 @@ document.addEventListener('DOMContentLoaded', function() { var nodes = document.querySelectorAll('[data-i18n]'); nodes.forEach(function(node) { - node.innerHTML = browser.i18n.getMessage(node.dataset.i18n); + node.childNodes[0].nodeValue = browser.i18n.getMessage(node.dataset.i18n); }); }); diff --git a/src/drivers/webextension/js/jsontodom.js b/src/drivers/webextension/js/jsontodom.js new file mode 100644 index 000000000..d77df83f8 --- /dev/null +++ b/src/drivers/webextension/js/jsontodom.js @@ -0,0 +1,63 @@ +jsonToDOM.namespaces = { + html: "http://www.w3.org/1999/xhtml", + xul: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" +}; + +jsonToDOM.defaultNamespace = jsonToDOM.namespaces.html; + +function jsonToDOM(jsonTemplate, doc, nodes) { + function namespace(name) { + var reElemNameParts = /^(?:(.*):)?(.*)$/.exec(name); + return { namespace: jsonToDOM.namespaces[reElemNameParts[1]], shortName: reElemNameParts[2] }; + } + + // Note that 'elemNameOrArray' is: either the full element name (eg. [html:]div) or an array of elements in JSON notation + function tag(elemNameOrArray, elemAttr) { + // Array of elements? Parse each one... + if (Array.isArray(elemNameOrArray)) { + var frag = doc.createDocumentFragment(); + Array.forEach(arguments, function(thisElem) { + frag.appendChild(tag.apply(null, thisElem)); + }); + return frag; + } + + // Single element? Parse element namespace prefix (if none exists, default to defaultNamespace), and create element + var elemNs = namespace(elemNameOrArray); + var elem = doc.createElementNS(elemNs.namespace || jsonToDOM.defaultNamespace, elemNs.shortName); + + // Set element's attributes and/or callback functions (eg. onclick) + for (var key in elemAttr) { + var val = elemAttr[key]; + if (nodes && key == "key") { + nodes[val] = elem; + continue; + } + + var attrNs = namespace(key); + if (typeof val == "function") { + // Special case for function attributes; don't just add them as 'on...' attributes, but as events, using addEventListener + elem.addEventListener(key.replace(/^on/, ""), val, false); + } + else { + // Note that the default namespace for XML attributes is, and should be, blank (ie. they're not in any namespace) + elem.setAttributeNS(attrNs.namespace || "", attrNs.shortName, val); + } + } + + // Create and append this element's children + var childElems = Array.slice(arguments, 2); + childElems.forEach(function(childElem) { + if (childElem != null) { + elem.appendChild( + childElem instanceof doc.defaultView.Node ? childElem : + Array.isArray(childElem) ? tag.apply(null, childElem) : + doc.createTextNode(childElem)); + } + }); + + return elem; + } + + return tag.apply(null, jsonTemplate); +} diff --git a/src/drivers/webextension/js/popup.js b/src/drivers/webextension/js/popup.js index eb760d34c..88fcf6eb8 100644 --- a/src/drivers/webextension/js/popup.js +++ b/src/drivers/webextension/js/popup.js @@ -9,7 +9,9 @@ if ( /complete|interacrive|loaded/.test(document.readyState) ) { popup.displayApps(response) } else { - document.addEventListener('DOMContentLoaded', function() { popup.displayApps(response) }); + document.addEventListener('DOMContentLoaded', function() { + popup.displayApps(response) + }); } }); }; @@ -26,45 +28,80 @@ displayApps: function(response) { var appName, confidence, version, - detectedApps = document.querySelector('#detected-apps'); - html = ''; + detectedApps = document.querySelector('#detected-apps'), + categories = [], + json = []; if ( response.tabCache && response.tabCache.count > 0 ) { for ( appName in response.tabCache.appsDetected ) { confidence = response.tabCache.appsDetected[appName].confidenceTotal; version = response.tabCache.appsDetected[appName].version; - html += - '
' + - '' + - '' + - '' + - '' + appName + '' + - ( version ? ' ' + version : '' ) + ( confidence < 100 ? ' (' + confidence + '% sure)' : '' ) + - '' + - ''; + categories = []; response.apps[appName].cats.forEach(function(cat) { - html += - '' + - '' + browser.i18n.getMessage('categoryName' + cat) + '' + - ''; + categories.push( + [ + 'a', { + target: '_blank', + href: 'https://wappalyzer.com/categories/' + popup.slugify(response.categories[cat].name) + }, [ + 'span', { + class: 'category' + }, [ + 'span', { + class: 'name' + }, + browser.i18n.getMessage('categoryName' + cat) + ] + ] + ] + ); }); - html += - '' + - '
'; + json.push( + [ + 'div', { + class: 'detected-app' + }, [ + 'a', { + target: '_blank', + href: 'https://wappalyzer.com/applications/' + popup.slugify(appName) + }, [ + 'img', { + src: 'images/icons/' + ( response.apps[appName].icon || 'default.svg' ) + } + ], [ + 'span', { + class: 'label' + }, [ + 'span', { + class: 'name' + }, + appName + ], + ( version ? ' ' + version : '' ) + ( confidence < 100 ? ' (' + confidence + '% sure)' : '' ) + ] + ], + categories + ] + ); } } else { - html = '
' + browser.i18n.getMessage('noAppsDetected') + '
'; + json = [ + 'div', { + class: 'empty' + }, + browser.i18n.getMessage('noAppsDetected') + ]; } - detectedApps.innerHTML = html; + detectedApps.appendChild(jsonToDOM(json, document, {})); // Force redraw after popup animation on Mac OS setTimeout(function() { - document.body.innerHTML += ' '; - }, 600); + document.body.appendChild(jsonToDOM([ 'span', { style: 'line-height: 1px;' }], document, {})); + }, 800); }, slugify: function(string) { diff --git a/src/drivers/webextension/popup.html b/src/drivers/webextension/popup.html index afa6e0976..72b598cda 100644 --- a/src/drivers/webextension/popup.html +++ b/src/drivers/webextension/popup.html @@ -7,6 +7,7 @@ +