Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(338)

Side by Side Diff: Source/devtools/front_end/DOMAgent.js

Issue 75253002: DevTools: [Elements] Implement "Copy CSS Path" context menu item for elements (Closed) Base URL: svn://svn.chromium.org/blink/trunk
Patch Set: Implement identifier escaping for id and class attribute values Created 7 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 /* 1 /*
2 * Copyright (C) 2009, 2010 Google Inc. All rights reserved. 2 * Copyright (C) 2009, 2010 Google Inc. All rights reserved.
3 * Copyright (C) 2009 Joseph Pecoraro 3 * Copyright (C) 2009 Joseph Pecoraro
4 * 4 *
5 * Redistribution and use in source and binary forms, with or without 5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are 6 * modification, are permitted provided that the following conditions are
7 * met: 7 * met:
8 * 8 *
9 * * Redistributions of source code must retain the above copyright 9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer. 10 * notice, this list of conditions and the following disclaimer.
(...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after
118 WebInspector.DOMNode.ShadowRootTypes = { 118 WebInspector.DOMNode.ShadowRootTypes = {
119 UserAgent: "user-agent", 119 UserAgent: "user-agent",
120 Author: "author" 120 Author: "author"
121 } 121 }
122 122
123 /** 123 /**
124 * @constructor 124 * @constructor
125 * @param {string} value 125 * @param {string} value
126 * @param {boolean} optimized 126 * @param {boolean} optimized
127 */ 127 */
128 WebInspector.DOMNode.XPathStep = function(value, optimized) 128 WebInspector.DOMNode.PathStep = function(value, optimized)
129 { 129 {
130 this.value = value; 130 this.value = value;
131 this.optimized = optimized; 131 this.optimized = optimized;
132 } 132 }
133 133
134 WebInspector.DOMNode.XPathStep.prototype = { 134 WebInspector.DOMNode.PathStep.prototype = {
135 toString: function() 135 toString: function()
136 { 136 {
137 return this.value; 137 return this.value;
138 } 138 }
139 } 139 }
140 140
141 WebInspector.DOMNode.prototype = { 141 WebInspector.DOMNode.prototype = {
142 /** 142 /**
143 * @return {Array.<WebInspector.DOMNode>} 143 * @return {Array.<WebInspector.DOMNode>}
144 */ 144 */
(...skipping 278 matching lines...) Expand 10 before | Expand all | Expand 10 after
423 { 423 {
424 if (!error) 424 if (!error)
425 InspectorFrontendHost.copyText(text); 425 InspectorFrontendHost.copyText(text);
426 } 426 }
427 DOMAgent.getOuterHTML(this.id, copy); 427 DOMAgent.getOuterHTML(this.id, copy);
428 }, 428 },
429 429
430 /** 430 /**
431 * @param {boolean} optimized 431 * @param {boolean} optimized
432 */ 432 */
433 copyCSSPath: function(optimized)
434 {
435 InspectorFrontendHost.copyText(this.cssPath(optimized));
436 },
437
438 /**
439 * @param {boolean} optimized
440 */
433 copyXPath: function(optimized) 441 copyXPath: function(optimized)
434 { 442 {
435 InspectorFrontendHost.copyText(this.xPath(optimized)); 443 InspectorFrontendHost.copyText(this.xPath(optimized));
436 }, 444 },
437 445
438 /** 446 /**
439 * @param {string} objectGroupId 447 * @param {string} objectGroupId
440 * @param {function(?Protocol.Error)=} callback 448 * @param {function(?Protocol.Error)=} callback
441 */ 449 */
442 eventListeners: function(objectGroupId, callback) 450 eventListeners: function(objectGroupId, callback)
(...skipping 235 matching lines...) Expand 10 before | Expand all | Expand 10 after
678 */ 686 */
679 isXMLNode: function() 687 isXMLNode: function()
680 { 688 {
681 return !!this.ownerDocument && !!this.ownerDocument.xmlVersion; 689 return !!this.ownerDocument && !!this.ownerDocument.xmlVersion;
682 }, 690 },
683 691
684 /** 692 /**
685 * @param {boolean} optimized 693 * @param {boolean} optimized
686 * @return {string} 694 * @return {string}
687 */ 695 */
696 cssPath: function(optimized)
pfeldman 2013/11/20 14:08:48 This interesting little feature is about to become
apavlov 2013/11/20 14:23:19 Sounds good.
697 {
698 if (this._nodeType !== Node.ELEMENT_NODE)
699 return "";
700
701 var steps = [];
702 var contextNode = this;
703 while (contextNode) {
704 var step = contextNode._cssPathValue(optimized);
705 if (!step)
706 break; // Error - bail out early.
707 steps.push(step);
708 if (step.optimized)
709 break;
710 contextNode = contextNode.parentNode;
711 }
712
713 steps.reverse();
714 return steps.join(" > ");
715 },
716
717 /**
718 * @param {boolean} optimized
719 * @return {WebInspector.DOMNode.PathStep}
720 */
721 _cssPathValue: function(optimized)
722 {
723 if (this._nodeType !== Node.ELEMENT_NODE)
724 return null;
725 if (optimized) {
726 var id = this.getAttribute("id");
727 if (id)
728 return new WebInspector.DOMNode.PathStep(idSelector(id), true);
729 var nodeNameLower = this._nodeName.toLowerCase();
730 if (nodeNameLower === "body" || nodeNameLower === "head" || nodeName Lower === "html")
731 return new WebInspector.DOMNode.PathStep(this.nodeNameInCorrectC ase(), true);
732 }
733 var nodeName = this.nodeNameInCorrectCase();
734 var parent = this.parentNode;
735 if (!parent || parent._nodeType === Node.DOCUMENT_NODE)
736 return new WebInspector.DOMNode.PathStep(nodeName, true);
737
738 /**
739 * @param {WebInspector.DOMNode} node
740 * @return {Object.<string, boolean>}
741 */
742 function elementClassNames(node)
743 {
744 var classAttribute = node.getAttribute("class");
745 if (!classAttribute)
746 return {};
747
748 return classAttribute.split(/\s+/g).filter(Boolean).keySet();
749 }
750
751 /**
752 * @param {string} id
753 * @return {string}
754 */
755 function idSelector(id)
756 {
757 return "#" + escapeIdentifierIfNeeded(id);
758 }
759
760 /**
761 * @param {string} ident
762 * @return {string}
763 */
764 function escapeIdentifierIfNeeded(ident)
765 {
766 if (isCSSIdentifier(ident))
767 return ident;
768 var result = "";
769 var shouldEscapeFirst = /^(?:[0-9]|-[0-9-]?)/.test(ident);
aandrey 2013/11/20 12:49:02 let's use similar regex to the one you already use
apavlov 2013/11/20 13:15:28 This regex does not cover the case of an identifie
aandrey 2013/11/20 16:20:49 in console: /^(?:[0-9]|-[0-9-]?)/.test("--a") !/^
apavlov 2013/11/21 07:31:35 Right. But I know what breaks. Valid unicode chars
770 var lastIndex = ident.length - 1;
771 for (var i = 0; i <= lastIndex; ++i) {
772 var c = ident[i];
773 result += ((!i && shouldEscapeFirst) || !isCSSIdentChar(c)) ? es capeAsciiChar(c, i === lastIndex) : c;
aandrey 2013/11/20 12:49:02 nit: hard to read, but I could not simplify this n
apavlov 2013/11/20 13:15:28 Fine with that, even though running this code migh
774 }
775 return result;
776 }
777
778 /**
779 * @param {string} c
780 * @param {boolean} isLast
781 * @return {string}
782 */
783 function escapeAsciiChar(c, isLast)
784 {
785 return "\\" + toHexByte(c) + (isLast ? "" : " ");
786 }
787
788 /**
789 * @param {string} c
790 */
791 function toHexByte(c)
792 {
793 var hexByte = c.charCodeAt(0).toString(16);
794 if (hexByte.length === 1)
795 hexByte = "0" + hexByte;
796 return hexByte;
797 }
798
799 /**
800 * @param {string} c
801 * @return {boolean}
802 */
803 function isCSSIdentChar(c)
804 {
805 if (/[a-zA-Z0-9_-]/.test(c))
aandrey 2013/11/20 12:49:02 /^[a-zA-Z0-9_-]$/
apavlov 2013/11/20 13:15:28 Does it make much sense given that |c| is guarante
aandrey 2013/11/20 16:20:49 I think adding ^ and $ restrictions should always
apavlov 2013/11/21 07:31:35 That's the case for multichar strings. In our case
806 return true;
807 return c.charCodeAt(0) >= 0xA0;
808 }
809
810 /**
811 * @param {string} value
812 * @return {boolean}
813 */
814 function isCSSIdentifier(value)
pfeldman 2013/11/20 14:08:48 Also, DOM should not know this much about CSS
apavlov 2013/11/20 14:23:19 I'm fine with adding a lazy-loaded class. Or will
815 {
816 return /^-?[a-zA-Z_][a-zA-Z0-9_-]*$/.test(value);
817 }
818
819 var uniqueClassNames = elementClassNames(this);
820 var uniqueClassNamesLeft = 0;
821 for (var name in uniqueClassNames)
822 ++uniqueClassNamesLeft;
823 var needsClassNames = false;
824 var needsNthChild = false;
825 var ownIndex = -1;
826 var siblings = parent.children();
827 for (var i = 0; (ownIndex === -1 || !needsNthChild) && i < siblings.leng th; ++i) {
828 var sibling = siblings[i];
829 if (sibling === this) {
830 ownIndex = i;
831 continue;
832 }
833 if (sibling.nodeNameInCorrectCase() !== nodeName)
834 continue;
835 if (!uniqueClassNamesLeft) {
836 needsNthChild = true;
837 continue;
838 }
839
840 needsClassNames = true;
841 var siblingClassNames = elementClassNames(sibling);
842 for (var siblingClass in siblingClassNames) {
843 if (!uniqueClassNames.hasOwnProperty(siblingClass))
844 continue;
845 delete uniqueClassNames[siblingClass];
846 if (!--uniqueClassNamesLeft) {
847 needsNthChild = true;
848 break;
849 }
850 }
851 }
852
853 var result = nodeName;
854 if (needsClassNames) {
855 for (var className in uniqueClassNames)
856 result += "." + escapeIdentifierIfNeeded(className);
857 }
858 if (needsNthChild)
859 result += ":nth-child(" + (ownIndex + 1) + ")";
860 return new WebInspector.DOMNode.PathStep(result, false);
861 },
862
863 /**
864 * @param {boolean} optimized
865 * @return {string}
866 */
688 xPath: function(optimized) 867 xPath: function(optimized)
689 { 868 {
690 if (this._nodeType === Node.DOCUMENT_NODE) 869 if (this._nodeType === Node.DOCUMENT_NODE)
691 return "/"; 870 return "/";
692 871
693 var steps = []; 872 var steps = [];
694 var contextNode = this; 873 var contextNode = this;
695 while (contextNode) { 874 while (contextNode) {
696 var step = contextNode._xPathValue(optimized); 875 var step = contextNode._xPathValue(optimized);
697 if (!step) 876 if (!step)
698 break; // Error - bail out early. 877 break; // Error - bail out early.
699 steps.push(step); 878 steps.push(step);
700 if (step.optimized) 879 if (step.optimized)
701 break; 880 break;
702 contextNode = contextNode.parentNode; 881 contextNode = contextNode.parentNode;
703 } 882 }
704 883
705 steps.reverse(); 884 steps.reverse();
706 return (steps.length && steps[0].optimized ? "" : "/") + steps.join("/") ; 885 return (steps.length && steps[0].optimized ? "" : "/") + steps.join("/") ;
707 }, 886 },
708 887
709 /** 888 /**
710 * @param {boolean} optimized 889 * @param {boolean} optimized
711 * @return {WebInspector.DOMNode.XPathStep} 890 * @return {WebInspector.DOMNode.PathStep}
712 */ 891 */
713 _xPathValue: function(optimized) 892 _xPathValue: function(optimized)
714 { 893 {
715 var ownValue; 894 var ownValue;
716 var ownIndex = this._xPathIndex(); 895 var ownIndex = this._xPathIndex();
717 if (ownIndex === -1) 896 if (ownIndex === -1)
718 return null; // Error. 897 return null; // Error.
719 898
720 switch (this._nodeType) { 899 switch (this._nodeType) {
721 case Node.ELEMENT_NODE: 900 case Node.ELEMENT_NODE:
722 if (optimized && this.getAttribute("id")) 901 if (optimized && this.getAttribute("id"))
723 return new WebInspector.DOMNode.XPathStep("//*[@id=\"" + this.ge tAttribute("id") + "\"]", true); 902 return new WebInspector.DOMNode.PathStep("//*[@id=\"" + this.get Attribute("id") + "\"]", true);
724 ownValue = this._localName; 903 ownValue = this._localName;
725 break; 904 break;
726 case Node.ATTRIBUTE_NODE: 905 case Node.ATTRIBUTE_NODE:
727 ownValue = "@" + this._nodeName; 906 ownValue = "@" + this._nodeName;
728 break; 907 break;
729 case Node.TEXT_NODE: 908 case Node.TEXT_NODE:
730 case Node.CDATA_SECTION_NODE: 909 case Node.CDATA_SECTION_NODE:
731 ownValue = "text()"; 910 ownValue = "text()";
732 break; 911 break;
733 case Node.PROCESSING_INSTRUCTION_NODE: 912 case Node.PROCESSING_INSTRUCTION_NODE:
734 ownValue = "processing-instruction()"; 913 ownValue = "processing-instruction()";
735 break; 914 break;
736 case Node.COMMENT_NODE: 915 case Node.COMMENT_NODE:
737 ownValue = "comment()"; 916 ownValue = "comment()";
738 break; 917 break;
739 case Node.DOCUMENT_NODE: 918 case Node.DOCUMENT_NODE:
740 ownValue = ""; 919 ownValue = "";
741 break; 920 break;
742 default: 921 default:
743 ownValue = ""; 922 ownValue = "";
744 break; 923 break;
745 } 924 }
746 925
747 if (ownIndex > 0) 926 if (ownIndex > 0)
748 ownValue += "[" + ownIndex + "]"; 927 ownValue += "[" + ownIndex + "]";
749 928
750 return new WebInspector.DOMNode.XPathStep(ownValue, this._nodeType === N ode.DOCUMENT_NODE); 929 return new WebInspector.DOMNode.PathStep(ownValue, this._nodeType === No de.DOCUMENT_NODE);
751 }, 930 },
752 931
753 /** 932 /**
754 * @return {number} 933 * @return {number}
755 */ 934 */
756 _xPathIndex: function() 935 _xPathIndex: function()
757 { 936 {
758 // Returns -1 in case of error, 0 if no siblings matching the same expre ssion, <XPath index among the same expression-matching sibling nodes> otherwise. 937 // Returns -1 in case of error, 0 if no siblings matching the same expre ssion, <XPath index among the same expression-matching sibling nodes> otherwise.
759 function areNodesSimilar(left, right) 938 function areNodesSimilar(left, right)
760 { 939 {
(...skipping 988 matching lines...) Expand 10 before | Expand all | Expand 10 after
1749 setInspectModeEnabled: function(enabled, inspectShadowDOM, config, callback) 1928 setInspectModeEnabled: function(enabled, inspectShadowDOM, config, callback)
1750 { 1929 {
1751 DOMAgent.setInspectModeEnabled(enabled, inspectShadowDOM, config, callba ck); 1930 DOMAgent.setInspectModeEnabled(enabled, inspectShadowDOM, config, callba ck);
1752 } 1931 }
1753 } 1932 }
1754 1933
1755 /** 1934 /**
1756 * @type {?WebInspector.DOMAgent} 1935 * @type {?WebInspector.DOMAgent}
1757 */ 1936 */
1758 WebInspector.domAgent = null; 1937 WebInspector.domAgent = null;
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698