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

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: More comments addressed, __proto__ bug fixed 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 102 matching lines...) Expand 10 before | Expand all | Expand 10 after
113 WebInspector.DOMNode.PseudoElementNames = { 113 WebInspector.DOMNode.PseudoElementNames = {
114 Before: "before", 114 Before: "before",
115 After: "after" 115 After: "after"
116 } 116 }
117 117
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 /**
124 * @constructor
125 * @param {string} value
126 * @param {boolean} optimized
127 */
128 WebInspector.DOMNode.XPathStep = function(value, optimized)
129 {
130 this.value = value;
131 this.optimized = optimized;
132 }
133
134 WebInspector.DOMNode.XPathStep.prototype = {
135 toString: function()
136 {
137 return this.value;
138 }
139 }
140
141 WebInspector.DOMNode.prototype = { 123 WebInspector.DOMNode.prototype = {
142 /** 124 /**
143 * @return {Array.<WebInspector.DOMNode>} 125 * @return {Array.<WebInspector.DOMNode>}
144 */ 126 */
145 children: function() 127 children: function()
146 { 128 {
147 return this._children ? this._children.slice() : null; 129 return this._children ? this._children.slice() : null;
148 }, 130 },
149 131
150 /** 132 /**
(...skipping 272 matching lines...) Expand 10 before | Expand all | Expand 10 after
423 { 405 {
424 if (!error) 406 if (!error)
425 InspectorFrontendHost.copyText(text); 407 InspectorFrontendHost.copyText(text);
426 } 408 }
427 DOMAgent.getOuterHTML(this.id, copy); 409 DOMAgent.getOuterHTML(this.id, copy);
428 }, 410 },
429 411
430 /** 412 /**
431 * @param {boolean} optimized 413 * @param {boolean} optimized
432 */ 414 */
415 copyCSSPath: function(optimized)
416 {
417 InspectorFrontendHost.copyText(this.cssPath(optimized));
418 },
419
420 /**
421 * @param {boolean} optimized
422 */
433 copyXPath: function(optimized) 423 copyXPath: function(optimized)
434 { 424 {
435 InspectorFrontendHost.copyText(this.xPath(optimized)); 425 InspectorFrontendHost.copyText(this.xPath(optimized));
436 }, 426 },
437 427
438 /** 428 /**
439 * @param {string} objectGroupId 429 * @param {string} objectGroupId
440 * @param {function(?Protocol.Error)=} callback 430 * @param {function(?Protocol.Error)=} callback
441 */ 431 */
442 eventListeners: function(objectGroupId, callback) 432 eventListeners: function(objectGroupId, callback)
(...skipping 16 matching lines...) Expand all
459 return path.join(","); 449 return path.join(",");
460 }, 450 },
461 451
462 /** 452 /**
463 * @param {boolean} justSelector 453 * @param {boolean} justSelector
464 * @return {string} 454 * @return {string}
465 */ 455 */
466 appropriateSelectorFor: function(justSelector) 456 appropriateSelectorFor: function(justSelector)
467 { 457 {
468 var lowerCaseName = this.localName() || this.nodeName().toLowerCase(); 458 var lowerCaseName = this.localName() || this.nodeName().toLowerCase();
469 459 if (this._nodeType !== Node.ELEMENT_NODE)
470 var id = this.getAttribute("id"); 460 return lowerCaseName;
471 if (id) {
472 var selector = "#" + id;
473 return (justSelector ? selector : lowerCaseName + selector);
474 }
475
476 var className = this.getAttribute("class");
477 if (className) {
478 var selector = "." + className.trim().replace(/\s+/g, ".");
479 return (justSelector ? selector : lowerCaseName + selector);
480 }
481
482 if (lowerCaseName === "input" && this.getAttribute("type")) 461 if (lowerCaseName === "input" && this.getAttribute("type"))
483 return lowerCaseName + "[type=\"" + this.getAttribute("type") + "\"] "; 462 return lowerCaseName + "[type=\"" + this.getAttribute("type") + "\"] ";
484 463
485 return lowerCaseName; 464 return this.cssPath(justSelector);
486 }, 465 },
487 466
488 /** 467 /**
489 * @param {WebInspector.DOMNode} node 468 * @param {WebInspector.DOMNode} node
490 * @return {boolean} 469 * @return {boolean}
491 */ 470 */
492 isAncestor: function(node) 471 isAncestor: function(node)
493 { 472 {
494 if (!node) 473 if (!node)
495 return false; 474 return false;
(...skipping 182 matching lines...) Expand 10 before | Expand all | Expand 10 after
678 */ 657 */
679 isXMLNode: function() 658 isXMLNode: function()
680 { 659 {
681 return !!this.ownerDocument && !!this.ownerDocument.xmlVersion; 660 return !!this.ownerDocument && !!this.ownerDocument.xmlVersion;
682 }, 661 },
683 662
684 /** 663 /**
685 * @param {boolean} optimized 664 * @param {boolean} optimized
686 * @return {string} 665 * @return {string}
687 */ 666 */
667 cssPath: function(optimized)
668 {
669 if (this._nodeType !== Node.ELEMENT_NODE)
670 return "";
671
672 var steps = [];
673 var contextNode = this;
674 while (contextNode) {
675 var step = WebInspector.DOMPresentationUtils.cssPathValue(contextNod e, optimized);
aandrey 2013/11/21 10:17:34 there is a dependency problem: "sdk" module (conta
676 if (!step)
677 break; // Error - bail out early.
678 steps.push(step);
679 if (step.optimized)
680 break;
681 contextNode = contextNode.parentNode;
682 }
683
684 steps.reverse();
685 return steps.join(" > ");
686 },
687
688 /**
689 * @param {boolean} optimized
690 * @return {WebInspector.DOMNodePathStep}
691 */
692 _cssPathValue: function(optimized)
aandrey 2013/11/21 10:17:34 remove this method
693 {
694 if (this._nodeType !== Node.ELEMENT_NODE)
695 return null;
696 if (optimized) {
697 var id = this.getAttribute("id");
698 if (id)
699 return new WebInspector.DOMNodePathStep(idSelector(id), true);
700 var nodeNameLower = this._nodeName.toLowerCase();
701 if (nodeNameLower === "body" || nodeNameLower === "head" || nodeName Lower === "html")
702 return new WebInspector.DOMNodePathStep(this.nodeNameInCorrectCa se(), true);
703 }
704 var nodeName = this.nodeNameInCorrectCase();
705 var parent = this.parentNode;
706 if (!parent || parent._nodeType === Node.DOCUMENT_NODE)
707 return new WebInspector.DOMNodePathStep(nodeName, true);
708
709 /**
710 * @param {WebInspector.DOMNode} node
711 * @return {Array.<string>}
712 */
713 function elementClassNames(node)
714 {
715 var classAttribute = node.getAttribute("class");
716 if (!classAttribute)
717 return [];
718
719 return classAttribute.split(/\s+/g).filter(Boolean);
720 }
721
722 /**
723 * @param {string} id
724 * @return {string}
725 */
726 function idSelector(id)
727 {
728 return "#" + escapeIdentifierIfNeeded(id);
729 }
730
731 /**
732 * @param {string} ident
733 * @return {string}
734 */
735 function escapeIdentifierIfNeeded(ident)
736 {
737 if (isCSSIdentifier(ident))
738 return ident;
739 var shouldEscapeFirst = /^(?:[0-9]|-[0-9-]?)/.test(ident);
740 var lastIndex = ident.length - 1;
741 return ident.replace(/./g, function(c, i) {
742 return ((shouldEscapeFirst && i === 0) || !isCSSIdentChar(c)) ? escapeAsciiChar(c, i === lastIndex) : c;
743 });
744 }
745
746 /**
747 * @param {string} c
748 * @param {boolean} isLast
749 * @return {string}
750 */
751 function escapeAsciiChar(c, isLast)
752 {
753 return "\\" + toHexByte(c) + (isLast ? "" : " ");
754 }
755
756 /**
757 * @param {string} c
758 */
759 function toHexByte(c)
760 {
761 var hexByte = c.charCodeAt(0).toString(16);
762 if (hexByte.length === 1)
763 hexByte = "0" + hexByte;
764 return hexByte;
765 }
766
767 /**
768 * @param {string} c
769 * @return {boolean}
770 */
771 function isCSSIdentChar(c)
772 {
773 if (/[a-zA-Z0-9_-]/.test(c))
774 return true;
775 return c.charCodeAt(0) >= 0xA0;
776 }
777
778 /**
779 * @param {string} value
780 * @return {boolean}
781 */
782 function isCSSIdentifier(value)
783 {
784 return /^-?[a-zA-Z_][a-zA-Z0-9_-]*$/.test(value);
785 }
786
787 var uniqueClassNamesArray = elementClassNames(this);
788 var needsClassNames = false;
789 var needsNthChild = false;
790 var ownIndex = -1;
791 var siblings = parent.children();
792 for (var i = 0; (ownIndex === -1 || !needsNthChild) && i < siblings.leng th; ++i) {
793 var sibling = siblings[i];
794 if (sibling === this) {
795 ownIndex = i;
796 continue;
797 }
798 if (needsNthChild)
799 continue;
800 if (sibling.nodeNameInCorrectCase() !== nodeName)
801 continue;
802
803 needsClassNames = true;
804 var ownClassNames = uniqueClassNamesArray.keySet();
805 var ownClassNameCount = 0;
806 for (var name in ownClassNames)
807 ++ownClassNameCount;
808 if (ownClassNameCount === 0) {
809 needsNthChild = true;
810 continue;
811 }
812 var siblingClassNames = elementClassNames(sibling).keySet();
813 for (var siblingClass in siblingClassNames) {
814 if (!ownClassNames.hasOwnProperty(siblingClass))
815 continue;
816 delete ownClassNames[siblingClass];
817 if (!--ownClassNameCount) {
818 needsNthChild = true;
819 break;
820 }
821 }
822 }
823
824 var result = nodeName;
825 if (needsNthChild) {
826 result += ":nth-child(" + (ownIndex + 1) + ")";
827 } else if (needsClassNames) {
828 for (var name in uniqueClassNamesArray.keySet())
829 result += "." + escapeIdentifierIfNeeded(name);
830 }
831 return new WebInspector.DOMNodePathStep(result, false);
832 },
833
834 /**
835 * @param {boolean} optimized
836 * @return {string}
837 */
688 xPath: function(optimized) 838 xPath: function(optimized)
689 { 839 {
690 if (this._nodeType === Node.DOCUMENT_NODE) 840 if (this._nodeType === Node.DOCUMENT_NODE)
691 return "/"; 841 return "/";
692 842
693 var steps = []; 843 var steps = [];
694 var contextNode = this; 844 var contextNode = this;
695 while (contextNode) { 845 while (contextNode) {
696 var step = contextNode._xPathValue(optimized); 846 var step = contextNode._xPathValue(optimized);
697 if (!step) 847 if (!step)
698 break; // Error - bail out early. 848 break; // Error - bail out early.
699 steps.push(step); 849 steps.push(step);
700 if (step.optimized) 850 if (step.optimized)
701 break; 851 break;
702 contextNode = contextNode.parentNode; 852 contextNode = contextNode.parentNode;
703 } 853 }
704 854
705 steps.reverse(); 855 steps.reverse();
706 return (steps.length && steps[0].optimized ? "" : "/") + steps.join("/") ; 856 return (steps.length && steps[0].optimized ? "" : "/") + steps.join("/") ;
707 }, 857 },
708 858
709 /** 859 /**
710 * @param {boolean} optimized 860 * @param {boolean} optimized
711 * @return {WebInspector.DOMNode.XPathStep} 861 * @return {WebInspector.DOMNodePathStep}
712 */ 862 */
713 _xPathValue: function(optimized) 863 _xPathValue: function(optimized)
714 { 864 {
715 var ownValue; 865 var ownValue;
716 var ownIndex = this._xPathIndex(); 866 var ownIndex = this._xPathIndex();
717 if (ownIndex === -1) 867 if (ownIndex === -1)
718 return null; // Error. 868 return null; // Error.
719 869
720 switch (this._nodeType) { 870 switch (this._nodeType) {
721 case Node.ELEMENT_NODE: 871 case Node.ELEMENT_NODE:
722 if (optimized && this.getAttribute("id")) 872 if (optimized && this.getAttribute("id"))
723 return new WebInspector.DOMNode.XPathStep("//*[@id=\"" + this.ge tAttribute("id") + "\"]", true); 873 return new WebInspector.DOMNodePathStep("//*[@id=\"" + this.getA ttribute("id") + "\"]", true);
724 ownValue = this._localName; 874 ownValue = this._localName;
725 break; 875 break;
726 case Node.ATTRIBUTE_NODE: 876 case Node.ATTRIBUTE_NODE:
727 ownValue = "@" + this._nodeName; 877 ownValue = "@" + this._nodeName;
728 break; 878 break;
729 case Node.TEXT_NODE: 879 case Node.TEXT_NODE:
730 case Node.CDATA_SECTION_NODE: 880 case Node.CDATA_SECTION_NODE:
731 ownValue = "text()"; 881 ownValue = "text()";
732 break; 882 break;
733 case Node.PROCESSING_INSTRUCTION_NODE: 883 case Node.PROCESSING_INSTRUCTION_NODE:
734 ownValue = "processing-instruction()"; 884 ownValue = "processing-instruction()";
735 break; 885 break;
736 case Node.COMMENT_NODE: 886 case Node.COMMENT_NODE:
737 ownValue = "comment()"; 887 ownValue = "comment()";
738 break; 888 break;
739 case Node.DOCUMENT_NODE: 889 case Node.DOCUMENT_NODE:
740 ownValue = ""; 890 ownValue = "";
741 break; 891 break;
742 default: 892 default:
743 ownValue = ""; 893 ownValue = "";
744 break; 894 break;
745 } 895 }
746 896
747 if (ownIndex > 0) 897 if (ownIndex > 0)
748 ownValue += "[" + ownIndex + "]"; 898 ownValue += "[" + ownIndex + "]";
749 899
750 return new WebInspector.DOMNode.XPathStep(ownValue, this._nodeType === N ode.DOCUMENT_NODE); 900 return new WebInspector.DOMNodePathStep(ownValue, this._nodeType === Nod e.DOCUMENT_NODE);
751 }, 901 },
752 902
753 /** 903 /**
754 * @return {number} 904 * @return {number}
755 */ 905 */
756 _xPathIndex: function() 906 _xPathIndex: function()
757 { 907 {
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. 908 // 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) 909 function areNodesSimilar(left, right)
760 { 910 {
(...skipping 988 matching lines...) Expand 10 before | Expand all | Expand 10 after
1749 setInspectModeEnabled: function(enabled, inspectShadowDOM, config, callback) 1899 setInspectModeEnabled: function(enabled, inspectShadowDOM, config, callback)
1750 { 1900 {
1751 DOMAgent.setInspectModeEnabled(enabled, inspectShadowDOM, config, callba ck); 1901 DOMAgent.setInspectModeEnabled(enabled, inspectShadowDOM, config, callba ck);
1752 } 1902 }
1753 } 1903 }
1754 1904
1755 /** 1905 /**
1756 * @type {?WebInspector.DOMAgent} 1906 * @type {?WebInspector.DOMAgent}
1757 */ 1907 */
1758 WebInspector.domAgent = null; 1908 WebInspector.domAgent = null;
OLDNEW
« no previous file with comments | « LayoutTests/inspector/elements/elements-css-path-expected.txt ('k') | Source/devtools/front_end/DOMPresentationUtils.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698