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

Side by Side Diff: Source/devtools/front_end/DOMPresentationUtils.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: Tentatively fix xpath test on Windows bot 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
« no previous file with comments | « Source/devtools/front_end/DOMAgent.js ('k') | Source/devtools/front_end/ElementsTreeOutline.js » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 /* 1 /*
2 * Copyright (C) 2011 Google Inc. All rights reserved. 2 * Copyright (C) 2011 Google Inc. All rights reserved.
3 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. 3 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
4 * Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com> 4 * Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com>
5 * Copyright (C) 2009 Joseph Pecoraro 5 * Copyright (C) 2009 Joseph Pecoraro
6 * 6 *
7 * Redistribution and use in source and binary forms, with or without 7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions 8 * modification, are permitted provided that the following conditions
9 * are met: 9 * are met:
10 * 10 *
(...skipping 149 matching lines...) Expand 10 before | Expand all | Expand 10 after
160 } 160 }
161 161
162 /** 162 /**
163 * @param {!WebInspector.DOMNode} node 163 * @param {!WebInspector.DOMNode} node
164 * @param {boolean=} justSelector 164 * @param {boolean=} justSelector
165 * @return {string} 165 * @return {string}
166 */ 166 */
167 WebInspector.DOMPresentationUtils.appropriateSelectorFor = function(node, justSe lector) 167 WebInspector.DOMPresentationUtils.appropriateSelectorFor = function(node, justSe lector)
168 { 168 {
169 var lowerCaseName = node.localName() || node.nodeName().toLowerCase(); 169 var lowerCaseName = node.localName() || node.nodeName().toLowerCase();
170 if (node.nodeType() !== Node.ELEMENT_NODE)
171 return lowerCaseName;
172 if (lowerCaseName === "input" && node.getAttribute("type") && !node.getAttri bute("id") && !node.getAttribute("class"))
173 return lowerCaseName + "[type=\"" + node.getAttribute("type") + "\"]";
174
175 return WebInspector.DOMPresentationUtils.cssPath(node, justSelector);
176 }
177
178 /**
179 * @param {!WebInspector.DOMNode} node
180 * @param {boolean=} optimized
181 * @return {string}
182 */
183 WebInspector.DOMPresentationUtils.cssPath = function(node, optimized)
184 {
185 if (node.nodeType() !== Node.ELEMENT_NODE)
186 return "";
187
188 var steps = [];
189 var contextNode = node;
190 while (contextNode) {
191 var step = WebInspector.DOMPresentationUtils._cssPathValue(contextNode, optimized);
192 if (!step)
193 break; // Error - bail out early.
194 steps.push(step);
195 if (step.optimized)
196 break;
197 contextNode = contextNode.parentNode;
198 }
199
200 steps.reverse();
201 return steps.join(" > ");
202 }
203
204 /**
205 * @param {!WebInspector.DOMNode} node
206 * @param {boolean=} optimized
207 * @return {WebInspector.DOMNodePathStep}
208 */
209 WebInspector.DOMPresentationUtils._cssPathValue = function(node, optimized)
210 {
211 if (node.nodeType() !== Node.ELEMENT_NODE)
212 return null;
170 213
171 var id = node.getAttribute("id"); 214 var id = node.getAttribute("id");
172 if (id) { 215 if (optimized) {
173 var selector = "#" + id; 216 if (id)
174 return (justSelector ? selector : lowerCaseName + selector); 217 return new WebInspector.DOMNodePathStep(idSelector(id), true);
175 } 218 var nodeNameLower = node.nodeName().toLowerCase();
176 219 if (nodeNameLower === "body" || nodeNameLower === "head" || nodeNameLowe r === "html")
177 var className = node.getAttribute("class"); 220 return new WebInspector.DOMNodePathStep(node.nodeNameInCorrectCase() , true);
178 if (className) { 221 }
179 var selector = "." + className.trim().replace(/\s+/g, "."); 222 var nodeName = node.nodeNameInCorrectCase();
180 return (justSelector ? selector : lowerCaseName + selector); 223
181 } 224 if (id)
182 225 return new WebInspector.DOMNodePathStep(nodeName + idSelector(id), true) ;
183 if (lowerCaseName === "input" && node.getAttribute("type")) 226 var parent = node.parentNode;
184 return lowerCaseName + "[type=\"" + node.getAttribute("type") + "\"]"; 227 if (!parent || parent.nodeType() === Node.DOCUMENT_NODE)
185 228 return new WebInspector.DOMNodePathStep(nodeName, true);
186 return lowerCaseName; 229
187 } 230 /**
231 * @param {WebInspector.DOMNode} node
232 * @return {Array.<string>}
233 */
234 function prefixedElementClassNames(node)
235 {
236 var classAttribute = node.getAttribute("class");
237 if (!classAttribute)
238 return [];
239
240 return classAttribute.split(/\s+/g).filter(Boolean).map(function(name) {
241 // The prefix is required to store "__proto__" in a object-based map .
242 return "$" + name;
243 });
244 }
245
246 /**
247 * @param {string} id
248 * @return {string}
249 */
250 function idSelector(id)
251 {
252 return "#" + escapeIdentifierIfNeeded(id);
253 }
254
255 /**
256 * @param {string} ident
257 * @return {string}
258 */
259 function escapeIdentifierIfNeeded(ident)
260 {
261 if (isCSSIdentifier(ident))
262 return ident;
263 var shouldEscapeFirst = /^(?:[0-9]|-[0-9-]?)/.test(ident);
264 var lastIndex = ident.length - 1;
265 return ident.replace(/./g, function(c, i) {
266 return ((shouldEscapeFirst && i === 0) || !isCSSIdentChar(c)) ? esca peAsciiChar(c, i === lastIndex) : c;
267 });
268 }
269
270 /**
271 * @param {string} c
272 * @param {boolean} isLast
273 * @return {string}
274 */
275 function escapeAsciiChar(c, isLast)
276 {
277 return "\\" + toHexByte(c) + (isLast ? "" : " ");
278 }
279
280 /**
281 * @param {string} c
282 */
283 function toHexByte(c)
284 {
285 var hexByte = c.charCodeAt(0).toString(16);
286 if (hexByte.length === 1)
287 hexByte = "0" + hexByte;
288 return hexByte;
289 }
290
291 /**
292 * @param {string} c
293 * @return {boolean}
294 */
295 function isCSSIdentChar(c)
296 {
297 if (/[a-zA-Z0-9_-]/.test(c))
298 return true;
299 return c.charCodeAt(0) >= 0xA0;
300 }
301
302 /**
303 * @param {string} value
304 * @return {boolean}
305 */
306 function isCSSIdentifier(value)
307 {
308 return /^-?[a-zA-Z_][a-zA-Z0-9_-]*$/.test(value);
309 }
310
311 var prefixedOwnClassNamesArray = prefixedElementClassNames(node);
312 var needsClassNames = false;
313 var needsNthChild = false;
314 var ownIndex = -1;
315 var siblings = parent.children();
316 for (var i = 0; (ownIndex === -1 || !needsNthChild) && i < siblings.length; ++i) {
317 var sibling = siblings[i];
318 if (sibling === node) {
319 ownIndex = i;
320 continue;
321 }
322 if (needsNthChild)
323 continue;
324 if (sibling.nodeNameInCorrectCase() !== nodeName)
325 continue;
326
327 needsClassNames = true;
328 var ownClassNames = prefixedOwnClassNamesArray.keySet();
329 var ownClassNameCount = 0;
330 for (var name in ownClassNames)
331 ++ownClassNameCount;
332 if (ownClassNameCount === 0) {
333 needsNthChild = true;
334 continue;
335 }
336 var siblingClassNamesArray = prefixedElementClassNames(sibling);
337 for (var j = 0; j < siblingClassNamesArray.length; ++j) {
338 var siblingClass = siblingClassNamesArray[j];
339 if (!ownClassNames.hasOwnProperty(siblingClass))
340 continue;
341 delete ownClassNames[siblingClass];
342 if (!--ownClassNameCount) {
343 needsNthChild = true;
344 break;
345 }
346 }
347 }
348
349 var result = nodeName;
350 if (needsNthChild) {
351 result += ":nth-child(" + (ownIndex + 1) + ")";
352 } else if (needsClassNames) {
353 for (var prefixedName in prefixedOwnClassNamesArray.keySet())
354 result += "." + escapeIdentifierIfNeeded(prefixedName.substr(1));
355 }
356
357 return new WebInspector.DOMNodePathStep(result, false);
358 }
359
360 /**
361 * @param {!WebInspector.DOMNode} node
362 * @param {boolean=} optimized
363 * @return {string}
364 */
365 WebInspector.DOMPresentationUtils.xPath = function(node, optimized)
366 {
367 if (node.nodeType() === Node.DOCUMENT_NODE)
368 return "/";
369
370 var steps = [];
371 var contextNode = node;
372 while (contextNode) {
373 var step = WebInspector.DOMPresentationUtils._xPathValue(contextNode, op timized);
374 if (!step)
375 break; // Error - bail out early.
376 steps.push(step);
377 if (step.optimized)
378 break;
379 contextNode = contextNode.parentNode;
380 }
381
382 steps.reverse();
383 return (steps.length && steps[0].optimized ? "" : "/") + steps.join("/");
384 }
385
386 /**
387 * @param {!WebInspector.DOMNode} node
388 * @param {boolean=} optimized
389 * @return {WebInspector.DOMNodePathStep}
390 */
391 WebInspector.DOMPresentationUtils._xPathValue = function(node, optimized)
392 {
393 var ownValue;
394 var ownIndex = WebInspector.DOMPresentationUtils._xPathIndex(node);
395 if (ownIndex === -1)
396 return null; // Error.
397
398 switch (node.nodeType()) {
399 case Node.ELEMENT_NODE:
400 if (optimized && node.getAttribute("id"))
401 return new WebInspector.DOMNodePathStep("//*[@id=\"" + node.getAttri bute("id") + "\"]", true);
402 ownValue = node.localName();
403 break;
404 case Node.ATTRIBUTE_NODE:
405 ownValue = "@" + node.nodeName();
406 break;
407 case Node.TEXT_NODE:
408 case Node.CDATA_SECTION_NODE:
409 ownValue = "text()";
410 break;
411 case Node.PROCESSING_INSTRUCTION_NODE:
412 ownValue = "processing-instruction()";
413 break;
414 case Node.COMMENT_NODE:
415 ownValue = "comment()";
416 break;
417 case Node.DOCUMENT_NODE:
418 ownValue = "";
419 break;
420 default:
421 ownValue = "";
422 break;
423 }
424
425 if (ownIndex > 0)
426 ownValue += "[" + ownIndex + "]";
427
428 return new WebInspector.DOMNodePathStep(ownValue, node.nodeType() === Node.D OCUMENT_NODE);
429 },
430
431 /**
432 * @param {!WebInspector.DOMNode} node
433 * @return {number}
434 */
435 WebInspector.DOMPresentationUtils._xPathIndex = function(node)
436 {
437 // Returns -1 in case of error, 0 if no siblings matching the same expressio n, <XPath index among the same expression-matching sibling nodes> otherwise.
438 function areNodesSimilar(left, right)
439 {
440 if (left === right)
441 return true;
442
443 if (left.nodeType() === Node.ELEMENT_NODE && right.nodeType() === Node.E LEMENT_NODE)
444 return left.localName() === right.localName();
445
446 if (left.nodeType() === right.nodeType())
447 return true;
448
449 // XPath treats CDATA as text nodes.
450 var leftType = left.nodeType() === Node.CDATA_SECTION_NODE ? Node.TEXT_N ODE : left.nodeType();
451 var rightType = right.nodeType() === Node.CDATA_SECTION_NODE ? Node.TEXT _NODE : right.nodeType();
452 return leftType === rightType;
453 }
454
455 var siblings = node.parentNode ? node.parentNode.children() : null;
456 if (!siblings)
457 return 0; // Root node - no siblings.
458 var hasSameNamedElements;
459 for (var i = 0; i < siblings.length; ++i) {
460 if (areNodesSimilar(node, siblings[i]) && siblings[i] !== node) {
461 hasSameNamedElements = true;
462 break;
463 }
464 }
465 if (!hasSameNamedElements)
466 return 0;
467 var ownIndex = 1; // XPath indices start with 1.
468 for (var i = 0; i < siblings.length; ++i) {
469 if (areNodesSimilar(node, siblings[i])) {
470 if (siblings[i] === node)
471 return ownIndex;
472 ++ownIndex;
473 }
474 }
475 return -1; // An error occurred: |node| not found in parent's children.
476 }
477
478 /**
479 * @constructor
480 * @param {string} value
481 * @param {boolean} optimized
482 */
483 WebInspector.DOMNodePathStep = function(value, optimized)
484 {
485 this.value = value;
486 this.optimized = optimized || false;
487 }
488
489 WebInspector.DOMNodePathStep.prototype = {
490 /**
491 * @return {string}
492 */
493 toString: function()
494 {
495 return this.value;
496 }
497 }
OLDNEW
« no previous file with comments | « Source/devtools/front_end/DOMAgent.js ('k') | Source/devtools/front_end/ElementsTreeOutline.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698