OLD | NEW |
| (Empty) |
1 /* | |
2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. | |
3 * Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com> | |
4 * Copyright (C) 2009 Joseph Pecoraro | |
5 * | |
6 * Redistribution and use in source and binary forms, with or without | |
7 * modification, are permitted provided that the following conditions | |
8 * are met: | |
9 * | |
10 * 1. Redistributions of source code must retain the above copyright | |
11 * notice, this list of conditions and the following disclaimer. | |
12 * 2. Redistributions in binary form must reproduce the above copyright | |
13 * notice, this list of conditions and the following disclaimer in the | |
14 * documentation and/or other materials provided with the distribution. | |
15 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of | |
16 * its contributors may be used to endorse or promote products derived | |
17 * from this software without specific prior written permission. | |
18 * | |
19 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY | |
20 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
22 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY | |
23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |
26 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | |
28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
29 */ | |
30 | |
31 WebInspector.ElementsTreeOutline = function() { | |
32 this.element = document.createElement("ol"); | |
33 this.element.addEventListener("mousedown", this._onmousedown.bind(this), fal
se); | |
34 this.element.addEventListener("mousemove", this._onmousemove.bind(this), fal
se); | |
35 this.element.addEventListener("mouseout", this._onmouseout.bind(this), false
); | |
36 | |
37 TreeOutline.call(this, this.element); | |
38 | |
39 this.includeRootDOMNode = true; | |
40 this.selectEnabled = false; | |
41 this.showInElementsPanelEnabled = false; | |
42 this.rootDOMNode = null; | |
43 this.focusedDOMNode = null; | |
44 } | |
45 | |
46 WebInspector.ElementsTreeOutline.prototype = { | |
47 get rootDOMNode() | |
48 { | |
49 return this._rootDOMNode; | |
50 }, | |
51 | |
52 set rootDOMNode(x) | |
53 { | |
54 if (this._rootDOMNode === x) | |
55 return; | |
56 | |
57 this._rootDOMNode = x; | |
58 | |
59 this.update(); | |
60 }, | |
61 | |
62 get focusedDOMNode() | |
63 { | |
64 return this._focusedDOMNode; | |
65 }, | |
66 | |
67 set focusedDOMNode(x) | |
68 { | |
69 if (this._focusedDOMNode === x) { | |
70 this.revealAndSelectNode(x); | |
71 return; | |
72 } | |
73 | |
74 this._focusedDOMNode = x; | |
75 | |
76 this.revealAndSelectNode(x); | |
77 | |
78 // The revealAndSelectNode() method might find a different element if th
ere is inlined text, | |
79 // and the select() call would change the focusedDOMNode and reenter thi
s setter. So to | |
80 // avoid calling focusedNodeChanged() twice, first check if _focusedDOMN
ode is the same | |
81 // node as the one passed in. | |
82 if (this._focusedDOMNode === x) { | |
83 this.focusedNodeChanged(); | |
84 | |
85 if (x && !this.suppressSelectHighlight) { | |
86 InspectorController.highlightDOMNode(x.id); | |
87 | |
88 if ("_restorePreviousHighlightNodeTimeout" in this) | |
89 clearTimeout(this._restorePreviousHighlightNodeTimeout); | |
90 | |
91 function restoreHighlightToHoveredNode() | |
92 { | |
93 var hoveredNode = WebInspector.hoveredDOMNode; | |
94 if (hoveredNode) | |
95 InspectorController.highlightDOMNode(hoveredNode.id); | |
96 else | |
97 InspectorController.hideDOMNodeHighlight(); | |
98 } | |
99 | |
100 this._restorePreviousHighlightNodeTimeout = setTimeout(restoreHi
ghlightToHoveredNode, 2000); | |
101 } | |
102 } | |
103 }, | |
104 | |
105 update: function() | |
106 { | |
107 this.removeChildren(); | |
108 | |
109 if (!this.rootDOMNode) | |
110 return; | |
111 | |
112 var treeElement; | |
113 if (this.includeRootDOMNode) { | |
114 treeElement = new WebInspector.ElementsTreeElement(this.rootDOMNode)
; | |
115 treeElement.selectable = this.selectEnabled; | |
116 this.appendChild(treeElement); | |
117 } else { | |
118 // FIXME: this could use findTreeElement to reuse a tree element if
it already exists | |
119 var node = this.rootDOMNode.firstChild; | |
120 while (node) { | |
121 treeElement = new WebInspector.ElementsTreeElement(node); | |
122 treeElement.selectable = this.selectEnabled; | |
123 this.appendChild(treeElement); | |
124 node = node.nextSibling; | |
125 } | |
126 } | |
127 | |
128 this.updateSelection(); | |
129 }, | |
130 | |
131 updateSelection: function() | |
132 { | |
133 if (!this.selectedTreeElement) | |
134 return; | |
135 var element = this.treeOutline.selectedTreeElement; | |
136 element.updateSelection(); | |
137 }, | |
138 | |
139 focusedNodeChanged: function(forceUpdate) {}, | |
140 | |
141 findTreeElement: function(node) | |
142 { | |
143 var treeElement = TreeOutline.prototype.findTreeElement.call(this, node,
isAncestorNode, parentNode); | |
144 if (!treeElement && node.nodeType === Node.TEXT_NODE) { | |
145 // The text node might have been inlined if it was short, so try to
find the parent element. | |
146 treeElement = TreeOutline.prototype.findTreeElement.call(this, node.
parentNode, isAncestorNode, parentNode); | |
147 } | |
148 | |
149 return treeElement; | |
150 }, | |
151 | |
152 revealAndSelectNode: function(node) | |
153 { | |
154 if (!node) | |
155 return; | |
156 | |
157 var treeElement = this.findTreeElement(node); | |
158 if (!treeElement) | |
159 return; | |
160 | |
161 treeElement.reveal(); | |
162 treeElement.select(); | |
163 }, | |
164 | |
165 _treeElementFromEvent: function(event) | |
166 { | |
167 var root = this.element; | |
168 | |
169 // We choose this X coordinate based on the knowledge that our list | |
170 // items extend nearly to the right edge of the outer <ol>. | |
171 var x = root.totalOffsetLeft + root.offsetWidth - 20; | |
172 | |
173 var y = event.pageY; | |
174 | |
175 // Our list items have 1-pixel cracks between them vertically. We avoid | |
176 // the cracks by checking slightly above and slightly below the mouse | |
177 // and seeing if we hit the same element each time. | |
178 var elementUnderMouse = this.treeElementFromPoint(x, y); | |
179 var elementAboveMouse = this.treeElementFromPoint(x, y - 2); | |
180 var element; | |
181 if (elementUnderMouse === elementAboveMouse) | |
182 element = elementUnderMouse; | |
183 else | |
184 element = this.treeElementFromPoint(x, y + 2); | |
185 | |
186 return element; | |
187 }, | |
188 | |
189 handleKeyEvent: function(event) | |
190 { | |
191 var selectedElement = this.selectedTreeElement; | |
192 if (!selectedElement) | |
193 return; | |
194 | |
195 // Delete or backspace pressed, delete the node. | |
196 if (event.keyCode === 8 || event.keyCode === 46) { | |
197 selectedElement.remove(); | |
198 return; | |
199 } | |
200 | |
201 // On Enter or Return start editing the first attribute | |
202 // or create a new attribute on the selected element. | |
203 if (event.keyIdentifier === "Enter") { | |
204 if (this._editing) | |
205 return; | |
206 | |
207 selectedElement._startEditing(); | |
208 | |
209 // prevent a newline from being immediately inserted | |
210 event.preventDefault(); | |
211 return; | |
212 } | |
213 | |
214 TreeOutline.prototype.handleKeyEvent.call(this, event); | |
215 }, | |
216 | |
217 _onmousedown: function(event) | |
218 { | |
219 var element = this._treeElementFromEvent(event); | |
220 | |
221 if (!element || element.isEventWithinDisclosureTriangle(event)) | |
222 return; | |
223 | |
224 element.select(); | |
225 }, | |
226 | |
227 _onmousemove: function(event) | |
228 { | |
229 var element = this._treeElementFromEvent(event); | |
230 if (element && this._previousHoveredElement === element) | |
231 return; | |
232 | |
233 if (this._previousHoveredElement) { | |
234 this._previousHoveredElement.hovered = false; | |
235 delete this._previousHoveredElement; | |
236 } | |
237 | |
238 if (element && !element.elementCloseTag) { | |
239 element.hovered = true; | |
240 this._previousHoveredElement = element; | |
241 } | |
242 | |
243 WebInspector.hoveredDOMNode = (element && !element.elementCloseTag ? ele
ment.representedObject : null); | |
244 }, | |
245 | |
246 _onmouseout: function(event) | |
247 { | |
248 var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY)
; | |
249 if (nodeUnderMouse && nodeUnderMouse.isDescendant(this.element)) | |
250 return; | |
251 | |
252 if (this._previousHoveredElement) { | |
253 this._previousHoveredElement.hovered = false; | |
254 delete this._previousHoveredElement; | |
255 } | |
256 | |
257 WebInspector.hoveredDOMNode = null; | |
258 } | |
259 } | |
260 | |
261 WebInspector.ElementsTreeOutline.prototype.__proto__ = TreeOutline.prototype; | |
262 | |
263 WebInspector.ElementsTreeElement = function(node) | |
264 { | |
265 var hasChildrenOverride = node.hasChildNodes() && !this._showInlineText(node
); | |
266 | |
267 // The title will be updated in onattach. | |
268 TreeElement.call(this, "", node, hasChildrenOverride); | |
269 | |
270 if (this.representedObject.nodeType == Node.ELEMENT_NODE) | |
271 this._canAddAttributes = true; | |
272 } | |
273 | |
274 WebInspector.ElementsTreeElement.prototype = { | |
275 get highlighted() | |
276 { | |
277 return this._highlighted; | |
278 }, | |
279 | |
280 set highlighted(x) | |
281 { | |
282 if (this._highlighted === x) | |
283 return; | |
284 | |
285 this._highlighted = x; | |
286 | |
287 if (this.listItemElement) { | |
288 if (x) | |
289 this.listItemElement.addStyleClass("highlighted"); | |
290 else | |
291 this.listItemElement.removeStyleClass("highlighted"); | |
292 } | |
293 }, | |
294 | |
295 get hovered() | |
296 { | |
297 return this._hovered; | |
298 }, | |
299 | |
300 set hovered(x) | |
301 { | |
302 if (this._hovered === x) | |
303 return; | |
304 | |
305 this._hovered = x; | |
306 | |
307 if (this.listItemElement) { | |
308 if (x) { | |
309 this.updateSelection(); | |
310 this.listItemElement.addStyleClass("hovered"); | |
311 if (this._canAddAttributes) | |
312 this._pendingToggleNewAttribute = setTimeout(this.toggleNewA
ttributeButton.bind(this, true), 500); | |
313 } else { | |
314 this.listItemElement.removeStyleClass("hovered"); | |
315 if (this._pendingToggleNewAttribute) { | |
316 clearTimeout(this._pendingToggleNewAttribute); | |
317 delete this._pendingToggleNewAttribute; | |
318 } | |
319 this.toggleNewAttributeButton(false); | |
320 } | |
321 } | |
322 }, | |
323 | |
324 toggleNewAttributeButton: function(visible) | |
325 { | |
326 function removeAddAttributeSpan() | |
327 { | |
328 if (this._addAttributeElement && this._addAttributeElement.parentNod
e) | |
329 this._addAttributeElement.parentNode.removeChild(this._addAttrib
uteElement); | |
330 delete this._addAttributeElement; | |
331 } | |
332 | |
333 if (!this._addAttributeElement && visible && !this._editing) { | |
334 var span = document.createElement("span"); | |
335 span.className = "add-attribute webkit-html-attribute-name"; | |
336 span.textContent = " ?=\"\""; | |
337 span.addEventListener("dblclick", removeAddAttributeSpan.bind(this),
false); | |
338 this._addAttributeElement = span; | |
339 | |
340 var tag = this.listItemElement.getElementsByClassName("webkit-html-t
ag")[0]; | |
341 this._insertInLastAttributePosition(tag, span); | |
342 } else if (!visible && this._addAttributeElement) | |
343 removeAddAttributeSpan.call(this); | |
344 }, | |
345 | |
346 updateSelection: function() | |
347 { | |
348 var listItemElement = this.listItemElement; | |
349 if (!listItemElement) | |
350 return; | |
351 | |
352 if (document.body.offsetWidth <= 0) { | |
353 // The stylesheet hasn't loaded yet or the window is closed, | |
354 // so we can't calculate what is need. Return early. | |
355 return; | |
356 } | |
357 | |
358 if (!this.selectionElement) { | |
359 this.selectionElement = document.createElement("div"); | |
360 this.selectionElement.className = "selection selected"; | |
361 listItemElement.insertBefore(this.selectionElement, listItemElement.
firstChild); | |
362 } | |
363 | |
364 this.selectionElement.style.height = listItemElement.offsetHeight + "px"
; | |
365 }, | |
366 | |
367 onattach: function() | |
368 { | |
369 this.listItemElement.addEventListener("mousedown", this.onmousedown.bind
(this), false); | |
370 | |
371 if (this._highlighted) | |
372 this.listItemElement.addStyleClass("highlighted"); | |
373 | |
374 if (this._hovered) { | |
375 this.updateSelection(); | |
376 this.listItemElement.addStyleClass("hovered"); | |
377 } | |
378 | |
379 this._updateTitle(); | |
380 | |
381 this._preventFollowingLinksOnDoubleClick(); | |
382 }, | |
383 | |
384 _preventFollowingLinksOnDoubleClick: function() | |
385 { | |
386 var links = this.listItemElement.querySelectorAll("li > .webkit-html-tag
> .webkit-html-attribute > .webkit-html-external-link, li > .webkit-html-tag >
.webkit-html-attribute > .webkit-html-resource-link"); | |
387 if (!links) | |
388 return; | |
389 | |
390 for (var i = 0; i < links.length; ++i) | |
391 links[i].preventFollowOnDoubleClick = true; | |
392 }, | |
393 | |
394 onpopulate: function() | |
395 { | |
396 if (this.children.length || this._showInlineText(this.representedObject)
) | |
397 return; | |
398 | |
399 this.updateChildren(); | |
400 }, | |
401 | |
402 updateChildren: function(fullRefresh) | |
403 { | |
404 WebInspector.domAgent.getChildNodesAsync(this.representedObject, this._u
pdateChildren.bind(this, fullRefresh)); | |
405 }, | |
406 | |
407 _updateChildren: function(fullRefresh) | |
408 { | |
409 if (fullRefresh) { | |
410 var selectedTreeElement = this.treeOutline.selectedTreeElement; | |
411 if (selectedTreeElement && selectedTreeElement.hasAncestor(this)) | |
412 this.select(); | |
413 this.removeChildren(); | |
414 } | |
415 | |
416 var treeElement = this; | |
417 var treeChildIndex = 0; | |
418 | |
419 function updateChildrenOfNode(node) | |
420 { | |
421 var treeOutline = treeElement.treeOutline; | |
422 var child = node.firstChild; | |
423 while (child) { | |
424 var currentTreeElement = treeElement.children[treeChildIndex]; | |
425 if (!currentTreeElement || currentTreeElement.representedObject
!== child) { | |
426 // Find any existing element that is later in the children l
ist. | |
427 var existingTreeElement = null; | |
428 for (var i = (treeChildIndex + 1); i < treeElement.children.
length; ++i) { | |
429 if (treeElement.children[i].representedObject === child)
{ | |
430 existingTreeElement = treeElement.children[i]; | |
431 break; | |
432 } | |
433 } | |
434 | |
435 if (existingTreeElement && existingTreeElement.parent === tr
eeElement) { | |
436 // If an existing element was found and it has the same
parent, just move it. | |
437 var wasSelected = existingTreeElement.selected; | |
438 treeElement.removeChild(existingTreeElement); | |
439 treeElement.insertChild(existingTreeElement, treeChildIn
dex); | |
440 if (wasSelected) | |
441 existingTreeElement.select(); | |
442 } else { | |
443 // No existing element found, insert a new element. | |
444 var newElement = new WebInspector.ElementsTreeElement(ch
ild); | |
445 newElement.selectable = treeOutline.selectEnabled; | |
446 treeElement.insertChild(newElement, treeChildIndex); | |
447 } | |
448 } | |
449 | |
450 child = child.nextSibling; | |
451 ++treeChildIndex; | |
452 } | |
453 } | |
454 | |
455 // Remove any tree elements that no longer have this node (or this node'
s contentDocument) as their parent. | |
456 for (var i = (this.children.length - 1); i >= 0; --i) { | |
457 if ("elementCloseTag" in this.children[i]) | |
458 continue; | |
459 | |
460 var currentChild = this.children[i]; | |
461 var currentNode = currentChild.representedObject; | |
462 var currentParentNode = currentNode.parentNode; | |
463 | |
464 if (currentParentNode === this.representedObject) | |
465 continue; | |
466 | |
467 var selectedTreeElement = this.treeOutline.selectedTreeElement; | |
468 if (selectedTreeElement && (selectedTreeElement === currentChild ||
selectedTreeElement.hasAncestor(currentChild))) | |
469 this.select(); | |
470 | |
471 this.removeChildAtIndex(i); | |
472 } | |
473 | |
474 updateChildrenOfNode(this.representedObject); | |
475 | |
476 var lastChild = this.children[this.children.length - 1]; | |
477 if (this.representedObject.nodeType == Node.ELEMENT_NODE && (!lastChild
|| !lastChild.elementCloseTag)) { | |
478 var title = "<span class=\"webkit-html-tag close\"></" + this.rep
resentedObject.nodeName.toLowerCase().escapeHTML() + "></span>"; | |
479 var item = new TreeElement(title, null, false); | |
480 item.selectable = false; | |
481 item.elementCloseTag = true; | |
482 this.appendChild(item); | |
483 } | |
484 }, | |
485 | |
486 onexpand: function() | |
487 { | |
488 this.treeOutline.updateSelection(); | |
489 }, | |
490 | |
491 oncollapse: function() | |
492 { | |
493 this.treeOutline.updateSelection(); | |
494 }, | |
495 | |
496 onreveal: function() | |
497 { | |
498 if (this.listItemElement) | |
499 this.listItemElement.scrollIntoViewIfNeeded(false); | |
500 }, | |
501 | |
502 onselect: function() | |
503 { | |
504 this.treeOutline.focusedDOMNode = this.representedObject; | |
505 this.updateSelection(); | |
506 }, | |
507 | |
508 onmousedown: function(event) | |
509 { | |
510 if (this._editing) | |
511 return; | |
512 | |
513 if (this.treeOutline.showInElementsPanelEnabled) { | |
514 WebInspector.showElementsPanel(); | |
515 WebInspector.panels.elements.focusedDOMNode = this.representedObject
; | |
516 } | |
517 | |
518 // Prevent selecting the nearest word on double click. | |
519 if (event.detail >= 2) | |
520 event.preventDefault(); | |
521 }, | |
522 | |
523 ondblclick: function(treeElement, event) | |
524 { | |
525 if (this._editing) | |
526 return; | |
527 | |
528 if (this._startEditingFromEvent(event, treeElement)) | |
529 return; | |
530 | |
531 if (this.treeOutline.panel) { | |
532 this.treeOutline.rootDOMNode = this.representedObject.parentNode; | |
533 this.treeOutline.focusedDOMNode = this.representedObject; | |
534 } | |
535 | |
536 if (this.hasChildren && !this.expanded) | |
537 this.expand(); | |
538 }, | |
539 | |
540 _insertInLastAttributePosition: function(tag, node) | |
541 { | |
542 if (tag.getElementsByClassName("webkit-html-attribute").length > 0) | |
543 tag.insertBefore(node, tag.lastChild); | |
544 else { | |
545 var nodeName = tag.textContent.match(/^<(.*?)>$/)[1]; | |
546 tag.textContent = ''; | |
547 tag.appendChild(document.createTextNode('<'+nodeName)); | |
548 tag.appendChild(node); | |
549 tag.appendChild(document.createTextNode('>')); | |
550 } | |
551 }, | |
552 | |
553 _startEditingFromEvent: function(event, treeElement) | |
554 { | |
555 if (this.treeOutline.focusedDOMNode != this.representedObject) | |
556 return; | |
557 | |
558 if (this.representedObject.nodeType != Node.ELEMENT_NODE && this.represe
ntedObject.nodeType != Node.TEXT_NODE) | |
559 return false; | |
560 | |
561 var textNode = event.target.enclosingNodeOrSelfWithClass("webkit-html-te
xt-node"); | |
562 if (textNode) | |
563 return this._startEditingTextNode(textNode); | |
564 | |
565 var attribute = event.target.enclosingNodeOrSelfWithClass("webkit-html-a
ttribute"); | |
566 if (attribute) | |
567 return this._startEditingAttribute(attribute, event.target); | |
568 | |
569 var newAttribute = event.target.enclosingNodeOrSelfWithClass("add-attrib
ute"); | |
570 if (newAttribute) | |
571 return this._addNewAttribute(treeElement.listItemElement); | |
572 | |
573 return false; | |
574 }, | |
575 | |
576 _startEditing: function() | |
577 { | |
578 if (this.treeOutline.focusedDOMNode !== this.representedObject) | |
579 return; | |
580 | |
581 var listItem = this._listItemNode; | |
582 | |
583 if (this._canAddAttributes) { | |
584 this.toggleNewAttributeButton(false); | |
585 var attribute = listItem.getElementsByClassName("webkit-html-attribu
te")[0]; | |
586 if (attribute) | |
587 return this._startEditingAttribute(attribute, attribute.getEleme
ntsByClassName("webkit-html-attribute-value")[0]); | |
588 | |
589 return this._addNewAttribute(listItem); | |
590 } | |
591 | |
592 if (this.representedObject.nodeType === Node.TEXT_NODE) { | |
593 var textNode = listItem.getElementsByClassName("webkit-html-text-nod
e")[0]; | |
594 if (textNode) | |
595 return this._startEditingTextNode(textNode); | |
596 return; | |
597 } | |
598 }, | |
599 | |
600 _addNewAttribute: function(listItemElement) | |
601 { | |
602 var attr = document.createElement("span"); | |
603 attr.className = "webkit-html-attribute"; | |
604 attr.style.marginLeft = "2px"; // overrides the .editing margin rule | |
605 attr.style.marginRight = "2px"; // overrides the .editing margin rule | |
606 var name = document.createElement("span"); | |
607 name.className = "webkit-html-attribute-name new-attribute"; | |
608 name.textContent = " "; | |
609 var value = document.createElement("span"); | |
610 value.className = "webkit-html-attribute-value"; | |
611 attr.appendChild(name); | |
612 attr.appendChild(value); | |
613 | |
614 var tag = listItemElement.getElementsByClassName("webkit-html-tag")[0]; | |
615 this._insertInLastAttributePosition(tag, attr); | |
616 return this._startEditingAttribute(attr, attr); | |
617 }, | |
618 | |
619 _triggerEditAttribute: function(attributeName) | |
620 { | |
621 var attributeElements = this.listItemElement.getElementsByClassName("web
kit-html-attribute-name"); | |
622 for (var i = 0, len = attributeElements.length; i < len; ++i) { | |
623 if (attributeElements[i].textContent === attributeName) { | |
624 for (var elem = attributeElements[i].nextSibling; elem; elem = e
lem.nextSibling) { | |
625 if (elem.nodeType !== Node.ELEMENT_NODE) | |
626 continue; | |
627 | |
628 if (elem.hasStyleClass("webkit-html-attribute-value")) | |
629 return this._startEditingAttribute(attributeElements[i].
parentNode, elem); | |
630 } | |
631 } | |
632 } | |
633 }, | |
634 | |
635 _startEditingAttribute: function(attribute, elementForSelection) | |
636 { | |
637 if (WebInspector.isBeingEdited(attribute)) | |
638 return true; | |
639 | |
640 var attributeNameElement = attribute.getElementsByClassName("webkit-html
-attribute-name")[0]; | |
641 if (!attributeNameElement) | |
642 return false; | |
643 | |
644 var attributeName = attributeNameElement.innerText; | |
645 | |
646 function removeZeroWidthSpaceRecursive(node) | |
647 { | |
648 if (node.nodeType === Node.TEXT_NODE) { | |
649 node.nodeValue = node.nodeValue.replace(/\u200B/g, ""); | |
650 return; | |
651 } | |
652 | |
653 if (node.nodeType !== Node.ELEMENT_NODE) | |
654 return; | |
655 | |
656 for (var child = node.firstChild; child; child = child.nextSibling) | |
657 removeZeroWidthSpaceRecursive(child); | |
658 } | |
659 | |
660 // Remove zero-width spaces that were added by nodeTitleInfo. | |
661 removeZeroWidthSpaceRecursive(attribute); | |
662 | |
663 this._editing = true; | |
664 | |
665 WebInspector.startEditing(attribute, this._attributeEditingCommitted.bin
d(this), this._editingCancelled.bind(this), attributeName); | |
666 window.getSelection().setBaseAndExtent(elementForSelection, 0, elementFo
rSelection, 1); | |
667 | |
668 return true; | |
669 }, | |
670 | |
671 _startEditingTextNode: function(textNode) | |
672 { | |
673 if (WebInspector.isBeingEdited(textNode)) | |
674 return true; | |
675 | |
676 this._editing = true; | |
677 | |
678 WebInspector.startEditing(textNode, this._textNodeEditingCommitted.bind(
this), this._editingCancelled.bind(this)); | |
679 window.getSelection().setBaseAndExtent(textNode, 0, textNode, 1); | |
680 | |
681 return true; | |
682 }, | |
683 | |
684 _attributeEditingCommitted: function(element, newText, oldText, attributeNam
e, moveDirection) | |
685 { | |
686 delete this._editing; | |
687 | |
688 // Before we do anything, determine where we should move | |
689 // next based on the current element's settings | |
690 var moveToAttribute; | |
691 var newAttribute; | |
692 if (moveDirection) { | |
693 var found = false; | |
694 var attributes = this.representedObject.attributes; | |
695 for (var i = 0, len = attributes.length; i < len; ++i) { | |
696 if (attributes[i].name === attributeName) { | |
697 found = true; | |
698 if (moveDirection === "backward" && i > 0) | |
699 moveToAttribute = attributes[i - 1].name; | |
700 else if (moveDirection === "forward" && i < attributes.lengt
h - 1) | |
701 moveToAttribute = attributes[i + 1].name; | |
702 else if (moveDirection === "forward" && i === attributes.len
gth - 1) | |
703 newAttribute = true; | |
704 } | |
705 } | |
706 | |
707 if (!found && moveDirection === "backward" && attributes.length > 0) | |
708 moveToAttribute = attributes[attributes.length - 1].name; | |
709 else if (!found && moveDirection === "forward" && !/^\s*$/.test(newT
ext)) | |
710 newAttribute = true; | |
711 } | |
712 | |
713 function moveToNextAttributeIfNeeded() { | |
714 if (moveToAttribute) | |
715 this._triggerEditAttribute(moveToAttribute); | |
716 else if (newAttribute) | |
717 this._addNewAttribute(this.listItemElement); | |
718 } | |
719 | |
720 var parseContainerElement = document.createElement("span"); | |
721 parseContainerElement.innerHTML = "<span " + newText + "></span>"; | |
722 var parseElement = parseContainerElement.firstChild; | |
723 | |
724 if (!parseElement) { | |
725 this._editingCancelled(element, attributeName); | |
726 moveToNextAttributeIfNeeded.call(this); | |
727 return; | |
728 } | |
729 | |
730 if (!parseElement.hasAttributes()) { | |
731 this.representedObject.removeAttribute(attributeName); | |
732 this._updateTitle(); | |
733 moveToNextAttributeIfNeeded.call(this); | |
734 return; | |
735 } | |
736 | |
737 var foundOriginalAttribute = false; | |
738 for (var i = 0; i < parseElement.attributes.length; ++i) { | |
739 var attr = parseElement.attributes[i]; | |
740 foundOriginalAttribute = foundOriginalAttribute || attr.name === att
ributeName; | |
741 try { | |
742 this.representedObject.setAttribute(attr.name, attr.value); | |
743 } catch(e) {} // ignore invalid attribute (innerHTML doesn't throw e
rrors, but this can) | |
744 } | |
745 | |
746 if (!foundOriginalAttribute) | |
747 this.representedObject.removeAttribute(attributeName); | |
748 | |
749 this._updateTitle(); | |
750 | |
751 this.treeOutline.focusedNodeChanged(true); | |
752 | |
753 moveToNextAttributeIfNeeded.call(this); | |
754 }, | |
755 | |
756 _textNodeEditingCommitted: function(element, newText) | |
757 { | |
758 delete this._editing; | |
759 | |
760 var textNode; | |
761 if (this.representedObject.nodeType == Node.ELEMENT_NODE) { | |
762 // We only show text nodes inline in elements if the element only | |
763 // has a single child, and that child is a text node. | |
764 textNode = this.representedObject.firstChild; | |
765 } else if (this.representedObject.nodeType == Node.TEXT_NODE) | |
766 textNode = this.representedObject; | |
767 | |
768 textNode.nodeValue = newText; | |
769 this._updateTitle(); | |
770 }, | |
771 | |
772 _editingCancelled: function(element, context) | |
773 { | |
774 delete this._editing; | |
775 | |
776 this._updateTitle(); | |
777 }, | |
778 | |
779 _updateTitle: function() | |
780 { | |
781 var title = this._nodeTitleInfo(this.representedObject, this.hasChildren
, WebInspector.linkifyURL).title; | |
782 this.title = "<span class=\"highlight\">" + title + "</span>"; | |
783 delete this.selectionElement; | |
784 this.updateSelection(); | |
785 this._preventFollowingLinksOnDoubleClick(); | |
786 }, | |
787 | |
788 _nodeTitleInfo: function(node, hasChildren, linkify) | |
789 { | |
790 var info = {title: "", hasChildren: hasChildren}; | |
791 | |
792 switch (node.nodeType) { | |
793 case Node.DOCUMENT_NODE: | |
794 info.title = "Document"; | |
795 break; | |
796 | |
797 case Node.ELEMENT_NODE: | |
798 info.title = "<span class=\"webkit-html-tag\"><" + node.nodeN
ame.toLowerCase().escapeHTML(); | |
799 | |
800 if (node.hasAttributes()) { | |
801 for (var i = 0; i < node.attributes.length; ++i) { | |
802 var attr = node.attributes[i]; | |
803 info.title += " <span class=\"webkit-html-attribute\"><s
pan class=\"webkit-html-attribute-name\">" + attr.name.escapeHTML() + "</span>=&
#8203;\""; | |
804 | |
805 var value = attr.value; | |
806 if (linkify && (attr.name === "src" || attr.name === "hr
ef")) { | |
807 var value = value.replace(/([\/;:\)\]\}])/g, "$1\u20
0B"); | |
808 info.title += linkify(attr.value, value, "webkit-htm
l-attribute-value", node.nodeName.toLowerCase() == "a"); | |
809 } else { | |
810 var value = value.escapeHTML(); | |
811 value = value.replace(/([\/;:\)\]\}])/g, "$1​"
); | |
812 info.title += "<span class=\"webkit-html-attribute-v
alue\">" + value + "</span>"; | |
813 } | |
814 info.title += "\"</span>"; | |
815 } | |
816 } | |
817 info.title += "></span>​"; | |
818 | |
819 // If this element only has a single child that is a text node, | |
820 // just show that text and the closing tag inline rather than | |
821 // create a subtree for them | |
822 | |
823 var textChild = onlyTextChild.call(node); | |
824 var showInlineText = textChild && textChild.textContent.length <
Preferences.maxInlineTextChildLength; | |
825 | |
826 if (showInlineText) { | |
827 info.title += "<span class=\"webkit-html-text-node\">" + tex
tChild.nodeValue.escapeHTML() + "</span>​<span class=\"webkit-html-tag\">&
lt;/" + node.nodeName.toLowerCase().escapeHTML() + "></span>"; | |
828 info.hasChildren = false; | |
829 } | |
830 break; | |
831 | |
832 case Node.TEXT_NODE: | |
833 if (isNodeWhitespace.call(node)) | |
834 info.title = "(whitespace)"; | |
835 else { | |
836 if (node.parentNode && node.parentNode.nodeName.toLowerCase(
) == "script") { | |
837 var newNode = document.createElement("span"); | |
838 newNode.textContent = node.textContent; | |
839 | |
840 var javascriptSyntaxHighlighter = new WebInspector.JavaS
criptSourceSyntaxHighlighter(null, null); | |
841 javascriptSyntaxHighlighter.syntaxHighlightLine(newNode,
null); | |
842 | |
843 info.title = "<span class=\"webkit-html-text-node webkit
-html-js-node\">" + newNode.innerHTML.replace(/^[\n\r]*/, "").replace(/\s*$/, ""
) + "</span>"; | |
844 } else if (node.parentNode && node.parentNode.nodeName.toLow
erCase() == "style") { | |
845 var newNode = document.createElement("span"); | |
846 newNode.textContent = node.textContent; | |
847 | |
848 var cssSyntaxHighlighter = new WebInspector.CSSSourceSyn
taxHighligher(null, null); | |
849 cssSyntaxHighlighter.syntaxHighlightLine(newNode, null); | |
850 | |
851 info.title = "<span class=\"webkit-html-text-node webkit
-html-css-node\">" + newNode.innerHTML.replace(/^[\n\r]*/, "").replace(/\s*$/, "
") + "</span>"; | |
852 } else { | |
853 info.title = "\"<span class=\"webkit-html-text-node\">"
+ node.nodeValue.escapeHTML() + "</span>\""; | |
854 } | |
855 } | |
856 break; | |
857 | |
858 case Node.COMMENT_NODE: | |
859 info.title = "<span class=\"webkit-html-comment\"><!--" + nod
e.nodeValue.escapeHTML() + "--></span>"; | |
860 break; | |
861 | |
862 case Node.DOCUMENT_TYPE_NODE: | |
863 info.title = "<span class=\"webkit-html-doctype\"><!DOCTYPE "
+ node.nodeName; | |
864 if (node.publicId) { | |
865 info.title += " PUBLIC \"" + node.publicId + "\""; | |
866 if (node.systemId) | |
867 info.title += " \"" + node.systemId + "\""; | |
868 } else if (node.systemId) | |
869 info.title += " SYSTEM \"" + node.systemId + "\""; | |
870 if (node.internalSubset) | |
871 info.title += " [" + node.internalSubset + "]"; | |
872 info.title += "></span>"; | |
873 break; | |
874 default: | |
875 info.title = node.nodeName.toLowerCase().collapseWhitespace().es
capeHTML(); | |
876 } | |
877 | |
878 return info; | |
879 }, | |
880 | |
881 _showInlineText: function(node) | |
882 { | |
883 if (node.nodeType === Node.ELEMENT_NODE) { | |
884 var textChild = onlyTextChild.call(node); | |
885 if (textChild && textChild.textContent.length < Preferences.maxInlin
eTextChildLength) | |
886 return true; | |
887 } | |
888 return false; | |
889 }, | |
890 | |
891 remove: function() | |
892 { | |
893 var parentElement = this.parent; | |
894 if (!parentElement) | |
895 return; | |
896 | |
897 var self = this; | |
898 function removeNodeCallback(removedNodeId) | |
899 { | |
900 // -1 is an error code, which means removing the node from the DOM f
ailed, | |
901 // so we shouldn't remove it from the tree. | |
902 if (removedNodeId === -1) | |
903 return; | |
904 | |
905 parentElement.removeChild(self); | |
906 } | |
907 | |
908 var callId = WebInspector.Callback.wrap(removeNodeCallback); | |
909 InspectorController.removeNode(callId, this.representedObject.id); | |
910 } | |
911 } | |
912 | |
913 WebInspector.ElementsTreeElement.prototype.__proto__ = TreeElement.prototype; | |
914 | |
915 WebInspector.didRemoveNode = WebInspector.Callback.processCallback; | |
OLD | NEW |