Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

initial commit

  • Loading branch information...
commit a15808ee7939ff6391ae50eadcaf62328d2e8240 0 parents
@amphro amphro authored
51 ElementContainer.js
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2011 Thomas Dvornik
+ * All rights reserved.
+ *
+ */
+
+function ElementContainer(ele) {
+ this.element = ele;
+ this.oldBorder = ele.style.border;
+ this.oldBgColor = ele.style.background;
+ this.offsets = this.getOffset();
+
+ this.overlay = document.createElement('div');
+ this.overlay.setAttribute('class','xpathElementOverlay');
+ this.overlay.style.left = this.offsets.left+'px';
+ this.overlay.style.top = this.offsets.top+'px';
+ this.overlay.style.width = this.element.offsetWidth+'px';
+ this.overlay.style.height = this.element.offsetHeight+'px';
+ this.overlay.style.display = 'none';
+ document.body.appendChild(this.overlay);
+}
+
+ElementContainer.prototype.highlight = function() {
+ this.element.style.border = 'solid 1px black';
+ //this.element.style.background = 'yellow';
+ this.overlay.style.display = 'none';
+};
+
+ElementContainer.prototype.select = function() {
+ this.element.style.border = 'solid 1px red';
+ //this.element.style.background = 'red';
+ this.overlay.style.display = '';
+};
+
+ElementContainer.prototype.clear = function() {
+ this.element.style.border = this.oldBorder;
+ //this.element.style.background = this.oldBgColor;
+ document.body.removeChild(this.overlay);
+};
+
+ElementContainer.prototype.getOffset = function() {
+ var el = this.element;
+ var _x = 0;
+ var _y = 0;
+ while( el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop ) ) {
+ _x += el.offsetLeft - el.scrollLeft;
+ _y += el.offsetTop - el.scrollTop;
+ el = el.offsetParent;
+ }
+ return { top: _y, left: _x };
+}
22 LICENSE.txt
@@ -0,0 +1,22 @@
+Copyright (c) 2011 Thomas Dvornik (http://www.thomasdvornik.com)
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
2  README
@@ -0,0 +1,2 @@
+Extension can be installed at
+https://chrome.google.com/webstore/detail/oemacabgcknpcikelclomjajcdpbilpf
266 XPathBox.js
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2011 Thomas Dvornik
+ * All rights reserved.
+ *
+ */
+
+function XPathBox() {
+ this.element = document.createElement('div');
+ this.element.setAttribute('id', 'xpathBox');
+ this.element.style.left = '100px';
+ this.element.style.top = '100px';
+
+ this.titleEle = document.createElement('span');
+ this.titleEle.setAttribute('id', 'xpathBoxTitle');
+ this.titleEle.innerHTML = 'Enter xPath: ';
+ this.element.appendChild(this.titleEle);
+
+ function find() {
+ var userInput = document.getElementById('xpathInput').value;
+ if (userInput) {
+ var count = 0;
+ if (document.getElementById('cssselectorBox').checked) {
+ console.log("CSS Selector: " + userInput);
+ count = cssSearch(userInput);
+ } else {
+ console.log("xPath: " + userInput);
+ count = xpathSearch(userInput);
+ }
+ var disWarn = count > 9 ? ', but only showing 10. Please refine your search.' : '.';
+ console.log("Search returned " + count + " results" + disWarn);
+ }
+ };
+
+ this.input = document.createElement('input');
+ this.input.setAttribute('id', 'xpathInput');
+ this.input.setAttribute('type', 'text');
+ this.input.setAttribute('name', 'inputbox');
+
+ this.input.onkeypress = function(event) {
+ if(event.keyCode == 13){
+ find();
+ }
+ };
+ this.element.appendChild(this.input);
+
+ this.button = document.createElement('input');
+ this.button.setAttribute('type', 'button');
+ this.button.setAttribute('name', 'button');
+ this.button.setAttribute('value', ' Find ');
+
+ this.button.onclick = find;
+
+ this.element.appendChild(this.button);
+
+ this.useCssDiv = document.createElement('span');
+ this.useCssDiv.innerHTML = "Use CSS:";
+
+ this.useCssDiv.setAttribute('id', 'useCssDiv');
+
+ this.cssCheckbox = document.createElement('input');
+ this.cssCheckbox.setAttribute('id', 'cssselectorBox');
+ this.cssCheckbox.setAttribute('type', 'checkbox');
+ this.cssCheckbox.setAttribute('name', 'cssselector');
+ this.cssCheckbox.setAttribute('alt', 'Enable CSS');
+
+ var checkbox = this.cssCheckbox;
+ var title = this.titleEle;
+
+ chrome.extension.sendRequest({att: "get", param: 'useCssSelectors'},
+ function(response) {
+ if (response.val === 'true') {
+ checkbox.checked = true;
+ title.innerHTML = 'Enter CSS: ';
+ }
+ });
+
+ function changeSearchTitle() {
+ var box = document.getElementById('xpathBoxTitle');
+ var checked = document.getElementById('cssselectorBox').checked;
+ if (checked) {
+ box.innerHTML = "Enter CSS: ";
+ } else {
+ box.innerHTML = "Enter xPath: ";
+ }
+ chrome.extension.sendRequest({att: "set", param: 'useCssSelectors', val: checked},
+ //TODO: Add some sort of error handeling?
+ function(response) {});
+ }
+
+ this.cssCheckbox.onclick = changeSearchTitle;
+
+ this.useCssDiv.appendChild(this.cssCheckbox);
+ this.element.appendChild(this.useCssDiv);
+
+ this.error = document.createElement('div');
+ this.error.setAttribute('class', 'xpathError');
+ this.error.setAttribute('style', 'display: none');
+ this.element.appendChild(this.error);
+
+ this.output = document.createElement('div');
+ this.output.setAttribute('width', '500px');
+ this.element.appendChild(this.output);
+
+ this.count = 0;
+
+ this.offsetx;
+ this.offsety;
+ this.nowX;
+ this.nowY;
+ this.movable = false;
+ this.visable = true;
+
+ this.foundElements = new Array();
+}
+
+XPathBox.prototype.highlightFoundElements = function(clearFoundElements) {
+ for (var i = 0; i < this.foundElements.length; i++) {
+ this.foundElements[i].highlight();
+ }
+}
+
+XPathBox.prototype.clearHighlights = function(clearFoundElements) {
+ clearFoundElements = typeof(clearFoundElements) != 'undefined' ? clearFoundElements : true;
+ for (var i = 0; i < this.foundElements.length; i++) {
+ this.foundElements[i].clear();
+ }
+ if (clearFoundElements) {
+ this.foundElements = new Array();
+ }
+};
+
+XPathBox.prototype.setOutput = function() {
+ this.output.innerHTML = 'Output:\n';
+};
+
+XPathBox.prototype.clearOutput = function() {
+ this.output.innerHTML = '';
+ this.clearHighlights();
+};
+
+XPathBox.prototype.setError = function(errorMsg) {
+ this.error.setAttribute('style', 'display: block');
+ this.error.innerHTML = errorMsg;
+};
+
+XPathBox.prototype.clearError = function() {
+ this.error.setAttribute('style', 'display: none');
+ this.error.innerHTML = '';
+ this.count = 0;
+};
+
+/**
+ * Append a row to the xPath box output in the following format.
+ * <number>. [output]
+ */
+XPathBox.prototype.appendOutput = function(elementOrText, origElement) {
+ this.count++;
+ var container = document.createElement('div');
+ container.setAttribute('class', 'xpathFoundContainer');
+
+ var number = document.createElement('div');
+ number.innerHTML = this.count + '.';
+ number.setAttribute('class', 'xpathFoundNumber');
+
+ var text = document.createElement('div');
+ text.innerHTML = elementOrText + '&nbsp;';
+ text.setAttribute('class', 'xpathFoundStyle');
+
+ container.appendChild(number);
+ container.appendChild(text);
+
+ this.output.appendChild(container);
+
+ if (origElement instanceof HTMLElement) {
+ var eleCont = new ElementContainer(origElement);
+ eleCont.highlight();
+ this.foundElements.push(eleCont);
+ container.onmouseover = function() {
+ eleCont.select();
+ };
+ container.onmouseout = function() {
+ eleCont.highlight();
+ };
+ }
+};
+
+/**
+ * Determines if the given element part of the xPath box
+ */
+XPathBox.prototype.isXPathBoxSelected = function(element) {
+ return element != this.input && element != this.button
+ && (element == this.element || element == this.output);
+};
+
+/**
+ * Sets the offset values so the xPath box will move from the current
+ * possition
+ */
+XPathBox.prototype.setOffset = function(x, y) {
+ this.offsetx = x;
+ this.offsety = y;
+
+ var temp = this.element.style.left;
+ temp = temp.substring(0, temp.length - 2);
+ this.nowX = parseInt(temp);
+ temp = this.element.style.top;
+ temp = temp.substring(0, temp.length - 2);
+ this.nowY = parseInt(temp);
+};
+
+/**
+ * Move the xPath box. NOTE: Must call setOffset first!!
+ */
+XPathBox.prototype.move = function(x, y) {
+ this.element.style.left = (this.nowX + x - this.offsetx) + 'px';
+ this.element.style.top = (this.nowY + y - this.offsety) + 'px';
+};
+
+/**
+ * Hide the xPath box
+ */
+XPathBox.prototype.hide = function() {
+ this.element.style.visibility = "hidden";
+ this.clearHighlights(false);
+};
+
+/**
+ * Show the xPath box
+ */
+XPathBox.prototype.show = function() {
+ this.element.style.visibility = "visible";
+ this.highlightFoundElements();
+};
+
+/**
+ * Hide/Show the xPath box
+ */
+XPathBox.prototype.toggleVisability = function() {
+ this.visable = !this.visable;
+ if (this.visable) {
+ this.show();
+ } else {
+ this.hide();
+ }
+};
+
+/**
+ * Determine if the xPath box is a parent of the given element by
+ * following and checking the element's parent's id.
+ */
+XPathBox.prototype.isParent = function(ele) {
+ if (ele.id == 'xpathBox') {
+ return true;
+ }
+ for (;ele = ele.parentNode;) {
+ if (ele.id == 'xpathBox') {
+ return true;
+ }
+ }
+ //while (ele) {
+ // if (ele == this.element)
+ // return true;
+ // ele = ele.parentNode;
+ //}
+ return false;
+};
39 background.html
@@ -0,0 +1,39 @@
+<script>
+/*
+ * Copyright (C) 2011 Thomas Dvornik
+ * All rights reserved.
+ *
+ */
+
+ chrome.browserAction.onClicked.addListener(function(tab) {
+ chrome.tabs.sendRequest(tab.id, {browserAction: "clicked"}, function(response) {
+ if (response.error == "load") {
+ chrome.tabs.insertCSS(null, {
+ file : "boxes.css"
+ });
+ chrome.tabs.executeScript(null, {
+ file : "XPathBox.js"
+ });
+ chrome.tabs.executeScript(null, {
+ file : "ElementContainer.js"
+ });
+ chrome.tabs.executeScript(null, {
+ file : "main.js"
+ });
+ }
+ });
+ });
+
+ chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
+ if (request.att == 'get') {
+ sendResponse({val: localStorage[request.param]});
+ }
+ else if (request.att == 'set') {
+ localStorage[request.param] = request.val;
+ sendResponse({});
+ }
+ else
+ sendResponse({}); // snub them.
+ });
+</script>
+
85 boxes.css
@@ -0,0 +1,85 @@
+#xpathBox {
+ display: block;
+ position: absolute;
+ width: 600px;
+ left: 100px;
+ top: 100px;
+ visibility: visible;
+ border: solid black 1px;
+ min-height: 25px;
+ background-color:rgba(0,200,150,0.8);
+ z-index: 1000000;
+ -moz-border-radius: 10px;
+ border-radius: 10px;
+ padding: 10px;
+ cursor: move;
+ font-family: "Times New Roman", Times, serif;
+ font-size: 16px;
+}
+
+#xpathInput {
+ width: 380px;
+}
+
+.xpathInput {
+ width: 1000px;
+}
+
+.xpathFoundContainer {
+ margin-top: 7px;
+}
+
+.xpathFoundNumber {
+ cursor: text;
+ font-size: 1.5em;
+ font-weight: 600;
+ /*color: white;
+ text-shadow: 0.05em 0.05em 0.08em black;*/
+ text-align: right;
+ width: 30px;
+ float: left;
+}
+
+#xpathBoxTitle {
+ float: left;
+ width: 80px;
+ margin-right: 6px;
+ text-align: right;
+}
+
+.xpathFoundStyle {
+ cursor: text;,
+ font-size: 1em;
+ border: solid 1px black;
+ background: white;
+ padding: 4px;
+ margin-left: 34px;
+ word-wrap: break-word;
+}
+
+.xpathError {
+ cursor: text;
+ font-size: 14px;
+ color: white;
+ border: solid darkred 1px;
+ background-color:red;
+ -moz-border-radius: 10px;
+ border-radius: 10px;
+ padding: 5px;
+ padding-left: 10px;
+ font-family: "Times New Roman", Times, serif;
+ font-weight: bold;
+ margin-top: 10px;
+}
+
+.xpathElementOverlay {
+ position: absolute;
+ background-color:rgba(0,0,0,.3);
+ border: 1px solid red;
+ z-index: 999999;
+}
+
+#useCssDiv {
+ font-size: .7em;
+ margin-left: 3px;
+}
BIN  icon-128.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  icon-16.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  icon-19.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  icon-48.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  icon.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
264 main.js
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2011 Thomas Dvornik
+ * All rights reserved.
+ *
+ */
+
+var xpathBox = new XPathBox();
+document.body.appendChild(xpathBox.element);
+
+document.oncontextmenu = handleContextMenu;
+document.onmousedown = handleMouseDown;
+document.onmouseup = handleMouseUp;
+document.onmousemove = handleMouseMove;
+
+function handleContextMenu(e) {
+ // We don't want the context menu when xPath box is open
+ // except on the input so the user can copy and paste with
+ // right click.
+ if (xpathBox.visable && e.target != xpathBox.input) {
+ e.preventDefault();
+ e.stopPropagation();
+
+ // Only display the path if the context is not on the
+ // xPath box. The user shouldn't want the path of a component
+ // we added.
+ if (!xpathBox.isParent(e.target)) {
+ var path;
+ if (document.getElementById('cssselectorBox').checked) {
+ console.log('\nCSS for selected element:');
+ path = getElementCSSPath(e.target);
+ } else {
+ console.log('\nxPath for selected element:');
+ path = getElementXPath(e.target);
+ }
+ console.log(path);
+ xpathBox.clearOutput();
+ xpathBox.clearError();
+ xpathBox.appendOutput(path);
+ }
+ return false;
+ }
+ return true;
+}
+
+function handleMouseDown(e) {
+ if (xpathBox.visable && xpathBox.isXPathBoxSelected(e.target)) {
+ // Don't want to move on right click
+ if (e.which == 3) {
+ e.preventDefault();
+ e.stopPropagation();
+ return false;
+ } else {
+ xpathBox.setOffset(e.clientX, e.clientY);
+ xpathBox.movable = true;
+ e.preventDefault();
+ e.stopPropagation();
+ }
+ }
+}
+
+function handleMouseUp() {
+ xpathBox.movable = false;
+}
+
+function handleMouseMove(e) {
+ if (xpathBox.visable && xpathBox.movable) {
+ xpathBox.move(e.clientX, e.clientY);
+ e.preventDefault();
+ e.stopPropagation();
+ return false;
+ }
+}
+
+function cssSearch(cssstring) {
+ xpathBox.clearOutput();
+ xpathBox.clearError();
+
+ var nodes = document.querySelectorAll(cssstring);
+
+ for (var i=0; i<nodes.length; i++) {
+ console.log(nodes[i]);
+ xpathBox.appendOutput(elementToString(nodes[i]), nodes[i]);
+
+ if (i > 9) {
+ xpathBox.setError("Only 10 results displayed, but "+count+" were found. Please refine your search");
+ break;
+ }
+ }
+
+ if (nodes.length == 0) {
+ xpathBox.setError("No results found");
+ }
+ return nodes.length;
+}
+
+// Refactor everything below this mark
+
+function xpathSearch(xpath) {
+ try {
+ xpathBox.clearOutput();
+ xpathBox.clearError();
+
+ var nodes = document.evaluate(xpath, document, null,
+ XPathResult.ANY_TYPE, null);
+
+ //xpathBox.appendOutput(nodes.resultType);
+ //return 1;
+
+ // Result types defined at https://developer.mozilla.org/en/introduction_to_using_xpath_in_javascript
+ // 1 is a number
+ if (nodes.resultType == '1') {
+ xpathBox.appendOutput(nodes.numberValue);
+ return 1;
+ }
+ // 3 is a boolean
+ if (nodes.resultType == '3') {
+ xpathBox.appendOutput(nodes.booleanValue);
+ return 1;
+ }
+
+ var node = nodes.iterateNext();
+ var count = 0, res = new Array();
+
+ try {
+ while (node) {
+ var parent = xpathBox.isParent(node);
+ if (!parent && count < 10) {
+ res.push(node);
+ }
+ if (!parent) {
+ count++;
+ }
+ node = nodes.iterateNext();
+ }
+ } catch (e) {
+ console.log(e);
+ xpathBox.setError(e);
+ }
+
+ for (var i = 0; i < res.length; i++) {
+ console.log(res[i]);
+ xpathBox.appendOutput(elementToString(res[i]), res[i]);
+
+ //TODO need to check for other object. For example, a query
+ // with '/text()' will display '[object Text]' in the output panel
+ }
+
+ if (count > 9) {
+ xpathBox.setError("Only 10 results displayed, but "+count+" were found. Please refine your search");
+ }
+ if (count == 0) {
+ xpathBox.setError("No results found");
+ }
+ return count;
+ }
+ catch (ex) {
+ xpathBox.clearOutput();
+ xpathBox.clearError();
+ /*var s = '';
+ for (var key in ex) {
+ s += key + ' ';
+ }
+ s = 'Name: '+ex.name +'; Message: '+ex.message+ '; Code: '+ex.code +'; Type: '+ex.TYPE_ERR;*/
+ if (ex.name == 'INVALID_EXPRESSION_ERR') {
+ var errMsg = 'Invalid xPath Expression.';
+ xpathBox.setError(errMsg);
+ console.log(errMsg);
+ } else {
+ xpathBox.setError(ex.message);
+ console.log(ex.message);
+ }
+ return 0;
+ }
+}
+
+function elementToString(element) {
+ if (element instanceof Text) {
+ var s = '';
+ for (var key in element) {
+ s += key + ' ';
+ }
+ return element.textContent;
+ }
+ if (!(element instanceof HTMLElement))
+ return element;
+
+ var string = '&lt;' + element.tagName.toLowerCase();
+
+ var i = 0, attNum = 0;
+ var atts = element.attributes;
+ for (; i < atts.length; i++) {
+ var nname = atts[i].nodeName;
+ string += ' ' + atts[i].nodeName + '="' + atts[i].nodeValue + '"';
+ }
+
+ string += '&gt;';
+ return string;
+}
+
+/*
+ * I want to display the html of the element, but right now the formatting gets all messed up.
+ * Going to put this function on hold until I get the extension done, then revisit this.
+*/
+function elementDisplayString(element) {
+ if (!(element instanceof HTMLElement))
+ return element;
+
+ var string = '<' + element.tagName.toLowerCase();
+
+ var attNum = 0;
+ var atts = element.attributes;
+ var i;
+ for (i = 0; i < atts.length; i++) {
+ var nname = atts[i].nodeName;
+ if (nname != "id" && nname != "style" && nname != "class") {
+ string += ' ' + atts[i].nodeName + '="' + atts[i].nodeValue + '"';
+ }
+ }
+
+ string += '>' + elementToString(element.innerHTML) + '</' + element.tagName + '>';
+ //alert(string);
+ return string;
+}
+
+function getElementXPath(elt) {
+ var path = "";
+ for (; elt && elt.nodeType == 1; elt = elt.parentNode) {
+ idx = getElementIdx(elt);
+ xname = elt.tagName.toLowerCase();
+ if (idx > 1)
+ xname += "[" + idx + "]";
+ path = "/" + xname + path;
+ }
+
+ return path;
+}
+
+function getElementCSSPath(elem) {
+ var tagName = elem.tagName.toLowerCase();
+ // id is unique, so we can stop here
+ if (elem.id) {
+ return "#" + elem.id;
+ }
+ // reached the top level element, so we can return
+ if (elem.tagName == "BODY") {
+ return '';
+ }
+ var path = getElementCSSPath(elem.parentNode);
+ if (elem.className) {
+ // concatenate all the classes together
+ return path + " " + tagName + "." + elem.className.split(' ').join('.');
+ }
+ return path + " " + tagName;
+}
+
+function getElementIdx(elt) {
+ var count = 1;
+ for ( var sib = elt.previousSibling; sib; sib = sib.previousSibling) {
+ if (sib.nodeType == 1 && sib.tagName == elt.tagName)
+ count++;
+ }
+
+ return count;
+}
24 manifest.json
@@ -0,0 +1,24 @@
+{
+ "name": "xPath Viewer",
+ "version": "0.1",
+ "description": "Search DOM elements on the current page with xPath expressions or right click on an element to see it's absolute xPath expression.",
+ "icons" : {
+ "16" : "icon-16.png",
+ "48" : "icon-48.png",
+ "128" : "icon-128.png"
+ },
+ "background_page": "background.html",
+ "permissions": [
+ "tabs", "<all_urls>"
+ ],
+ "content_scripts": [
+ {
+ "matches": ["<all_urls>"],
+ "js": ["messager.js"]
+ }
+ ],
+ "browser_action": {
+ "default_icon": "icon.png",
+ "default_title": "xPath Viewer"
+ }
+}
16 messager.js
@@ -0,0 +1,16 @@
+/*
+ * Copyright (C) 2011 Thomas Dvornik
+ * All rights reserved.
+ *
+ */
+
+chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
+ if (request.browserAction == 'clicked') {
+ if (window.xpathBox) {
+ window.xpathBox.toggleVisability();
+ }
+ else {
+ sendResponse({error: "load"});
+ }
+ }
+});
Please sign in to comment.
Something went wrong with that request. Please try again.